Curlpit is a PSR-15 middleware orchestrator for PHP – with branching, looping, and declarative flow control built in.
Most middleware stacks are pipelines: request goes in, response comes out, linearly. Curlpit treats the middleware stack as an instruction sequence – with a program counter, named labels, conditional jumps, and loops. Business logic that would otherwise be hardcoded in handlers can be expressed as configuration.
composer require curlpit/curlpit
Then install a PSR-7/17 implementation of your choice:
# nyholm/psr7 (recommended – lightweight, zero dependencies)
composer require nyholm/psr7 nyholm/psr7-server
# or guzzlehttp/psr7
composer require guzzlehttp/psr7
| Standard PSR-15 | Curlpit | |
|---|---|---|
| Execution | Linear chain | Instruction sequence (program counter) |
| Branching | None | JumpMiddleware (conditional goto) |
| Looping | None | LoopMiddleware + LoopContext |
| State | Informal | Explicit Set/Get/Inc/Dec middleware |
| Error handling | Manual | Built-in ErrorHandlerMiddleware |
| Config | Code only | JSON-declarable |
Extend Application, override instantiate() to wire up your dependencies, point it at a middleware.json:
use Curlpit\App\Application;
use Curlpit\Core\Emitter;
class MyApp extends Application
{
protected function instantiate(string $class, array $options): MiddlewareInterface
{
return match ($class) {
MyMiddleware::class => new MyMiddleware($this->responseFactory),
default => parent::instantiate($class, $options),
};
}
}
$app = new MyApp($responseFactory, $streamFactory);
$response = $app->handle($serverRequest);
(new Emitter())->emit($response);
{
"middleware": [
{ "Curlpit\\Core\\Middleware\\ErrorHandlerMiddleware": { "debug": false } },
{ "My\\AuthMiddleware": {} },
{
"Curlpit\\Core\\Middleware\\JumpMiddleware": {
"condition": { "type": "attr", "name": "user_role", "eq": "admin" },
"jump_to_label": "admin"
}
},
{ "My\\PublicDispatch": {} },
{ "My\\AdminDispatch": { "label": "admin" } }
]
}
{ "type": "always" }
{ "type": "never" }
{ "type": "attr", "name": "status", "eq": "active" }
{ "type": "attr", "name": "retries", "lte": 3 }
{ "type": "context", "name": "has_more" }
{ "type": "context", "name": "count", "gt": 0 }
Operators: eq, neq, gt, gte, lt, lte. Without an operator, truthy check.
RequestHandler – PSR-15 handler with program counter and label-based jumpsJumpMiddleware – conditional branch to a named labelLoopMiddleware – repeat a sub-handler while a condition holdsLoopContext – mutable state container for loop iterationsRoutingMiddleware – path pattern matching with {param} placeholders, 404/405 awareDispatchMiddleware – resolves and calls the matched handler via an injected resolver callableErrorHandlerMiddleware – catches all exceptions, returns JSON or plaintext based on Accept headerSetVariableMiddleware / GetVariableMiddleware – read/write request attributesIncrementMiddleware / DecrementMiddleware – numeric counters in request attributesConfigLoader – loads and validates middleware.json, standalone and cacheableEmitter – sends PSR-7 responses to the SAPI with chunked streamingDBCommander – a Norton Commander-style MySQL manager built on Curlpit.
MIT