{"id":13669365,"url":"https://github.com/phly/conduit","last_synced_at":"2025-04-07T16:18:54.811Z","repository":{"id":19603015,"uuid":"22853913","full_name":"phly/conduit","owner":"phly","description":"Middleware for PHP","archived":false,"fork":false,"pushed_at":"2015-05-21T17:38:12.000Z","size":962,"stargazers_count":189,"open_issues_count":0,"forks_count":13,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-31T14:13:10.114Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phly.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-11T20:34:32.000Z","updated_at":"2023-07-19T17:44:06.000Z","dependencies_parsed_at":"2022-08-24T14:00:37.813Z","dependency_job_id":null,"html_url":"https://github.com/phly/conduit","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fconduit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fconduit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fconduit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fconduit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phly","download_url":"https://codeload.github.com/phly/conduit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247685628,"owners_count":20979085,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T08:01:11.116Z","updated_at":"2025-04-07T16:18:54.784Z","avatar_url":"https://github.com/phly.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"Conduit\n=======\n\n\u003e ## Abandoned! Or, rather, rebranded!\n\u003e\n\u003e phly/conduit has moved to the zendframework organization as\n\u003e [zend-stratigility](https://github.com/zendframework/zend-stratigility)\n\u003e (from Strata, meaning \"layer,\" and \"agility\").\n\u003e\n\u003e Please use that package instead, and contribute issues and pull requests\n\u003e against it I have closed issues and pull requests against phly/conduit at this\n\u003e time.\n\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phly/conduit/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phly/conduit/?branch=master)\n[![Code Coverage](https://scrutinizer-ci.com/g/phly/conduit/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/phly/conduit/?branch=master)\n[![Scrutinizer Build Status](https://scrutinizer-ci.com/g/phly/conduit/badges/build.png?b=master)](https://scrutinizer-ci.com/g/phly/conduit/build-status/master)\n\nConduit is a port of [Sencha Connect](https://github.com/senchalabs/connect) to PHP. It allows you to build applications out of _middleware_.\n\nInstallation and Requirements\n-----------------------------\n\nInstall this library using composer:\n\n```console\n$ composer require phly/http phly/conduit\n```\n\nConduit has the following dependencies (which are managed by Composer):\n\n- `psr/http-message`, which provides the interfaces specified in [PSR-7](http://www.php-fig.org/psr/psr-7), and type-hinted against in this package. In order to use Conduit, you will need an implementation of PSR-7; one such package is [phly/http](https://github.com/phly/http) (and hence the reference to it in the install line above).\n- `zendframework/zend-escaper`, used by the `FinalHandler` for escaping error messages prior to passing them to the response.\n\nYou can provide your own request and response implementations if desired as long as they implement the PSR HTTP message interfaces.\n\nUsage\n-----\n\nCreating an application consists of 3 steps:\n\n- Create middleware or a middleware pipeline\n- Create a server, using the middleware\n- Instruct the server to listen for a request\n\n```php\nuse Phly\\Conduit\\MiddlewarePipe;\nuse Phly\\Http\\Server;\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$app    = new MiddlewarePipe();\n$server = Server::createServer($app,\n  $_SERVER,\n  $_GET,\n  $_POST,\n  $_COOKIE,\n  $_FILES\n);\n$server-\u003elisten();\n```\n\nThe above example is useless by itself until you pipe middleware into the application.\n\nMiddleware\n----------\n\nWhat is middleware?\n\nMiddleware is code that exists between the request and response, and which can take the incoming request, perform actions based on it, and either complete the response or pass delegation on to the next middleware in the queue.\n\n```php\nuse Phly\\Conduit\\MiddlewarePipe;\nuse Phly\\Http\\Server;\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n$app    = new MiddlewarePipe();\n$server = Server::createServer($app, $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);\n\n// Landing page\n$app-\u003epipe('/', function ($req, $res, $next) {\n    if ($req-\u003egetUri()-\u003egetPath() !== '/') {\n        return $next($req, $res);\n    }\n    return $res-\u003eend('Hello world!');\n});\n\n// Another page\n$app-\u003epipe('/foo', function ($req, $res, $next) {\n    return $res-\u003eend('FOO!');\n});\n\n$server-\u003elisten();\n```\n\nIn the above example, we have two examples of middleware. The first is a landing page, and listens at the path `/`. If the path is an exact match, it completes the response. If it is not, it delegates to the next middleware in the stack. The second middleware matches on the path `/foo` -- meaning it will match `/foo`, `/foo/`, and any path beneath. In that case, it will complete the response with its own message. If no paths match at this point, a \"final handler\" is composed by default to report 404 status.\n\nSo, concisely put, _middleware are PHP callables that accept a request and response object, and do something with it_.\n\nMiddleware can decide more processing can be performed by calling the `$next` callable that is passed as the third argument. With this paradigm, you can build a workflow engine for handling requests -- for instance, you could have middleware perform the following:\n\n- Handle authentication details\n- Perform content negotiation\n- Perform HTTP negotiation\n- Route the path to a more appropriate, specific handler\n\nEach middleware can itself be middleware, and can attach to specific paths -- allowing you to mix and match applications under a common domain. As an example, you could put API middleware next to middleware that serves its documentation, next to middleware that serves files, and segregate each by URI:\n\n```php\n$app-\u003epipe('/api', $apiMiddleware);\n$app-\u003epipe('/docs', $apiDocMiddleware);\n$app-\u003epipe('/files', $filesMiddleware);\n```\n\nThe handlers in each middleware attached this way will see a URI with that path segment stripped -- allowing them to be developed separately and re-used under any path you wish.\n\nWithin Conduit, middleware can be:\n\n- Any PHP callable that accepts, minimally, a [PSR-7](https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md) request and a response (in that order), and, optionally, a callable (for invoking the next middleware in the queue, if any).\n- An object implementing `Phly\\Conduit\\MiddlewareInterface`. `Phly\\Conduit\\MiddlewarePipe` implements this interface.\n\nError Handlers\n--------------\n\nTo handle errors, you can write middleware that accepts **exactly** four arguments:\n\n```php\nfunction ($error, $request, $response, $next) { }\n```\n\nAlternately, you can implement `Phly\\Conduit\\ErrorMiddlewareInterface`.\n\nWhen using `MiddlewarePipe`, as the queue is executed, if `$next()` is called with an argument, or if an exception is thrown, middleware will iterate through the queue until the first such error handler is found. That error handler can either complete the request, or itself call `$next()`. **Error handlers that call `$next()` SHOULD call it with the error it received itself, or with another error.**\n\nError handlers are usually attached at the end of middleware, to prevent attempts at executing non-error-handling middleware, and to ensure they can intercept errors from any other handlers.\n\nCreating Middleware\n-------------------\n\nTo create middleware, write a callable capable of receiving minimally a request and a response object, and optionally a callback to call the next in the chain.  In your middleware, you can handle as much or as little of the request as you want -- including delegating to other middleware. If your middleware accepts a third argument, `$next`, if it is unable to complete the request, or allows further processing, it can call it to return handling to the parent middleware.\n\nAs an example, consider the following middleware which will use an external router to map the incoming request path to a handler; if unable to map the request, it returns processing to the next middleware.\n\n```php\nfunction ($req, $res, $next) use ($router) {\n    $path = $req-\u003egetUri()-\u003egetPath();\n\n    // Route the path\n    $route = $router-\u003eroute($path);\n    if (! $route) {\n        return $next($req, $res);\n    }\n\n    $handler = $route-\u003egetHandler();\n    return $handler($req, $res, $next);\n}\n```\n\nMiddleware written in this way can be any of the following:\n\n- Closures (as shown above)\n- Functions\n- Static class methods\n- PHP array callbacks (e.g., `[ $dispatcher, 'dispatch' ]`, where `$dispatcher` is a class instance)\n- Invokable PHP objects (i.e., instances of classes implementing `__invoke()`)\n- Objects implementing `Phly\\Conduit\\MiddlewareInterface` (including `Phly\\Conduit\\MiddlewarePipe`)\n\nIn all cases, if you wish to implement typehinting, the signature is:\n\n```php\nfunction (\n    Psr\\Http\\Message\\ServerRequestInterface $request,\n    Psr\\Http\\Message\\ResponseInterface $response,\n    callable $next = null\n) {\n}\n```\n\nThe implementation Conduit offers also allows you to write specialized error handler middleware. The signature is the same as for normal middleware, except that it expects an additional argument prepended to the signature, `$error`.  (Alternately, you can implement `Phly\\Conduit\\ErrorMiddlewareInterface`.) The signature is:\n\n```php\nfunction (\n    $error, // Can be any type\n    Psr\\Http\\Message\\ServerRequestInterface $request,\n    Psr\\Http\\Message\\ResponseInterface $response,\n    callable $next\n) {\n}\n```\n\nExecuting and composing middleware\n----------------------------------\n\nThe easiest way to execute middleware is to write closures and attach them to a `Phly\\Conduit\\MiddlewarePipe` instance. You can nest `MiddlewarePipe` instances to create groups of related middleware, and attach them using a base path so they only execute if that path is matched.\n\n```php\n$api = new MiddlewarePipe();  // API middleware collection\n$api-\u003epipe(/* ... */);        // repeat as necessary\n\n$app = new MiddlewarePipe();  // Middleware representing the application\n$app-\u003epipe('/api', $api);     // API middleware attached to the path \"/api\"\n```\n\n\nAnother approach is to extend the `Phly\\Conduit\\MiddlewarePipe` class itself -- particularly if you want to allow attaching other middleware to your own middleware. In such a case, you will generally override the `__invoke()` method to perform any additional logic you have, and then call on the parent in order to iterate through your stack of middleware:\n\n```php\nuse Phly\\Conduit\\MiddlewarePipe;\nuse Psr\\Http\\Message\\ServerRequestInterface as Request;\nuse Psr\\Http\\Message\\ResponseInterface as Response;\n\nclass CustomMiddleware extends MiddlewarePipe\n{\n    public function __invoke(Request $request, Response $response, callable $next = null)\n    {\n        // perform some work...\n\n        // delegate to parent\n        parent::__invoke($request, $response, $next);\n\n        // maybe do more work?\n    }\n}\n```\n\nAnother approach using this method would be to override the constructor to add in specific middleware, perhaps using configuration provided. In this case, make sure to also call `parent::__construct()` to ensure the middleware queue is initialized; I recommend doing this as the first action of the method.\n\n```php\nuse Phly\\Conduit\\MiddlewarePipe;\n\nclass CustomMiddleware extends MiddlewarePipe\n{\n    public function __construct($configuration)\n    {\n        parent::__construct();\n\n        // do something with configuration ...\n\n        // attach some middleware ...\n\n        $this-\u003epipe(/* some middleware */);\n    }\n}\n```\n\nThese approaches are particularly suited for cases where you may want to implement a specific workflow for an application segment using existing middleware, but do not necessarily want that middleware applied to all requests in the application.\n\nAPI\n---\n\nThe following make up the primary API of Conduit.\n\n### Middleware\n\n`Phly\\Conduit\\MiddlewarePipe` is the primary application interface, and has been discussed previously. Its API is:\n\n```php\nclass MiddlewarePipe implements MiddlewareInterface\n{\n    public function pipe($path, $middleware = null);\n    public function __invoke(\n        Psr\\Http\\Message\\ServerRequestInterface $request = null,\n        Psr\\Http\\Message\\ResponseInterface $response = null,\n        callable $out = null\n    );\n}\n```\n\n`pipe()` takes up to two arguments. If only one argument is provided, `$middleware` will be assigned that value, and `$path` will be re-assigned to the value `/`; this is an indication that the `$middleware` should be invoked for any path. If `$path` is provided, the `$middleware` will only be executed for that path and any subpaths.\n\nMiddleware is executed in the order in which it is piped to the `MiddlewarePipe` instance.\n\n`__invoke()` is itself middleware. If `$out` is not provided, an instance of\n`Phly\\Conduit\\FinalHandler` will be created, and used in the event that the pipe\nstack is exhausted. The callable should use the same signature as `Next()`:\n\n```php\nfunction (\n    Psr\\Http\\Message\\ServerRequestInterface $request,\n    Psr\\Http\\Message\\ResponseInterface $response,\n    $err = null\n) {\n}\n```\n\nInternally, `MiddlewarePipe` creates an instance of `Phly\\Conduit\\Next`, feeding it its queue, executes it, and returns a response.\n\n### Next\n\n`Phly\\Conduit\\Next` is primarily an implementation detail of middleware, and exists to allow delegating to middleware registered later in the stack.\n\nBecause `Psr\\Http\\Message`'s interfaces are immutable, if you make changes to your Request and/or Response instances, you will have new instances, and will need to make these known to the next middleware in the chain. `Next` expects these arguments for every invocation. Additionally, if an error condition has occurred, you may pass an optional third argument, `$err`, representing the error condition.\n\n```php\nclass Next\n{\n    public function __invoke(\n        Psr\\Http\\Message\\ServerRequestInterface $request,\n        Psr\\Http\\Message\\ResponseInterface $response,\n        $err = null\n    );\n}\n```\n\nYou should **always** either capture or return the return value of `$next()` when calling it in your application. The expected return value is a response instance, but if it is not, you may want to return the response provided to you.\n\nAs examples:\n\n#### Providing an altered request:\n\n```php\nfunction ($request, $response, $next) use ($bodyParser)\n{\n    $bodyParams = $bodyParser($request);\n    return $next(\n        $request-\u003ewithBodyParams($bodyParams), // Next will pass the new\n        $response                              // request instance\n    );\n}\n```\n\n#### Providing an altered response:\n\n```php\nfunction ($request, $response, $next)\n{\n    $updated = $response-\u003eaddHeader('Cache-Control', [\n        'public',\n        'max-age=18600',\n        's-maxage=18600',\n    ]);\n    return $next(\n        $request,\n        $updated\n    );\n}\n```\n\n#### Providing both an altered request and response:\n\n```php\nfunction ($request, $response, $next) use ($bodyParser)\n{\n    $updated = $response-\u003eaddHeader('Cache-Control', [\n        'public',\n        'max-age=18600',\n        's-maxage=18600',\n    ]);\n    return $next(\n        $request-\u003ewithBodyParams($bodyParser($request)),\n        $updated\n    );\n}\n```\n\n#### Returning a response to complete the request\n\nIf you have no changes to the response, and do not want further middleware in the pipeline to execute, do not call `$next()` and simply return from your middleware. However, it's almost always better and more predictable to return the response instance, as this will ensure it propagates back up to all callers.\n\n```php\nfunction ($request, $response, $next)\n{\n    $response = $response-\u003eaddHeader('Cache-Control', [\n        'public',\n        'max-age=18600',\n        's-maxage=18600',\n    ]);\n    return $response;\n}\n```\n\nOne caveat: if you are in a nested middleware or not the first in the stack, all parent and/or previous middleware must also call `return $next(/* ... */)` for this to work correctly.\n\nAs such, _I recommend always returning `$next()` when invoking it in your middleware_:\n\n```php\nreturn $next(/* ... */);\n```\n\nAnd, if not calling `$next()`, returning the response instance:\n\n```php\nreturn $response\n```\n\n#### Raising an error condition\n\nTo raise an error condition, pass a non-null value as the third argument to `$next()`:\n\n```php\nfunction ($request, $response, $next)\n{\n    try {\n        // try some operation...\n    } catch (Exception $e) {\n        return $next($request, $response, $e); // Next registered error middleware will be invoked\n    }\n}\n```\n\n### FinalHandler\n\n`Phly\\Conduit\\FinalHandler` is a default implementation of middleware to execute when the stack exhausts itself. It expects three arguments when invoked: a request instance, a response instance, and an error condition (or `null` for no error). It returns a response.\n\n`FinalHandler` allows an optional argument during instantiation, `$options`, an array of options with which to configure itself. These options currently include:\n\n- `env`, the application environment. If set to \"production\", no stack traces will be provided.\n- `onerror`, a callable to execute if an error is passed when `FinalHandler` is invoked. The callable is invoked with the error (which will be `null` in the absence of an error), the request, and the response, in that order.\n\n### HTTP Messages\n\n#### Phly\\Conduit\\Http\\Request\n\n`Phly\\Conduit\\Http\\Request` acts as a decorator for a `Psr\\Http\\Message\\ServerRequestInterface` instance. The primary reason is to allow composing middleware such that you always have access to the original request instance.\n\nAs an example, consider the following:\n\n```php\n$app1 = new Middleware();\n$app1-\u003epipe('/foo', $fooCallback);\n\n$app2 = new Middleware();\n$app2-\u003epipe('/root', $app1);\n\n$server = Server::createServer($app2 /* ... */);\n```\n\nIn the above, if the URI of the original incoming request is `/root/foo`, what `$fooCallback` will receive is a URI with a past consisting of only `/foo`. This practice ensures that middleware can be nested safely and resolve regardless of the nesting level.\n\nIf you want access to the full URI — for instance, to construct a fully qualified URI to your current middleware — `Phly\\Conduit\\Http\\Request` contains a method, `getOriginalRequest()`, which will always return the original request provided to the application:\n\n```php\nfunction ($request, $response, $next)\n{\n    $location = $request-\u003egetOriginalRequest()-\u003egetUri()-\u003egetPath() . '/[:id]';\n    $response = $response-\u003esetHeader('Location', $location);\n    $response = $response-\u003esetStatus(302);\n    return $response;\n}\n```\n\n#### Phly\\Conduit\\Http\\Response\n\n`Phly\\Conduit\\Http\\Response` acts as a decorator for a `Psr\\Http\\Message\\ResponseInterface` instance, and also implements `Phly\\Conduit\\Http\\ResponseInterface`, which provides the following convenience methods:\n\n- `write()`, which proxies to the `write()` method of the composed response stream.\n- `end()`, which marks the response as complete; it can take an optional argument, which, when provided, will be passed to the `write()` method. Once `end()` has been called, the response is immutable.\n- `isComplete()` indicates whether or not `end()` has been called.\n\nAdditionally, it provides access to the original response created by the server via the method `getOriginalResponse()`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphly%2Fconduit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphly%2Fconduit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphly%2Fconduit/lists"}