{"id":13828389,"url":"https://github.com/woohoolabs/harmony","last_synced_at":"2025-12-30T05:48:26.034Z","repository":{"id":732862,"uuid":"23979011","full_name":"woohoolabs/harmony","owner":"woohoolabs","description":"A simple and flexible PHP middleware dispatcher based on PSR-7, PSR-11, and PSR-15","archived":false,"fork":false,"pushed_at":"2023-08-19T21:39:14.000Z","size":812,"stargazers_count":163,"open_issues_count":3,"forks_count":16,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-09-24T02:42:43.553Z","etag":null,"topics":["microframework","middleware-dispatchers","php","psr-11","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/woohoolabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null}},"created_at":"2014-09-12T22:30:53.000Z","updated_at":"2024-06-13T04:10:29.000Z","dependencies_parsed_at":"2024-01-18T05:29:54.102Z","dependency_job_id":"4d2acfa7-73e7-4c8e-a637-99475215f390","html_url":"https://github.com/woohoolabs/harmony","commit_stats":{"total_commits":476,"total_committers":12,"mean_commits":"39.666666666666664","dds":"0.031512605042016806","last_synced_commit":"8728aa7793a43e5a8d4b22036f0aaa1dd7a52612"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woohoolabs%2Fharmony","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woohoolabs%2Fharmony/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woohoolabs%2Fharmony/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woohoolabs%2Fharmony/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/woohoolabs","download_url":"https://codeload.github.com/woohoolabs/harmony/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225492420,"owners_count":17482869,"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":["microframework","middleware-dispatchers","php","psr-11","psr-15","psr-7"],"created_at":"2024-08-04T09:02:44.212Z","updated_at":"2025-12-16T23:09:42.915Z","avatar_url":"https://github.com/woohoolabs.png","language":"PHP","readme":"# Woohoo Labs. Harmony\n\n[![Latest Version on Packagist][ico-version]][link-version]\n[![Software License][ico-license]](LICENSE)\n[![Build Status][ico-build]][link-build]\n[![Coverage Status][ico-coverage]][link-coverage]\n[![Quality Score][ico-code-quality]][link-code-quality]\n[![Total Downloads][ico-downloads]][link-downloads]\n[![Gitter][ico-support]][link-support]\n\n**Woohoo Labs. Harmony is a PSR-15 compatible middleware dispatcher.**\n\nHarmony was born to be a totally flexible and almost invisible framework for your application. That's why Harmony\nsupports the [PSR-7](https://www.php-fig.org/psr/psr-7/), [PSR-11](https://www.php-fig.org/psr/psr-11/), as well as the [PSR-15](https://www.php-fig.org/psr/psr-15/) standards.\n\n## Table of Contents\n\n* [Introduction](#introduction)\n* [Install](#install)\n* [Basic Usage](#basic-usage)\n* [Advanced Usage](#advanced-usage)\n* [Examples](#examples)\n* [Versioning](#versioning)\n* [Change Log](#change-log)\n* [Testing](#testing)\n* [Contributing](#contributing)\n* [Support](#support)\n* [Credits](#credits)\n* [License](#license)\n\n## Introduction\n\n### Rationale\n\nThis blog post explains the idea best why Harmony was started back in 2014: http://www.catonmat.net/blog/frameworks-dont-make-sense/\n\n### Features\n\n- High performance due to Harmony's simplicity\n- High flexibility thanks to the vast middleware ecosystem of PSR-15\n- Full control over HTTP messages via PSR-7\n- Support for many DI Containers via PSR-11 (formerly known as Container-Interop)\n\n### Why Harmony?\n\nThere are a lot of very similar middleware dispatcher libraries out there, like\n[Laminas-Stratigility](https://github.com/laminas/laminas-stratigility),\n[Slim Framework 3](https://www.slimframework.com/docs/concepts/middleware.html) or\n[Relay](http://relayphp.com/). You might ask yourself, what is the purpose of yet another library with the same\nfunctionality?\n\nWe believe Harmony offers two key features which justify its existence:\n\n- It is the most simple library of all. Although simplicity is subjective, one thing is certain: Harmony offers the\nbare minimum functionality of what a library like this would need. That's why Harmony itself fits into a single class of ~140 lines.\n\n- As of version 3, Harmony natively supports the concept of [Conditions](#defining-conditions) which is a rare\nfeature for middleware dispatchers. This eases dealing with a major weakness of the middleware-oriented approach,\nnamely, the ability to invoke middleware conditionally.\n\n### Use cases\n\nCertainly, Harmony won't suit the needs of all projects and teams: this framework works best for an experienced team with a\nlonger term project. Less experienced teams - especially if they have short deadlines - should probably choose a framework\nwith more features - working out-of-the box - in order to speed up development in its initial phase. Harmony's flexibility\nis the most advantageous when your software should be supported for a longer time.\n\n### Concepts\n\nWoohoo Labs. Harmony is built upon two main concepts: middleware, which promote separation of concerns, and common\ninterfaces, making it possible to rely on loosely coupled components.\n\nBy using middleware, you can easily take hands on the course of action of the request-response lifecycle: you can\nauthenticate before routing, do some logging after the response has been sent, or you can even dispatch multiple\nroutes in one request. This all can be achieved because everything in Harmony is a middleware, so the framework\nitself only consists of cc. 140 lines of code. This is why there is no framework-wide configuration, only middleware\ncan be configured. What you do with Harmony depends only on your imagination and needs.\n\nBut middleware must work in cooperation (the router and the dispatcher are particularly tightly coupled to each other).\nThat's why it is also important to provide common interfaces for the distinct components of the framework.\n\nNaturally, we decided to use [PSR-7](https://www.php-fig.org/psr/psr-7/)\nfor modelling the HTTP request and response. In order to facilitate the usage of different DI Containers, we adapted\n[PSR-11 (former Container-Interop)](https://www.php-fig.org/psr/psr-11/)\nwhich is supported by various containers out of the box.\n\n### Middleware interface design\n\nWoohoo Labs. Harmony's middleware interface design is based on the the\n[PSR-15](https://www.php-fig.org/psr/psr-15/) de-facto standard.\n\nIf you want to learn about the specifics of this style, please refer to the following articles which describe the\nvery concept:\n\n- [PSR-15 Meta Document](https://www.php-fig.org/psr/psr-15/meta/)\n- [PSR-15](https://mwop.net/blog/2018-01-23-psr-15.html)\n\n## Install\n\nThe only thing you need before getting started is [Composer](https://getcomposer.org).\n\n### Install a PSR-7 implementation:\n\nBecause Harmony requires a PSR-7 implementation (a package which provides the `psr/http-message-implementation` virtual\npackage), you must install one first. You may use [Laminas Diactoros](https://github.com/laminas/laminas-diactoros) or\nany other library of your preference:\n\n```bash\n$ composer require laminas/laminas-diactoros\n```\n\n### Install Harmony:\n\nTo install the latest version of this library, run the command below:\n\n```bash\n$ composer require woohoolabs/harmony\n```\n\n\u003e Note: The tests and examples won't be downloaded by default. You have to use `composer require woohoolabs/harmony --prefer-source`\nor clone the repository if you need them.\n\nHarmony 6.2+ requires PHP 7.4 at least, but you may use Harmony 6.1 for PHP 7.1+.\n\n### Install the optional dependencies:\n\nIf you want to use the default middleware stack then you have to require the following dependencies too:\n\n```bash\n$ composer require nikic/fast-route # FastRouteMiddleware needs it\n$ composer require laminas/laminas-httphandlerrunner # LaminasEmitterMiddleware needs it\n```\n\n## Basic Usage\n\n### Define your endpoints:\n\nThe following example applies only if you use the\n[default dispatcher middleware](https://github.com/woohoolabs/harmony/blob/master/src/Middleware/DispatcherMiddleware.php).\nThere are two important things to note here: first, each dispatchable endpoint receives a `Psr\\Http\\Message\\ServerRequestInterface`\nand a `Psr\\Http\\Message\\ResponseInterface` object as parameter and the latter is expected to be manipulated and returned. Secondly,\nyou can not only use class methods as endpoints, it is possible to define other callables too (see below in the routing section).\n\n```php\nnamespace App\\Controllers;\n\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass UserController\n{\n    public function getUsers(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface\n    {\n        $users = [\"Steve\", \"Arnie\", \"Jason\", \"Bud\"];\n        $response-\u003egetBody()-\u003ewrite(json_encode($users));\n\n        return $response;\n    }\n\n    public function updateUser(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface\n    {\n        $userId = $request-\u003egetAttribute(\"id\");\n        $userData = $request-\u003egetParsedBody();\n\n        // Updating user...\n\n        return $response;\n    }\n}\n```\n\n### Define your routes:\n\nThe following example applies only if you use the\n[default router middleware](https://github.com/woohoolabs/harmony/blob/master/src/Middleware/FastRouteMiddleware.php)\nwhich is based on [FastRoute](https://github.com/nikic/FastRoute), the library of Nikita Popov. We chose to use it by\ndefault because of its performance and simplicity. You can read more about it\n[in Nikita's blog](https://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html).\n\nLet's add the routes for the aforementioned endpoints to FastRoute:\n\n```php\nuse App\\Controllers\\UserController;\n\n$router = FastRoute\\simpleDispatcher(function (FastRoute\\RouteCollector $r) {\n    // An anonymous function endpoint\n    $r-\u003eaddRoute(\"GET\", \"/me\", function (ServerRequestInterface $request, ResponseInterface $response) {\n            // ...\n    });\n\n    // Class method endpoints\n    $r-\u003eaddRoute(\"GET\", \"/users\", [UserController::class, \"getUsers\"]);\n    $r-\u003eaddRoute(\"POST\", \"/users/{id}\", [UserController::class, \"updateUser\"]);\n});\n```\n\n### Finally, launch the app:\n\n```php\nuse Laminas\\Diactoros\\Response;\nuse Laminas\\Diactoros\\ServerRequestFactory;\nuse Laminas\\HttpHandlerRunner\\Emitter\\SapiEmitter;\nuse WoohooLabs\\Harmony\\Harmony;\nuse WoohooLabs\\Harmony\\Middleware\\DispatcherMiddleware;\nuse WoohooLabs\\Harmony\\Middleware\\FastRouteMiddleware;\nuse WoohooLabs\\Harmony\\Middleware\\LaminasEmitterMiddleware;\n\n$harmony = new Harmony(ServerRequestFactory::fromGlobals(), new Response());\n$harmony\n    -\u003eaddMiddleware(new LaminasEmitterMiddleware(new SapiEmitter()))\n    -\u003eaddMiddleware(new FastRouteMiddleware($router))\n    -\u003eaddMiddleware(new DispatcherMiddleware())\n    -\u003erun();\n```\n\nYou have to register all the prior middleware in order for the framework to function properly:\n- `LaminasEmitterMiddleware` sends the response to the ether via [laminas-httphandlerrunner](https://github.com/laminas/laminas-httphandlerrunner)\n- `FastRouteMiddleware` takes care of routing (`$router`  was configured in the previous step)\n- `DispatcherMiddleware` dispatches a controller which belongs to the request's current route\n\nNote that there is a second optional argument of `Harmony::addMiddleware()` with which you can define the ID of a\nmiddleware (doing so is necessary if you want to call `Harmony::getMiddleware()` somewhere in your code).\n\nOf course, it is completely up to you how you add additional middleware or how you replace them with your own\nimplementations. When you'd like to go live, call `$harmony-\u003erun()`!\n\n## Advanced Usage\n\n### Using invokable class controllers\n\nMost of the time, you will define your endpoints (~controller actions) as regular callables as shown in the\nsection about the default router:\n\n```php\n$router-\u003eaddRoute(\"GET\", \"/users/me\", [\\App\\Controllers\\UserController::class, \"getMe\"]);\n```\n\nNowadays, there is an increasing popularity of controllers containing only one action. In this case it is a general\npractice to implement the `__invoke()` magic method. When following this school of thought, your route definition can be\nsimplified as seen below:\n\n```php\n$router-\u003eaddRoute(\"GET\", \"/users/me\", \\App\\Controllers\\GetMe::class);\n```\n\nNote: In case you use a different router or dispatcher than the default ones, please make sure if the feature is available\nfor you.\n\nIf you are interested in how you could benefit from invokable controllers in the context of the Action-Domain-Responder\npattern, you can find an insightful description in [Paul M. Jones' blog post](http://paul-m-jones.com/archives/6006).\n\n### Using your favourite DI Container with Harmony\n\nThe motivation of creating Woohoo Labs. Harmony was to become able to change every single aspect of the framework.\nThat's why you can use any DI Container you want.\n\nFor this purpose, we chose to build upon [PSR-11](https://www.php-fig.org/psr/psr-11/) - the most widespread common\ninterface for DI Containers - in the built-in `DispatcherMiddleware`.\n\nIt's also important to know that the `DispatcherMiddleware` uses the `BasicContainer` by default. It's nothing more\nthan a very silly DIC which tries to create objects based on their class name (so calling `$basicContainer-\u003eget(Foo::class)`\nwould create a new `Foo` instance).\n\nBut if you provide an argument to the middleware's constructor, you can use your favourite PSR-11 compliant DI Container\ntoo. Let's have a look at an example where one would like to swap `BasicContainer` with [Zen](https://github.com/woohoolabs/zen):\n\n```php\n$container = new MyContainer();\n$harmony-\u003eaddMiddleware(new DispatcherMiddleware($container));\n```\n\n### Creating custom middleware\n\nNew middleware also has to implement the [PSR-15](https://www.php-fig.org/psr/psr-15/) `MiddlewareInterface`. Let's\nsee an example:\n\n```php\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\MiddlewareInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\nuse Psr\\Log\\LoggerInterface;\n\nclass LoggerMiddleware implements MiddlewareInterface\n{\n    private LoggerInterface $logger;\n\n    public function __construct(LoggerInterface $logger)\n    {\n        $this-\u003elogger = $logger;\n    }\n\n    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface\n    {\n        // Perform logging before handling the request\n        $this-\u003elogger-\u003einfo(\"Request needs to be handled\");\n\n        // Invoking the remaining middleware\n        $response = $handler-\u003ehandle($request);\n\n        // Perform logging after the request has been handled\n        $this-\u003elogger-\u003einfo(\"Request was successfully handled\");\n\n        // Return the response\n        return $response;\n    }\n}\n```\n\nAnd when you are ready, attach it to Harmony:\n\n```php\n$harmony-\u003eaddMiddleware(new LoggerMiddleware(new Logger()));\n```\n\nWhat to do if you do not want to invoke the remaining middleware (possibly because of an error)? Then you can simply\nmanipulate and return a response whose \"prototype\" was passed to the middleware in its constructor. You can see this\nin action in the following example:\n\n```php\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\MiddlewareInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\n\nclass AuthenticationMiddleware implements MiddlewareInterface\n{\n    protected string $apiKey;\n    protected ResponseInterface $errorResponsePrototype;\n\n    public function __construct(string $apiKey, ResponseInterface $errorResponsePrototype)\n    {\n        $this-\u003eapiKey = $apiKey;\n        $this-\u003eerrorResponsePrototype = $errorResponsePrototype;\n    }\n\n    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface\n    {\n        // Return Error 401 \"Unauthorized\" if the provided API key doesn't match the expected one\n        if ($request-\u003egetHeader(\"x-api-key\") !== [$this-\u003eapiKey]) {\n            return $this-\u003eerrorResponsePrototype-\u003ewithStatus(401);\n        }\n\n        // Invoke the remaining middleware if authentication was successful\n        return $handler-\u003ehandle($request);\n    }\n}\n```\n\nThen\n\n```php\n$harmony-\u003eaddMiddleware(new AuthenticationMiddleware(\"123\"), new Response());\n```\n\n### Defining conditions\n\nNon-trivial applications often need some kind of branching during the execution of their middleware pipeline. A possible\nuse-case is when they want to perform authentication only for some of their endpoints or when they want to check for a\nCSRF token if the request method is `POST`. With Harmony 2 branching was also easy to handle, but Harmony 3+ helps you to\noptimize the performance of conditional logic in your middleware.\n\nLet's revisit our authentication middleware example from the last section! This time, we only want to authenticate\nendpoints below the `/users` path. In Harmony 2, we had to achieve it with something like this:\n\n```php\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\MiddlewareInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\n\nclass AuthenticationMiddleware implements MiddlewareInterface\n{\n    protected string $securedPath;\n    protected MyAuthenticatorInterface $authenticator;\n    protected ResponseInterface $errorResponsePrototype;\n\n    public function __construct(string $securedPath, MyAuthenticatorInterface $authenticator, ResponseInterface $errorResponsePrototype)\n    {\n        $this-\u003esecuredPath = $securedPath;\n        $this-\u003eauthenticator = $authenticator;\n        $this-\u003eerrorResponsePrototype = $errorResponsePrototype;\n    }\n\n    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface\n    {\n        // Invoke the remaining middleware and cancel authentication if the current URL is for a public endpoint\n        if (substr($request-\u003egetUri()-\u003egetPath(), 0, strlen($this-\u003esecuredPath)) !== $this-\u003esecuredPath) {\n            return $handler-\u003ehandle($request);\n        }\n\n        // Return Error 401 \"Unauthorized\" if authentication fails\n        if ($this-\u003eauthenticator-\u003eauthenticate($request) === false) {\n            return $this-\u003eerrorResponsePrototype-\u003ewithStatusCode(401);\n        }\n\n        // Invoke the remaining middleware otherwise\n        return $handler-\u003ehandle($request);\n    }\n}\n```\n\nAnd finally attach the middleware to Harmony:\n\n```php\n$harmony-\u003eaddMiddleware(new AuthenticationMiddleware(\"/users\", new ApiKeyAuthenticator(\"123\"), new Response()));\n```\n\nYou had to check the current URI inside the middleware and the problem was solved. The downside of doing this is\nthat `AuthenticationMiddleware` and all its dependencies are instantiated for each request even though authentication\nis not needed at all! This can be a major inconvenience if you depend on a big object graph.\n\nIn Harmony 3+, however, you are able to use conditions in order to optimize the number of invoked middleware. In this case\nyou can utilize the built-in `PathPrefixCondition`. You only have to attach it to Harmony:\n\n```php\n$harmony-\u003eaddCondition(\n    new PathPrefixCondition([\"/users\"]),\n    static function (Harmony $harmony) {\n        $harmony-\u003eaddMiddleware(new AuthenticationMiddleware(new ApiKeyAuthenticator(\"123\")));\n    }\n);\n```\n\nThis way, `AuthenticationMiddleware` will only be instantiated when `PathPrefixCondition` evaluates to `true`\n(when the current URI path starts with `/users`). Furthermore, you are able to attach more middleware to Harmony in\nthe anonymous function. They will be executed together, as if they were part of a containing middleware.\n\nHere is the complete list of the built-in conditions:\n\n- [`ExactPathCondition`](https://github.com/woohoolabs/harmony/blob/master/src/Condition/ExactPathCondition.php):\nEvaluates to true if the current URI path exactly matches any of the allowed paths.\n\n- [`PathPrefixCondition`](https://github.com/woohoolabs/harmony/blob/master/src/Condition/PathPrefixCondition.php):\nEvaluates to true if the current URI path starts with any of the allowed path prefixes.\n\n- [`HttpMethodCondition`](https://github.com/woohoolabs/harmony/blob/master/src/Condition/HttpMethodCondition.php):\nEvaluates to true if the current HTTP method matches any of the allowed HTTP methods.\n\n## Examples\n\nIf you want to see a really basic application structure in action, have a look at the\n[examples](https://github.com/woohoolabs/harmony/tree/master/examples). If `docker-compose` and `make` is available on your system,\nthen run the following commands in order to try out the example app:\n\n```bash\ncp .env.dist .env      # You can now edit the settings in the .env file\nmake composer-install  # Install the Composer dependencies\nmake up                # Start the webserver\n```\n\n\u003e If you don't have `make`, you can copy the underlying commands, and directly use them in your terminal.\n\nFinally, the example app is available at `localhost:8080`.\n\n\u003e If you modified the `.env` file, you should change the port to the value of the `HOST_WEB_PORT` variable.\n\nExample URIs:\n- `GET /books/1`\n- `GET /users/1`\n- `GET /me`\n\nWhen you finished your work, simply stop the webserver:\n\n```bash\nmake down\n```\n\nIf the prerequisites are not available for you, you have to set up a webserver on your host, install PHP, as\nwell as the dependencies via `Composer`.\n\n## Versioning\n\nThis library follows [SemVer v2.0.0](https://semver.org/).\n\n## Change Log\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Testing\n\nHarmony has a PHPUnit test suite. To run the tests, run the following command from the project folder:\n\n``` bash\n$ phpunit\n```\n\nAdditionally, you may run `docker-compose up` or `make test` to execute the tests.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Support\n\nPlease see [SUPPORT](SUPPORT.md) for details.\n\n## Credits\n\n- [Máté Kocsis][link-author]\n- [All Contributors][link-contributors]\n\n## License\n\nThe MIT License (MIT). Please see the [License File](LICENSE) for more information.\n\n[ico-version]: https://img.shields.io/packagist/v/woohoolabs/harmony.svg\n[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg\n[ico-build]: https://img.shields.io/github/workflow/status/woohoolabs/harmony/Continuous%20Integration\n[ico-coverage]: https://img.shields.io/codecov/c/github/woohoolabs/harmony\n[ico-code-quality]: https://img.shields.io/scrutinizer/g/woohoolabs/harmony.svg\n[ico-downloads]: https://img.shields.io/packagist/dt/woohoolabs/harmony.svg\n[ico-support]: https://badges.gitter.im/woohoolabs/harmony.svg\n\n[link-version]: https://packagist.org/packages/woohoolabs/harmony\n[link-build]: https://github.com/woohoolabs/harmony/actions\n[link-coverage]: https://codecov.io/gh/woohoolabs/harmony\n[link-code-quality]: https://scrutinizer-ci.com/g/woohoolabs/harmony\n[link-downloads]: https://packagist.org/packages/woohoolabs/harmony\n[link-support]: https://gitter.im/woohoolabs/harmony?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\n[link-author]: https://github.com/kocsismate\n[link-contributors]: ../../contributors\n","funding_links":[],"categories":["PHP","Table of Contents"],"sub_categories":["Zend Framework 3"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoohoolabs%2Fharmony","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwoohoolabs%2Fharmony","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoohoolabs%2Fharmony/lists"}