diff --git a/.travis.yml b/.travis.yml index e6af653..5e38ab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,38 @@ language: php php: - - 5.5 - 5.6 - 7.0 - hhvm +sudo: false + +cache: + directories: + - $HOME/.composer/cache + - vendor + +git: + depth: 1 + +env: + global: + - PHPUNIT_VERSION="^5.4" + matrix: include: - - php: 5.5 - env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"' + - php: 5.6 + env: + - COMPOSER_FLAGS="--prefer-stable --prefer-lowest" services: - redis-server before_script: - - bash -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then phpenv config-add .travis.php.ini; fi' + - bash -c 'if [ "${TRAVIS_PHP_VERSION}" != "hhvm" ]; then phpenv config-add .travis.php.ini; fi' - travis_retry composer self-update - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source + - travis_retry composer require phpunit/phpunit:${PHPUNIT_VERSION} script: - vendor/bin/phpunit diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fcd43e..cb80f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). _..._ +## 3.0.0 - ? + +- Replace routing with dispatching, see MIGRATING for details + ## 2.1.0 - ? - Replace generic exceptions with specific exceptions in handlers diff --git a/MIGRATING.md b/MIGRATING.md new file mode 100644 index 0000000..ecfc065 --- /dev/null +++ b/MIGRATING.md @@ -0,0 +1,140 @@ +Migrating +========= + +Use this document to help you transition away from deprecated code. + +## Switching from Routing to Dispatching + +Version 3.0.0 replaces the routing callback with dispatching callables, which +are typically defined as classes with an `__invoke` method in your project. + +### Changes + +Routing that used to be defined as: + +```php +Application::build() +// ... +->setRouting(function (Equip\Directory $directory) { + return $directory + ->get('/', Domain\Welcome::class) + ->get('/hello[/{name}]', Domain\Hello::class) + ->post('/hello[/{name}]', Domain\Hello::class) + ; // End of routing +}) +``` + +Would now be defined as: + +```php +Application::build() +// ... +->setDispatching([ + Project\Hello\HelloDispatcher::class, + Project\Welcome\WelcomeDispatcher::class, +]) +``` + +### Temporary Solution + +As a temporary solution, you may reuse the routing function for dispatching: + +```php +Application::build() +// ... +->setDispatching([ + function (Equip\Directory $directory) { + return $directory + ->get('/', Domain\Welcome::class) + ->get('/hello[/{name}]', Domain\Hello::class) + ->post('/hello[/{name}]', Domain\Hello::class) + ; // End of routing + }, +]) +``` + + +### Splitting Dispatchers + +The long term solution for handling dispatching is to add separate classes for +each resource you want to dispatch. Splitting the current routing would become: + +```php +namespace Equip\Project\Hello; + +use Equip\Directory; + +class HelloDispatcher +{ + public function __invoke(Directory $directory) + { + return $directory + ->get('/hello[/{name}]', HelloDomain::class) + ->post('/hello[/{name}]', HelloDomain::class) + ; + } +} +``` + +```php +namespace Equip\Project\Welcome; + +use Equip\Directory; + +class WelcomeDispatcher +{ + public function __invoke(Directory $directory) + { + return $directory->get('/', WelcomeDomain::class); + } +} +``` + +For simplicity, you may only want to have a single dispatcher when starting a +new project and as your application grows you can switch to separate files. + + +### Resource Based Structure + +This also implies a change towards resource-based code structure, as opposed to +type-based structure. Instead of organizing your files by the type: + +``` +src/ + Domain/ + Hello.php + Welcome.php + Exceptions/ + HelloError.php + WelcomeError.php +``` + +Files are logically grouped by the resource they represent: + +``` +src/ + Hello/ + HelloDomain.php + HelloError.php + Welcome/ + WelcomeDomain.php + WelcomeError.php +``` + +It is recommended that your organize your Equip projects this way but not required. +The benefit to doing so that it becomes very easy to determine what code can be +found based on its name. + +If you have common code that is shared between different resource contexts, feel +free to create a "common" directory: + +``` +src/ + Common/ + Filesystem.php + Database.php + Hello/ + ... + Welcome/ + ... +``` diff --git a/composer.json b/composer.json index 314e741..b81208f 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,6 @@ "minimum-stability": "stable", "require": { "php": ">=5.5", - "equip/adr": "^2.0", "equip/config": "^1.0.2", "equip/structure": "^1.0", "filp/whoops": "^2.0", @@ -29,7 +28,6 @@ "josegonzalez/dotenv": "^2.0", "league/plates": "^3.1", "monolog/monolog": "^1.19", - "phpunit/phpunit": "^4.8|^5.0", "zendframework/zend-diactoros": "^1.0.4" }, "suggest": { diff --git a/docs/index.md b/docs/index.md index 80987b1..311a05b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -107,7 +107,6 @@ The following configurations are typically used by default: * [`AurynConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/AurynConfiguration.php) - Use the `Injector` instance as a singleton and to resolve [actions](https://github.com/pmjones/adr#controller-vs-action) * [`DiactorosConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/DiactorosConfiguration.php) - Use [Diactoros](https://github.com/zendframework/zend-diactoros/) for the framework [PSR-7](http://www.php-fig.org/psr/psr-7/) implementation -* [`PayloadConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/PayloadConfiguration.php) - Use the default Equip class as the implementation for [`PayloadInterface`](https://github.com/equip/adr/blob/master/src/PayloadInterface.php) * [`RelayConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/RelayConfiguration.php) - Use [Relay](http://relayphp.com) for the framework middleware dispatcher * [`WhoopsConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/WhoopsConfiguration.php) - Use [Whoops](http://filp.github.io/whoops/) for handling exceptions @@ -118,7 +117,6 @@ The following configurations are available but not used by default: * [`EnvConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/EnvConfiguration.php) - Use [Dotenv](https://github.com/josegonzalez/php-dotenv) to populate the content of [`Env`](https://github.com/equip/framework/blob/master/src/Env.php) * [`MonologConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/MonologConfiguration.php) - Use [Monolog](https://github.com/Seldaek/monolog/) for the framework [PSR-3](http://www.php-fig.org/psr/psr-3/) implementation * [`PlatesConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/PlatesConfiguration.php) - Configure the [Plates](http://platesphp.com/) template engine -* [`PlatesResponderConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/PlatesResponderConfiguration.php) - Use [Plates](http://platesphp.com/) as the default [responder](#responders) * [`RedisConfiguration`](https://github.com/equip/framework/blob/master/src/Configuration/RedisConfiguration.php) - Use [Redis](http://redis.io) for in-memory store #### Setting The Env File @@ -163,7 +161,6 @@ Equip\Application::build() ->setConfiguration([ Equip\Configuration\AurynConfiguration::class, Equip\Configuration\DiactorosConfiguration::class, - Equip\Configuration\PayloadConfiguration::class, Equip\Configuration\RelayConfiguration::class, Equip\Configuration\WhoopsConfiguration::class, ]) @@ -232,23 +229,24 @@ Equip\Application::build() Equip uses [FastRoute](https://github.com/nikic/FastRoute) internally for routing. As such, it uses that library's URI pattern syntax; see [its documentation](https://github.com/nikic/FastRoute#defining-routes) for more details. -The directory maps URIs to the corresponding [domain](https://github.com/pmjones/adr#model-vs-domain) that the should be used. This is implemented in the Equip [`Directory`](https://github.com/equip/framework/blob/master/src/Directory.php) class. Here is an example of what configuring an instance of it could look like: +The directory maps URIs to the corresponding [action](#actions) that should be used. This is implemented in the Equip [`Directory`](https://github.com/equip/framework/blob/master/src/Directory.php) class. Here is an example of what configuring an instance of it could look like: ```php -use Acme\Domain; +use Acme\Action; Equip\Application::build() // ... ->setRouting(function (Equip\Directory $directory) { return $directory - ->any('/', Domain\Providers::class) - ->get('/providers', Domain\GetProviders::class) - ->get('/providers/{provider}', Domain\GetProvider::class) - ->post('/providers/{provider}', Domain\SynchronizeProvider::class) - ->post('/providers/{provider}/connection', Domain\ActivateProvider::class) - ->delete('/providers/{provider}/connection', Domain\DeactivateProvider::class) - ->get('/providers/{provider}/configuration', Domain\GetProviderConfiguration::class) - ->put('/providers/{provider}/configuration', Domain\ChangeProviderConfiguration::class) + ->any('/', Action\Providers::class) + ->get('/providers', Action\ListProviders::class) + ->get('/providers/{provider}', Action\GetProvider::class) + ->post('/providers/{provider}', Action\CreateProvider::class) + ->put('/providers/{provider}', Action\UpdateProvider::class) + ->post('/providers/{provider}/connection', Action\ActivateProvider::class) + ->delete('/providers/{provider}/connection', Action\DeactivateProvider::class) + ->get('/providers/{provider}/configuration', Action\GetProviderConfiguration::class) + ->put('/providers/{provider}/configuration', Action\UpdateProviderConfiguration::class) ; // End of routing }) ->run(); @@ -256,19 +254,6 @@ Equip\Application::build() *It is very important to remember that __the `Directory` object is immutable__! You must __always__ return the directory or changes will be lost.* -It is also possible to provide an [`Action`](https://github.com/equip/framework/blob/master/src/Action.php) object instead of a domain class if you want to modify the responder or input class that will be used to handle the action: - -```php -$directory->get('/login', new Equip\Action( - Domain\Login::class, - Acme\Responder::class, - Acme\Input::class -)); -``` - -If an `Action` object is not provided one will be constructed with the provided `Domain` reference instead. - - #### Object Routing An alternative way to implement routing configuration involves using an [invokable](http://php.net/manual/en/language.oop5.magic.php#object.invoke) object. This encapsulation allows for niceties such as non-public methods that can make routing code more concise. @@ -277,7 +262,7 @@ An alternative way to implement routing configuration involves using an [invokab // src/Routing.php namespace Acme; -use Acme\Domain; +use Acme\Action; use Equip\Directory; class Routing @@ -285,13 +270,10 @@ class Routing public function __invoke(Directory $directory) { return $directory - ->get('/providers', Domain\GetProviders::class) - ->get('/providers/{provider}', Domain\GetProvider::class) - ->post('/providers/{provider}', Domain\SynchronizeProvider::class) - ->post('/providers/{provider}/connection', Domain\ActivateProvider::class) - ->delete('/providers/{provider}/connection', Domain\DeactivateProvider::class) - ->get('/providers/{provider}/configuration', Domain\GetProviderConfiguration::class) - ->put('/providers/{provider}/configuration', Domain\ChangeProviderConfiguration::class) + ->any('/', Action\Providers::class) + ->get('/providers', Action\ListProviders::class) + // ... + ->put('/providers/{provider}/configuration', Action\UpdateProviderConfiguration::class) ; // End of routing } } @@ -335,11 +317,11 @@ For a Equip application to handle requests properly it will require some middlew The following middlewares are typically used by default, in this order: -* [`Relay\Middleware\ResponseSender`](https://github.com/relayphp/Relay.Middleware/blob/master/src/ResponseSender.php) - Outputs data from the [PSR-7 Response object](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php) to be sent back to the client -* [`Equip\Handler\ExceptionHandler`](https://github.com/equip/framework/blob/master/src/Handler/ExceptionHandler.php) - Handles exceptions thrown by subsequent middlewares and domains by [logging the exception](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#13-context) and returning an appropriate application-level response -* [`Equip\Handler\RouteHandler`](https://github.com/equip/framework/blob/master/src/Handler/RouteHandler.php) - Resolves the request route to the corresponding action to execute -* [`Equip\Handler\ContentHandler`](https://github.com/equip/framework/blob/master/src/Handler/ContentHandler.php) - Parses request bodies encoded in common formats and makes the parsed version available via the `getParsedBody()` method of the [PSR-7 Request object](https://github.com/php-fig/http-message/blob/master/src/ServerRequestInterface.php) -* [`Equip\Handler\ActionHandler`](https://github.com/equip/framework/blob/master/src/Handler/ActionHandler.php) - Invokes the [domain](https://github.com/pmjones/adr#model-vs-domain) corresponding to the resolved [action](https://github.com/pmjones/adr#controller-vs-action), applies the [responder](https://github.com/pmjones/adr#view-vs-responder) to the resulting payload, and returns the resulting response +* [`Relay\Middleware\ResponseSender`](https://github.com/relayphp/Relay.Middleware/blob/master/src/ResponseSender.php) - Outputs data from the [PSR-7 Response object](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php) to be sent back to the client. +* [`Equip\Handler\ExceptionHandler`](https://github.com/equip/framework/blob/master/src/Handler/ExceptionHandler.php) - Handles exceptions thrown by subsequent middlewares and domains by [logging the exception](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#13-context) and returning an appropriate application response. +* [`Equip\Handler\RouteHandler`](https://github.com/equip/framework/blob/master/src/Handler/RouteHandler.php) - Resolves the request route to the corresponding action to execute. +* [`Equip\Handler\ContentHandler`](https://github.com/equip/framework/blob/master/src/Handler/ContentHandler.php) - Parses request bodies encoded in common formats and makes the parsed version available via the `getParsedBody()` method of the [PSR-7 Request object](https://github.com/php-fig/http-message/blob/master/src/ServerRequestInterface.php). +* [`Equip\Handler\ActionHandler`](https://github.com/equip/framework/blob/master/src/Handler/ActionHandler.php) - Provides a boundary between HTTP details and your [domain logic](https://en.wikipedia.org/wiki/Business_logic). This is the entry point into the [ADR triad](http://pmjones.io/adr/) that invokes [actions](#actions) to read the request and populate the HTTP response. #### Custom Middleware @@ -382,110 +364,184 @@ Equip\Application::build() *Typically you will want to place your custom middleware immediately before the `ActionHandler`.* -## Domains +## Actions -[Domain](https://github.com/pmjones/adr#model-vs-domain) classes are the application entry point into your project-specific code. They implement [`DomainInterface`](https://github.com/equip/domain/blob/master/src/DomainInterface.php), which contains a single method `__invoke()` that takes in an array and returns an instance of a class implementing [`PayloadInterface`](https://github.com/equip/domain/blob/master/src/PayloadInterface.php). +Actions provide the boundary between the HTTP request/response life cycle and your domain logic. All actions must implement [`ActionInterface`](https://github.com/equip/framework/blob/master/src/Contracts/ActionInterface.php), which contains a single `__invoke()` method that takes a request and a response and returns a modified response. -The array accepted by `__invoke()` is created internally via the Equip [`Input`](https://github.com/equip/adr/blob/master/src/Input.php) class, which aggregates data from the request in a fashion similar to how PHP itself aggregates request data into the [`$_REQUEST`](http://php.net/manual/en/reserved.variables.request.php) superglobal. +How you choose to implement your actions is entirely up to you. The following is an example of how a user login action could be defined. -Equip provides a native implementation of [`PayloadInterface`](https://github.com/equip/domain/blob/master/src/PayloadInterface.php) in the form of its [`Payload`](https://github.com/equip/framework/blob/master/src/Payload.php) class. Once the domain class has returned the payload instance, Equip then passes it off to the appropriate [responder](https://github.com/pmjones/adr#view-vs-responder) to be used in constructing the application response. - -Rather than having the domain class directly instantiate [`Payload`](https://github.com/equip/framework/blob/master/src/Payload.php) or another implementation of [`PayloadInterface`](https://github.com/equip/domain/blob/master/src/PayloadInterface.php), it's recommended that you make domain classes accept an initial payload instance as a constructor parameter, ideally [typehinted](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration) against [`PayloadInterface`](https://github.com/equip/domain/blob/master/src/PayloadInterface.php). This allows domains to be unit tested independently of any particular payload implementation. - -Here's an example of a domain class. +### Action Example ```php -namespace Acme\Domain; +namespace Acme\Action; -use Equip\Adr\DomainInterface; -use Equip\Adr\PayloadInterface; +use Acme\Domain\Authentication; +use Acme\Input\LoginInput; +use Equip\Contract\ActionInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; -class Foo implements DomainInterface +class LoginAction implements ActionInterface { - protected $pdo; - protected $payload; + /** + * @var Authentication + */ + private $auth; + + /** + * @var LoginResponder + */ + private $responder; public function __construct( - \PDO $pdo, - PayloadInterface $payload + Authentication $auth, + LoginResponder $responder ) { - $this->pdo = $pdo; - $this->payload = $payload; + $this->auth = $auth; + $this->responder = $responder; } - public function __invoke(array $input) + public function __invoke(ServerRequestInterface $request, ResponseInterface $response) { - // ... - return $this->payload; + $input = $this->input($request); + $errors = $this->auth->validate($input); + + if (!empty($errors)) { + return $this->responder->incomplete($response, $errors); + } + + if (!$this->auth->canLogin($input)) { + return $this->responder->invalid($response, $input->toArray(true)); + } + + $token = $this->auth->token($input); + + return $this->responder->success($response, $token); + } + + private function input(ServerRequestInterface $request) + { + $body = $request->getParsedBody(); + + return new LoginInput($body); } } ``` -Note that the constructor of this domain class declares two parameters, a `\PDO` instance and a payload instance. If a request is made for the URI corresponding to this domain class in the [router configuration](#router), Equip will use the [Auryn configuration](#dependency-injection-container) to instantiate the domain class with the dependencies declared in its constructor. Typically, the constructor is used to store references to dependencies in instance properties so as to be able to use them later in `__invoke()`. - -Also note that `__invoke()` returns the payload. The core Equip implementation [`Payload`](https://github.com/equip/framework/blob/master/src/Payload.php) provides an immutable implementation of [`PayloadInterface`](https://github.com/equip/adr/blob/master/src/PayloadInterface.php), which also allows for code like this: +This action can only be used with a server request that contains a body. It uses a domain-specific class `Authentication` that contains this application's rules for handling logins. It operates on a value object called `LoginInput` that contains this logic: ```php -return $this->payload - ->withStatus(Payload::OK) - ->withOutput(['foo' => 'bar']); -``` +namespace Acme\Input; + +class LoginInput +{ + private $email; + private $password; + + public function __construct(array $input) + { + if (!empty($input['email'])) { + $this->email = filter_var($input['email'], FILTER_VALIDATE_EMAIL); + } + if (!empty($input['password'])) { + $this->password = $input['password']; + } + } + + public function email() + { + return $this->email; + } -## Responders + public function password() + { + return $this->password; + } -[Responders](https://github.com/pmjones/adr#view-vs-responder) accept the payload returned by the domain and return a [PSR-7 Response object](https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php). They implement [`ResponderInterface`](https://github.com/equip/adr/blob/master/src/ResponderInterface.php), which like [`DomainInterface`](https://github.com/equip/domain/blob/master/src/DomainInterface.php) declares a single method `__invoke()`. Instead of a third callable parameter, however, it receives an instance of [`PayloadInterface`](https://github.com/equip/domain/blob/master/src/PayloadInterface.php). + public function toArray($public = false) + { + $values = get_object_vars($this); -Equip provides a few native responder implementations. + if ($public) { + $values['password'] = '*********'; + } -### Chained Responder + return $values; + } +} +``` -[`ChainedResponder`](https://github.com/equip/framework/blob/master/src/Responder/ChainedResponder.php) is the default responder which allows multiple responders to be applied to the same response instance. This is intended to allow for [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in configuring different areas of the response. The `ChainedResponder` extends [`Equip\Structure\Set`](https://github.com/equip/structure/blob/master/src/Set.php). +The details of the `Authentication` class are not important here. Just know that each of its methods take `LoginInput` as their only parameter and operate on the values within it. -By default [`ChainedResponder`](https://github.com/equip/framework/blob/master/src/Responder/ChainedResponder.php) includes [all the default responders](https://github.com/equip/framework/blob/master/src/Responder). Responders can be added using its `withValue()` method or overwritten entirely using its `withValues()` method. +Finally, we have a custom responder that handles every possible state of the login action: incomplete, invalid, and success. -### Formatted Responder +```php +namespace Acme\Action; -[`FormattedResponder`](https://github.com/equip/framework/blob/master/src/Responder/FormattedResponder.php) uses the [Negotiation](https://github.com/willdurand/negotiation) library to support [content negotiation](https://en.wikipedia.org/wiki/Content_negotiation). When a desirable format has been founded, it uses an appropriate implementation of [`FormatterInterface`](https://github.com/equip/framework/blob/master/src/Formatter/FormatterInterface.php) to encode the payload data and return it as a string. The `FormattedResponder` extends [`Equip\Structure\Dictionary`](https://github.com/equip/structure/blob/master/src/Dictionary.php). +use Equip\Formatter\JsonFormatter; +use Psr\Http\Message\ResponseInterface; -Here are the formatter implementations that are natively supported: +class LoginResponder +{ + /** + * @var JsonFormatter + */ + private $formatter; -* [`JsonFormatter`](https://github.com/equip/framework/blob/master/src/Formatter/JsonFormatter.php) - Encodes the payload as [JSON](http://www.json.org/) -* [`PlatesFormatter`](https://github.com/equip/framework/blob/master/src/Formatter/PlatesFormatter.php) - Applies the payload data to a [Plates](http://platesphp.com/) template specified in the payload and returns the result + public function __construct( + JsonFormatter $formatter + ) { + $this->formatter = $formatter; + } -By default [`FormattedResponder`](https://github.com/equip/framework/blob/master/src/Responder/FormattedResponder.php) includes `JsonFormatter`. Responders can be added using its `withValue()` method or overwritten entirely using its `withValues()` method. + public function incomplete(ResponseInterface $response, array $errors) + { + return $this + ->write($response, $errors) + ->withStatus(400); + } -### Redirect + public function invalid(ResponseInterface $response, array $input) + { + return $this + ->write($response, [ + 'error' => 'Could not validate credentials', + 'input' => $input, + ]) + ->withStatus(403); + } -[`RedirectResponder`](https://github.com/equip/framework/blob/master/src/Responder/RedirectResponder.php) allows a redirection to be embedded in the payload. In order to activate it requires a `redirect` message to be attached to the [`PayloadInterface`](https://github.com/equip/adr/blob/master/src/PayloadInterface.php). Optionally the `status` can be set when a [`302 Found`](https://en.wikipedia.org/wiki/HTTP_302) is not the correct response. + public function success(ResponseInterface $response, $token) + { + return $this + ->write($response, compact('token')) + ->withStatus(200); + } + + private function write(ResponseInterface $response, $output) + { + $body = $response->getBody(); -Modifying the payload to trigger a redirect is very easy: + $body->write($this->formatter->format($output)); -```php -// For a basic redirect -return $payload->withMessages([ - 'redirect' => '/login', -]); - -// For a permanent redirect, also set "status" -return $payload->withMessages([ - 'redirect' => '/permalink', - 'status' => 301, -]); + return $response; + } +} ``` -### Default Setup +This simple responder has separate methods for each possible state that take the exact parameters necessary to generate the response. The response content will always be in JSON format. -Actions, responders, and formatters work together to generate a response. By default `Action` instances created by [routing](#routing) use [`ChainedResponder`](https://github.com/equip/framework/blob/master/src/Responder/ChainedResponder.php) as the responder. By using a custom action you can change the responder for that action. By default the [`ChainedResponder`](https://github.com/equip/framework/blob/master/src/Responder/ChainedResponder.php) includes the [`FormattedResponder`](https://github.com/equip/framework/blob/master/src/Responder/FormattedResponder.php) which delegates formatting to [`JsonFormatter`](https://github.com/equip/framework/blob/master/src/Formatter/JsonFormatter.php). +This loose structure around actions, input, and responders allows each project to define exactly the right response for the application. There are no hard rules about how the actions are defined, so long as each takes a request and produces a response. -### Using Plates +## Formatters -Using [`PlatesFormatter`](https://github.com/equip/framework/blob/master/src/Formatter/PlatesFormatter.php) requires changing the formatters used by [`FormattedResponder`](https://github.com/equip/framework/blob/master/src/Responder/FormattedResponder.php). The easiest way to do this is by using the `PlatesResponderConfiguration` as in the example below: +Equip includes formatters that can be used in responders to simplify the formatting of domain output for the response: -```php -Equip\Application::build() -->setConfiguration([ - // ... - Equip\Configuration\PlatesResponderConfiguration::class -]) -// ... -``` +* [`JsonFormatter`](https://github.com/equip/framework/blob/master/src/Formatter/JsonFormatter.php) - Encodes the content as [JSON](http://www.json.org/). +* [`PlatesFormatter`](https://github.com/equip/framework/blob/master/src/Formatter/PlatesFormatter.php) - Encodes the content using a [Plates](http://platesphp.com/) template set by the responder. + +All formatters must implement [`FormatterInterface`](https://github.com/equip/framework/blob/master/src/Formatter/FormatterInterface.php). + +### Content Negotiation + +Equip does not include any form of [content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation). However, all formatters include a static `accepts()` method that returns a list of the content types supported by the formatter. This list can be used to help determine which formatter supports a requested content type. diff --git a/src/Action.php b/src/Action.php deleted file mode 100644 index 6a661da..0000000 --- a/src/Action.php +++ /dev/null @@ -1,79 +0,0 @@ -domain = $domain; - - if ($responder) { - $this->responder = $responder; - } - - if ($input) { - $this->input = $input; - } - } - - /** - * Returns the domain specification. - * - * @return DomainInterface - */ - public function getDomain() - { - return $this->domain; - } - - /** - * Returns the responder specification. - * - * @return ResponderInterface - */ - public function getResponder() - { - return $this->responder; - } - - /** - * Returns the input specification. - * - * @return InputInterface - */ - public function getInput() - { - return $this->input; - } -} diff --git a/src/Application.php b/src/Application.php index ac128de..3a74138 100644 --- a/src/Application.php +++ b/src/Application.php @@ -5,6 +5,7 @@ use Auryn\Injector; use Equip\Configuration\ConfigurationSet; use Equip\Directory; +use Equip\Dispatching\DispatchingSet; use Equip\Middleware\MiddlewareSet; use Relay\Relay; @@ -16,15 +17,17 @@ class Application * @param Injector $injector * @param ConfigurationSet $configuration * @param MiddlewareSet $middleware + * @param DispatchingSet $dispatching * * @return static */ public static function build( Injector $injector = null, ConfigurationSet $configuration = null, - MiddlewareSet $middleware = null + MiddlewareSet $middleware = null, + DispatchingSet $dispatching = null ) { - return new static($injector, $configuration, $middleware); + return new static($injector, $configuration, $middleware, $dispatching); } /** @@ -43,23 +46,26 @@ public static function build( private $middleware; /** - * @var callable|string + * @var DispatchingSet */ - private $routing; + private $dispatching; /** * @param Injector $injector * @param ConfigurationSet $configuration * @param MiddlewareSet $middleware + * @param DispatchingSet $dispatching */ public function __construct( Injector $injector = null, ConfigurationSet $configuration = null, - MiddlewareSet $middleware = null + MiddlewareSet $middleware = null, + DispatchingSet $dispatching = null ) { $this->injector = $injector ?: new Injector; $this->configuration = $configuration ?: new ConfigurationSet; $this->middleware = $middleware ?: new MiddlewareSet; + $this->dispatching = $dispatching ?: new DispatchingSet; } /** @@ -89,15 +95,15 @@ public function setMiddleware(array $middleware) } /** - * Change routing + * Change dispatching * - * @param callable|string $routing + * @param array $dispatching * * @return self */ - public function setRouting($routing) + public function setDispatching(array $dispatching) { - $this->routing = $routing; + $this->dispatching = $this->dispatching->withValues($dispatching); return $this; } @@ -114,7 +120,7 @@ public function run($runner = Relay::class) return $this->injector ->share($this->middleware) - ->prepare(Directory::class, $this->routing) + ->prepare(Directory::class, $this->dispatching) ->execute($runner); } } diff --git a/src/Configuration/DiactorosConfiguration.php b/src/Configuration/DiactorosConfiguration.php index 2bf79cf..e7f8033 100644 --- a/src/Configuration/DiactorosConfiguration.php +++ b/src/Configuration/DiactorosConfiguration.php @@ -17,13 +17,9 @@ class DiactorosConfiguration implements ConfigurationInterface */ public function apply(Injector $injector) { + // Wrong type hint. see https://github.com/relayphp/Relay.Relay/issues/25 $injector->alias( RequestInterface::class, - // It should not be necessary to force all requests to be server - // requests, except that Relay uses the wrong type hint: - // https://github.com/relayphp/Relay.Relay/issues/25 - // - // 'Zend\Diactoros\Request' ServerRequest::class ); diff --git a/src/Configuration/PayloadConfiguration.php b/src/Configuration/PayloadConfiguration.php deleted file mode 100644 index 1809266..0000000 --- a/src/Configuration/PayloadConfiguration.php +++ /dev/null @@ -1,22 +0,0 @@ -alias( - PayloadInterface::class, - Payload::class - ); - } -} - diff --git a/src/Configuration/PlatesResponderConfiguration.php b/src/Configuration/PlatesResponderConfiguration.php deleted file mode 100644 index 4fed65d..0000000 --- a/src/Configuration/PlatesResponderConfiguration.php +++ /dev/null @@ -1,17 +0,0 @@ -prepare(FormattedResponder::class, function (FormattedResponder $responder) { - return $responder->withValue(PlatesFormatter::class, 1.0); - }); - } -} diff --git a/src/Contract/ActionInterface.php b/src/Contract/ActionInterface.php new file mode 100644 index 0000000..ffbc591 --- /dev/null +++ b/src/Contract/ActionInterface.php @@ -0,0 +1,25 @@ +action(self::ANY, $path, $domainOrAction); + return $this->action(self::ANY, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function get($path, $domainOrAction) + public function get($path, $action) { - return $this->action(self::GET, $path, $domainOrAction); + return $this->action(self::GET, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function post($path, $domainOrAction) + public function post($path, $action) { - return $this->action(self::POST, $path, $domainOrAction); + return $this->action(self::POST, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function put($path, $domainOrAction) + public function put($path, $action) { - return $this->action(self::PUT, $path, $domainOrAction); + return $this->action(self::PUT, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function patch($path, $domainOrAction) + public function patch($path, $action) { - return $this->action(self::PATCH, $path, $domainOrAction); + return $this->action(self::PATCH, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function head($path, $domainOrAction) + public function head($path, $action) { - return $this->action(self::HEAD, $path, $domainOrAction); + return $this->action(self::HEAD, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function delete($path, $domainOrAction) + public function delete($path, $action) { - return $this->action(self::DELETE, $path, $domainOrAction); + return $this->action(self::DELETE, $path, $action); } /** * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function options($path, $domainOrAction) + public function options($path, $action) { - return $this->action(self::OPTIONS, $path, $domainOrAction); + return $this->action(self::OPTIONS, $path, $action); } /** * @param string $method * @param string $path - * @param string|Action $domainOrAction + * @param string $action * * @return static */ - public function action($method, $path, $domainOrAction) + public function action($method, $path, $action) { - if ($domainOrAction instanceof Action) { - $action = $domainOrAction; - } else { - $action = new Action($domainOrAction); - } - return $this->withValue(sprintf('%s %s', $method, $path), $action); } @@ -178,7 +172,7 @@ protected function assertValid(array $data) parent::assertValid($data); foreach ($data as $value) { - if (!is_object($value) || !$value instanceof Action) { + if (!is_subclass_of($value, ActionInterface::class)) { throw DirectoryException::invalidEntry($value); } } diff --git a/src/Dispatching/DispatchingSet.php b/src/Dispatching/DispatchingSet.php new file mode 100644 index 0000000..ea7a200 --- /dev/null +++ b/src/Dispatching/DispatchingSet.php @@ -0,0 +1,50 @@ +make($dispatch); + } + $directory = $dispatch($directory); + } + + return $directory; + } + + /** + * @inheritDoc + * + * @throws DispatchingException + * If $classes does not conform to type expectations. + */ + protected function assertValid(array $classes) + { + parent::assertValid($classes); + + foreach ($classes as $dispatching) { + if (!(is_callable($dispatching) || method_exists($dispatching, '__invoke'))) { + throw DispatchingException::notInvokable($dispatching); + } + } + } +} diff --git a/src/Exception/DirectoryException.php b/src/Exception/DirectoryException.php index bc8c614..ea98cb7 100644 --- a/src/Exception/DirectoryException.php +++ b/src/Exception/DirectoryException.php @@ -2,13 +2,13 @@ namespace Equip\Exception; -use Equip\Action; +use Equip\Contract\ActionInterface; use InvalidArgumentException; class DirectoryException extends InvalidArgumentException { /** - * @param mixed $value + * @param string|object $value * * @return static */ @@ -19,9 +19,9 @@ public static function invalidEntry($value) } return new static(sprintf( - 'Directory entry `%s` is not an `%s` instance', + 'Directory entry `%s` must be an `%s` instance', $value, - Action::class + ActionInterface::class )); } } diff --git a/src/Exception/DispatchingException.php b/src/Exception/DispatchingException.php new file mode 100644 index 0000000..b9dd0a8 --- /dev/null +++ b/src/Exception/DispatchingException.php @@ -0,0 +1,25 @@ +getOutput(), $this->options()); + return json_encode($content, $this->options()); } /** diff --git a/src/Formatter/PlatesFormatter.php b/src/Formatter/PlatesFormatter.php index c02d6a2..72cf7ed 100644 --- a/src/Formatter/PlatesFormatter.php +++ b/src/Formatter/PlatesFormatter.php @@ -2,7 +2,6 @@ namespace Equip\Formatter; -use Equip\Adr\PayloadInterface; use Equip\Formatter\HtmlFormatter; use League\Plates\Engine; @@ -13,6 +12,11 @@ class PlatesFormatter extends HtmlFormatter */ private $engine; + /** + * @var string + */ + private $template; + /** * @param Engine $engine */ @@ -22,23 +26,25 @@ public function __construct(Engine $engine) } /** - * @inheritDoc + * Get a copy that uses a different template. + * + * @param string $template + * + * @return static */ - public function body(PayloadInterface $payload) + public function withTemplate($template) { - return $this->render($payload); + $copy = clone $this; + $copy->template = $template; + + return $copy; } /** - * @param PayloadInterface $payload - * - * @return string + * @inheritDoc */ - private function render(PayloadInterface $payload) + public function format($content) { - $template = $payload->getSetting('template'); - $output = $payload->getOutput(); - - return $this->engine->render($template, $output); + return $this->engine->render($this->template, $content); } } diff --git a/src/Handler/ActionHandler.php b/src/Handler/ActionHandler.php index c65974b..686f2ae 100644 --- a/src/Handler/ActionHandler.php +++ b/src/Handler/ActionHandler.php @@ -2,11 +2,7 @@ namespace Equip\Handler; -use Equip\Action; -use Equip\Adr\DomainInterface; -use Equip\Adr\InputInterface; -use Equip\Adr\PayloadInterface; -use Equip\Adr\ResponderInterface; +use Equip\Contract\ActionInterface; use Equip\Resolver\ResolverTrait; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -39,68 +35,29 @@ public function __invoke( $action = $request->getAttribute(self::ACTION_ATTRIBUTE); $request = $request->withoutAttribute(self::ACTION_ATTRIBUTE); + if (is_string($action)) { + $action = $this->resolve($action); + } + $response = $this->handle($action, $request, $response); return $next($request, $response); } /** - * Use the action collaborators to get a response. + * Invoke the action to prepare the response. * - * @param Action $action + * @param ActionInterface $action * @param ServerRequestInterface $request * @param ResponseInterface $response * * @return ResponseInterface */ private function handle( - Action $action, + ActionInterface $action, ServerRequestInterface $request, ResponseInterface $response ) { - $domain = $this->resolve($action->getDomain()); - $input = $this->resolve($action->getInput()); - $responder = $this->resolve($action->getResponder()); - - $payload = $this->payload($domain, $input, $request); - $response = $this->response($responder, $request, $response, $payload); - - return $response; - } - - /** - * Execute the domain to get a payload using input from the request. - * - * @param DomainInterface $domain - * @param InputInterface $input - * @param ServerRequestInterface $request - * - * @return PayloadInterface - */ - private function payload( - DomainInterface $domain, - InputInterface $input, - ServerRequestInterface $request - ) { - return $domain($input($request)); - } - - /** - * Execute the responder to marshall the payload into the response. - * - * @param ResponderInterface $responder - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @param PayloadInterface $payload - * - * @return ResponseInterface - */ - private function response( - ResponderInterface $responder, - ServerRequestInterface $request, - ResponseInterface $response, - PayloadInterface $payload - ) { - return $responder($request, $response, $payload); + return $action($request, $response); } } diff --git a/src/Input.php b/src/Input.php deleted file mode 100644 index ca79a9e..0000000 --- a/src/Input.php +++ /dev/null @@ -1,58 +0,0 @@ -getAttributes(); - $body = $this->body($request); - $cookies = $request->getCookieParams(); - $query = $request->getQueryParams(); - $uploads = $request->getUploadedFiles(); - - // Order matters here! Important values go last! - return array_replace( - $query, - $body, - $uploads, - $cookies, - $attrs - ); - } - - /** - * @param ServerRequestInterface $request - * - * @return array - */ - private function body(ServerRequestInterface $request) - { - $body = $request->getParsedBody(); - - if (empty($body)) { - return []; - } - - if (is_object($body)) { - // Because the parsed body may also be represented as an object, - // additional parsing is required. This is a bit dirty but works - // very well for anonymous objects. - $body = json_decode(json_encode($body), true); - } - - return $body; - } -} diff --git a/src/Payload.php b/src/Payload.php deleted file mode 100644 index 802dc93..0000000 --- a/src/Payload.php +++ /dev/null @@ -1,151 +0,0 @@ -status = $status; - - return $copy; - } - - /** - * @inheritDoc - */ - public function getStatus() - { - return $this->status; - } - - /** - * @inheritDoc - */ - public function withInput(array $input) - { - $copy = clone $this; - $copy->input = $input; - - return $copy; - } - - /** - * @inheritDoc - */ - public function getInput() - { - return $this->input; - } - - /** - * @inheritDoc - */ - public function withOutput(array $output) - { - $copy = clone $this; - $copy->output = $output; - - return $copy; - } - - /** - * @inheritDoc - */ - public function getOutput() - { - return $this->output; - } - - /** - * @inheritDoc - */ - public function withMessages(array $messages) - { - $copy = clone $this; - $copy->messages = $messages; - - return $copy; - } - - /** - * @inheritDoc - */ - public function getMessages() - { - return $this->messages; - } - - /** - * @inheritDoc - */ - public function withSetting($name, $value) - { - $copy = clone $this; - $copy->settings[$name] = $value; - - return $copy; - } - - /** - * @inheritDoc - */ - public function withoutSetting($name) - { - $copy = clone $this; - unset($copy->settings[$name]); - - return $copy; - } - - /** - * @inheritDoc - */ - public function getSetting($name) - { - if (isset($this->settings[$name])) { - return $this->settings[$name]; - } - - return null; - } - - /** - * @inheritDoc - */ - public function getSettings() - { - return $this->settings; - } -} diff --git a/src/Responder/ChainedResponder.php b/src/Responder/ChainedResponder.php deleted file mode 100644 index 1ee78b6..0000000 --- a/src/Responder/ChainedResponder.php +++ /dev/null @@ -1,67 +0,0 @@ -resolver = $resolver; - - parent::__construct($responders); - } - - /** - * @inheritDoc - */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - PayloadInterface $payload - ) { - foreach ($this as $responder) { - $responder = $this->resolve($responder); - $response = $responder($request, $response, $payload); - } - - return $response; - } - - /** - * @inheritDoc - * - * @throws ResponderException - * If $classes does not implement the correct interface. - */ - protected function assertValid(array $classes) - { - parent::assertValid($classes); - - foreach ($classes as $responder) { - if (!is_subclass_of($responder, ResponderInterface::class)) { - throw ResponderException::invalidClass($responder); - } - } - } -} diff --git a/src/Responder/FormattedResponder.php b/src/Responder/FormattedResponder.php deleted file mode 100644 index 0f0b73b..0000000 --- a/src/Responder/FormattedResponder.php +++ /dev/null @@ -1,168 +0,0 @@ - 1.0, - ] - ) { - $this->negotiator = $negotiator; - $this->resolver = $resolver; - - parent::__construct($formatters); - } - - /** - * @inheritDoc - */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - PayloadInterface $payload - ) { - if ($this->hasOutput($payload)) { - $response = $this->format($request, $response, $payload); - } - - return $response; - } - - /** - * Determine if the payload has usable output - * - * @param PayloadInterface $payload - * - * @return boolean - */ - protected function hasOutput(PayloadInterface $payload) - { - return (bool) $payload->getOutput(); - } - - /** - * Retrieve a map of accepted priorities with the responsible formatter. - * - * @return array - */ - protected function priorities() - { - $priorities = []; - - foreach ($this as $formatter => $quality) { - foreach ($formatter::accepts() as $type) { - $priorities[$type] = $formatter; - } - } - - return $priorities; - } - - /** - * Retrieve the formatter to use for the current request. - * - * Uses content negotiation to find the best available output format for - * the requested content type. - * - * @param ServerRequestInterface $request - * - * @return FormatterInterface - */ - protected function formatter(ServerRequestInterface $request) - { - $accept = $request->getHeaderLine('Accept'); - $priorities = $this->priorities(); - - if (!empty($accept)) { - $preferred = $this->negotiator->getBest($accept, array_keys($priorities)); - } - - if (!empty($preferred)) { - $formatter = $priorities[$preferred->getValue()]; - } else { - $formatter = array_shift($priorities); - } - - return $this->resolve($formatter); - } - - /** - * Update the response by formatting the payload. - * - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @param PayloadInterface $payload - * - * @return ResponseInterface - */ - protected function format( - ServerRequestInterface $request, - ResponseInterface $response, - PayloadInterface $payload - ) { - $formatter = $this->formatter($request); - - $response = $response->withHeader('Content-Type', $formatter->type()); - // Overwrite the body instead of making a copy and dealing with the stream. - $response->getBody()->write($formatter->body($payload)); - - return $response; - } - - /** - * @inheritDoc - * - * @throws FormatterException - * If $classes does not implement the correct interface, - * or does not have a quality value. - */ - protected function assertValid(array $classes) - { - parent::assertValid($classes); - - foreach ($classes as $formatter => $quality) { - if (!is_subclass_of($formatter, FormatterInterface::class)) { - throw FormatterException::invalidClass($formatter); - } - - if (!is_float($quality)) { - throw FormatterException::needsQuality($formatter); - } - } - } - - /** - * @inheritDoc - */ - protected function sortValues() - { - arsort($this->values); - } -} diff --git a/src/Responder/RedirectResponder.php b/src/Responder/RedirectResponder.php deleted file mode 100644 index d043631..0000000 --- a/src/Responder/RedirectResponder.php +++ /dev/null @@ -1,28 +0,0 @@ -getSetting('redirect'); - - if (!empty($location)) { - $response = $response->withHeader('Location', $location); - } - - return $response; - } -} diff --git a/src/Responder/StatusResponder.php b/src/Responder/StatusResponder.php deleted file mode 100644 index 37144f4..0000000 --- a/src/Responder/StatusResponder.php +++ /dev/null @@ -1,70 +0,0 @@ -http_status = $http_status; - } - - /** - * @inheritDoc - */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - PayloadInterface $payload - ) { - if ($this->hasStatus($payload)) { - $response = $this->status($response, $payload); - } - - return $response; - } - - /** - * Determine if the payload has a status. - * - * @param PayloadInterface $payload - * - * @return boolean - */ - private function hasStatus(PayloadInterface $payload) - { - return (bool) $payload->getStatus(); - } - - /** - * Get the response with the status code from the payload. - * - * @param ResponseInterface $response - * @param PayloadInterface $payload - * - * @return ResponseInterface - */ - private function status( - ResponseInterface $response, - PayloadInterface $payload - ) { - $status = $payload->getStatus(); - $code = $this->http_status->getStatusCode($status); - - return $response->withStatus($code); - } -} diff --git a/tests/ActionTest.php b/tests/ActionTest.php deleted file mode 100644 index 2bd413a..0000000 --- a/tests/ActionTest.php +++ /dev/null @@ -1,35 +0,0 @@ -getMock(DomainInterface::class)); - $action = new Action($domain); - - $this->assertSame($domain, $action->getDomain()); - $this->assertSame(Input::class, $action->getInput()); - $this->assertSame(ChainedResponder::class, $action->getResponder()); - - $responder = get_class($this->getMock(ResponderInterface::class)); - $action = new Action($domain, $responder); - - $this->assertSame($responder, $action->getResponder()); - - $input = get_class($this->getMock(InputInterface::class)); - $action = new Action($domain, null, $input); - - $this->assertSame($input, $action->getInput()); - $this->assertSame(ChainedResponder::class, $action->getResponder()); - } -} diff --git a/tests/ApplicationTest.php b/tests/ApplicationTest.php index cf9585a..f123f22 100644 --- a/tests/ApplicationTest.php +++ b/tests/ApplicationTest.php @@ -7,6 +7,7 @@ use Equip\Configuration\ConfigurationInterface; use Equip\Configuration\ConfigurationSet; use Equip\Directory; +use Equip\Dispatching\DispatchingSet; use Equip\Middleware\MiddlewareSet; use PHPUnit_Framework_TestCase as TestCase; use ReflectionObject; @@ -23,7 +24,7 @@ private function assertApplication($app) 'injector' => Injector::class, 'configuration' => ConfigurationSet::class, 'middleware' => MiddlewareSet::class, - 'routing' => null, + 'dispatching' => DispatchingSet::class, ]; foreach ($props as $name => $expected) { @@ -37,10 +38,6 @@ private function assertApplication($app) $props[$name] = $value; } - - if (!empty($props['routing'])) { - $this->assertTrue(is_callable($props['routing'])); - } } public function testBuild() @@ -51,11 +48,12 @@ public function testBuild() public function testCreate() { - $injector = $this->getMock(Injector::class); - $configuration = $this->getMock(ConfigurationSet::class); - $middleware = $this->getMock(MiddlewareSet::class); + $injector = $this->createMock(Injector::class); + $configuration = $this->createMock(ConfigurationSet::class); + $middleware = $this->createMock(MiddlewareSet::class); + $dispatching = $this->createMock(DispatchingSet::class); - $app = new Application($injector, $configuration, $middleware); + $app = new Application($injector, $configuration, $middleware, $dispatching); $this->assertApplication($app); } @@ -63,10 +61,10 @@ public function testCreate() public function testSetConfiguration() { $data = [ - $this->getMock(ConfigurationInterface::class), + $this->createMock(ConfigurationInterface::class), ]; - $configuration = $this->getMock(ConfigurationSet::class); + $configuration = $this->createMock(ConfigurationSet::class); $configuration ->expects($this->once()) ->method('withValues') @@ -82,10 +80,10 @@ public function testSetConfiguration() public function testSetMiddleware() { $data = [ - $this->getMock(MiddlewareInterface::class), + $this->createMock(MiddlewareInterface::class), ]; - $middleware = $this->getMock(MiddlewareSet::class); + $middleware = $this->createMock(MiddlewareSet::class); $middleware ->expects($this->once()) ->method('withValues') @@ -98,28 +96,35 @@ public function testSetMiddleware() $this->assertApplication($app); } - public function testSetRouting() + public function testSetDispatching() { - $app = new Application(); + $data = [ + function ($directory) { + return $directory; + }, + ]; - // Routing can be a closure ... - $app->setRouting(function () { - }); - $this->assertApplication($app); + $dispatching = $this->createMock(DispatchingSet::class); + $dispatching + ->expects($this->once()) + ->method('withValues') + ->with($data) + ->willReturn(clone $dispatching); + + $app = new Application(null, null, null, $dispatching); + $app->setDispatching($data); - // ... or a callback - $app->setRouting([$this, __FUNCTION__]); $this->assertApplication($app); + } public function testRun() { - $injector = $this->getMock(Injector::class); - $middleware = $this->getMock(MiddlewareSet::class); - $config1 = $this->getMock(ConfigurationInterface::class); - $config2 = $this->getMock(ConfigurationInterface::class); - $routing = function () { - }; + $injector = $this->createMock(Injector::class); + $middleware = $this->createMock(MiddlewareSet::class); + $dispatching = $this->createMock(DispatchingSet::class); + $config1 = $this->createMock(ConfigurationInterface::class); + $config2 = $this->createMock(ConfigurationInterface::class); $config1 ->expects($this->once()) @@ -146,7 +151,7 @@ public function testRun() $injector ->expects($this->once()) ->method('prepare') - ->with(Directory::class, $routing) + ->with(Directory::class, $dispatching) ->willReturnSelf(); $injector @@ -154,9 +159,8 @@ public function testRun() ->method('execute') ->with(Relay::class); - $app = Application::build($injector, null, $middleware); + $app = Application::build($injector, null, $middleware, $dispatching); $app->setConfiguration([get_class($config1), $config2]); - $app->setRouting($routing); $app->run(); } } diff --git a/tests/Configuration/EnvConfigurationTest.php b/tests/Configuration/EnvConfigurationTest.php index 435a275..dca44e4 100644 --- a/tests/Configuration/EnvConfigurationTest.php +++ b/tests/Configuration/EnvConfigurationTest.php @@ -4,6 +4,7 @@ use Equip\Configuration\EnvConfiguration; use Equip\Env; +use Equip\Exception\EnvException; use josegonzalez\Dotenv\Loader; class EnvConfigurationTest extends ConfigurationTestCase @@ -42,22 +43,24 @@ public function testApply() $this->destroyEnv(); } - /** - * @expectedException \Equip\Exception\EnvException - * @expectedExceptionMessageRegExp /unable to automatically detect/i - */ public function testUnableToDetect() { - $config = new EnvConfiguration; + $this->setExpectedExceptionRegExp( + EnvException::class, + '/unable to automatically detect/i' + ); + + new EnvConfiguration; } - /** - * @expectedException \Equip\Exception\EnvException - * @expectedExceptionMessageRegExp /environment file .* does not exist/i - */ public function testInvalidRoot() { - $config = new EnvConfiguration('/tmp/bad/path/.env'); + $this->setExpectedExceptionRegExp( + EnvException::class, + '/environment file .* does not exist/i' + ); + + new EnvConfiguration('/tmp/bad/path/.env'); } /** diff --git a/tests/Configuration/PayloadConfigurationTest.php b/tests/Configuration/PayloadConfigurationTest.php deleted file mode 100644 index 7d09084..0000000 --- a/tests/Configuration/PayloadConfigurationTest.php +++ /dev/null @@ -1,24 +0,0 @@ -injector->make(PayloadInterface::class); - - $this->assertInstanceOf(Payload::class, $payload); - } -} diff --git a/tests/Configuration/PlatesResponderConfigurationTest.php b/tests/Configuration/PlatesResponderConfigurationTest.php deleted file mode 100644 index 867ac5f..0000000 --- a/tests/Configuration/PlatesResponderConfigurationTest.php +++ /dev/null @@ -1,32 +0,0 @@ -markTestSkipped('Plates is not installed'); - } - - return [ - new AurynConfiguration, - new PlatesResponderConfiguration, - ]; - } - - public function testApply() - { - $responder = $this->injector->make(FormattedResponder::class); - - $this->assertArrayHasKey(PlatesFormatter::class, $responder); - $this->assertSame(1.0, $responder[PlatesFormatter::class]); - } -} diff --git a/tests/Configuration/RedisConfigurationTest.php b/tests/Configuration/RedisConfigurationTest.php index a358e23..af722d3 100644 --- a/tests/Configuration/RedisConfigurationTest.php +++ b/tests/Configuration/RedisConfigurationTest.php @@ -12,9 +12,13 @@ class RedisConfigurationTest extends TestCase { public function testApply() { + if (!class_exists(Redis::class)) { + $this->markTestSkipped('Redis is not installed'); + } + $injector = new Injector; $injector->delegate(Redis::class, function() { - $redisMock = $this->getMock(Redis::class, ['connect']); + $redisMock = $this->createMock(Redis::class, ['connect']); $redisMock ->expects($this->once()) @@ -24,7 +28,7 @@ public function testApply() }); $config = new RedisConfiguration( - $this->getMock(Env::class) + $this->createMock(Env::class) ); $config->apply($injector); diff --git a/tests/DirectoryTest.php b/tests/DirectoryTest.php index ab40013..ffa6c83 100644 --- a/tests/DirectoryTest.php +++ b/tests/DirectoryTest.php @@ -2,12 +2,14 @@ namespace EquipTests; -use Equip\Adr\DomainInterface; +use Equip\Contract\ActionInterface; use Equip\Directory; +use Equip\Exception\DirectoryException; use Equip\Input; use Equip\Structure\Dictionary; +use PHPUnit_Framework_TestCase as TestCase; -class DirectoryTest extends DirectoryTestCase +class DirectoryTest extends TestCase { /** * @var Directory @@ -24,31 +26,23 @@ public function testDictionary() $this->assertInstanceOf(Dictionary::class, $this->directory); } - /** - * @expectedException \Equip\Exception\DirectoryException - * @expectedExceptionRegExp /entry .* is not an action/i - */ public function testInvalidAction() { - $directory = $this->directory->withValue('GET /', $this); + $this->setExpectedExceptionRegExp( + DirectoryException::class, + '/Directory entry .* must be an .* instance/i' + ); + + $this->directory->withValue('GET /', $this); } public function testAction() { - $action = $this->getMockAction(); + $action = $this->createMock(ActionInterface::class); $directory = $this->directory->action('LIST', '/', $action); $this->assertTrue($directory->hasValue('LIST /')); - $this->assertSame($action, $directory->getValue("LIST /")); - } - - public function testActionWithDomain() - { - $domain = get_class($this->getMockDomain()); - $directory = $this->directory->action('LIST', '/', $domain); - $action = $directory->getValue('LIST /'); - - $this->assertSame($domain, $action->getDomain()); + $this->assertSame($action, $directory->getValue('LIST /')); } /** @@ -56,7 +50,7 @@ public function testActionWithDomain() */ public function testActionMethods($method) { - $action = $this->getMockAction(); + $action = $this->createMock(ActionInterface::class); $callback = [$this->directory, strtolower($method)]; $directory = call_user_func($callback, '/', $action); $match = constant(get_class($directory).'::'.$method); @@ -75,7 +69,7 @@ public function dataHttpMethods() ['PATCH'], ['HEAD'], ['DELETE'], - ['OPTIONS'] + ['OPTIONS'], ]; } diff --git a/tests/DirectoryTestCase.php b/tests/DirectoryTestCase.php deleted file mode 100644 index 801d5d5..0000000 --- a/tests/DirectoryTestCase.php +++ /dev/null @@ -1,45 +0,0 @@ -getMockDomain()); - } - - $action = $this->getMockBuilder(Action::class); - - $action->setConstructorArgs([ - $domain, - $input, - $responder, - ]); - - return $action->getMock(); - } - - /** - * @return DomainInterface - */ - protected function getMockDomain() - { - return $this->getMock(DomainInterface::class); - } -} diff --git a/tests/Dispatching/DispatchingSetTest.php b/tests/Dispatching/DispatchingSetTest.php new file mode 100644 index 0000000..00f5b13 --- /dev/null +++ b/tests/Dispatching/DispatchingSetTest.php @@ -0,0 +1,50 @@ +setExpectedExceptionRegExp( + DispatchingException::class, + '/Dispatcher .* is not invokable/i' + ); + + new DispatchingSet([__CLASS__]); + } + + public function testWithValidEntries() + { + $dispatchers = [ + function () { + } + ]; + $dispatching = new DispatchingSet($dispatchers); + $this->assertSame($dispatchers, $dispatching->toArray()); + } + + public function testPrepareDirectory() + { + $dispatchers = [ + function (Directory $directory) { + return $directory->get('/', $this->createMock(ActionInterface::class)); + } + ]; + + $dispatching = new DispatchingSet($dispatchers); + $directory = new Directory; + + $prepared = $dispatching($directory, $this->createMock(Injector::class)); + + $this->assertInstanceOf(Directory::class, $prepared); + $this->assertNotSame($prepared, $directory); + } +} diff --git a/tests/Fake/FakeDomain.php b/tests/Fake/FakeDomain.php deleted file mode 100644 index 05b3611..0000000 --- a/tests/Fake/FakeDomain.php +++ /dev/null @@ -1,17 +0,0 @@ -withStatus(Payload::STATUS_OK) - ->withOutput(['success' => true, 'input' => $input]); - } -} diff --git a/tests/Fake/FakeExceptionHandler.php b/tests/Fake/FakeExceptionHandler.php deleted file mode 100644 index e2bce37..0000000 --- a/tests/Fake/FakeExceptionHandler.php +++ /dev/null @@ -1,17 +0,0 @@ -withOutput([ + $content = [ 'success' => true, - ]); + ]; - $body = $this->formatter->body($payload); + $body = $this->formatter->format($content); $this->assertEquals('{"success":true}', $body); } diff --git a/tests/Formatter/PlatesFormatterTest.php b/tests/Formatter/PlatesFormatterTest.php index 5db1347..10b100d 100644 --- a/tests/Formatter/PlatesFormatterTest.php +++ b/tests/Formatter/PlatesFormatterTest.php @@ -2,7 +2,6 @@ namespace EquipTests\Formatter; -use Equip\Adr\PayloadInterface; use Equip\Formatter\PlatesFormatter; use League\Plates\Engine; use PHPUnit_Framework_TestCase as TestCase; @@ -38,23 +37,15 @@ public function testType() public function testResponse() { $template = 'test'; - $output = [ + $content = [ 'header' => 'header', 'body' => 'body', 'footer' => 'footer' ]; - $payload = $this->getMock(PayloadInterface::class); - - $payload->expects($this->any()) - ->method('getSetting') - ->willReturn($template); - - $payload->expects($this->any()) - ->method('getOutput') - ->willReturn($output); - - $body = $this->formatter->body($payload); + $body = $this->formatter + ->withTemplate($template) + ->format($content); $this->assertEquals("

header

\n

body

\nfooter\n", $body); } diff --git a/tests/Handler/ActionHandlerTest.php b/tests/Handler/ActionHandlerTest.php index 31a252a..a732dfe 100644 --- a/tests/Handler/ActionHandlerTest.php +++ b/tests/Handler/ActionHandlerTest.php @@ -1,7 +1,7 @@ injector->make(Response::class); $handler = $this->injector->make(ActionHandler::class); - $action = new Action(FakeDomain::class); + $action = $this->createMock(ActionInterface::class); + + $action + ->expects($this->once()) + ->method('__invoke') + ->willReturn($response); $request = $request->withAttribute(ActionHandler::ACTION_ATTRIBUTE, $action); - $request = $request->withAttribute('test', true); $response = $handler($request, $response, function ($request, $response) { $this->assertInstanceOf(Response::class, $response); return $response; }); - - $body = json_decode($response->getBody(), true); - - $this->assertTrue($body['success']); - $this->assertTrue($body['input']['test']); } } diff --git a/tests/Handler/DispatchHandlerTest.php b/tests/Handler/DispatchHandlerTest.php index 705283e..d9e371d 100644 --- a/tests/Handler/DispatchHandlerTest.php +++ b/tests/Handler/DispatchHandlerTest.php @@ -2,15 +2,17 @@ namespace EquipTests\Handler; -use EquipTests\DirectoryTestCase; +use Equip\Contract\ActionInterface; use Equip\Directory; +use Equip\Exception\HttpException; use Equip\Handler\ActionHandler; use Equip\Handler\DispatchHandler; use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequest; use Zend\Diactoros\Uri; +use PHPUnit_Framework_TestCase as TestCase; -class DispatchHandlerTest extends DirectoryTestCase +class DispatchHandlerTest extends TestCase { /** * @var Directory @@ -24,7 +26,7 @@ protected function setUp() public function testHandle() { - $action = $this->getMockAction(); + $action = $this->createMock(ActionInterface::class); $directory = $this->directory->get('/[{name}]', $action); $request = $this->getRequest('GET', '/tester'); $response = new Response; @@ -40,7 +42,7 @@ public function testHandle() public function testPrefixed() { - $action = $this->getMockAction(); + $action = $this->createMock(ActionInterface::class); $directory = $this->directory->withPrefix('prefix'); $directory = $directory->get('/[{name}]', $action); $request = $this->getRequest('GET', '/prefix/tester'); @@ -55,12 +57,13 @@ public function testPrefixed() $this->dispatch($directory, $request, $response, $next); } - /** - * @expectedException \Equip\Exception\HttpException - * @expectedExceptionRegExp /cannot find any resource at/i - */ public function testNotFoundException() { + $this->setExpectedExceptionRegExp( + HttpException::class, + '/cannot find any resource at/i' + ); + $handler = new DispatchHandler($this->directory); $request = $this->getRequest('GET', '/'); $response = new Response; @@ -75,17 +78,19 @@ function ($request, $response) { ); } - /** - * @expectedException \Equip\Exception\HttpException - * @expectedExceptionRegExp /cannot access resource .* using method/i - */ public function testMethodNotAllowedException() { + $this->setExpectedExceptionRegExp( + HttpException::class, + '/cannot access resource .* using method/i' + ); + + $action = $this->createMock(ActionInterface::class); $handler = new DispatchHandler($this->directory); $request = $this->getRequest('POST'); $response = new Response; - $directory = $this->directory->get('/', $this->getMockAction()); + $directory = $this->directory->get('/', $action); return $this->dispatch( $directory, diff --git a/tests/Handler/ExceptionHandlerTest.php b/tests/Handler/ExceptionHandlerTest.php index 85f8026..cecfcf3 100644 --- a/tests/Handler/ExceptionHandlerTest.php +++ b/tests/Handler/ExceptionHandlerTest.php @@ -111,7 +111,7 @@ class MockMonologConfiguration extends TestCase implements ConfigurationInterfac public function apply(Injector $injector) { $injector->delegate(LoggerInterface::class, function () { - $loggerMock = $this->getMock(LoggerInterface::class); + $loggerMock = $this->createMock(LoggerInterface::class); $loggerMock ->expects($this->atLeastOnce()) diff --git a/tests/Handler/JsonContentHandlerTest.php b/tests/Handler/JsonContentHandlerTest.php index 855d525..130e516 100644 --- a/tests/Handler/JsonContentHandlerTest.php +++ b/tests/Handler/JsonContentHandlerTest.php @@ -1,7 +1,7 @@ setExpectedExceptionRegExp( + HttpException::class, + '/json.* syntax error/i', + 400 + ); + $request = $this->getRequest( $mime = 'application/json', $body = '{not json}' ); $response = new Response; $handler = new JsonContentHandler; - $resolved = $handler($request, $response, function ($req, $res) { + + $handler($request, $response, function ($req, $res) { $this->fail('Handler callback unexpectedly invoked'); }); } diff --git a/tests/InputTest.php b/tests/InputTest.php deleted file mode 100644 index 24971f1..0000000 --- a/tests/InputTest.php +++ /dev/null @@ -1,155 +0,0 @@ -execute(new ServerRequest); - $this->assertEmpty($found); - } - - public function testQueryParams() - { - $query = [ - 'query' => 'string', - ]; - - $request = new ServerRequest; - $request = $request->withQueryParams($query); - - $found = $this->execute($request); - $this->assertSame($query, $found); - } - - public function dataParsedBody() - { - $data = []; - - // Null body should be an empty array - $body = null; - $expected = []; - $data[] = [$expected, $body]; - - // Array body should remain the same - $body = [ - 'body' => 'parsed', - 'has' => [ - 'other' => 'values', - ], - ]; - $expected = $body; - $data[] = [$expected, $body]; - - // Object body should be converted to an array - $body = json_decode(json_encode($body)); - $data[] = [$expected, $body]; - - return $data; - } - /** - * @dataProvider dataParsedBody - */ - public function testParsedBody($expected, $body) - { - $request = new ServerRequest; - $request = $request->withParsedBody($body); - - $input = $this->execute($request); - $this->assertSame($expected, $input); - } - - public function testUploadedFiles() - { - $files = [ - 'file' => $this->getMock(UploadedFileInterface::class), - ]; - - $request = new ServerRequest; - $request = $request->withUploadedFiles($files); - - $found = $this->execute($request); - $this->assertSame($files, $found); - } - - public function testCookieParams() - { - $cookies = [ - 'cookie' => 'nomnomnom', - ]; - - $request = new ServerRequest; - $request = $request->withCookieParams($cookies); - - $found = $this->execute($request); - $this->assertSame($cookies, $found); - } - - public function testAttributes() - { - $attrs = [ - 'attr' => 'stored', - ]; - - $request = new ServerRequest; - foreach ($attrs as $name => $value) { - $request = $request->withAttribute($name, $value); - } - - $found = $this->execute($request); - $this->assertSame($attrs, $found); - } - - public function testMerge() - { - $request = new ServerRequest; - - $value = [ - 'merge' => 'query', - ]; - $request = $request->withQueryParams($value); - $this->assertSame($value, $this->execute($request)); - - $value = [ - 'merge' => 'body', - ]; - $request = $request->withParsedBody($value); - $this->assertSame($value, $this->execute($request)); - - $value = [ - 'merge' => $this->getMock(UploadedFileInterface::class), - ]; - $request = $request->withParsedBody($value); - $this->assertSame($value, $this->execute($request)); - - $value = [ - 'merge' => 'cookie', - ]; - $request = $request->withCookieParams($value); - $this->assertSame($value, $this->execute($request)); - - $value = [ - 'merge' => 'attr', - ]; - $request = $request->withAttribute(key($value), current($value)); - $this->assertSame($value, $this->execute($request)); - } - - /** - * Collect input from the request - * - * @param ServerRequest $request - * - * @return array - */ - private function execute(ServerRequest $request) - { - return call_user_func(new Input, $request); - } -} diff --git a/tests/Middleware/MiddlewareSetTest.php b/tests/Middleware/MiddlewareSetTest.php index 99160b6..8a914ce 100644 --- a/tests/Middleware/MiddlewareSetTest.php +++ b/tests/Middleware/MiddlewareSetTest.php @@ -23,7 +23,7 @@ public function testWithInvalidEntries() public function testWithValidEntries() { $middleware = [ - $this->getMock(MiddlewareInterface::class), + $this->createMock(MiddlewareInterface::class), function () { } ]; diff --git a/tests/PayloadTest.php b/tests/PayloadTest.php deleted file mode 100644 index ac2c427..0000000 --- a/tests/PayloadTest.php +++ /dev/null @@ -1,76 +0,0 @@ -withStatus(Payload::STATUS_OK); - - $this->assertEmpty($load->getStatus()); - $this->assertSame(Payload::STATUS_OK, $copy->getStatus()); - } - - public function testInput() - { - $input = [ - 'test' => true - ]; - - $load = new Payload; - $copy = $load->withInput($input); - - $this->assertEmpty($load->getInput()); - $this->assertSame($input, $copy->getInput()); - } - - public function testMessages() - { - $messages = [ - 'username' => 'not found', - 'password' => 'invalid' - ]; - - $load = new Payload; - $copy = $load->withMessages($messages); - - $this->assertEmpty($load->getMessages()); - $this->assertSame($messages, $copy->getMessages()); - } - - public function testOutput() - { - $output = [ - 'collection' => [] - ]; - - $load = new Payload; - $copy = $load->withOutput($output); - - $this->assertEmpty($load->getOutput()); - $this->assertSame($output, $copy->getOutput()); - } - - public function testSettings() - { - $name = 'template'; - $value = 'index'; - - $load = new Payload; - $copy = $load->withSetting($name, $value); - - $this->assertEmpty($load->getSettings()); - $this->assertSame($value, $copy->getSetting($name)); - $this->assertSame([$name => $value], $copy->getSettings()); - - $empty = $copy->withoutSetting($name); - - $this->assertNull($empty->getSetting($name)); - $this->assertEmpty($empty->getSettings()); - } -} diff --git a/tests/Responder/ChainedResponderTest.php b/tests/Responder/ChainedResponderTest.php deleted file mode 100644 index 030c835..0000000 --- a/tests/Responder/ChainedResponderTest.php +++ /dev/null @@ -1,96 +0,0 @@ -responder = $this->injector->make(ChainedResponder::class); - } - - public function testDefaultResponders() - { - $responders = $this->responder->toArray(); - - $this->assertContains(FormattedResponder::class, $responders); - $this->assertContains(RedirectResponder::class, $responders); - } - - public function testInvalidResponder() - { - $this->setExpectedExceptionRegExp( - ResponderException::class, - '/Responder class .* must implement .*ResponderInterface/i' - ); - - $this->responder->withValue(get_class($this)); - } - - public function testAddResponder() - { - $responder = $this->getMockResponder(); - $chained = $this->responder->withValue($responder); - - $this->assertContains($responder, $chained); - - $response = $this->execute($chained); - $this->assertInstanceOf(ResponseInterface::class, $response); - } - - public function testResponse() - { - $response = $this->execute($this->responder); - $this->assertInstanceOf(ResponseInterface::class, $response); - } - - private function getMockResponder() - { - $responder = $this->getMockBuilder(ResponderInterface::class)->getMock(); - $responder - ->expects($this->once()) - ->method('__invoke') - ->with( - $this->isInstanceOf(ServerRequestInterface::class), - $this->isInstanceOf(ResponseInterface::class), - $this->isInstanceOf(PayloadInterface::class) - ) - ->will($this->returnArgument(1)); - - return $responder; - } - - private function execute($responder) - { - $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class)->getMock(); - $payload = $this->getMockBuilder(PayloadInterface::class)->getMock(); - - return call_user_func($responder, $request, $response, $payload); - } -} diff --git a/tests/Responder/FormattedResponderTest.php b/tests/Responder/FormattedResponderTest.php deleted file mode 100644 index 563ef5f..0000000 --- a/tests/Responder/FormattedResponderTest.php +++ /dev/null @@ -1,129 +0,0 @@ -responder = $this->injector->make(FormattedResponder::class); - } - - public function testFormatters() - { - $formatters = $this->responder->toArray(); - - $this->assertArrayHasKey(JsonFormatter::class, $formatters); - - unset($formatters[JsonFormatter::class]); - - $formatters = $this->responder->withValues($formatters)->toArray(); - - $this->assertArrayNotHasKey(JsonFormatter::class, $formatters); - - // Append another one with high quality - $formatters[JsonFormatter::class] = 1.0; - - $formatters = $this->responder->withValues($formatters)->toArray(); - $sortedcopy = $formatters; - } - - public function testSorting() - { - $a = $this->getMockBuilder(FormatterInterface::class) - ->setMockClassName('FooFormatter') - ->getMock(); - - $b = $this->getMockBuilder(FormatterInterface::class) - ->setMockClassName('BarFormatter') - ->getMock(); - - $values = [ - get_class($a) => 0.5, - get_class($b) => 1.0, - ]; - - $responder = $this->responder->withValues($values); - $formatters = $responder->toArray(); - - $this->assertNotSame($values, $formatters); - - arsort($values); - - $this->assertSame($values, $formatters); - } - - public function testInvalidResponder() - { - $this->setExpectedExceptionRegExp( - FormatterException::class, - '/Formatter class .* must implement .*FormatterInterface/i' - ); - - $this->responder->withValue(get_class($this), 1.0); - } - - public function testInvalidResponderQuality() - { - $this->setExpectedExceptionRegExp( - FormatterException::class, - '/No quality have been set for the .*/ii' - ); - - $this->responder->withValue(JsonFormatter::class, false); - } - - public function testResponse() - { - $request = new ServerRequest; - $request = $request->withHeader('Accept', 'application/json'); - - $response = new Response; - - $payload = new Payload; - $payload = $payload - ->withOutput(['test' => 'test']); - - $response = call_user_func($this->responder, $request, $response, $payload); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals(['application/json'], $response->getHeader('Content-Type')); - $this->assertEquals('{"test":"test"}', (string) $response->getBody()); - } - - public function testEmptyPayload() - { - $payload = new Payload; - $request = $this->getMock(ServerRequestInterface::class); - $response = $this->getMock(ResponseInterface::class); - $returned = call_user_func($this->responder, $request, $response, $payload); - $this->assertSame($returned, $response); - } -} diff --git a/tests/Responder/RedirectResponderTest.php b/tests/Responder/RedirectResponderTest.php deleted file mode 100644 index abc86e1..0000000 --- a/tests/Responder/RedirectResponderTest.php +++ /dev/null @@ -1,46 +0,0 @@ -responder = new RedirectResponder; - } - - public function testRedirect() - { - $payload = (new Payload())->withSetting('redirect', '/'); - - $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock(); - $response = new Response; - - $response = call_user_func($this->responder, $request, $response, $payload); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals('/', $response->getHeaderLine('Location')); - } - - public function testEmptyPayload() - { - $payload = new Payload; - $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class)->getMock(); - $returned = call_user_func($this->responder, $request, $response, $payload); - $this->assertSame($returned, $response); - } -} diff --git a/tests/Responder/StatusResponderTest.php b/tests/Responder/StatusResponderTest.php deleted file mode 100644 index 618b57d..0000000 --- a/tests/Responder/StatusResponderTest.php +++ /dev/null @@ -1,49 +0,0 @@ -responder = new StatusResponder( - new Httpstatus - ); - } - - public function testStatus() - { - $payload = new Payload; - $payload = $payload->withStatus(Payload::STATUS_OK); - - $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock(); - $response = new Response; - - $response = call_user_func($this->responder, $request, $response, $payload); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals(200, $response->getStatusCode()); - } - - public function testEmptyPayload() - { - $payload = new Payload; - $request = $this->getMockBuilder(ServerRequestInterface::class)->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class)->getMock(); - $returned = call_user_func($this->responder, $request, $response, $payload); - $this->assertSame($returned, $response); - } -}