{"id":20903517,"url":"https://github.com/phly/psr7examples","last_synced_at":"2025-10-05T18:29:58.916Z","repository":{"id":28997531,"uuid":"32524410","full_name":"phly/psr7examples","owner":"phly","description":"PSR-7 stream examples","archived":false,"fork":false,"pushed_at":"2018-09-20T21:56:57.000Z","size":2692,"stargazers_count":47,"open_issues_count":2,"forks_count":4,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-01T18:12:12.174Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-19T13:55:43.000Z","updated_at":"2025-02-23T21:53:53.000Z","dependencies_parsed_at":"2022-07-27T17:18:47.602Z","dependency_job_id":null,"html_url":"https://github.com/phly/psr7examples","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fpsr7examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fpsr7examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fpsr7examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phly%2Fpsr7examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phly","download_url":"https://codeload.github.com/phly/psr7examples/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253877265,"owners_count":21977632,"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-11-18T13:13:52.863Z","updated_at":"2025-10-05T18:29:58.910Z","avatar_url":"https://github.com/phly.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PSR-7 Stream Examples\n\n\u003e :warning: **Archived 2025-08-17**\n\u003e \n\u003e Use at your own risk.\n\n[PSR-7](https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md)\nuses `Psr\\Http\\Message\\StreamableInterface` to represent content for a\nmessage. This has a variety of benefits, as outlined in the specification.\nHowever, for some, the model poses some conceptual challenges:\n\n- What if I want to emit a file on the server, like I might with `fpassthru()`\n  or `stream_copy_to_stream($fileHandle, fopen('php://output'))`?\n- What if I want to use a callback to produce my output?\n- What if I want to use output buffering and/or `echo`/`printf`/etc.\n  directly?\n- What if I want to iterate over a data structure and iteratively output content?\n\nThese patterns are all possible with creative implementations of\n`StreamableInterface`.\n\n- The file [copy-stream.php](public/copy-stream.php) demonstrates how you would\n  emit a file.\n- [CallbackStream](src/CallbackStream.php) and [php-output.php](public/php-output.php)\n  demonstrate using a callback to generate and return content.\n- [CallbackStream](src/CallbackStream.php) and [php-output.php](public/php-output.php)\n  also demonstrate how you might use a callback to allow direct output from your\n  code, without first aggregating it.\n- [IteratorStream](src/IteratorStream.php) and the files [iterator.php](public/iterator.php)\n  and [generator.php](public/generator.php) demonstrate using iterators and\n  generators for creating output.\n\nIn each, the assumption is that the application will short-circuit on receiving\na response as a return value. Most modern frameworks do this already, and it's a\nguiding principle of middleware.\n\nThe code in this repository uses [phly/http](https://github.com/phly/http) as\nthe PSR-7 implementation; any PSR-7 implementation should behave similarly.\n\n## Analyzing the code\n\n### Emitting a file\n\nFor those who are accustomed to using `readfile()`, `fpassthru()` or copying a\nstream into `php://output` via `stream_copy_to_stream()`, PSR-7 will look and\nfeel different. Typically, you will not use the aforementioned techniques when\nbuilding an application to work with PSR-7, as they bypass the HTTP message\nentirely, and delegate it to PHP itself.\n\nThe problem with using these built-in PHP methods is that you cannot test your\ncode as easily, as it now has side-effects. One major reason to adopt PSR-7 is\nif you want to be able to test your web-facing code without worrying about side\neffects. Adopting frameworks or application architectures that work with HTTP\nmessages lets you pass in a request, and make assertions on the response.\n\nIn the case of emitting a file, this means that you will:\n\n- Create a `Stream` instance, passing it the file location.\n- Provide appropriate headers to the response.\n- Provide your stream instance to the response.\n- Return your response.\n\nWhich looks like what we have in [copy-stream.php](public/copy-stream.php):\n\n```php\n$image = __DIR__ . '/cuervo.jpg';\n\nreturn (new Response())\n    -\u003ewithHeader('Content-Type', 'image/jpeg')\n    -\u003ewithHeader('Content-Length', (string) filesize($image))\n    -\u003ewithBody(new Stream($image));\nreturn $response;\n```\n\nThe assumption is that returning a response will bubble out of your application;\nmost modern frameworks do this already, as does middleware. As such, you will\ntypically have minimal additional overhead from the time you create the response\nuntil it's streaming your file back to the client.\n\n### Direct output\n\nJust like the above example, for those accustomed to directly calling `echo`, or\nsending data directly to the `php://output` stream, PSR-7 will feel strange.\nHowever, as noted before as well, these are actions that have side effects that\nact as a barrier to testing and other quality assurance activities.\n\nThere _is_ a way to accomodate these, however, with a little trickery: wrapping any\noutput-emitting code in a callback, and passing this to a callback-enabled\nstream implementation. The [CallbackStream](src/CallbackStream.php) implementation\nin this repo is one potential way to accomplish it.\n\nAs an example, from [php-output.php](public/php-output.php):\n\n```php\n$output = new CallbackStream(function () use ($request) {\n    printf(\"The requested URI was: %s\u003cbr\u003e\\n\", $request-\u003egetUri());\n    return '';\n});\nreturn (new Response())\n    -\u003ewithHeader('Content-Type', 'text/html')\n    -\u003ewithBody($output);\n```\n\nThis has a few benefits over directly emitting output from within your\nweb-facing code:\n\n- We can ensure our headers are sent before emitting output.\n- We can set a non-200 status code if desired.\n- We can test the various aspects of the response separately from the output.\n- We still get the benefits of the output buffer.\n\nAs noted previously, returning a response will generally bubble out of the\napplication immediately, making this a very viable option for emitting output\ndirectly.\n\n(Note: the callback could also aggregate content and return it as a string if\ndesired; I wanted to demonstrate specifically how it can be used to work with\noutput buffering.)\n\n### Iterators and generators\n\nRuby's Rack specification uses an iterable body for response messages, instead\nof a stream. In some situations, such as returning large data sets, this could\nbe tremendously useful. Can PSR-7 accomplish it?\n\nThe answer is, succinctly, yes. The [IteratorStream](src/IteratorStream.php)\nimplementation in this repo is a rough prototype showing how it may work; usage\nwould be as in [iterator.php](public/iterator.php):\n\n```php\n$output = new IteratorStream(new ArrayObject([\n    \"Foo!\u003cbr\u003e\\n\",\n    \"Bar!\u003cbr\u003e\\n\",\n    \"Baz!\u003cbr\u003e\\n\",\n]));\nreturn (new Response())\n    -\u003ewithHeader('Content-Type', 'text/html')\n    -\u003ewithBody($output);\n```\n\nor, with a generator per [generator.php](public/generator.php):\n\n```php\n$generator = function ($count) {\n    while ($count) {\n        --$count;\n        yield(uniqid() . \"\u003cbr\u003e\\n\");\n    }\n};\n\n$output = new IteratorStream($generator(10));\n\nreturn (new Response())\n    -\u003ewithHeader('Content-Type', 'text/html')\n    -\u003ewithBody($output);\n```\n\nThis is a nice approach, as you can iteratively generate the data returned; if\nyou are worried about data overhead from aggregating the data before returning\nit, you can always use `print` or `echo` statements instead of aggregation\nwithin the iterator stream implementation.\n\n## Testing it out\n\nYou can test it out for yourself:\n\n- Clone this repo\n- Run `composer install`\n- Run `cd public ; php -S 0:8080` in the directory, and then browse to\n  `http://localhost:8080/{filename}`, where `{filename}` is one of:\n  - `copy-stream.php`\n  - `generator.php`\n  - `iterator.php`\n  - `php-output.php`\n\n## Improvements\n\nThis was a quick repository built to demonstrate that PSR-7 fulfills these\nscenarios; however, they are far from comprehensive. Some ideas:\n\n- `IteratorStream` could and likely should allow providing a separator, and\n  potentially preamble/postfix for wrapping content.\n- `IteratorStream` and `CallbackStream` could be optimized to emit output\n  directly instead of aggregating + returning, if you are worried about large\n  data sets.\n- `CallbackStream` could cache the contents to allow multiple reads (though\n  using `detach()` would allow it already).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphly%2Fpsr7examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphly%2Fpsr7examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphly%2Fpsr7examples/lists"}