PHPackages                             curlpit/curlpit - 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. curlpit/curlpit

ActiveLibrary[Framework](/categories/framework)

curlpit/curlpit
===============

A PSR-15 middleware orchestrator for PHP – with branching, looping, and declarative flow control built in

1.1.2(2mo ago)04MITPHPPHP ^8.1CI passing

Since Mar 13Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/cURLpit/cURLpit)[ Packagist](https://packagist.org/packages/curlpit/curlpit)[ RSS](/packages/curlpit-curlpit/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (8)Versions (4)Used By (0)

[cURLpit](https://github.com/curlpit/curlpit)
=============================================

[](#curlpit)

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.

Installation
------------

[](#installation)

```
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

```

How it differs
--------------

[](#how-it-differs)

Standard PSR-15CurlpitExecutionLinear chainInstruction sequence (program counter)BranchingNone`JumpMiddleware` (conditional goto)LoopingNone`LoopMiddleware` + `LoopContext`StateInformalExplicit Set/Get/Inc/Dec middlewareError handlingManualBuilt-in `ErrorHandlerMiddleware`ConfigCode onlyJSON-declarableQuick start
-----------

[](#quick-start)

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);
```

Flow config (middleware.json)
-----------------------------

[](#flow-config-middlewarejson)

```
{
  "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" } }
  ]
}
```

Condition DSL
-------------

[](#condition-dsl)

```
{ "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.

Built-in middleware
-------------------

[](#built-in-middleware)

- **`RequestHandler`** – PSR-15 handler with program counter and label-based jumps
- **`JumpMiddleware`** – conditional branch to a named label (`else_label` for two-way branch)
- **`LoopMiddleware`** – repeat a sub-handler while a condition holds
- **`TryMiddleware`** – execute a sub-handler, jump to catch label on exception
- **`LoopContext`** – mutable state container for loop iterations
- **`RoutingMiddleware`** – path pattern matching with `{param}` placeholders, 404/405 aware
- **`DispatchMiddleware`** – resolves and calls the matched handler via an injected resolver callable
- **`ErrorHandlerMiddleware`** – catches all exceptions, returns JSON or plaintext based on Accept header
- **`SetVariableMiddleware`** / **`GetVariableMiddleware`** – read/write request attributes
- **`IncrementMiddleware`** / **`DecrementMiddleware`** – numeric counters in request attributes
- **`ConfigLoader`** – loads and validates `middleware.json`, standalone and cacheable
- **`Emitter`** – sends PSR-7 responses to the SAPI with chunked streaming

Using third-party PSR-15 middleware
-----------------------------------

[](#using-third-party-psr-15-middleware)

Any PSR-15 compliant middleware works with Curlpit without modification or wrappers – including inside loops and try bodies. Curlpit's flow control state (`__pc`, `__jump_to`) travels in request attributes and is invisible to third-party middleware.

### Static wiring (explicit)

[](#static-wiring-explicit)

For middleware with complex constructor dependencies, wire them up in `instantiate()`:

```
protected function instantiate(string $class, array $options): MiddlewareInterface
{
    return match ($class) {
        \Middlewares\AccessLog::class => new AccessLog($this->buildLogger()),
        default                       => parent::instantiate($class, $options),
    };
}
```

Declarative autowire
--------------------

[](#declarative-autowire)

Curlpit supports **fully declarative middleware configuration** via `middleware.json`.

For simpler cases, there is **no need to register or wire anything in your application code**.
All dependencies, configuration values, and even method calls can be defined in a single place.

Unlike reflection-based autowire (as found in PHP-DI or Symfony), declarative autowire is **explicit**: you describe the full object graph in JSON. Nothing is inferred automatically – which means no surprises, no hidden reflection overhead, and no extra dependencies.

---

### Example

[](#example)

Install any PSR-15 middleware:

```
composer require middlewares/http-authentication
composer require middlewares/access-log monolog/monolog

```

Generate a password hash:

```
php -r "echo password_hash('your password', PASSWORD_DEFAULT);"

```

Define everything in `middleware.json`:

```
{
  "middleware": [
    {
      "Middlewares\\AccessLog": {
        "autowire": {
          "logger": {
            "class": "Monolog\\Logger",
            "args": [
              "access",
              [
                {
                  "class": "Monolog\\Handler\\StreamHandler",
                  "args": ["../logs/access.log", 200]
                }
              ]
            ]
          }
        }
      }
    },
    {
      "Middlewares\\BasicAuthentication": {
        "autowire": {
          "users": { "admin": "$2y$12$abc123..." }
        },
        "calls": [["verifyHash", []]]
      }
    }
  ]
}
```

And that's it.

> **Note:** Declarative autowire is experimental. The API may change in future versions.

PSR-11 container integration
----------------------------

[](#psr-11-container-integration)

> **This is an optional integration.** Most projects will not need it. If declarative autowire covers your use case, there is nothing to install or configure here.

For projects where middleware share services (loggers, database connections, etc.) and a PSR-11 container is already in use, Curlpit provides `ContainerApplication` – a drop-in replacement for `Application` that resolves middleware from any PSR-11 compatible container.

Install `psr/container` and a compatible container implementation of your choice:

```
composer require psr/container
composer require php-di/php-di   # or symfony/dependency-injection, etc.

```

```
use Curlpit\App\ContainerApplication;
use Curlpit\Core\Emitter;

$app      = new ContainerApplication($responseFactory, $streamFactory, $container);
$response = $app->handle($serverRequest);
(new Emitter())->emit($response);
```

The `middleware.json` flow config stays unchanged. `ContainerApplication` only affects how middleware instances are created.

### Resolution order

[](#resolution-order)

1. **PSR-11 container** – if the container knows the class, it resolves it (interface binding, singletons, full DI power)
2. **Declarative autowire** – if the container does not know the class, Curlpit's built-in JSON-based wiring handles it as usual

This means you can mix both approaches per middleware: use the container for your own middleware, declarative autowire for simpler third-party ones.

### What the container adds over declarative autowire

[](#what-the-container-adds-over-declarative-autowire)

Declarative autowirePSR-11 containerExtra dependenciesNone`psr/container` + a container implInterface → implementation bindingNoYesShared instances (singletons)NoYesWiring lives in`middleware.json`PHP (container definitions)Best forThird-party middleware, simple casesYour own middleware, shared services### Example with PHP-DI

[](#example-with-php-di)

```
use DI\ContainerBuilder;

$builder = new ContainerBuilder();
$builder->addDefinitions([
    // one shared PDO across all middleware
    PDO::class => \DI\factory(fn() => new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass')),

    // interface → implementation binding
    Psr\Log\LoggerInterface::class => \DI\factory(function () {
        $logger = new Monolog\Logger('app');
        $logger->pushHandler(new Monolog\Handler\StreamHandler('../logs/app.log'));
        return $logger;
    }),

    // constructor dependencies resolved automatically from the container
    App\Middleware\AuthMiddleware::class  => \DI\autowire(),
    App\Middleware\AuditMiddleware::class => \DI\autowire(),
]);

$app = new ContainerApplication($responseFactory, $streamFactory, $builder->build());
```

In `middleware.json`, these middleware need no `autowire` block – an empty options object is enough:

```
{
  "middleware": [
    { "Curlpit\\Core\\Middleware\\ErrorHandlerMiddleware": { "debug": false } },
    { "App\\Middleware\\AuthMiddleware": {} },
    { "App\\Middleware\\AuditMiddleware": {} }
  ]
}
```

### Conflict detection

[](#conflict-detection)

If a class is registered in the container **and** has a declarative `autowire` block in `middleware.json`, the container takes precedence and the `autowire` block is ignored. Curlpit will emit an `E_USER_NOTICE` to make the stale configuration visible:

```
[cURLpit] 'App\Middleware\AuthMiddleware' is registered in the container AND has a
declarative autowire block in middleware.json. The container takes precedence; the
autowire block is ignored. Remove it from middleware.json to suppress this notice.

```

This typically surfaces after a refactor where a container registration was added but the JSON `autowire` block was not cleaned up. Remove the `autowire` block from `middleware.json` to resolve it.

Example project
---------------

[](#example-project)

[DBCommander](https://github.com/curlpit/dbcommander) – a Norton Commander-style MySQL manager built on Curlpit.

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance86

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~14 days

Total

3

Last Release

72d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/1241020f73e73cefa543459e6858cbb25c36a4a9b5bbb3e446f2f424430a44b7?d=identicon)[cURLpit](/maintainers/cURLpit)

---

Top Contributors

[![cURLpit](https://avatars.githubusercontent.com/u/89288740?v=4)](https://github.com/cURLpit "cURLpit (18 commits)")

---

Tags

phpmiddlewarepsr-15flowpipeline

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/curlpit-curlpit/health.svg)

```
[![Health](https://phpackages.com/badges/curlpit-curlpit/health.svg)](https://phpackages.com/packages/curlpit-curlpit)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.8k19.1M1.7k](/packages/cakephp-cakephp)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k12](/packages/tempest-framework)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[typo3/cms-core

TYPO3 CMS Core

3312.9M4.8k](/packages/typo3-cms-core)[cakephp/authentication

Authentication plugin for CakePHP

1143.9M95](/packages/cakephp-authentication)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

84735.1k](/packages/flow-php-flow)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
