{"id":24016346,"url":"https://github.com/decodelabs/harvest","last_synced_at":"2025-04-15T14:07:46.263Z","repository":{"id":204578629,"uuid":"711934355","full_name":"decodelabs/harvest","owner":"decodelabs","description":"PSR-15 HTTP stack without the mess","archived":false,"fork":false,"pushed_at":"2025-04-14T08:47:57.000Z","size":169,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2025-04-15T14:07:28.363Z","etag":null,"topics":["http","php","psr-15","psr-7"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"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/decodelabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-30T13:21:43.000Z","updated_at":"2025-04-14T08:48:01.000Z","dependencies_parsed_at":"2023-11-28T09:29:50.241Z","dependency_job_id":"7891e61d-9b94-486f-a1e8-d61ce311d4b9","html_url":"https://github.com/decodelabs/harvest","commit_stats":null,"previous_names":["decodelabs/harvest"],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decodelabs%2Fharvest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decodelabs%2Fharvest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decodelabs%2Fharvest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/decodelabs%2Fharvest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/decodelabs","download_url":"https://codeload.github.com/decodelabs/harvest/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249085438,"owners_count":21210267,"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":["http","php","psr-15","psr-7"],"created_at":"2025-01-08T08:48:59.670Z","updated_at":"2025-04-15T14:07:46.256Z","avatar_url":"https://github.com/decodelabs.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Harvest\n\n[![PHP from Packagist](https://img.shields.io/packagist/php-v/decodelabs/harvest?style=flat)](https://packagist.org/packages/decodelabs/harvest)\n[![Latest Version](https://img.shields.io/packagist/v/decodelabs/harvest.svg?style=flat)](https://packagist.org/packages/decodelabs/harvest)\n[![Total Downloads](https://img.shields.io/packagist/dt/decodelabs/harvest.svg?style=flat)](https://packagist.org/packages/decodelabs/harvest)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/decodelabs/harvest/integrate.yml?branch=develop)](https://github.com/decodelabs/harvest/actions/workflows/integrate.yml)\n[![PHPStan](https://img.shields.io/badge/PHPStan-enabled-44CC11.svg?longCache=true\u0026style=flat)](https://github.com/phpstan/phpstan)\n[![License](https://img.shields.io/packagist/l/decodelabs/harvest?style=flat)](https://packagist.org/packages/decodelabs/harvest)\n\n### PSR-15 HTTP stack without the mess\n\nHarvest provides a unified PSR-15 HTTP stack with a simple, expressive API on top of PHP Fibers to avoid common pitfalls of other PSR-15 implementations such as call stack size, memory usage and Middleware traversal.\n\n---\n\n## Installation\n\nInstall via Composer:\n\n```bash\ncomposer require decodelabs/harvest\n```\n\n## Usage\n\nHarvest provides the full PSR-15 stack, including Request, Response, Middleware and Handler interfaces.\n\n```php\nuse DecodeLabs\\Harvest;\nuse DecodeLabs\\Harvest\\Dispatcher;\nuse DecodeLabs\\Harvest\\Middleware\\ContentSecurityPolicy;\n\n// Create a Dispatcher\n$dispatcher = new Dispatcher(\n    $myPsrContainer // Ideally initialize with a PSR-11 container\n);\n\n// Add middleware\n$dispatcher-\u003eadd(\n    'ErrorHandler', // Resolve by name via container / Archetype\n\n    new ContentSecurityPolicy(), // Add middleware instance\n\n    function($request, $handler) {\n        // Add middleware callback\n        // $handler is the next middleware in the stack\n        // $request is the current request\n\n        // Return a response\n        return Harvest::text('Hello World!');\n    }\n);\n\n$request = Harvest::createRequestFromEnvironment();\n$response = $dispatcher-\u003edispatch($request);\n```\n\nString names passed to the Dispatcher will resolve via the optional PSR Container and then Archetype which has a default mapping for \u003ccode\u003eDecodeLabs\\Harvest\\Middleware\u003c/code\u003e but can easily be extended with:\n\n```php\nuse DecodeLabs\\Archetype;\nuse DecodeLabs\\Harvest\\Middleware;\n\nArchetype::map(Middleware::class, MyMiddlewareNamespace::class);\n```\n\n### Fibers\n\nHarvest uses PHP Fibers to _flatten_ the call stack within the dispatch loop - this makes for considerably less _noise_ when debugging and understanding Exception call stacks.\n\nInstead of a call stack that grows by at least 2 frames for every Middleware instance in the queue (which gets problematic very quickly), Harvest utilises the flexbility of Fibers to break out of the stack at each call to the _next_ HTTP handler and effectively run each Middleware as if it were in a flat list, but without breaking Exception handling or any of the semantics of stacking the Middleware contexts.\n\n### Transports\n\nOnce a Response has been generated, you can then use an instance of a Harvest \u003ccode\u003eTransport\u003c/code\u003e to send it to the client.\n\nHarvest currently provides a Generic Transport implementation that uses PHP's built in header and output stream functions.\n\n```php\nuse DecodeLabs\\Harvest;\n\n$transport = Harvest::createTransport(\n    // $name - a null name will default to the Generic transport\n);\n\n$transport-\u003esendResponse(\n    $request, $response\n);\n\nexit;\n```\n\n### Responses\n\nHarvest provides easy shortcuts for creating Response instances:\n\n```php\nuse DecodeLabs\\Harvest;\n\n$text = Harvest::text('Hello World!'); // Text\n\n$customText = Harvest::text('Hello World!', 201, [\n    'Custom-Header' =\u003e 'header-value'\n]);\n\n$html = Harvest::html('\u003ch1\u003eHello World!\u003c/h1\u003e'); // HTML\n\n$json = Harvest::json([\n    'whatever-data' =\u003e 'Hello World!'\n]); // JSON\n\n$xml = Harvest::xml($xmlString); // XML\n\n$redirect = Harvest::redirect('/some/other/path'); // Redirect\n\n$file = Harvest::stream('/path/to/file'); // Stream\n\n$resource = Harvest::stream(Harvest::createStreamFromResource($resource)); // Stream\n\n$generator = Harvest::generator(function() {\n    yield 'Custom content';\n    yield ' can be written';\n    yield ' from a generator';\n}, 200, [\n    'Content-Type' =\u003e 'text/plain'\n]);\n```\n\n## Licensing\n\nHarvest is licensed under the MIT License. See [LICENSE](./LICENSE) for the full license text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecodelabs%2Fharvest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdecodelabs%2Fharvest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdecodelabs%2Fharvest/lists"}