PHPackages                             smallwork/smallwork - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. smallwork/smallwork

ActiveFramework[Framework](/categories/framework)

smallwork/smallwork
===================

Small footprint full-stack AI framework for PHP

10PHP

Since Feb 27Pushed 4mo ago1 watchersCompare

[ Source](https://github.com/atr0t0s/smallwork)[ Packagist](https://packagist.org/packages/smallwork/smallwork)[ RSS](/packages/smallwork-smallwork/feed)WikiDiscussions master Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Smallwork
=========

[](#smallwork)

A small footprint full-stack AI framework for PHP. Build AI-powered web applications with a unified multi-provider AI gateway, server-rendered templates, JSON APIs, vector search, and enterprise features — all without the overhead of a large framework.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Composer
- cURL extension (for AI providers)
- Optional: Redis, Qdrant, PostgreSQL with pgvector

Quick Start
-----------

[](#quick-start)

```
# Install dependencies
composer install

# Copy environment config
cp .env.example .env

# Start development server
php smallwork serve

# Or use PHP directly
php -S localhost:8080 -t public
```

Visit `http://localhost:8080` to verify it's running.

Project Structure
-----------------

[](#project-structure)

```
smallwork/
├── public/              # Web root (single entry point)
│   ├── index.php
│   └── .htaccess
├── config/
│   ├── app.php          # Application settings
│   ├── database.php     # Database connections
│   ├── auth.php         # JWT and RBAC config
│   ├── ai.php           # AI provider config
│   └── routes/
│       ├── api.php      # API route definitions
│       └── web.php      # Web route definitions
├── src/                 # Framework source code
│   ├── Core/            # Router, Request, Response, Container, Middleware
│   ├── Database/        # Query builder, migrations, adapters
│   ├── Auth/            # JWT, API keys, roles
│   ├── View/            # Template engine, HTMX helpers
│   ├── AI/              # Gateway, providers, chat, embeddings, search
│   ├── Console/         # CLI commands
│   └── Testing/         # Test helpers
├── app/                 # Your application code
│   ├── Controllers/
│   ├── Models/
│   ├── Middleware/
│   ├── Views/
│   └── Prompts/
├── database/
│   └── migrations/
├── storage/
│   ├── logs/
│   └── cache/
└── tests/
    ├── Unit/
    └── Integration/

```

Configuration
-------------

[](#configuration)

Copy `.env.example` to `.env` and set your values:

```
APP_NAME=Smallwork
APP_ENV=local
APP_DEBUG=true

DB_DRIVER=sqlite
DB_DATABASE=storage/database.sqlite

REDIS_HOST=127.0.0.1
REDIS_PORT=6379

AI_PROVIDER=openai
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GROK_API_KEY=xai-...
```

Access environment variables anywhere with the `env()` helper:

```
$debug = env('APP_DEBUG', false);    // Casts "true"/"false" to booleans
$name  = env('APP_NAME', 'Default');
```

---

Routing
-------

[](#routing)

Define routes in `config/routes/web.php` or `config/routes/api.php`. The router is passed as `$router`.

### Basic Routes

[](#basic-routes)

```
$router->get('/hello', function (Request $request) {
    return Response::json(['message' => 'Hello, world!']);
});

$router->post('/users', function (Request $request) {
    $data = $request->json();
    return Response::json(['created' => $data['name']], 201);
});

$router->put('/users/{id}', function (Request $request) {
    $id = $request->param('id');
    return Response::json(['updated' => $id]);
});

$router->delete('/users/{id}', function (Request $request) {
    return Response::empty();
});
```

### Route Parameters

[](#route-parameters)

```
$router->get('/posts/{slug}/comments/{id}', function (Request $request) {
    $slug = $request->param('slug');
    $id   = $request->param('id');
    return Response::json(compact('slug', 'id'));
});
```

### Route Groups

[](#route-groups)

Groups share a URL prefix and optional middleware:

```
$router->group('/api/v1', function (Router $r) {
    $r->get('/users', [UserController::class, 'index']);
    $r->post('/users', [UserController::class, 'store']);

    $r->group('/admin', function (Router $r) {
        $r->get('/stats', [AdminController::class, 'stats']);
    }, middleware: ['AdminOnly']);

}, middleware: ['AuthMiddleware']);
```

### Per-Route Middleware

[](#per-route-middleware)

```
$router->get('/dashboard', [DashController::class, 'index'], middleware: ['AuthMiddleware']);
```

---

Request
-------

[](#request)

The `Request` object wraps all HTTP input:

```
// Query string: /search?q=php&page=2
$query = $request->query('q');          // "php"
$page  = $request->query('page', 1);   // "2"
$all   = $request->query();            // ['q' => 'php', 'page' => '2']

// POST / form data
$name = $request->input('name');

// JSON body (auto-parsed)
$data  = $request->json();             // Full decoded array
$email = $request->json('email');      // Specific key

// Headers (case-insensitive)
$token = $request->header('Authorization');
$all   = $request->headers();

// Route parameters
$id = $request->param('id');

// Raw body
$raw = $request->rawBody();

// Method helpers
$request->isGet();
$request->isPost();

// Custom attributes (set by middleware)
$user = $request->getAttribute('user');
```

---

Response
--------

[](#response)

Build responses with static factory methods:

```
// JSON
return Response::json(['users' => $users]);
return Response::json(['error' => 'Not found'], 404);

// HTML
return Response::html('Hello');

// Empty (204 No Content)
return Response::empty();

// Redirect
return Response::redirect('/login');
return Response::redirect('/new-location', 301);

// Server-Sent Events (SSE) streaming
return Response::stream(function () {
    echo "data: chunk 1\n\n";
    flush();
    echo "data: chunk 2\n\n";
    flush();
});
```

Responses are immutable — `withHeader` and `withCookie` return new instances:

```
return Response::json($data)
    ->withHeader('X-Request-Id', $requestId)
    ->withHeader('Cache-Control', 'no-store')
    ->withCookie('session', $token, maxAge: 3600);
```

---

Middleware
----------

[](#middleware)

Middleware follows the onion model. Each middleware receives the `Request` and a `$next` callable:

```
class TimingMiddleware
{
    public function handle(Request $request, callable $next): Response
    {
        $start = microtime(true);
        $response = $next($request);
        $elapsed = round((microtime(true) - $start) * 1000, 2);
        return $response->withHeader('X-Response-Time', "{$elapsed}ms");
    }
}
```

### Registering Middleware

[](#registering-middleware)

```
// Global middleware (all routes)
$app->addMiddleware(new CorsMiddleware());
$app->addMiddleware(new RateLimitMiddleware(maxRequests: 100, windowSeconds: 60));

// Route group middleware
$router->group('/api', function (Router $r) {
    // routes here
}, middleware: ['AuthMiddleware']);

// Per-route middleware
$router->get('/admin', $handler, middleware: ['AdminOnly']);
```

### Built-in Middleware

[](#built-in-middleware)

**CORS**

```
$cors = new CorsMiddleware(
    allowedOrigins: ['https://myapp.com', 'https://staging.myapp.com'],
    allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    maxAge: 86400,
);
$app->addMiddleware($cors);
```

**Rate Limiting**

```
$limiter = new RateLimitMiddleware(
    maxRequests: 60,   // requests per window
    windowSeconds: 60, // window size
);
$app->addMiddleware($limiter);
// Returns 429 Too Many Requests when exceeded, with X-RateLimit-* headers
```

**Authentication** (see [Authentication](#authentication) section)

**Role/Permission** (see [Authorization](#authorization) section)

---

Dependency Injection Container
------------------------------

[](#dependency-injection-container)

The container supports binding, singletons, instances, and auto-wiring:

```
$container = $app->container();

// Bind a factory (new instance each time)
$container->bind(Mailer::class, fn() => new Mailer(env('SMTP_HOST')));

// Singleton (created once, reused)
$container->singleton(Gateway::class, function () {
    $gw = new Gateway('openai');
    $gw->register('openai', new OpenAIProvider(
        baseUrl: 'https://api.openai.com/v1',
        apiKey: env('OPENAI_API_KEY'),
    ));
    return $gw;
});

// Register an existing instance
$container->instance('config', $configArray);

// Resolve
$mailer = $container->resolve(Mailer::class);

// Auto-wire (resolves constructor dependencies via type hints)
$controller = $container->make(UserController::class);
```

---

Database
--------

[](#database)

### Connecting

[](#connecting)

Configure in `config/database.php` or `.env`:

```
// SQLite
$adapter = Connection::create([
    'driver'   => 'sqlite',
    'database' => 'storage/database.sqlite',
]);

// MySQL
$adapter = Connection::create([
    'driver'   => 'mysql',
    'host'     => '127.0.0.1',
    'port'     => 3306,
    'database' => 'myapp',
    'username' => 'root',
    'password' => '',
]);

// PostgreSQL
$adapter = Connection::create([
    'driver'   => 'pgsql',
    'host'     => '127.0.0.1',
    'port'     => 5432,
    'database' => 'myapp',
    'username' => 'postgres',
    'password' => '',
]);
```

### Query Builder

[](#query-builder)

Fluent interface for building SQL queries:

```
$db = Connection::create($config);
$qb = new QueryBuilder($db, 'users');

// SELECT
$users = $qb->select('id', 'name', 'email')
    ->where('active', '=', 1)
    ->orderBy('created_at', 'DESC')
    ->limit(10)
    ->offset(20)
    ->get();

// Single row
$user = (new QueryBuilder($db, 'users'))
    ->where('id', '=', 42)
    ->first();

// Count
$total = (new QueryBuilder($db, 'users'))
    ->where('active', '=', 1)
    ->count();

// INSERT (returns last insert ID)
$id = (new QueryBuilder($db, 'users'))->insert([
    'name'  => 'Alice',
    'email' => 'alice@example.com',
]);

// UPDATE (returns affected row count)
$affected = (new QueryBuilder($db, 'users'))
    ->where('id', '=', 42)
    ->update(['name' => 'Bob']);

// DELETE
$deleted = (new QueryBuilder($db, 'users'))
    ->where('active', '=', 0)
    ->delete();

// JOIN
$posts = (new QueryBuilder($db, 'posts'))
    ->select('posts.title', 'users.name')
    ->join('users', 'posts.user_id', '=', 'users.id')
    ->get();

// GROUP BY
$counts = (new QueryBuilder($db, 'orders'))
    ->select('status')
    ->groupBy('status')
    ->get();
```

### Transactions

[](#transactions)

```
$db->beginTransaction();
try {
    (new QueryBuilder($db, 'accounts'))
        ->where('id', '=', 1)
        ->update(['balance' => 900]);

    (new QueryBuilder($db, 'accounts'))
        ->where('id', '=', 2)
        ->update(['balance' => 1100]);

    $db->commit();
} catch (\Throwable $e) {
    $db->rollback();
    throw $e;
}
```

### Migrations

[](#migrations)

Create migration files in `database/migrations/`:

```
