├── .coveralls.yml ├── .docheader ├── .gitignore ├── .php_cs ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build ├── .gitignore └── logs │ └── .gitignore ├── composer.json ├── config └── prooph_middleware.config.php ├── docs ├── book │ ├── factories.md │ ├── middleware.md │ └── zend_expressive.md └── bookdown.json ├── phpunit.xml.dist ├── src ├── CommandMiddleware.php ├── Container │ ├── AbstractMiddlewareFactory.php │ ├── CommandMiddlewareFactory.php │ ├── EventMiddlewareFactory.php │ ├── MessageMiddlewareFactory.php │ └── QueryMiddlewareFactory.php ├── EventMiddleware.php ├── Exception │ ├── ExceptionInterface.php │ ├── InvalidArgumentException.php │ └── RuntimeException.php ├── MessageMiddleware.php ├── MetadataGatherer.php ├── NoopMetadataGatherer.php ├── QueryMiddleware.php └── Response │ └── ResponseStrategy.php └── tests ├── CommandMiddlewareTest.php ├── Container ├── CommandMiddlewareFactoryTest.php ├── EventMiddlewareFactoryTest.php ├── MessageMiddlewareFactoryTest.php ├── QueryMiddlewareFactoryTest.php └── StubMetadataGatherer.php ├── EventMiddlewareTest.php ├── MessageMiddlewareTest.php ├── NoopMetadataGathererTest.php └── QueryMiddlewareTest.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | # for php-coveralls 2 | service_name: travis-ci 3 | coverage_clover: build/logs/clover.xml 4 | -------------------------------------------------------------------------------- /.docheader: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of prooph/psr7-middleware. 3 | * (c) 2016-%year% prooph software GmbH 4 | * (c) 2016-%year% Sascha-Oliver Prolic 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | docs/html 4 | .idea 5 | .php_cs.cache 6 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | getFinder()->in(__DIR__); 5 | 6 | $cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; 7 | 8 | $config->setCacheFile($cacheDir . '/.php_cs.cache'); 9 | 10 | return $config; 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | fast_finish: true 5 | include: 6 | - php: 7.1 7 | env: 8 | - DEPENDENCIES="" 9 | - EXECUTE_CS_CHECK=true 10 | - TEST_COVERAGE=true 11 | - php: 7.1 12 | env: 13 | - DEPENDENCIES="--prefer-lowest --prefer-stable" 14 | - php: 7.2 15 | env: 16 | - DEPENDENCIES="" 17 | - php: 7.2 18 | env: 19 | - DEPENDENCIES="--prefer-lowest --prefer-stable" 20 | 21 | cache: 22 | directories: 23 | - $HOME/.composer/cache 24 | - $HOME/.php-cs-fixer 25 | - $HOME/.local 26 | 27 | before_script: 28 | - mkdir -p "$HOME/.php-cs-fixer" 29 | - phpenv config-rm xdebug.ini 30 | - composer self-update 31 | - composer update --prefer-dist $DEPENDENCIES 32 | 33 | script: 34 | - if [[ $TEST_COVERAGE == 'true' ]]; then php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml; else ./vendor/bin/phpunit; fi 35 | - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run; fi 36 | 37 | after_success: 38 | - if [[ $TEST_COVERAGE == 'true' ]]; then php vendor/bin/coveralls -v; fi 39 | 40 | notifications: 41 | webhooks: 42 | urls: 43 | - https://webhooks.gitter.im/e/61c75218816eebde4486 44 | on_success: change # options: [always|never|change] default: always 45 | on_failure: always # options: [always|never|change] default: always 46 | on_start: never # options: [always|never|change] default: always 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.8](https://github.com/prooph/psr7-middleware/tree/v0.8) 4 | 5 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.7...v0.8) 6 | 7 | **Implemented enhancements:** 8 | 9 | - add tests [\#33](https://github.com/prooph/psr7-middleware/pull/33) ([prolic](https://github.com/prolic)) 10 | - test php 7.2 on travis [\#32](https://github.com/prooph/psr7-middleware/pull/32) ([prolic](https://github.com/prolic)) 11 | 12 | **Closed issues:** 13 | 14 | - Interop-Config classed as optional dependency but is required. [\#30](https://github.com/prooph/psr7-middleware/issues/30) 15 | - http-middleware-compatibility [\#27](https://github.com/prooph/psr7-middleware/issues/27) 16 | 17 | ## [v0.7](https://github.com/prooph/psr7-middleware/tree/v0.7) (2017-10-26) 18 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.6...v0.7) 19 | 20 | **Implemented enhancements:** 21 | 22 | - Improvement/psr15 compatibility [\#28](https://github.com/prooph/psr7-middleware/pull/28) ([basz](https://github.com/basz)) 23 | 24 | ## [v0.6](https://github.com/prooph/psr7-middleware/tree/v0.6) (2017-05-31) 25 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.5...v0.6) 26 | 27 | **Implemented enhancements:** 28 | 29 | - Use http-interop for middlewares PSR-15 [\#21](https://github.com/prooph/psr7-middleware/issues/21) 30 | - Get message name from request attribute [\#25](https://github.com/prooph/psr7-middleware/pull/25) ([codeliner](https://github.com/codeliner)) 31 | 32 | **Merged pull requests:** 33 | 34 | - Message defaults [\#26](https://github.com/prooph/psr7-middleware/pull/26) ([codeliner](https://github.com/codeliner)) 35 | 36 | ## [v0.5](https://github.com/prooph/psr7-middleware/tree/v0.5) (2017-04-11) 37 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.4...v0.5) 38 | 39 | **Implemented enhancements:** 40 | 41 | - Implement PSR-15 [\#24](https://github.com/prooph/psr7-middleware/pull/24) ([bgaleotti](https://github.com/bgaleotti)) 42 | 43 | **Closed issues:** 44 | 45 | - Use $next instead of return $response so other middlewares can be executed [\#11](https://github.com/prooph/psr7-middleware/issues/11) 46 | 47 | **Merged pull requests:** 48 | 49 | - use interface [\#20](https://github.com/prooph/psr7-middleware/pull/20) ([basz](https://github.com/basz)) 50 | - Composer [\#18](https://github.com/prooph/psr7-middleware/pull/18) ([basz](https://github.com/basz)) 51 | - update to use psr\container [\#17](https://github.com/prooph/psr7-middleware/pull/17) ([basz](https://github.com/basz)) 52 | 53 | ## [v0.4](https://github.com/prooph/psr7-middleware/tree/v0.4) (2016-12-13) 54 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.3...v0.4) 55 | 56 | **Implemented enhancements:** 57 | 58 | - Updates [\#15](https://github.com/prooph/psr7-middleware/pull/15) ([prolic](https://github.com/prolic)) 59 | - Added tests for gatherers and also removed deprecated call `$this-\>setExpectedException\(\)` [\#12](https://github.com/prooph/psr7-middleware/pull/12) ([bweston92](https://github.com/bweston92)) 60 | 61 | **Merged pull requests:** 62 | 63 | - Add license header checker [\#13](https://github.com/prooph/psr7-middleware/pull/13) ([malukenho](https://github.com/malukenho)) 64 | 65 | ## [v0.3](https://github.com/prooph/psr7-middleware/tree/v0.3) (2016-05-08) 66 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.2...v0.3) 67 | 68 | **Merged pull requests:** 69 | 70 | - Prepare 0.3 Release [\#10](https://github.com/prooph/psr7-middleware/pull/10) ([codeliner](https://github.com/codeliner)) 71 | - update factories to interop-config 1.0 [\#9](https://github.com/prooph/psr7-middleware/pull/9) ([sandrokeil](https://github.com/sandrokeil)) 72 | 73 | ## [v0.2](https://github.com/prooph/psr7-middleware/tree/v0.2) (2016-03-17) 74 | [Full Changelog](https://github.com/prooph/psr7-middleware/compare/v0.1...v0.2) 75 | 76 | **Closed issues:** 77 | 78 | - Add possibility to get metadata from request [\#6](https://github.com/prooph/psr7-middleware/issues/6) 79 | - coveralls.io hook not working [\#2](https://github.com/prooph/psr7-middleware/issues/2) 80 | 81 | **Merged pull requests:** 82 | 83 | - Fix broken tests [\#8](https://github.com/prooph/psr7-middleware/pull/8) ([codeliner](https://github.com/codeliner)) 84 | - introduce a MetadataGatherer concept [\#7](https://github.com/prooph/psr7-middleware/pull/7) ([basz](https://github.com/basz)) 85 | - fix coveralls config [\#5](https://github.com/prooph/psr7-middleware/pull/5) ([prolic](https://github.com/prolic)) 86 | - Remove src\_dir config entry [\#4](https://github.com/prooph/psr7-middleware/pull/4) ([codeliner](https://github.com/codeliner)) 87 | - Add satooshi/php-coveralls to require dev [\#3](https://github.com/prooph/psr7-middleware/pull/3) ([codeliner](https://github.com/codeliner)) 88 | 89 | ## [v0.1](https://github.com/prooph/psr7-middleware/tree/v0.1) (2016-02-09) 90 | **Merged pull requests:** 91 | 92 | - add command, event, query and message middleware [\#1](https://github.com/prooph/psr7-middleware/pull/1) ([sandrokeil](https://github.com/sandrokeil)) 93 | 94 | 95 | 96 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2017, prooph software GmbH 2 | Copyright (c) 2015-2017, Sascha-Oliver Prolic 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of the prooph software GmbH nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PSR-7 middleware for prooph components 2 | Consume prooph messages (commands, queries and events) with a PSR-7 middleware. Please refer to the 3 | [service-bus component documentation](https://github.com/prooph/service-bus) to see how to configure the different bus 4 | types. 5 | 6 | ## Deprecated 7 | Please use [prooph/http-middleware](https://github.com/prooph/http-middleware) instead. 8 | 9 | [![Build Status](https://travis-ci.org/prooph/psr7-middleware.svg?branch=master)](https://travis-ci.org/prooph/psr7-middleware) 10 | [![Coverage Status](https://coveralls.io/repos/github/prooph/psr7-middleware/badge.svg?branch=master)](https://coveralls.io/github/prooph/psr7-middleware?branch=master) 11 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/prooph/improoph) 12 | 13 | ## Middleware 14 | For every bus system a middleware exists and one Middleware to rule them all. 15 | 16 | * `CommandMiddleware`: Dispatches the message data to the command bus system 17 | * `QueryMiddleware`: Dispatches the message data to the query bus system 18 | * `EventMiddleware`: Dispatches the message data to the event bus system 19 | * `MessageMiddleware`: Dispatches the message data to the appropriated bus system depending on message type 20 | 21 | ## Installation 22 | You can install `prooph/psr7-middleware` via Composer by adding `"prooph/psr7-middleware": "^0.1"` 23 | as requirement to your composer.json. 24 | 25 | ## Documentation 26 | 27 | Documentation is [in the docs tree](docs/book/), and can be compiled using [bookdown](http://bookdown.io). 28 | 29 | ```console 30 | $ php ./vendor/bin/bookdown docs/bookdown.json 31 | $ php -S 0.0.0.0:8080 -t docs/html/ 32 | ``` 33 | 34 | Then browse to [http://localhost:8080/](http://localhost:8080/) 35 | 36 | ## Support 37 | 38 | - Ask questions on Stack Overflow tagged with [#prooph](https://stackoverflow.com/questions/tagged/prooph). 39 | - File issues at [https://github.com/prooph/psr7-middleware/issues](https://github.com/prooph/psr7-middleware/issues). 40 | - Say hello in the [prooph gitter](https://gitter.im/prooph/improoph) chat. 41 | 42 | ## Contribute 43 | 44 | Please feel free to fork and extend existing or add new plugins and send a pull request with your changes! 45 | To establish a consistent code quality, please provide unit tests for all your changes and may adapt the documentation. 46 | 47 | ## License 48 | 49 | Released under the [New BSD License](LICENSE). 50 | -------------------------------------------------------------------------------- /build/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | * 3 | -------------------------------------------------------------------------------- /build/logs/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | * 3 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prooph/psr7-middleware", 3 | "description": "PSR-7 Middleware for prooph components", 4 | "type": "library", 5 | "license": "BSD-3-Clause", 6 | "homepage": "http://prooph-software.com/", 7 | "authors": [ 8 | { 9 | "name": "Alexander Miertsch", 10 | "email": "contact@prooph.de", 11 | "homepage": "http://prooph-software.com/" 12 | }, 13 | { 14 | "name": "Sandro Keil", 15 | "email": "contact@prooph.de", 16 | "homepage": "http://prooph-software.com/" 17 | }, 18 | { 19 | "name": "Sascha-Oliver Prolic", 20 | "email": "saschaprolic@googlemail.com" 21 | }, 22 | { 23 | "name": "Bas Kamer", 24 | "email": "baskamer@gmail.com" 25 | } 26 | ], 27 | "keywords": [ 28 | "prooph", 29 | "psr-7", 30 | "middleware", 31 | "cqrs", 32 | "service bus", 33 | "event sourcing", 34 | "messaging", 35 | "psr7", 36 | "psr-15", 37 | "psr15" 38 | ], 39 | "abandoned": "prooph/http-middleware", 40 | "minimum-stability": "dev", 41 | "prefer-stable": true, 42 | "require": { 43 | "php": "^7.1", 44 | "prooph/common": "^4.1", 45 | "prooph/service-bus" : "^6.0", 46 | "psr/http-message": "^1.0", 47 | "react/promise" : "^2.2", 48 | "fig/http-message-util": "^1.1", 49 | "webimpress/http-middleware-compatibility": "^0.1.4" 50 | }, 51 | "require-dev": { 52 | "phpunit/phpunit": "^6.0", 53 | "phpspec/prophecy": "^1.7", 54 | "prooph/php-cs-fixer-config": "^0.3", 55 | "satooshi/php-coveralls": "^1.0", 56 | "psr/container": "^1.0", 57 | "sandrokeil/interop-config": "^2.0.1", 58 | "prooph/bookdown-template": "^0.2.3", 59 | "zendframework/zend-servicemanager": "^3.1", 60 | "http-interop/http-middleware": "^0.5.0" 61 | }, 62 | "suggest": { 63 | "prooph/http-middleware": "prooph/psr7-middleware has been abandoned, use prooph/http-middlware instead", 64 | "psr/container": "^1.0 for usage of provided factories", 65 | "sandrokeil/interop-config": "^2.0 for usage of provided factories" 66 | }, 67 | "conflict": { 68 | "sandrokeil/interop-config": "<2.0.1" 69 | }, 70 | "autoload": { 71 | "psr-4": { 72 | "Prooph\\Psr7Middleware\\": "src/" 73 | } 74 | }, 75 | "autoload-dev": { 76 | "psr-4": { 77 | "ProophTest\\Psr7Middleware\\": "tests/" 78 | } 79 | }, 80 | "extra": { 81 | "branch-alias": { 82 | "dev-develop": "0.5-dev" 83 | } 84 | }, 85 | "scripts": { 86 | "check": [ 87 | "@cs", 88 | "@test" 89 | ], 90 | "coveralls": "coveralls", 91 | "cs": "php-cs-fixer fix -v --diff --dry-run", 92 | "cs-fix": "php-cs-fixer fix -v --diff", 93 | "check-license": "docheader check src/ tests/", 94 | "test": "phpunit", 95 | "test-coverage": "phpunit --coverage-clover build/logs/clover.xml", 96 | "docs": "bookdown docs/bookdown.json" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /config/prooph_middleware.config.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | /** 17 | * This file contains default configuration for prooph/psr7-middleware 18 | * It is meant to be used together with at least one of the container-aware factories 19 | * shipped with this package. Please refer to src/Container for the factories and 20 | * register them in your Interop\Container\ContainerInterface of choice 21 | * Then make this config available as service id `config` within your container 22 | * (possibly merged into your application configuration) 23 | */ 24 | return [ 25 | //vendor key to avoid merge conflicts with other packages when merged into application config 26 | 'prooph' => [ 27 | //component key to avoid merge conflicts with other prooph components when merged into application config 28 | 'middleware' => [ 29 | //This section will be used by \Prooph\Psr7Middleware\Container\QueryMiddlewareFactory 30 | 'query' => [ 31 | // container/service id / instance to return the data 32 | // see \Prooph\Psr7Middleware\Response\ResponseStrategy for an example 33 | 'response_strategy' => 'custom_response_strategy', 34 | // container/service id / instance, see \Prooph\Common\Messaging\MessageFactory for more details 35 | 'message_factory' => 'custom_message_factory', 36 | // container/service id for the QueryBus instance, default QueryBus::class 37 | 'query_bus' => 'custom_query_bus', 38 | // container/service id for the MetadataGatherer instance, default NoopMetadataGatherer::class 39 | // 'metadata_gatherer' => \Prooph\Psr7Middleware\MetadataGatherer::class 40 | ], 41 | //This section will be used by \Prooph\Psr7Middleware\Container\CommandMiddlewareFactory 42 | 'command' => [ 43 | // container/service id, see \Prooph\Common\Messaging\MessageFactory for more details 44 | 'message_factory' => \Prooph\Common\Messaging\FQCNMessageFactory::class, 45 | // container/service id for the CommandBus instance, default CommandBus::class 46 | 'command_bus' => 'custom_command_bus', 47 | ], 48 | //This section will be used by \Prooph\Psr7Middleware\Container\EventMiddlewareFactory 49 | 'event' => [ 50 | // container/service id, see \Prooph\Common\Messaging\MessageFactory for more details 51 | 'message_factory' => \Prooph\Common\Messaging\FQCNMessageFactory::class, 52 | // container/service id for the EventBus instance, default EventBus::class 53 | 'event_bus' => 'custom_event_bus', 54 | ], 55 | //This section will be used by \Prooph\Psr7Middleware\Container\MessageMiddlewareFactory 56 | 'message' => [ 57 | // container/service id to return the data 58 | // see \Prooph\Psr7Middleware\Response\ResponseStrategy for an example, only used for query bus 59 | 'response_strategy' => 'custom_response_strategy', 60 | // container/service id, see \Prooph\Common\Messaging\MessageFactory for more details 61 | // must have support for all bus types e.g. command, event and query 62 | 'message_factory' => 'custom_message_factory', 63 | // container/service id for the MessageBus instance, default MessageBus::class 64 | 'message_bus' => 'custom_message_bus', 65 | ], 66 | ], 67 | ], 68 | ]; 69 | -------------------------------------------------------------------------------- /docs/book/factories.md: -------------------------------------------------------------------------------- 1 | # Interop + Factories 2 | Instead of providing a module, a bundle, a bridge or similar framework integration `prooph/psr7-middleware` ships with 3 | *interop factories*. 4 | 5 | ## Factory-Driven Middleware Creation 6 | 7 | The concept behind these factories is simple but powerful. It allows us to provide you with bootstrapping logic for 8 | the message buses without the need to rely on a specific framework. However, the factories have three requirements. 9 | 10 | ### Requirements 11 | 12 | 1. Your Inversion of Control container must implement the [interop-container interface](https://github.com/container-interop/container-interop). 13 | 2. [interop-config](https://github.com/sandrokeil/interop-config) must be installed 14 | 3. The application configuration should be registered with the service id `config` in the container. 15 | 16 | *Note: Don't worry, if your environment doesn't provide the requirements. You can 17 | always bootstrap a middleware by hand. Just look at the factories for inspiration in this case.* 18 | 19 | ## Customizing via Configuration 20 | 21 | In the `config` folder of `prooph/psr7-middleware` you will find example configuration files. 22 | Configuration is a simple PHP array flavored with some comments to help you understand the structure. 23 | 24 | Now follow the simple steps below to integrate `prooph/psr7-middleware` in your framework and/or application. 25 | 26 | 1. Merge configuration into your application config either by hand or by using the mechanism of your framework. 27 | 2. Customize the configuration so that it meet your needs. The comments in the config file will tell you more. 28 | 3. (Only required if not done by your framework) Make your application config available as a service in the 29 | Inversion of Control container. Use `config` as the service id (common id for application config). 30 | 4. Register the middleware as services in your IoC container and use the located in `src/Container` to create the different middleware. 31 | How you can register a middlware depends on your container. Some containers like [zend-servicemanager](https://github.com/zendframework/zend-servicemanager) 32 | or [pimple-interop](https://github.com/moufmouf/pimple-interop) allow you to map a service id to an `invokable factory`. 33 | If you use such an IoC container you are lucky. In this case you can use the `prooph/psr7-middleware` factories as-is. 34 | We recommend using `Prooph\Middleware\ Note: The middleware uses an array for the message data 6 | 7 | ## CommandMiddleware 8 | The `CommandMiddleware` dispatches the message data to the command bus system. This middleware needs an request attribute 9 | (`$request->getAttribute(\Prooph\Psr7Middleware\CommandMiddleware::NAME_ATTRIBUTE)`) called `prooph_command_name`. 10 | This name is used for the `\Prooph\Common\Messaging\MessageFactory` to create the `\Prooph\Common\Messaging\Message` 11 | object. The data for the command is extracted from the body of the request (`$request->getParsedBody()`) and must be an 12 | array. 13 | 14 | ## QueryMiddleware 15 | The `QueryMiddleware` dispatches the message data to the query bus system. This middleware needs an request attribute 16 | (`$request->getAttribute(\Prooph\Psr7Middleware\QueryMiddleware::NAME_ATTRIBUTE)`) called `prooph_query_name`. 17 | This name is used for the `\Prooph\Common\Messaging\MessageFactory` to create the `\Prooph\Common\Messaging\Message` 18 | object. The data for the query is extracted from query params of the request (`$request->getQueryParams()`) and must be an 19 | array. 20 | 21 | There is a special behaviour implemented. If you send a *POST* HTTP request, then the parsed body data (`$request->getParsedBody()`) 22 | will be added to the payload under the key `data`. `data` is a reserved key if you use a *POST* HTTP request. However, it's 23 | not recommended to use a *POST* HTTP request here. Use it only if you know what you do. 24 | 25 | ## EventMiddleware 26 | The `EventMiddleware` dispatches the message data to the event bus system. This middleware needs an request attribute 27 | (`$request->getAttribute(\Prooph\Psr7Middleware\EventMiddleware::NAME_ATTRIBUTE)`) called `prooph_event_name`. 28 | This name is used for the `\Prooph\Common\Messaging\MessageFactory` to create the `\Prooph\Common\Messaging\Message` 29 | object. The data for the event is extracted from the body of the request (`$request->getParsedBody()`) and must be an 30 | array. 31 | 32 | *Note:* 33 | 34 | The `EventMiddleware` is commonly used for external event messages. An event comes from your domain, which was caused 35 | by a command. It makes no sense to use this middleware in your project, if you only use a command bus with event sourcing. 36 | In this case you will use the [event store bus bridge)](https://github.com/prooph/event-store-bus-bridge "Marry CQRS with Event Sourcing"). 37 | 38 | ## MessageMiddleware 39 | The `MessageMiddleware` dispatches the message data to the suitable bus system depending on message type. The data 40 | for the message is extracted from the body of the request (`$request->getParsedBody()`) and must be an array. The 41 | `message_name` is extracted from the parsed body data. This name is used for the `\Prooph\Common\Messaging\MessageFactory` 42 | to create the `\Prooph\Common\Messaging\Message` object. Your specific message data must be located under the `payload` 43 | key. The value of `$request->getParsedBody()` is an array like this: 44 | 45 | ``` 46 | [ 47 | 'message_name' => 'command:register-user', 48 | 'payload' => [ 49 | 'name' => 'prooph' 50 | ], 51 | 'metadata' => [] 52 | // other keys like uuid 53 | ] 54 | ``` 55 | 56 | **Important:** The provided message factory must handle all 3 types (command, query, event) of messages depending on 57 | provided message name. It's recommended to use an prefix or something else in the message name to determine the correct 58 | message type. 59 | -------------------------------------------------------------------------------- /docs/book/zend_expressive.md: -------------------------------------------------------------------------------- 1 | # Zend Expressive integration 2 | The [Zend Expressive](https://github.com/zendframework/zend-expressive) integration is very easy, because you can use 3 | the predefined factories and configuration examples of the specific prooph component. 4 | 5 | > Take a look at the Zend Expressive [prooph components in action](https://github.com/prooph/proophessor-do "proophessor-do example app") 6 | example app. 7 | 8 | ## Routes 9 | Here is an example for the `AuraRouter` to call the `CommandMiddleware` for the `register-user` command. 10 | 11 | ```php 12 | // routes.php 13 | 14 | /** @var \Zend\Expressive\Application $app */ 15 | $app->post('/api/commands/register-user', [ 16 | \Prooph\Psr7Middleware\CommandMiddleware::class, 17 | ], 'command::register-user') 18 | ->setOptions([ 19 | 'values' => [ 20 | \Prooph\Psr7Middleware\CommandMiddleware::NAME_ATTRIBUTE => \Prooph\ProophessorDo\Model\User\Command\RegisterUser::class, 21 | ], 22 | ]); 23 | ``` 24 | 25 | ## Metadata Gatherer 26 | 27 | QueryMiddleware, CommandMiddleware and EventMiddleware have a MetadataGatherer injected that is capable of retrieving attributes derived from the ServerRequestInterface and pass those with messages as metadata. 28 | 29 | By default a Noop (returns an empty array) instance is used, but it is very easy to change that. 30 | 31 | First write an implementation of MetadataGatherer; 32 | 33 | ```php 34 | namespace My\Psr7Middleware; 35 | 36 | use Psr\Http\Message\ServerRequestInterface; 37 | use Prooph\Psr7Middleware\MetadataGatherer; 38 | 39 | final class MyMetadataGatherer implements MetadataGatherer 40 | { 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function getFromRequest(ServerRequestInterface $request) { 45 | return [ 46 | 'identity' => $request->getAttribute('identity'), 47 | 'request_uuid' => $request->getAttribute('request_uuid') 48 | ]; 49 | } 50 | } 51 | 52 | ``` 53 | 54 | Then define it in container and prooph configuration; 55 | 56 | ```php 57 | return [ 58 | 'dependencies' => [ 59 | 'factories' => [ 60 | \My\Psr7Middleware\MyMetadataGatherer::class => \Zend\ServiceManager\Factory\InvokableFactory::class 61 | ], 62 | ], 63 | 'prooph' => [ 64 | 'middleware' => [ 65 | 'query' => [ 66 | 'metadata_gatherer' => \My\Psr7Middleware\MyMetadataGatherer::class 67 | ], 68 | ], 69 | ], 70 | ... 71 | ``` 72 | 73 | -------------------------------------------------------------------------------- /docs/bookdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "PSR-7 middleware for prooph components", 3 | "content": [ 4 | {"intro": "../README.md"}, 5 | {"overview": "book/middleware.md"}, 6 | {"factories": "book/factories.md"}, 7 | {"zend-expressive-integration": "book/zend_expressive.md"} 8 | ], 9 | "tocDepth": 2, 10 | "target": "./html", 11 | "template": "../vendor/prooph/bookdown-template/templates/main.php" 12 | } 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | 20 | ./src/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/CommandMiddleware.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | use Fig\Http\Message\StatusCodeInterface; 17 | use Prooph\Common\Messaging\MessageFactory; 18 | use Prooph\Psr7Middleware\Exception\RuntimeException; 19 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 20 | use Prooph\ServiceBus\CommandBus; 21 | use Psr\Http\Message\ServerRequestInterface; 22 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 23 | use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; 24 | 25 | /** 26 | * Command messages describe actions your model can handle. 27 | * 28 | * The CommandBus is designed to dispatch a message to only one handler or message producer. It does not return a 29 | * result. Sending a command means fire and forget and enforces the Tell-Don't-Ask principle. 30 | */ 31 | final class CommandMiddleware implements MiddlewareInterface 32 | { 33 | /** 34 | * Identifier to execute specific command 35 | * 36 | * @var string 37 | */ 38 | public const NAME_ATTRIBUTE = 'prooph_command_name'; 39 | 40 | /** 41 | * Dispatches command 42 | * 43 | * @var CommandBus 44 | */ 45 | private $commandBus; 46 | 47 | /** 48 | * Creates message depending on command name 49 | * 50 | * @var MessageFactory 51 | */ 52 | private $commandFactory; 53 | 54 | /** 55 | * Gatherer of metadata from the request object 56 | * 57 | * @var MetadataGatherer 58 | */ 59 | private $metadataGatherer; 60 | 61 | /** 62 | * Generate HTTP response with status code 63 | * 64 | * @var ResponseStrategy 65 | */ 66 | private $responseStrategy; 67 | 68 | public function __construct( 69 | CommandBus $commandBus, 70 | MessageFactory $commandFactory, 71 | MetadataGatherer $metadataGatherer, 72 | ResponseStrategy $responseStrategy 73 | ) { 74 | $this->commandBus = $commandBus; 75 | $this->commandFactory = $commandFactory; 76 | $this->metadataGatherer = $metadataGatherer; 77 | $this->responseStrategy = $responseStrategy; 78 | } 79 | 80 | public function process(ServerRequestInterface $request, HandlerInterface $handler) 81 | { 82 | $commandName = $request->getAttribute(self::NAME_ATTRIBUTE); 83 | 84 | if (null === $commandName) { 85 | throw new RuntimeException( 86 | \sprintf('Command name attribute ("%s") was not found in request.', self::NAME_ATTRIBUTE), 87 | StatusCodeInterface::STATUS_BAD_REQUEST 88 | ); 89 | } 90 | 91 | try { 92 | $command = $this->commandFactory->createMessageFromArray($commandName, [ 93 | 'payload' => $request->getParsedBody(), 94 | 'metadata' => $this->metadataGatherer->getFromRequest($request), 95 | ]); 96 | 97 | $this->commandBus->dispatch($command); 98 | 99 | return $this->responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED); 100 | } catch (\Throwable $e) { 101 | throw new RuntimeException( 102 | \sprintf('An error occurred during dispatching of command "%s"', $commandName), 103 | StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, 104 | $e 105 | ); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Container/AbstractMiddlewareFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Container; 15 | 16 | use Interop\Config\ConfigurationTrait; 17 | use Interop\Config\RequiresConfigId; 18 | use Prooph\Psr7Middleware\Exception\InvalidArgumentException; 19 | use Psr\Container\ContainerInterface; 20 | 21 | /** 22 | * Base class for factories 23 | */ 24 | abstract class AbstractMiddlewareFactory implements RequiresConfigId 25 | { 26 | use ConfigurationTrait; 27 | 28 | /** 29 | * @var string 30 | */ 31 | protected $configId; 32 | 33 | /** 34 | * Creates a new instance from a specified config, specifically meant to be used as static factory. 35 | * 36 | * In case you want to use another config key than provided by the factories, you can add the following factory to 37 | * your config: 38 | * 39 | * 40 | * [CommandMiddlewareFactory::class, 'other'], 43 | * ]; 44 | * 45 | * 46 | * @return mixed 47 | * @throws InvalidArgumentException 48 | */ 49 | public static function __callStatic(string $name, array $arguments) 50 | { 51 | if (! isset($arguments[0]) || ! $arguments[0] instanceof ContainerInterface) { 52 | throw new InvalidArgumentException( 53 | \sprintf('The first argument must be of type %s', ContainerInterface::class) 54 | ); 55 | } 56 | 57 | return (new static($name))->__invoke($arguments[0]); 58 | } 59 | 60 | public function __construct(string $configId) 61 | { 62 | // ensure BC 63 | $this->configId = \method_exists($this, 'containerId') ? $this->containerId() : $configId; 64 | } 65 | 66 | public function dimensions(): iterable 67 | { 68 | return ['prooph', 'middleware']; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Container/CommandMiddlewareFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Container; 15 | 16 | use Interop\Config\ConfigurationTrait; 17 | use Interop\Config\ProvidesDefaultOptions; 18 | use Interop\Config\RequiresMandatoryOptions; 19 | use Prooph\Psr7Middleware\CommandMiddleware; 20 | use Prooph\Psr7Middleware\NoopMetadataGatherer; 21 | use Prooph\ServiceBus\CommandBus; 22 | use Psr\Container\ContainerInterface; 23 | 24 | final class CommandMiddlewareFactory extends AbstractMiddlewareFactory implements ProvidesDefaultOptions, RequiresMandatoryOptions 25 | { 26 | use ConfigurationTrait; 27 | 28 | public function __construct(string $configId = 'command') 29 | { 30 | parent::__construct($configId); 31 | } 32 | 33 | public function __invoke(ContainerInterface $container): CommandMiddleware 34 | { 35 | $options = $this->options($container->get('config'), $this->configId); 36 | 37 | if (isset($options['metadata_gatherer'])) { 38 | $gatherer = $container->get($options['metadata_gatherer']); 39 | } else { 40 | $gatherer = new NoopMetadataGatherer(); 41 | } 42 | 43 | return new CommandMiddleware( 44 | $container->get($options['command_bus']), 45 | $container->get($options['message_factory']), 46 | $gatherer, 47 | $container->get($options['response_strategy']) 48 | ); 49 | } 50 | 51 | public function defaultOptions(): iterable 52 | { 53 | return ['command_bus' => CommandBus::class]; 54 | } 55 | 56 | public function mandatoryOptions(): iterable 57 | { 58 | return ['message_factory']; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Container/EventMiddlewareFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Container; 15 | 16 | use Interop\Config\ConfigurationTrait; 17 | use Interop\Config\ProvidesDefaultOptions; 18 | use Interop\Config\RequiresMandatoryOptions; 19 | use Prooph\Psr7Middleware\EventMiddleware; 20 | use Prooph\Psr7Middleware\NoopMetadataGatherer; 21 | use Prooph\ServiceBus\EventBus; 22 | use Psr\Container\ContainerInterface; 23 | 24 | final class EventMiddlewareFactory extends AbstractMiddlewareFactory implements ProvidesDefaultOptions, RequiresMandatoryOptions 25 | { 26 | use ConfigurationTrait; 27 | 28 | public function __construct(string $configId = 'event') 29 | { 30 | parent::__construct($configId); 31 | } 32 | 33 | public function __invoke(ContainerInterface $container): EventMiddleware 34 | { 35 | $options = $this->options($container->get('config'), $this->configId); 36 | 37 | if (isset($options['metadata_gatherer'])) { 38 | $gatherer = $container->get($options['metadata_gatherer']); 39 | } else { 40 | $gatherer = new NoopMetadataGatherer(); 41 | } 42 | 43 | return new EventMiddleware( 44 | $container->get($options['event_bus']), 45 | $container->get($options['message_factory']), 46 | $gatherer, 47 | $container->get($options['response_strategy']) 48 | ); 49 | } 50 | 51 | public function defaultOptions(): iterable 52 | { 53 | return ['event_bus' => EventBus::class]; 54 | } 55 | 56 | public function mandatoryOptions(): iterable 57 | { 58 | return ['message_factory']; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Container/MessageMiddlewareFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Container; 15 | 16 | use Interop\Config\ConfigurationTrait; 17 | use Interop\Config\ProvidesDefaultOptions; 18 | use Interop\Config\RequiresMandatoryOptions; 19 | use Prooph\Psr7Middleware\MessageMiddleware; 20 | use Prooph\ServiceBus\CommandBus; 21 | use Prooph\ServiceBus\EventBus; 22 | use Prooph\ServiceBus\QueryBus; 23 | use Psr\Container\ContainerInterface; 24 | 25 | final class MessageMiddlewareFactory extends AbstractMiddlewareFactory implements ProvidesDefaultOptions, RequiresMandatoryOptions 26 | { 27 | use ConfigurationTrait; 28 | 29 | public function __construct(string $configId = 'message') 30 | { 31 | parent::__construct($configId); 32 | } 33 | 34 | public function __invoke(ContainerInterface $container): MessageMiddleware 35 | { 36 | $options = $this->options($container->get('config'), $this->configId); 37 | 38 | return new MessageMiddleware( 39 | $container->get($options['command_bus']), 40 | $container->get($options['query_bus']), 41 | $container->get($options['event_bus']), 42 | $container->get($options['message_factory']), 43 | $container->get($options['response_strategy']) 44 | ); 45 | } 46 | 47 | public function defaultOptions(): iterable 48 | { 49 | return [ 50 | 'command_bus' => CommandBus::class, 51 | 'event_bus' => EventBus::class, 52 | 'query_bus' => QueryBus::class, 53 | ]; 54 | } 55 | 56 | public function mandatoryOptions(): iterable 57 | { 58 | return ['message_factory', 'response_strategy']; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Container/QueryMiddlewareFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Container; 15 | 16 | use Interop\Config\ConfigurationTrait; 17 | use Interop\Config\ProvidesDefaultOptions; 18 | use Interop\Config\RequiresMandatoryOptions; 19 | use Prooph\Psr7Middleware\NoopMetadataGatherer; 20 | use Prooph\Psr7Middleware\QueryMiddleware; 21 | use Prooph\ServiceBus\QueryBus; 22 | use Psr\Container\ContainerInterface; 23 | 24 | final class QueryMiddlewareFactory extends AbstractMiddlewareFactory implements ProvidesDefaultOptions, RequiresMandatoryOptions 25 | { 26 | use ConfigurationTrait; 27 | 28 | public function __construct(string $configId = 'query') 29 | { 30 | parent::__construct($configId); 31 | } 32 | 33 | public function __invoke(ContainerInterface $container): QueryMiddleware 34 | { 35 | $options = $this->options($container->get('config'), $this->configId); 36 | 37 | if (isset($options['metadata_gatherer'])) { 38 | $gatherer = $container->get($options['metadata_gatherer']); 39 | } else { 40 | $gatherer = new NoopMetadataGatherer(); 41 | } 42 | 43 | return new QueryMiddleware( 44 | $container->get($options['query_bus']), 45 | $container->get($options['message_factory']), 46 | $container->get($options['response_strategy']), 47 | $gatherer 48 | ); 49 | } 50 | 51 | public function defaultOptions(): iterable 52 | { 53 | return ['query_bus' => QueryBus::class]; 54 | } 55 | 56 | public function mandatoryOptions(): iterable 57 | { 58 | return ['message_factory', 'response_strategy']; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/EventMiddleware.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | use Fig\Http\Message\StatusCodeInterface; 17 | use Prooph\Common\Messaging\MessageFactory; 18 | use Prooph\Psr7Middleware\Exception\RuntimeException; 19 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 20 | use Prooph\ServiceBus\EventBus; 21 | use Psr\Http\Message\ServerRequestInterface; 22 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 23 | use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; 24 | 25 | /** 26 | * Event messages describe things that happened while your model handled a command. 27 | * 28 | * The EventBus is able to dispatch a message to n listeners. Each listener can be a message handler or message 29 | * producer. Like commands the EventBus doesn't return anything. 30 | */ 31 | final class EventMiddleware implements MiddlewareInterface 32 | { 33 | /** 34 | * Identifier to execute specific event 35 | * 36 | * @var string 37 | */ 38 | public const NAME_ATTRIBUTE = 'prooph_event_name'; 39 | 40 | /** 41 | * Dispatches event 42 | * 43 | * @var EventBus 44 | */ 45 | private $eventBus; 46 | 47 | /** 48 | * Creates message depending on event name 49 | * 50 | * @var MessageFactory 51 | */ 52 | private $eventFactory; 53 | 54 | /** 55 | * Gatherer of metadata from the request object 56 | * 57 | * @var MetadataGatherer 58 | */ 59 | private $metadataGatherer; 60 | 61 | /** 62 | * Generate HTTP response with status code 63 | * 64 | * @var ResponseStrategy 65 | */ 66 | private $responseStrategy; 67 | 68 | public function __construct( 69 | EventBus $eventBus, 70 | MessageFactory $eventFactory, 71 | MetadataGatherer $metadataGatherer, 72 | ResponseStrategy $responseStrategy 73 | ) { 74 | $this->eventBus = $eventBus; 75 | $this->eventFactory = $eventFactory; 76 | $this->metadataGatherer = $metadataGatherer; 77 | $this->responseStrategy = $responseStrategy; 78 | } 79 | 80 | public function process(ServerRequestInterface $request, HandlerInterface $handler) 81 | { 82 | $eventName = $request->getAttribute(self::NAME_ATTRIBUTE); 83 | 84 | if (null === $eventName) { 85 | throw new RuntimeException( 86 | \sprintf('Event name attribute ("%s") was not found in request.', self::NAME_ATTRIBUTE), 87 | StatusCodeInterface::STATUS_BAD_REQUEST 88 | ); 89 | } 90 | 91 | try { 92 | $event = $this->eventFactory->createMessageFromArray($eventName, [ 93 | 'payload' => $request->getParsedBody(), 94 | 'metadata' => $this->metadataGatherer->getFromRequest($request), 95 | ]); 96 | 97 | $this->eventBus->dispatch($event); 98 | 99 | return $this->responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED); 100 | } catch (\Throwable $e) { 101 | throw new RuntimeException( 102 | \sprintf('An error occurred during dispatching of event "%s"', $eventName), 103 | StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, 104 | $e 105 | ); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Exception; 15 | 16 | interface ExceptionInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Exception; 15 | 16 | use InvalidArgumentException as PhpInvalidArgumentException; 17 | 18 | class InvalidArgumentException extends PhpInvalidArgumentException implements ExceptionInterface 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Exception; 15 | 16 | use RuntimeException as PhpRuntimeException; 17 | 18 | class RuntimeException extends PhpRuntimeException implements ExceptionInterface 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/MessageMiddleware.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | use Fig\Http\Message\StatusCodeInterface; 17 | use Prooph\Common\Messaging\Message; 18 | use Prooph\Common\Messaging\MessageDataAssertion; 19 | use Prooph\Common\Messaging\MessageFactory; 20 | use Prooph\Psr7Middleware\Exception\RuntimeException; 21 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 22 | use Prooph\ServiceBus\CommandBus; 23 | use Prooph\ServiceBus\EventBus; 24 | use Prooph\ServiceBus\QueryBus; 25 | use Psr\Http\Message\ServerRequestInterface; 26 | use Ramsey\Uuid\Uuid; 27 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 28 | use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; 29 | 30 | /** 31 | * One middleware for all message types (event, command and query) 32 | * 33 | * This class handles event, command and query messages depending on given request body data. 34 | */ 35 | final class MessageMiddleware implements MiddlewareInterface 36 | { 37 | /** 38 | * Dispatches command 39 | * 40 | * @var CommandBus 41 | */ 42 | private $commandBus; 43 | 44 | /** 45 | * Dispatches query 46 | * 47 | * @var QueryBus 48 | */ 49 | private $queryBus; 50 | 51 | /** 52 | * Dispatches event 53 | * 54 | * @var EventBus 55 | */ 56 | private $eventBus; 57 | 58 | /** 59 | * Creates message depending on name 60 | * 61 | * @var MessageFactory 62 | */ 63 | private $messageFactory; 64 | 65 | /** 66 | * Generate HTTP response with result from Promise 67 | * 68 | * @var ResponseStrategy 69 | */ 70 | private $responseStrategy; 71 | 72 | public function __construct( 73 | CommandBus $commandBus, 74 | QueryBus $queryBus, 75 | EventBus $eventBus, 76 | MessageFactory $messageFactory, 77 | ResponseStrategy $responseStrategy 78 | ) { 79 | $this->commandBus = $commandBus; 80 | $this->queryBus = $queryBus; 81 | $this->eventBus = $eventBus; 82 | $this->messageFactory = $messageFactory; 83 | $this->responseStrategy = $responseStrategy; 84 | } 85 | 86 | public function process(ServerRequestInterface $request, HandlerInterface $handler) 87 | { 88 | $payload = null; 89 | $messageName = 'UNKNOWN'; 90 | 91 | try { 92 | $payload = $request->getParsedBody(); 93 | 94 | if (\is_array($payload) && isset($payload['message_name'])) { 95 | $messageName = $payload['message_name']; 96 | } 97 | 98 | $messageName = $request->getAttribute('message_name', $messageName); 99 | 100 | $payload['message_name'] = $messageName; 101 | 102 | if (! isset($payload['uuid'])) { 103 | $payload['uuid'] = Uuid::uuid4(); 104 | } 105 | 106 | if (! isset($payload['created_at'])) { 107 | $payload['created_at'] = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); 108 | } 109 | 110 | if (! isset($payload['metadata'])) { 111 | $payload['metadata'] = []; 112 | } 113 | 114 | MessageDataAssertion::assert($payload); 115 | 116 | $message = $this->messageFactory->createMessageFromArray($messageName, $payload); 117 | 118 | switch ($message->messageType()) { 119 | case Message::TYPE_COMMAND: 120 | $this->commandBus->dispatch($message); 121 | 122 | return $this->responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED); 123 | case Message::TYPE_EVENT: 124 | $this->eventBus->dispatch($message); 125 | 126 | return $this->responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED); 127 | case Message::TYPE_QUERY: 128 | return $this->responseStrategy->fromPromise( 129 | $this->queryBus->dispatch($message) 130 | ); 131 | default: 132 | throw new RuntimeException( 133 | \sprintf( 134 | 'Invalid message type "%s" for message "%s".', 135 | $message->messageType(), 136 | $messageName 137 | ), 138 | StatusCodeInterface::STATUS_BAD_REQUEST 139 | ); 140 | } 141 | } catch (\Assert\InvalidArgumentException $e) { 142 | throw new RuntimeException( 143 | $e->getMessage(), 144 | StatusCodeInterface::STATUS_BAD_REQUEST, 145 | $e 146 | ); 147 | } catch (\Throwable $e) { 148 | throw new RuntimeException( 149 | \sprintf('An error occurred during dispatching of message "%s"', $messageName), 150 | StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, 151 | $e 152 | ); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/MetadataGatherer.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | use Psr\Http\Message\ServerRequestInterface; 17 | 18 | /** 19 | * Interface MetadataGatherer 20 | * 21 | * Gatherer of metadata from the request object 22 | */ 23 | interface MetadataGatherer 24 | { 25 | /** 26 | * Gets metadata from the request 27 | */ 28 | public function getFromRequest(ServerRequestInterface $request): array; 29 | } 30 | -------------------------------------------------------------------------------- /src/NoopMetadataGatherer.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | use Psr\Http\Message\ServerRequestInterface; 17 | 18 | final class NoopMetadataGatherer implements MetadataGatherer 19 | { 20 | public function getFromRequest(ServerRequestInterface $request): array 21 | { 22 | return []; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/QueryMiddleware.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware; 15 | 16 | use Fig\Http\Message\RequestMethodInterface; 17 | use Fig\Http\Message\StatusCodeInterface; 18 | use Prooph\Common\Messaging\MessageFactory; 19 | use Prooph\Psr7Middleware\Exception\RuntimeException; 20 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 21 | use Prooph\ServiceBus\QueryBus; 22 | use Psr\Http\Message\ServerRequestInterface; 23 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 24 | use Webimpress\HttpMiddlewareCompatibility\MiddlewareInterface; 25 | 26 | /** 27 | * Query messages describe available information that can be fetched from your (read) model. 28 | * 29 | * The QueryBus also dispatches a message to only one finder (special query message handler) but it returns a 30 | * `React\Promise\Promise`. The QueryBus hands over the query message to a finder but also a `React\Promise\Deferred` 31 | * which needs to be resolved by the finder. We use promises to allow finders to handle queries asynchronous for 32 | * example using curl_multi_exec. 33 | */ 34 | final class QueryMiddleware implements MiddlewareInterface 35 | { 36 | /** 37 | * Identifier to execute specific query 38 | * 39 | * @var string 40 | */ 41 | public const NAME_ATTRIBUTE = 'prooph_query_name'; 42 | 43 | /** 44 | * Dispatches query 45 | * 46 | * @var QueryBus 47 | */ 48 | private $queryBus; 49 | 50 | /** 51 | * Creates message depending on query name 52 | * 53 | * @var MessageFactory 54 | */ 55 | private $queryFactory; 56 | 57 | /** 58 | * Generate HTTP response with result from Promise 59 | * 60 | * @var ResponseStrategy 61 | */ 62 | private $responseStrategy; 63 | 64 | /** 65 | * Gatherer of metadata from the request object 66 | * 67 | * @var MetadataGatherer 68 | */ 69 | private $metadataGatherer; 70 | 71 | public function __construct( 72 | QueryBus $queryBus, 73 | MessageFactory $queryFactory, 74 | ResponseStrategy $responseStrategy, 75 | MetadataGatherer $metadataGatherer 76 | ) { 77 | $this->queryBus = $queryBus; 78 | $this->queryFactory = $queryFactory; 79 | $this->responseStrategy = $responseStrategy; 80 | $this->metadataGatherer = $metadataGatherer; 81 | } 82 | 83 | public function process(ServerRequestInterface $request, HandlerInterface $handler) 84 | { 85 | $queryName = $request->getAttribute(self::NAME_ATTRIBUTE); 86 | 87 | if (null === $queryName) { 88 | throw new RuntimeException( 89 | \sprintf('Query name attribute ("%s") was not found in request.', self::NAME_ATTRIBUTE), 90 | StatusCodeInterface::STATUS_BAD_REQUEST 91 | ); 92 | } 93 | $payload = $request->getQueryParams(); 94 | 95 | if ($request->getMethod() === RequestMethodInterface::METHOD_POST) { 96 | $payload['data'] = $request->getParsedBody(); 97 | } 98 | 99 | try { 100 | $query = $this->queryFactory->createMessageFromArray($queryName, [ 101 | 'payload' => $payload, 102 | 'metadata' => $this->metadataGatherer->getFromRequest($request), 103 | ]); 104 | 105 | return $this->responseStrategy->fromPromise( 106 | $this->queryBus->dispatch($query) 107 | ); 108 | } catch (\Throwable $e) { 109 | throw new RuntimeException( 110 | \sprintf('An error occurred during dispatching of query "%s"', $queryName), 111 | StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR, 112 | $e 113 | ); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Response/ResponseStrategy.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\Psr7Middleware\Response; 15 | 16 | use Psr\Http\Message\ResponseInterface; 17 | use React\Promise\PromiseInterface; 18 | 19 | /** 20 | * Generates HTTP responses. 21 | * 22 | * This is an example how to generate valid JsonResponses: 23 | * 24 | * 25 | * final class JsonResponse implements ResponseStrategy 26 | * { 27 | * public function fromPromise(\React\Promise\PromiseInterface $promise): ResponseInterface 28 | * { 29 | * $data = null; 30 | * 31 | * $promise->done(function($result) use (&$data) { 32 | * $data = $result; 33 | * }); 34 | * 35 | * return new \Zend\Diactoros\Response\JsonResponse($data); 36 | * } 37 | * 38 | * public function withStatus(int $statusCode): ResponseInterface 39 | * { 40 | * return new \Zend\Diactoros\Response\JsonResponse([], $statusCode); 41 | * } 42 | * } 43 | * 44 | */ 45 | interface ResponseStrategy 46 | { 47 | /** 48 | * Generates a valid HTTP response with result data from Promise object 49 | */ 50 | public function fromPromise(PromiseInterface $promise): ResponseInterface; 51 | 52 | /** 53 | * Generates a valid HTTP response with given status code. 54 | */ 55 | public function withStatus(int $statusCode): ResponseInterface; 56 | } 57 | -------------------------------------------------------------------------------- /tests/CommandMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware; 15 | 16 | use Fig\Http\Message\StatusCodeInterface; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\Message; 19 | use Prooph\Common\Messaging\MessageFactory; 20 | use Prooph\Psr7Middleware\CommandMiddleware; 21 | use Prooph\Psr7Middleware\Exception\RuntimeException; 22 | use Prooph\Psr7Middleware\MetadataGatherer; 23 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 24 | use Prooph\ServiceBus\CommandBus; 25 | use Prophecy\Argument; 26 | use Psr\Http\Message\ResponseInterface; 27 | use Psr\Http\Message\ServerRequestInterface; 28 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 29 | 30 | /** 31 | * Test integrity of \Prooph\Psr7Middleware\CommandMiddleware 32 | */ 33 | class CommandMiddlewareTest extends TestCase 34 | { 35 | /** 36 | * @test 37 | */ 38 | public function it_throws_exception_if_command_name_attribute_is_not_set(): void 39 | { 40 | $commandBus = $this->prophesize(CommandBus::class); 41 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 42 | 43 | $messageFactory = $this->prophesize(MessageFactory::class); 44 | 45 | $request = $this->prophesize(ServerRequestInterface::class); 46 | $request->getAttribute(CommandMiddleware::NAME_ATTRIBUTE)->willReturn(null)->shouldBeCalled(); 47 | 48 | $gatherer = $this->prophesize(MetadataGatherer::class); 49 | $gatherer->getFromRequest($request)->shouldNotBeCalled(); 50 | 51 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 52 | $responseStrategy->withStatus()->shouldNotBeCalled(); 53 | 54 | $handler = $this->prophesize(HandlerInterface::class); 55 | 56 | $this->expectException(RuntimeException::class); 57 | $this->expectExceptionMessage(\sprintf('Command name attribute ("%s") was not found in request.', CommandMiddleware::NAME_ATTRIBUTE)); 58 | 59 | $middleware = new CommandMiddleware($commandBus->reveal(), $messageFactory->reveal(), $gatherer->reveal(), $responseStrategy->reveal()); 60 | 61 | $middleware->process($request->reveal(), $handler->reveal()); 62 | } 63 | 64 | /** 65 | * @test 66 | */ 67 | public function it_throws_exception_if_dispatch_failed(): void 68 | { 69 | $commandName = 'stdClass'; 70 | $payload = ['user_id' => 123]; 71 | 72 | $commandBus = $this->prophesize(CommandBus::class); 73 | $commandBus->dispatch(Argument::type(Message::class))->willThrow( 74 | new \Exception('Error') 75 | ); 76 | 77 | $message = $this->prophesize(Message::class); 78 | 79 | $messageFactory = $this->prophesize(MessageFactory::class); 80 | $messageFactory 81 | ->createMessageFromArray( 82 | $commandName, 83 | ['payload' => $payload, 'metadata' => []] 84 | ) 85 | ->willReturn($message->reveal()); 86 | 87 | $request = $this->prophesize(ServerRequestInterface::class); 88 | $request->getAttribute(CommandMiddleware::NAME_ATTRIBUTE)->willReturn($commandName); 89 | 90 | $gatherer = $this->prophesize(MetadataGatherer::class); 91 | $gatherer->getFromRequest($request)->shouldNotBeCalled(); 92 | 93 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 94 | $responseStrategy->withStatus()->shouldNotBeCalled(); 95 | 96 | $handler = $this->prophesize(HandlerInterface::class); 97 | 98 | $this->expectException(RuntimeException::class); 99 | $this->expectExceptionCode(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); 100 | $this->expectExceptionMessage('An error occurred during dispatching of command "stdClass"'); 101 | 102 | $middleware = new CommandMiddleware($commandBus->reveal(), $messageFactory->reveal(), $gatherer->reveal(), $responseStrategy->reveal()); 103 | 104 | $middleware->process($request->reveal(), $handler->reveal()); 105 | } 106 | 107 | /** 108 | * @test 109 | */ 110 | public function it_dispatches_the_command(): void 111 | { 112 | $commandName = 'stdClass'; 113 | $payload = ['user_id' => 123]; 114 | 115 | $commandBus = $this->prophesize(CommandBus::class); 116 | $commandBus->dispatch(Argument::type(Message::class))->shouldBeCalled(); 117 | 118 | $message = $this->prophesize(Message::class); 119 | 120 | $messageFactory = $this->prophesize(MessageFactory::class); 121 | $messageFactory 122 | ->createMessageFromArray( 123 | $commandName, 124 | ['payload' => $payload, 'metadata' => []] 125 | ) 126 | ->willReturn($message->reveal()) 127 | ->shouldBeCalled(); 128 | 129 | $request = $this->prophesize(ServerRequestInterface::class); 130 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 131 | $request->getAttribute(CommandMiddleware::NAME_ATTRIBUTE)->willReturn($commandName)->shouldBeCalled(); 132 | 133 | $gatherer = $this->prophesize(MetadataGatherer::class); 134 | $gatherer->getFromRequest($request->reveal())->willReturn([])->shouldBeCalled(); 135 | 136 | $response = $this->prophesize(ResponseInterface::class); 137 | 138 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 139 | $responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED)->willReturn($response); 140 | 141 | $handler = $this->prophesize(HandlerInterface::class); 142 | 143 | $middleware = new CommandMiddleware($commandBus->reveal(), $messageFactory->reveal(), $gatherer->reveal(), $responseStrategy->reveal()); 144 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/Container/CommandMiddlewareFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware\Container; 15 | 16 | use Interop\Config\Exception\MandatoryOptionNotFoundException; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\MessageFactory; 19 | use Prooph\Psr7Middleware\CommandMiddleware; 20 | use Prooph\Psr7Middleware\Container\CommandMiddlewareFactory; 21 | use Prooph\Psr7Middleware\Exception\InvalidArgumentException; 22 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 23 | use Prooph\ServiceBus\CommandBus; 24 | use Prophecy\Prophecy\ObjectProphecy; 25 | use Psr\Container\ContainerInterface; 26 | 27 | class CommandMiddlewareFactoryTest extends TestCase 28 | { 29 | /** 30 | * @test 31 | */ 32 | public function it_implements_config_interop(): void 33 | { 34 | $factory = new CommandMiddlewareFactory(); 35 | 36 | self::assertInstanceOf(\Interop\Config\RequiresConfigId::class, $factory); 37 | self::assertInstanceOf(\Interop\Config\RequiresMandatoryOptions::class, $factory); 38 | self::assertInstanceOf(\Interop\Config\ProvidesDefaultOptions::class, $factory); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function it_creates_command_middleware(): void 45 | { 46 | $factory = new CommandMiddlewareFactory(); 47 | $container = $this->getValidConfiguredContainer('command', null); 48 | 49 | self::assertInstanceOf(CommandMiddleware::class, $factory($container->reveal())); 50 | } 51 | 52 | /** 53 | * @test 54 | */ 55 | public function it_throws_exception_if_option_is_missing(): void 56 | { 57 | $this->expectException(MandatoryOptionNotFoundException::class); 58 | 59 | $factory = new CommandMiddlewareFactory(); 60 | $container = $this->prophesize(ContainerInterface::class); 61 | 62 | $container->has('config')->willReturn(true); 63 | $container->get('config')->willReturn([ 64 | 'prooph' => [ 65 | 'middleware' => [ 66 | 'command' => [ 67 | ], 68 | ], 69 | ], 70 | ]); 71 | 72 | $factory($container->reveal()); 73 | } 74 | 75 | /** 76 | * @test 77 | */ 78 | public function it_creates_command_middleware_from_static_call(): void 79 | { 80 | $container = $this->getValidConfiguredContainer('other_config_id', null); 81 | 82 | $factory = [CommandMiddlewareFactory::class, 'other_config_id']; 83 | self::assertInstanceOf(CommandMiddleware::class, $factory($container->reveal())); 84 | } 85 | 86 | /** 87 | * @test 88 | */ 89 | public function it_creates_command_middleware_incl_metadata_gatherer(): void 90 | { 91 | $container = $this->getValidConfiguredContainer('other_config_id', new StubMetadataGatherer()); 92 | 93 | $factory = [CommandMiddlewareFactory::class, 'other_config_id']; 94 | self::assertInstanceOf(CommandMiddleware::class, $factory($container->reveal())); 95 | } 96 | 97 | /** 98 | * @test 99 | */ 100 | public function it_throws_invalid_argument_exception_without_container_on_static_call(): void 101 | { 102 | $this->expectException(InvalidArgumentException::class); 103 | $this->expectExceptionMessage('The first argument must be of type Psr\Container\ContainerInterface'); 104 | 105 | CommandMiddlewareFactory::other_config_id(); 106 | } 107 | 108 | private function getValidConfiguredContainer(string $configId, ?StubMetadataGatherer $gatherer): ObjectProphecy 109 | { 110 | $container = $this->prophesize(ContainerInterface::class); 111 | $strategy = $this->prophesize(ResponseStrategy::class); 112 | $messageFactory = $this->prophesize(MessageFactory::class); 113 | 114 | $config = [ 115 | 'prooph' => [ 116 | 'middleware' => [ 117 | $configId => [ 118 | 'message_factory' => 'custom_message_factory', 119 | 'response_strategy' => 'JsonResponseStrategy', 120 | ], 121 | ], 122 | ], 123 | ]; 124 | 125 | if (null !== $gatherer) { 126 | $config['prooph']['middleware'][$configId]['metadata_gatherer'] = \get_class($gatherer); 127 | $container->get(\get_class($gatherer))->willReturn($gatherer); 128 | } 129 | 130 | $container->has('config')->willReturn(true); 131 | $container->get('config')->willReturn($config); 132 | 133 | $container->has('custom_message_factory')->willReturn(true); 134 | $container->get('custom_message_factory')->willReturn($messageFactory); 135 | $container->has('JsonResponseStrategy')->willReturn(true); 136 | $container->get('JsonResponseStrategy')->willReturn($strategy); 137 | $container->has(CommandBus::class)->willReturn(true); 138 | $container->get(CommandBus::class)->willReturn($this->prophesize(CommandBus::class)); 139 | 140 | return $container; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /tests/Container/EventMiddlewareFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware\Container; 15 | 16 | use Interop\Config\Exception\MandatoryOptionNotFoundException; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\MessageFactory; 19 | use Prooph\Psr7Middleware\Container\EventMiddlewareFactory; 20 | use Prooph\Psr7Middleware\EventMiddleware; 21 | use Prooph\Psr7Middleware\Exception\InvalidArgumentException; 22 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 23 | use Prooph\ServiceBus\EventBus; 24 | use Prophecy\Prophecy\ObjectProphecy; 25 | use Psr\Container\ContainerInterface; 26 | 27 | class EventMiddlewareFactoryTest extends TestCase 28 | { 29 | /** 30 | * @test 31 | */ 32 | public function it_implements_config_interop(): void 33 | { 34 | $factory = new EventMiddlewareFactory(); 35 | 36 | self::assertInstanceOf(\Interop\Config\RequiresConfigId::class, $factory); 37 | self::assertInstanceOf(\Interop\Config\RequiresMandatoryOptions::class, $factory); 38 | self::assertInstanceOf(\Interop\Config\ProvidesDefaultOptions::class, $factory); 39 | } 40 | 41 | /** 42 | * @test 43 | */ 44 | public function it_creates_event_middleware(): void 45 | { 46 | $factory = new EventMiddlewareFactory(); 47 | $container = $this->getValidConfiguredContainer('event', null); 48 | 49 | $this->assertInstanceOf(EventMiddleware::class, $factory($container->reveal())); 50 | } 51 | 52 | /** 53 | * @test 54 | */ 55 | public function it_creates_event_middleware_with_another_gatherer(): void 56 | { 57 | $factory = new EventMiddlewareFactory(); 58 | $container = $this->getValidConfiguredContainer('event', new StubMetadataGatherer()); 59 | 60 | $this->assertInstanceOf(EventMiddleware::class, $factory($container->reveal())); 61 | } 62 | 63 | /** 64 | * @test 65 | */ 66 | public function it_throws_exception_if_option_is_missing(): void 67 | { 68 | $this->expectException(MandatoryOptionNotFoundException::class); 69 | 70 | $factory = new EventMiddlewareFactory(); 71 | $container = $this->prophesize(ContainerInterface::class); 72 | 73 | $container->has('config')->willReturn(true); 74 | $container->get('config')->willReturn([ 75 | 'prooph' => [ 76 | 'middleware' => [ 77 | 'event' => [ 78 | ], 79 | ], 80 | ], 81 | ]); 82 | 83 | $this->assertInstanceOf(EventMiddleware::class, $factory($container->reveal())); 84 | } 85 | 86 | /** 87 | * @test 88 | */ 89 | public function it_creates_event_middleware_from_static_call(): void 90 | { 91 | $container = $this->getValidConfiguredContainer('other_config_id', null); 92 | 93 | $factory = [EventMiddlewareFactory::class, 'other_config_id']; 94 | self::assertInstanceOf(EventMiddleware::class, $factory($container->reveal())); 95 | } 96 | 97 | /** 98 | * @test 99 | */ 100 | public function it_throws_invalid_argument_exception_without_container_on_static_call(): void 101 | { 102 | $this->expectException(InvalidArgumentException::class); 103 | $this->expectExceptionMessage('The first argument must be of type Psr\Container\ContainerInterface'); 104 | 105 | EventMiddlewareFactory::other_config_id(); 106 | } 107 | 108 | private function getValidConfiguredContainer(string $configId, ?StubMetadataGatherer $gatherer): ObjectProphecy 109 | { 110 | $container = $this->prophesize(ContainerInterface::class); 111 | $strategy = $this->prophesize(ResponseStrategy::class); 112 | $messageFactory = $this->prophesize(MessageFactory::class); 113 | 114 | $config = [ 115 | 'prooph' => [ 116 | 'middleware' => [ 117 | $configId => [ 118 | 'message_factory' => 'custom_message_factory', 119 | 'response_strategy' => 'JsonResponseStrategy', 120 | ], 121 | ], 122 | ], 123 | ]; 124 | 125 | if (null !== $gatherer) { 126 | $config['prooph']['middleware'][$configId]['metadata_gatherer'] = \get_class($gatherer); 127 | $container->get(\get_class($gatherer))->willReturn($gatherer); 128 | } 129 | 130 | $container->has('config')->willReturn(true); 131 | $container->get('config')->willReturn($config); 132 | 133 | $container->has('custom_message_factory')->willReturn(true); 134 | $container->get('custom_message_factory')->willReturn($messageFactory); 135 | $container->has('JsonResponseStrategy')->willReturn(true); 136 | $container->get('JsonResponseStrategy')->willReturn($strategy); 137 | $container->has(EventBus::class)->willReturn(true); 138 | $container->get(EventBus::class)->willReturn($this->prophesize(EventBus::class)); 139 | 140 | return $container; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /tests/Container/MessageMiddlewareFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware\Container; 15 | 16 | use Interop\Config\Exception\MandatoryOptionNotFoundException; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\MessageFactory; 19 | use Prooph\Psr7Middleware\Container\MessageMiddlewareFactory; 20 | use Prooph\Psr7Middleware\Exception\InvalidArgumentException; 21 | use Prooph\Psr7Middleware\MessageMiddleware; 22 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 23 | use Prooph\ServiceBus\CommandBus; 24 | use Prooph\ServiceBus\EventBus; 25 | use Prooph\ServiceBus\QueryBus; 26 | use Prophecy\Prophecy\ObjectProphecy; 27 | use Psr\Container\ContainerInterface; 28 | 29 | class MessageMiddlewareFactoryTest extends TestCase 30 | { 31 | /** 32 | * @test 33 | */ 34 | public function it_implements_config_interop(): void 35 | { 36 | $factory = new MessageMiddlewareFactory(); 37 | 38 | self::assertInstanceOf(\Interop\Config\RequiresConfigId::class, $factory); 39 | self::assertInstanceOf(\Interop\Config\RequiresMandatoryOptions::class, $factory); 40 | self::assertInstanceOf(\Interop\Config\ProvidesDefaultOptions::class, $factory); 41 | } 42 | 43 | /** 44 | * @test 45 | */ 46 | public function it_creates_message_middleware(): void 47 | { 48 | $factory = new MessageMiddlewareFactory(); 49 | $container = $this->getValidConfiguredContainer('message'); 50 | 51 | $this->assertInstanceOf(MessageMiddleware::class, $factory($container->reveal())); 52 | } 53 | 54 | /** 55 | * @test 56 | */ 57 | public function it_throws_exception_if_option_is_missing(): void 58 | { 59 | $this->expectException(MandatoryOptionNotFoundException::class); 60 | 61 | $factory = new MessageMiddlewareFactory(); 62 | $container = $this->prophesize(ContainerInterface::class); 63 | 64 | $container->has('config')->willReturn(true); 65 | $container->get('config')->willReturn([ 66 | 'prooph' => [ 67 | 'middleware' => [ 68 | 'message' => [ 69 | 'message_factory' => 'custom_message_factory', 70 | ], 71 | ], 72 | ], 73 | ]); 74 | 75 | $this->assertInstanceOf(MessageMiddleware::class, $factory($container->reveal())); 76 | } 77 | 78 | /** 79 | * @test 80 | */ 81 | public function it_creates_message_middleware_from_static_call(): void 82 | { 83 | $container = $this->getValidConfiguredContainer('other_config_id'); 84 | 85 | $factory = [MessageMiddlewareFactory::class, 'other_config_id']; 86 | self::assertInstanceOf(MessageMiddleware::class, $factory($container->reveal())); 87 | } 88 | 89 | /** 90 | * @test 91 | */ 92 | public function it_throws_invalid_argument_exception_without_container_on_static_call(): void 93 | { 94 | $this->expectException(InvalidArgumentException::class); 95 | $this->expectExceptionMessage('The first argument must be of type Psr\Container\ContainerInterface'); 96 | 97 | MessageMiddlewareFactory::other_config_id(); 98 | } 99 | 100 | private function getValidConfiguredContainer(string $configId): ObjectProphecy 101 | { 102 | $container = $this->prophesize(ContainerInterface::class); 103 | $strategy = $this->prophesize(ResponseStrategy::class); 104 | $messageFactory = $this->prophesize(MessageFactory::class); 105 | 106 | $container->has('config')->willReturn(true); 107 | $container->get('config')->willReturn([ 108 | 'prooph' => [ 109 | 'middleware' => [ 110 | $configId => [ 111 | 'message_factory' => 'custom_message_factory', 112 | 'response_strategy' => 'JsonResponseStrategy', 113 | ], 114 | ], 115 | ], 116 | ]); 117 | 118 | $container->has('custom_message_factory')->willReturn(true); 119 | $container->get('custom_message_factory')->willReturn($messageFactory); 120 | $container->has('JsonResponseStrategy')->willReturn(true); 121 | $container->get('JsonResponseStrategy')->willReturn($strategy); 122 | 123 | $container->has(CommandBus::class)->willReturn(true); 124 | $container->get(CommandBus::class)->willReturn($this->prophesize(CommandBus::class)); 125 | $container->has(EventBus::class)->willReturn(true); 126 | $container->get(EventBus::class)->willReturn($this->prophesize(EventBus::class)); 127 | $container->has(QueryBus::class)->willReturn(true); 128 | $container->get(QueryBus::class)->willReturn($this->prophesize(QueryBus::class)); 129 | 130 | return $container; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tests/Container/QueryMiddlewareFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware\Container; 15 | 16 | use Interop\Config\Exception\MandatoryOptionNotFoundException; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\MessageFactory; 19 | use Prooph\Psr7Middleware\Container\QueryMiddlewareFactory; 20 | use Prooph\Psr7Middleware\Exception\InvalidArgumentException; 21 | use Prooph\Psr7Middleware\QueryMiddleware; 22 | use Prooph\ServiceBus\QueryBus; 23 | use Prophecy\Prophecy\ObjectProphecy; 24 | use Psr\Container\ContainerInterface; 25 | 26 | class QueryMiddlewareFactoryTest extends TestCase 27 | { 28 | /** 29 | * @test 30 | */ 31 | public function it_implements_config_interop(): void 32 | { 33 | $factory = new QueryMiddlewareFactory(); 34 | 35 | self::assertInstanceOf(\Interop\Config\RequiresConfigId::class, $factory); 36 | self::assertInstanceOf(\Interop\Config\RequiresMandatoryOptions::class, $factory); 37 | self::assertInstanceOf(\Interop\Config\ProvidesDefaultOptions::class, $factory); 38 | } 39 | 40 | /** 41 | * @test 42 | */ 43 | public function it_creates_query_middleware(): void 44 | { 45 | $factory = new QueryMiddlewareFactory(); 46 | $container = $this->getValidConfiguredContainer('query', null); 47 | 48 | $this->assertInstanceOf(QueryMiddleware::class, $factory($container->reveal())); 49 | } 50 | 51 | /** 52 | * @test 53 | */ 54 | public function it_creates_query_middleware_with_another_gatherer(): void 55 | { 56 | $factory = new QueryMiddlewareFactory(); 57 | $container = $this->getValidConfiguredContainer('query', new StubMetadataGatherer()); 58 | 59 | $this->assertInstanceOf(QueryMiddleware::class, $factory($container->reveal())); 60 | } 61 | 62 | /** 63 | * @test 64 | */ 65 | public function it_throws_exception_if_option_is_missing(): void 66 | { 67 | $this->expectException(MandatoryOptionNotFoundException::class); 68 | 69 | $factory = new QueryMiddlewareFactory(); 70 | $container = $this->prophesize(ContainerInterface::class); 71 | 72 | $container->has('config')->willReturn(true); 73 | $container->get('config')->willReturn([ 74 | 'prooph' => [ 75 | 'middleware' => [ 76 | 'query' => [ 77 | 'message_factory' => 'custom_message_factory', 78 | ], 79 | ], 80 | ], 81 | ]); 82 | 83 | $this->assertInstanceOf(QueryMiddleware::class, $factory($container->reveal())); 84 | } 85 | 86 | /** 87 | * @test 88 | */ 89 | public function it_creates_query_middleware_from_static_call(): void 90 | { 91 | $container = $this->getValidConfiguredContainer('other_config_id', null); 92 | 93 | $factory = [QueryMiddlewareFactory::class, 'other_config_id']; 94 | self::assertInstanceOf(QueryMiddleware::class, $factory($container->reveal())); 95 | } 96 | 97 | /** 98 | * @test 99 | */ 100 | public function it_throws_invalid_argument_exception_without_container_on_static_call(): void 101 | { 102 | $this->expectException(InvalidArgumentException::class); 103 | $this->expectExceptionMessage('The first argument must be of type Psr\Container\ContainerInterface'); 104 | 105 | QueryMiddlewareFactory::other_config_id(); 106 | } 107 | 108 | private function getValidConfiguredContainer(string $configId, ?StubMetadataGatherer $gatherer): ObjectProphecy 109 | { 110 | $container = $this->prophesize(ContainerInterface::class); 111 | $strategy = $this->prophesize(\Prooph\Psr7Middleware\Response\ResponseStrategy::class); 112 | $messageFactory = $this->prophesize(MessageFactory::class); 113 | 114 | $config = [ 115 | 'prooph' => [ 116 | 'middleware' => [ 117 | $configId => [ 118 | 'message_factory' => 'custom_message_factory', 119 | 'response_strategy' => 'JsonResponseStrategy', 120 | ], 121 | ], 122 | ], 123 | ]; 124 | 125 | if (null !== $gatherer) { 126 | $config['prooph']['middleware'][$configId]['metadata_gatherer'] = \get_class($gatherer); 127 | $container->get(\get_class($gatherer))->willReturn($gatherer); 128 | } 129 | 130 | $container->has('config')->willReturn(true); 131 | $container->get('config')->willReturn($config); 132 | 133 | $container->has('custom_message_factory')->willReturn(true); 134 | $container->get('custom_message_factory')->willReturn($messageFactory); 135 | $container->has('JsonResponseStrategy')->willReturn(true); 136 | $container->get('JsonResponseStrategy')->willReturn($strategy); 137 | $container->has(QueryBus::class)->willReturn(true); 138 | $container->get(QueryBus::class)->willReturn($this->prophesize(QueryBus::class)); 139 | 140 | return $container; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /tests/Container/StubMetadataGatherer.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware\Container; 15 | 16 | use Prooph\Psr7Middleware\MetadataGatherer; 17 | use Psr\Http\Message\ServerRequestInterface; 18 | 19 | final class StubMetadataGatherer implements MetadataGatherer 20 | { 21 | public function getFromRequest(ServerRequestInterface $request): array 22 | { 23 | return []; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/EventMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware; 15 | 16 | use Fig\Http\Message\StatusCodeInterface; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\Message; 19 | use Prooph\Common\Messaging\MessageFactory; 20 | use Prooph\Psr7Middleware\EventMiddleware; 21 | use Prooph\Psr7Middleware\Exception\RuntimeException; 22 | use Prooph\Psr7Middleware\MetadataGatherer; 23 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 24 | use Prooph\ServiceBus\EventBus; 25 | use Prophecy\Argument; 26 | use Psr\Http\Message\ResponseInterface; 27 | use Psr\Http\Message\ServerRequestInterface; 28 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 29 | 30 | /** 31 | * Test integrity of \Prooph\Psr7Middleware\EventMiddleware 32 | */ 33 | class EventMiddlewareTest extends TestCase 34 | { 35 | /** 36 | * @test 37 | */ 38 | public function it_throws_exception_if_event_name_attribute_is_not_set(): void 39 | { 40 | $eventBus = $this->prophesize(EventBus::class); 41 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 42 | 43 | $messageFactory = $this->prophesize(MessageFactory::class); 44 | 45 | $request = $this->prophesize(ServerRequestInterface::class); 46 | $request->getAttribute(EventMiddleware::NAME_ATTRIBUTE)->willReturn(null)->shouldBeCalled(); 47 | 48 | $gatherer = $this->prophesize(MetadataGatherer::class); 49 | $gatherer->getFromRequest($request)->shouldNotBeCalled(); 50 | 51 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 52 | $responseStrategy->withStatus()->shouldNotBeCalled(); 53 | 54 | $handler = $this->prophesize(HandlerInterface::class); 55 | 56 | $this->expectException(RuntimeException::class); 57 | $this->expectExceptionMessage(\sprintf('Event name attribute ("%s") was not found in request.', EventMiddleware::NAME_ATTRIBUTE)); 58 | 59 | $middleware = new EventMiddleware($eventBus->reveal(), $messageFactory->reveal(), $gatherer->reveal(), $responseStrategy->reveal()); 60 | 61 | $middleware->process($request->reveal(), $handler->reveal()); 62 | } 63 | 64 | /** 65 | * @test 66 | */ 67 | public function it_throws_exception_if_dispatch_failed(): void 68 | { 69 | $eventName = 'stdClass'; 70 | $payload = ['user_id' => 123]; 71 | 72 | $eventBus = $this->prophesize(EventBus::class); 73 | $eventBus->dispatch(Argument::type(Message::class))->willThrow( 74 | new \Exception('Error') 75 | ); 76 | 77 | $message = $this->prophesize(\Prooph\Common\Messaging\Message::class); 78 | 79 | $messageFactory = $this->prophesize(MessageFactory::class); 80 | $messageFactory 81 | ->createMessageFromArray( 82 | $eventName, 83 | ['payload' => $payload, 'metadata' => []] 84 | ) 85 | ->willReturn($message->reveal()); 86 | 87 | $request = $this->prophesize(ServerRequestInterface::class); 88 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 89 | $request->getAttribute(EventMiddleware::NAME_ATTRIBUTE)->willReturn($eventName)->shouldBeCalled(); 90 | 91 | $gatherer = $this->prophesize(MetadataGatherer::class); 92 | $gatherer->getFromRequest($request)->shouldBeCalled(); 93 | 94 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 95 | $responseStrategy->withStatus()->shouldNotBeCalled(); 96 | 97 | $handler = $this->prophesize(HandlerInterface::class); 98 | 99 | $this->expectException(RuntimeException::class); 100 | $this->expectExceptionCode(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); 101 | $this->expectExceptionMessage('An error occurred during dispatching of event "stdClass"'); 102 | 103 | $middleware = new EventMiddleware($eventBus->reveal(), $messageFactory->reveal(), $gatherer->reveal(), $responseStrategy->reveal()); 104 | 105 | $middleware->process($request->reveal(), $handler->reveal()); 106 | } 107 | 108 | /** 109 | * @test 110 | */ 111 | public function it_dispatches_the_event(): void 112 | { 113 | $eventName = 'stdClass'; 114 | $payload = ['user_id' => 123]; 115 | 116 | $eventBus = $this->prophesize(EventBus::class); 117 | $eventBus->dispatch(Argument::type(Message::class))->shouldBeCalled(); 118 | 119 | $message = $this->prophesize(\Prooph\Common\Messaging\Message::class); 120 | 121 | $messageFactory = $this->prophesize(MessageFactory::class); 122 | $messageFactory 123 | ->createMessageFromArray( 124 | $eventName, 125 | ['payload' => $payload, 'metadata' => []] 126 | ) 127 | ->willReturn($message->reveal()) 128 | ->shouldBeCalled(); 129 | 130 | $request = $this->prophesize(ServerRequestInterface::class); 131 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 132 | $request->getAttribute(EventMiddleware::NAME_ATTRIBUTE)->willReturn($eventName)->shouldBeCalled(); 133 | 134 | $response = $this->prophesize(ResponseInterface::class); 135 | 136 | $gatherer = $this->prophesize(MetadataGatherer::class); 137 | $gatherer->getFromRequest($request->reveal())->willReturn([])->shouldBeCalled(); 138 | 139 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 140 | $responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED)->willReturn($response); 141 | 142 | $handler = $this->prophesize(HandlerInterface::class); 143 | 144 | $middleware = new EventMiddleware($eventBus->reveal(), $messageFactory->reveal(), $gatherer->reveal(), $responseStrategy->reveal()); 145 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/MessageMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware; 15 | 16 | use Fig\Http\Message\StatusCodeInterface; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Messaging\Message; 19 | use Prooph\Common\Messaging\MessageFactory; 20 | use Prooph\Psr7Middleware\Exception\RuntimeException; 21 | use Prooph\Psr7Middleware\MessageMiddleware; 22 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 23 | use Prooph\ServiceBus\CommandBus; 24 | use Prooph\ServiceBus\EventBus; 25 | use Prooph\ServiceBus\QueryBus; 26 | use Prophecy\Argument; 27 | use Psr\Http\Message\ResponseInterface; 28 | use Psr\Http\Message\ServerRequestInterface; 29 | use React\Promise\Promise; 30 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 31 | 32 | /** 33 | * Test integrity of \Prooph\Psr7Middleware\MessageMiddleware 34 | */ 35 | class MessageMiddlewareTest extends TestCase 36 | { 37 | /** 38 | * @test 39 | */ 40 | public function it_throws_exception_if_message_is_not_well_formed(): void 41 | { 42 | $commandBus = $this->prophesize(CommandBus::class); 43 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 44 | 45 | $queryBus = $this->prophesize(QueryBus::class); 46 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 47 | 48 | $eventBus = $this->prophesize(EventBus::class); 49 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 50 | 51 | $messageFactory = $this->prophesize(MessageFactory::class); 52 | $messageFactory->createMessageFromArray(Argument::type('string'), Argument::type('array'))->shouldNotBeCalled(); 53 | 54 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 55 | $responseStrategy->fromPromise(Argument::type(Promise::class))->shouldNotBeCalled(); 56 | $responseStrategy->withStatus(Argument::any())->shouldNotBeCalled(); 57 | 58 | $request = $this->prophesize(ServerRequestInterface::class); 59 | $request->getParsedBody()->willReturn(['message_name' => 'test'])->shouldBeCalled(); 60 | $request->getAttribute('message_name', 'test')->willReturn('test')->shouldBeCalled(); 61 | 62 | $handler = $this->prophesize(HandlerInterface::class); 63 | 64 | $this->expectException(RuntimeException::class); 65 | $this->expectExceptionMessage('MessageData must contain a key payload'); 66 | 67 | $middleware = new MessageMiddleware( 68 | $commandBus->reveal(), 69 | $queryBus->reveal(), 70 | $eventBus->reveal(), 71 | $messageFactory->reveal(), 72 | $responseStrategy->reveal() 73 | ); 74 | 75 | $middleware->process($request->reveal(), $handler->reveal()); 76 | } 77 | 78 | /** 79 | * @test 80 | */ 81 | public function it_thows_exception_if_message_type_is_unknown(): void 82 | { 83 | $payload = $this->getPayload('unknown'); 84 | 85 | $message = $this->prophesize(Message::class); 86 | $message->messageType()->shouldBeCalled()->willReturn('unkown'); 87 | 88 | $commandBus = $this->prophesize(CommandBus::class); 89 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 90 | 91 | $queryBus = $this->prophesize(QueryBus::class); 92 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 93 | 94 | $eventBus = $this->prophesize(EventBus::class); 95 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 96 | 97 | $messageFactory = $this->prophesize(MessageFactory::class); 98 | $messageFactory 99 | ->createMessageFromArray( 100 | $payload['message_name'], 101 | $payload 102 | ) 103 | ->willReturn($message->reveal()) 104 | ->shouldBeCalled(); 105 | 106 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 107 | $responseStrategy->fromPromise(Argument::type(Promise::class))->shouldNotBeCalled(); 108 | $responseStrategy->withStatus(Argument::any())->shouldNotBeCalled(); 109 | 110 | $request = $this->prophesize(ServerRequestInterface::class); 111 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 112 | $request->getAttribute('message_name', 'unknown')->willReturn('unknown')->shouldBeCalled(); 113 | 114 | $handler = $this->prophesize(HandlerInterface::class); 115 | 116 | $this->expectException(RuntimeException::class); 117 | $this->expectExceptionMessage('An error occurred during dispatching of message "unknown"'); 118 | 119 | $middleware = new MessageMiddleware( 120 | $commandBus->reveal(), 121 | $queryBus->reveal(), 122 | $eventBus->reveal(), 123 | $messageFactory->reveal(), 124 | $responseStrategy->reveal() 125 | ); 126 | 127 | $middleware->process($request->reveal(), $handler->reveal()); 128 | } 129 | 130 | public function providerMessageTypes(): array 131 | { 132 | return [ 133 | [Message::TYPE_COMMAND], 134 | [Message::TYPE_EVENT], 135 | [Message::TYPE_QUERY], 136 | ]; 137 | } 138 | 139 | /** 140 | * @test 141 | * @dataProvider providerMessageTypes 142 | */ 143 | public function it_throws_exception_if_dispatch_failed(string $messageType): void 144 | { 145 | $payload = $this->getPayload('name.' . $messageType); 146 | 147 | $message = $this->prophesize(Message::class); 148 | $message->messageType()->shouldBeCalled()->willReturn($messageType); 149 | 150 | $commandBus = $this->prophesize(CommandBus::class); 151 | $queryBus = $this->prophesize(QueryBus::class); 152 | $eventBus = $this->prophesize(EventBus::class); 153 | 154 | switch ($messageType) { 155 | case Message::TYPE_COMMAND: 156 | $commandBus->dispatch(Argument::type(Message::class))->shouldBeCalled()->willThrow( 157 | new \Exception('Error') 158 | ); 159 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 160 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 161 | break; 162 | case Message::TYPE_QUERY: 163 | $queryBus->dispatch(Argument::type(Message::class))->shouldBeCalled()->willThrow( 164 | new \Exception('Error') 165 | ); 166 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 167 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 168 | break; 169 | case Message::TYPE_EVENT: 170 | $eventBus->dispatch(Argument::type(Message::class))->shouldBeCalled()->willThrow( 171 | new \Exception('Error') 172 | ); 173 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 174 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 175 | break; 176 | default: 177 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 178 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 179 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 180 | break; 181 | } 182 | 183 | $messageFactory = $this->prophesize(MessageFactory::class); 184 | $messageFactory 185 | ->createMessageFromArray( 186 | $payload['message_name'], 187 | $payload 188 | ) 189 | ->willReturn($message->reveal()) 190 | ->shouldBeCalled(); 191 | 192 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 193 | $responseStrategy->fromPromise(Argument::type(Promise::class))->shouldNotBeCalled(); 194 | $responseStrategy->withStatus(Argument::any())->shouldNotBeCalled(); 195 | 196 | $request = $this->prophesize(ServerRequestInterface::class); 197 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 198 | $request->getAttribute('message_name', 'name.' . $messageType)->willReturn('name.' . $messageType)->shouldBeCalled(); 199 | 200 | $handler = $this->prophesize(HandlerInterface::class); 201 | 202 | $this->expectException(RuntimeException::class); 203 | $this->expectExceptionCode(StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR); 204 | 205 | $middleware = new MessageMiddleware( 206 | $commandBus->reveal(), 207 | $queryBus->reveal(), 208 | $eventBus->reveal(), 209 | $messageFactory->reveal(), 210 | $responseStrategy->reveal() 211 | ); 212 | 213 | $middleware->process($request->reveal(), $handler->reveal()); 214 | } 215 | 216 | /** 217 | * @test 218 | */ 219 | public function it_dispatches_the_command(): void 220 | { 221 | $payload = $this->getPayload('command'); 222 | 223 | $commandBus = $this->prophesize(CommandBus::class); 224 | $commandBus->dispatch(Argument::type(Message::class))->shouldBeCalled(); 225 | 226 | $queryBus = $this->prophesize(QueryBus::class); 227 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 228 | 229 | $eventBus = $this->prophesize(EventBus::class); 230 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 231 | 232 | $message = $this->prophesize(Message::class); 233 | $message->messageType()->shouldBeCalled()->willReturn(Message::TYPE_COMMAND); 234 | 235 | $messageFactory = $this->prophesize(MessageFactory::class); 236 | $messageFactory 237 | ->createMessageFromArray( 238 | $payload['message_name'], 239 | $payload 240 | ) 241 | ->willReturn($message->reveal()) 242 | ->shouldBeCalled(); 243 | 244 | $request = $this->prophesize(ServerRequestInterface::class); 245 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 246 | $request->getAttribute('message_name', 'command')->willReturn('command')->shouldBeCalled(); 247 | 248 | $response = $this->prophesize(ResponseInterface::class); 249 | 250 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 251 | $responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED)->willReturn($response); 252 | 253 | $handler = $this->prophesize(HandlerInterface::class); 254 | 255 | $middleware = new MessageMiddleware( 256 | $commandBus->reveal(), 257 | $queryBus->reveal(), 258 | $eventBus->reveal(), 259 | $messageFactory->reveal(), 260 | $responseStrategy->reveal() 261 | ); 262 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 263 | } 264 | 265 | /** 266 | * @test 267 | */ 268 | public function it_dispatches_the_event(): void 269 | { 270 | $payload = $this->getPayload('event'); 271 | 272 | $commandBus = $this->prophesize(CommandBus::class); 273 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 274 | 275 | $queryBus = $this->prophesize(QueryBus::class); 276 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 277 | 278 | $eventBus = $this->prophesize(EventBus::class); 279 | $eventBus->dispatch(Argument::type(Message::class))->shouldBeCalled(); 280 | 281 | $message = $this->prophesize(Message::class); 282 | $message->messageType()->shouldBeCalled()->willReturn(Message::TYPE_EVENT); 283 | 284 | $messageFactory = $this->prophesize(MessageFactory::class); 285 | $messageFactory 286 | ->createMessageFromArray( 287 | $payload['message_name'], 288 | $payload 289 | ) 290 | ->willReturn($message->reveal()) 291 | ->shouldBeCalled(); 292 | 293 | $request = $this->prophesize(ServerRequestInterface::class); 294 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 295 | $request->getAttribute('message_name', 'event')->willReturn('event')->shouldBeCalled(); 296 | 297 | $response = $this->prophesize(ResponseInterface::class); 298 | 299 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 300 | $responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED)->willReturn($response); 301 | 302 | $handler = $this->prophesize(HandlerInterface::class); 303 | 304 | $middleware = new MessageMiddleware( 305 | $commandBus->reveal(), 306 | $queryBus->reveal(), 307 | $eventBus->reveal(), 308 | $messageFactory->reveal(), 309 | $responseStrategy->reveal() 310 | ); 311 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 312 | } 313 | 314 | /** 315 | * @test 316 | */ 317 | public function it_dispatches_the_query(): void 318 | { 319 | $payload = $this->getPayload('query'); 320 | 321 | $commandBus = $this->prophesize(CommandBus::class); 322 | $commandBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 323 | 324 | $queryBus = $this->prophesize(QueryBus::class); 325 | $queryBus->dispatch(Argument::type(Message::class))->shouldBeCalled()->willReturn( 326 | $this->prophesize(Promise::class)->reveal() 327 | ); 328 | 329 | $eventBus = $this->prophesize(EventBus::class); 330 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 331 | 332 | $message = $this->prophesize(Message::class); 333 | $message->messageType()->shouldBeCalled()->willReturn(Message::TYPE_QUERY); 334 | 335 | $messageFactory = $this->prophesize(MessageFactory::class); 336 | $messageFactory 337 | ->createMessageFromArray( 338 | $payload['message_name'], 339 | $payload 340 | ) 341 | ->willReturn($message->reveal()) 342 | ->shouldBeCalled(); 343 | 344 | $request = $this->prophesize(ServerRequestInterface::class); 345 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 346 | $request->getAttribute('message_name', 'query')->willReturn('query')->shouldBeCalled(); 347 | 348 | $response = $this->prophesize(ResponseInterface::class); 349 | 350 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 351 | $responseStrategy->fromPromise(Argument::type(Promise::class))->willReturn($response); 352 | 353 | $handler = $this->prophesize(HandlerInterface::class); 354 | 355 | $middleware = new MessageMiddleware( 356 | $commandBus->reveal(), 357 | $queryBus->reveal(), 358 | $eventBus->reveal(), 359 | $messageFactory->reveal(), 360 | $responseStrategy->reveal() 361 | ); 362 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 363 | } 364 | 365 | /** 366 | * @test 367 | */ 368 | public function it_prefers_message_name_from_request_if_set(): void 369 | { 370 | $payload = $this->getPayload('command'); 371 | 372 | $commandBus = $this->prophesize(CommandBus::class); 373 | $commandBus->dispatch(Argument::type(Message::class))->shouldBeCalled(); 374 | 375 | $queryBus = $this->prophesize(QueryBus::class); 376 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 377 | 378 | $eventBus = $this->prophesize(EventBus::class); 379 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 380 | 381 | $message = $this->prophesize(Message::class); 382 | $message->messageType()->shouldBeCalled()->willReturn(Message::TYPE_COMMAND); 383 | 384 | $payloadWithUpdatedMessageName = \array_merge($payload, ['message_name' => 'name_from_request']); 385 | 386 | $messageFactory = $this->prophesize(MessageFactory::class); 387 | $messageFactory 388 | ->createMessageFromArray( 389 | 'name_from_request', 390 | $payloadWithUpdatedMessageName 391 | ) 392 | ->willReturn($message->reveal()) 393 | ->shouldBeCalled(); 394 | 395 | $request = $this->prophesize(ServerRequestInterface::class); 396 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 397 | $request->getAttribute('message_name', 'command')->willReturn('name_from_request')->shouldBeCalled(); 398 | 399 | $response = $this->prophesize(ResponseInterface::class); 400 | 401 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 402 | $responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED)->willReturn($response); 403 | 404 | $handler = $this->prophesize(HandlerInterface::class); 405 | 406 | $middleware = new MessageMiddleware( 407 | $commandBus->reveal(), 408 | $queryBus->reveal(), 409 | $eventBus->reveal(), 410 | $messageFactory->reveal(), 411 | $responseStrategy->reveal() 412 | ); 413 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 414 | } 415 | 416 | /** 417 | * @test 418 | */ 419 | public function it_applies_defaults_if_only_message_name_and_payload_is_given() 420 | { 421 | $payload = $this->getPayload('command'); 422 | 423 | unset($payload['uuid'], $payload['metadata'], $payload['created_at']); 424 | 425 | $commandBus = $this->prophesize(CommandBus::class); 426 | $commandBus->dispatch(Argument::type(Message::class))->shouldBeCalled(); 427 | 428 | $queryBus = $this->prophesize(QueryBus::class); 429 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 430 | 431 | $eventBus = $this->prophesize(EventBus::class); 432 | $eventBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 433 | 434 | $message = $this->prophesize(Message::class); 435 | $message->messageType()->shouldBeCalled()->willReturn(Message::TYPE_COMMAND); 436 | 437 | $messageFactory = $this->prophesize(MessageFactory::class); 438 | $messageFactory 439 | ->createMessageFromArray( 440 | $payload['message_name'], 441 | Argument::allOf( 442 | Argument::withKey('uuid'), 443 | Argument::withKey('message_name'), 444 | Argument::withKey('payload'), 445 | Argument::withKey('created_at'), 446 | Argument::withKey('metadata') 447 | ) 448 | ) 449 | ->willReturn($message->reveal()) 450 | ->shouldBeCalled(); 451 | 452 | $request = $this->prophesize(ServerRequestInterface::class); 453 | $request->getParsedBody()->willReturn($payload)->shouldBeCalled(); 454 | $request->getAttribute('message_name', 'command')->willReturn('command')->shouldBeCalled(); 455 | 456 | $response = $this->prophesize(ResponseInterface::class); 457 | 458 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 459 | $responseStrategy->withStatus(StatusCodeInterface::STATUS_ACCEPTED)->willReturn($response); 460 | 461 | $handler = $this->prophesize(HandlerInterface::class); 462 | 463 | $middleware = new MessageMiddleware( 464 | $commandBus->reveal(), 465 | $queryBus->reveal(), 466 | $eventBus->reveal(), 467 | $messageFactory->reveal(), 468 | $responseStrategy->reveal() 469 | ); 470 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 471 | } 472 | 473 | /** 474 | * Returns a full configured payload array 475 | */ 476 | private function getPayload(string $messageName): array 477 | { 478 | return [ 479 | 'message_name' => $messageName, 480 | 'uuid' => '08db0554-8e07-49a3-9cf2-d28dd9ec10ab', 481 | 'version' => 1, 482 | 'payload' => ['user' => 'prooph'], 483 | 'metadata' => ['system' => 'production'], 484 | 'created_at' => \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.u', '2016-02-02T11:45:39.000000'), 485 | ]; 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /tests/NoopMetadataGathererTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Prooph\Psr7Middleware\MetadataGatherer; 18 | use Prooph\Psr7Middleware\NoopMetadataGatherer; 19 | use Psr\Http\Message\ServerRequestInterface; 20 | 21 | /** 22 | * Test integrity of \Prooph\Psr7Middleware\NoopMetadataGathererTest 23 | */ 24 | class NoopMetadataGathererTest extends TestCase 25 | { 26 | /** 27 | * @test 28 | */ 29 | public function it_implements_metadata_gatherer_interface(): void 30 | { 31 | $gatherer = new NoopMetadataGatherer(); 32 | 33 | self::assertInstanceOf(MetadataGatherer::class, $gatherer); 34 | } 35 | 36 | /** 37 | * @test 38 | */ 39 | public function it_return_array(): void 40 | { 41 | $gatherer = new NoopMetadataGatherer(); 42 | $request = $this->prophesize(ServerRequestInterface::class); 43 | 44 | $this->assertInternalType('array', $gatherer->getFromRequest($request->reveal())); 45 | $this->assertEmpty($gatherer->getFromRequest($request->reveal())); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/QueryMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2016-2018 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\Psr7Middleware; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Prooph\Common\Messaging\Message; 18 | use Prooph\Common\Messaging\MessageFactory; 19 | use Prooph\Psr7Middleware\Exception\RuntimeException; 20 | use Prooph\Psr7Middleware\MetadataGatherer; 21 | use Prooph\Psr7Middleware\QueryMiddleware; 22 | use Prooph\Psr7Middleware\Response\ResponseStrategy; 23 | use Prooph\ServiceBus\QueryBus; 24 | use Prophecy\Argument; 25 | use Psr\Http\Message\ResponseInterface; 26 | use Psr\Http\Message\ServerRequestInterface; 27 | use React\Promise\Promise; 28 | use Webimpress\HttpMiddlewareCompatibility\HandlerInterface; 29 | 30 | /** 31 | * Test integrity of \Prooph\Psr7Middleware\QueryMiddleware 32 | */ 33 | class QueryMiddlewareTest extends TestCase 34 | { 35 | /** 36 | * @test 37 | */ 38 | public function it_throws_exception_if_query_name_attribute_is_not_set(): void 39 | { 40 | $queryBus = $this->prophesize(QueryBus::class); 41 | $queryBus->dispatch(Argument::type(Message::class))->shouldNotBeCalled(); 42 | 43 | $messageFactory = $this->prophesize(MessageFactory::class); 44 | 45 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 46 | $responseStrategy->fromPromise(Argument::type(Promise::class))->shouldNotBeCalled(); 47 | 48 | $request = $this->prophesize(ServerRequestInterface::class); 49 | $request->getAttribute(QueryMiddleware::NAME_ATTRIBUTE)->willReturn(null)->shouldBeCalled(); 50 | 51 | $gatherer = $this->prophesize(MetadataGatherer::class); 52 | $gatherer->getFromRequest($request->reveal())->shouldNotBeCalled(); 53 | 54 | $handler = $this->prophesize(HandlerInterface::class); 55 | 56 | $this->expectException(RuntimeException::class); 57 | $this->expectExceptionMessage(\sprintf('Query name attribute ("%s") was not found in request.', QueryMiddleware::NAME_ATTRIBUTE)); 58 | 59 | $middleware = new QueryMiddleware($queryBus->reveal(), $messageFactory->reveal(), $responseStrategy->reveal(), $gatherer->reveal()); 60 | 61 | $middleware->process($request->reveal(), $handler->reveal()); 62 | } 63 | 64 | /** 65 | * @test 66 | */ 67 | public function it_throws_exception_if_dispatch_failed(): void 68 | { 69 | $queryName = 'stdClass'; 70 | $payload = ['user_id' => 123]; 71 | 72 | $queryBus = $this->prophesize(QueryBus::class); 73 | $queryBus->dispatch(Argument::type(Message::class))->willThrow( 74 | new \Exception('Error') 75 | ); 76 | 77 | $message = $this->prophesize(Message::class); 78 | 79 | $messageFactory = $this->prophesize(MessageFactory::class); 80 | $messageFactory 81 | ->createMessageFromArray( 82 | $queryName, 83 | ['payload' => $payload, 'metadata' => []] 84 | ) 85 | ->willReturn($message->reveal()) 86 | ->shouldBeCalled(); 87 | 88 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 89 | $responseStrategy->fromPromise(Argument::type(Promise::class))->shouldNotBeCalled(); 90 | 91 | $request = $this->prophesize(ServerRequestInterface::class); 92 | $request->getMethod()->shouldBeCalled(); 93 | $request->getQueryParams()->willReturn($payload)->shouldBeCalled(); 94 | $request->getAttribute(QueryMiddleware::NAME_ATTRIBUTE)->willReturn($queryName)->shouldBeCalled(); 95 | 96 | $gatherer = $this->prophesize(MetadataGatherer::class); 97 | $gatherer->getFromRequest($request->reveal())->willReturn([])->shouldBeCalled(); 98 | 99 | $handler = $this->prophesize(HandlerInterface::class); 100 | 101 | $this->expectException(RuntimeException::class); 102 | $this->expectExceptionMessage('An error occurred during dispatching of query "stdClass"'); 103 | 104 | $middleware = new QueryMiddleware($queryBus->reveal(), $messageFactory->reveal(), $responseStrategy->reveal(), $gatherer->reveal()); 105 | 106 | $middleware->process($request->reveal(), $handler->reveal()); 107 | } 108 | 109 | /** 110 | * @test 111 | */ 112 | public function it_dispatches_the_query(): void 113 | { 114 | $queryName = 'stdClass'; 115 | $payload = ['user_id' => 123]; 116 | 117 | $queryBus = $this->prophesize(QueryBus::class); 118 | $queryBus->dispatch(Argument::type(Message::class))->shouldBeCalled()->willReturn( 119 | $this->prophesize(Promise::class)->reveal() 120 | ); 121 | 122 | $message = $this->prophesize(Message::class); 123 | 124 | $messageFactory = $this->prophesize(MessageFactory::class); 125 | $messageFactory 126 | ->createMessageFromArray( 127 | $queryName, 128 | ['payload' => $payload, 'metadata' => []] 129 | ) 130 | ->willReturn($message->reveal()) 131 | ->shouldBeCalled(); 132 | 133 | $request = $this->prophesize(ServerRequestInterface::class); 134 | $request->getMethod()->shouldBeCalled(); 135 | $request->getQueryParams()->willReturn($payload)->shouldBeCalled(); 136 | $request->getAttribute(QueryMiddleware::NAME_ATTRIBUTE)->willReturn($queryName)->shouldBeCalled(); 137 | 138 | $response = $this->prophesize(ResponseInterface::class); 139 | 140 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 141 | $responseStrategy->fromPromise(Argument::type(Promise::class))->willReturn($response); 142 | 143 | $gatherer = $this->prophesize(MetadataGatherer::class); 144 | $gatherer->getFromRequest($request)->shouldBeCalled(); 145 | 146 | $handler = $this->prophesize(HandlerInterface::class); 147 | 148 | $middleware = new QueryMiddleware($queryBus->reveal(), $messageFactory->reveal(), $responseStrategy->reveal(), $gatherer->reveal()); 149 | 150 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 151 | } 152 | 153 | /** 154 | * @test 155 | */ 156 | public function it_dispatches_the_query_with_post_data(): void 157 | { 158 | $queryName = 'stdClass'; 159 | $parsedBody = ['filter' => []]; 160 | $payload = ['user_id' => 123]; 161 | 162 | $queryBus = $this->prophesize(QueryBus::class); 163 | $queryBus->dispatch(Argument::type(Message::class))->shouldBeCalled()->willReturn( 164 | $this->prophesize(Promise::class)->reveal() 165 | ); 166 | 167 | $message = $this->prophesize(Message::class); 168 | 169 | $messageFactory = $this->prophesize(MessageFactory::class); 170 | $messageFactory 171 | ->createMessageFromArray( 172 | $queryName, 173 | ['payload' => \array_merge($payload, ['data' => $parsedBody]), 'metadata' => []] 174 | ) 175 | ->willReturn($message->reveal()) 176 | ->shouldBeCalled(); 177 | 178 | $request = $this->prophesize(ServerRequestInterface::class); 179 | $request->getMethod()->willReturn('POST')->shouldBeCalled(); 180 | $request->getParsedBody()->willReturn($parsedBody)->shouldBeCalled(); 181 | $request->getQueryParams()->willReturn($payload)->shouldBeCalled(); 182 | $request->getAttribute(QueryMiddleware::NAME_ATTRIBUTE)->willReturn($queryName)->shouldBeCalled(); 183 | 184 | $response = $this->prophesize(ResponseInterface::class); 185 | 186 | $responseStrategy = $this->prophesize(ResponseStrategy::class); 187 | $responseStrategy->fromPromise(Argument::type(Promise::class))->willReturn($response); 188 | 189 | $gatherer = $this->prophesize(MetadataGatherer::class); 190 | $gatherer->getFromRequest($request)->shouldBeCalled(); 191 | 192 | $handler = $this->prophesize(HandlerInterface::class); 193 | 194 | $middleware = new QueryMiddleware($queryBus->reveal(), $messageFactory->reveal(), $responseStrategy->reveal(), $gatherer->reveal()); 195 | 196 | $this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal())); 197 | } 198 | } 199 | --------------------------------------------------------------------------------