├── .codeclimate.yml ├── .editorconfig ├── .pullapprove.yml ├── .travis.php.ini ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── Action.php ├── Application.php ├── Configuration ├── AurynConfiguration.php ├── DiactorosConfiguration.php ├── EnvConfiguration.php ├── MonologConfiguration.php ├── PayloadConfiguration.php ├── PlatesConfiguration.php ├── PlatesResponderConfiguration.php ├── RedisConfiguration.php ├── RelayConfiguration.php └── WhoopsConfiguration.php ├── Directory.php ├── Exception ├── DirectoryException.php ├── EnvException.php ├── FormatterException.php ├── HttpException.php ├── MiddlewareException.php └── ResponderException.php ├── Formatter ├── FormatterInterface.php ├── HtmlFormatter.php ├── JsonFormatter.php └── PlatesFormatter.php ├── Handler ├── ActionHandler.php ├── ContentHandler.php ├── DispatchHandler.php ├── ExceptionHandler.php ├── ExceptionHandlerPreferences.php ├── FormContentHandler.php └── JsonContentHandler.php ├── Input.php ├── Middleware └── MiddlewareSet.php ├── Payload.php ├── Resolver ├── AurynResolver.php └── ResolverTrait.php └── Responder ├── ChainedResponder.php ├── FormattedResponder.php ├── RedirectResponder.php └── StatusResponder.php /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | PHP: true 3 | exclude_paths: 4 | - tests/* 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # PHP/JS Formatting 12 | [*.{php,js,json}] 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | indent_style = space 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.pullapprove.yml: -------------------------------------------------------------------------------- 1 | author_approval: ignored 2 | approve_by_comment: true 3 | reset_on_push: false 4 | approve_regex: '^:\+1:' 5 | reject_regex: '^:\-1:' 6 | reviewers: 7 | - 8 | name: stack 9 | required: 1 10 | members: 11 | - ameech 12 | - dolfelt 13 | - elazar 14 | - sctape 15 | - shadowhand 16 | -------------------------------------------------------------------------------- /.travis.php.ini: -------------------------------------------------------------------------------- 1 | extension = redis.so 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## Unreleased 7 | 8 | _..._ 9 | 10 | ## 2.1.0 - ? 11 | 12 | - Replace generic exceptions with specific exceptions in handlers 13 | - Add URI prefix support for routing with `Directory` class 14 | 15 | ## 2.0.1 - 2016-05-31 16 | 17 | - Remove deprecation note from JSON and form content handlers 18 | 19 | ## 2.0.0 - 2016-05-27 20 | 21 | - Update Plates formatter to use Payload settings for template 22 | - Update redirect responder to use Payload settings for target location 23 | - Allow resolver to work with already instantiated objects 24 | - Separate response formatting from HTTP status code with `StatusResponder` 25 | - Replace `AbstractFormatter` with a `FormatterInterface` 26 | - Remove dependency on Arbiter 27 | - Remove compatibility `StructureWithDataAlias` compatibility trait from: 28 | - ConfigurationSet 29 | - Directory 30 | - Env 31 | - ExceptionHandlerPreferences 32 | - ChainedResponder 33 | - FormattedResponder 34 | 35 | ## 1.8.1 - 2016-04-29 36 | 37 | - Small bugfix to ensure exceptions are logged correctly in `ExceptionHandler` 38 | 39 | ## 1.8.0 - 2016-04-28 40 | 41 | - Add optional support for `LoggerInterface` to log exceptions caught by `ExceptionHandler` 42 | - Add `MonologConfiguration` 43 | 44 | ## 1.7.0 - 2016-04-13 45 | 46 | - Add `PlatesConfiguration` 47 | 48 | ## 1.6.0 - 2016-04-11 49 | 50 | - Bump nikic/fast-route dependency version to 0.8 51 | 52 | ## 1.5.1 - 2016-03-11 53 | 54 | - Bump equip/adr dependency version to 1.3 55 | 56 | ## 1.5.0 - 2016-03-01 57 | 58 | - Add Redis configuration 59 | 60 | ## 1.4.0 - 2016-03-01 61 | 62 | - Add support for `getHttpStatus` for any exception caught by `ExceptionHandler` 63 | - Switch to Relay.Middleware content handling 64 | 65 | ## 1.3.0 - 2016-02-13 66 | 67 | - Upgrade Whoops exception handler to v2 68 | - Switch from `destrukt/destrukt` to `equip/structure` 69 | 70 | ## 1.2.0 - 2016-02-11 71 | 72 | - `AbstractFormatter` and all its subclasses can now use the new `PayloadInterface` status codes added to equip/adr v1.1.0 73 | 74 | ## 1.1.0 - 2016-01-08 75 | 76 | - Ensure that sorting on `FormattedResponder` formatters is preserved by using `SortedDictionary` 77 | 78 | ## 1.0.0 - 2016-01-05 79 | 80 | - Changed from `Spark` to `Equip` namespace 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Daniel Olfelt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Equip Framework 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/equip/framework.svg)](https://packagist.org/packages/equip/framework) 4 | [![License](https://img.shields.io/packagist/l/equip/framework.svg)](https://github.com/equip/framework/blob/master/LICENSE) 5 | [![Build Status](https://travis-ci.org/equip/framework.svg)](https://travis-ci.org/equip/framework) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/equip/framework/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/equip/framework/?branch=master) 7 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/equip/framework/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/equip/framework/?branch=master) 8 | 9 | A tiny and powerful PHP micro-framework created and maintained by the engineering team at [When I Work](http://wheniwork.com). 10 | 11 | Attempts to be [PSR-1](http://www.php-fig.org/psr/psr-1/), 12 | [PSR-2](http://www.php-fig.org/psr/psr-2/), 13 | [PSR-3](http://www.php-fig.org/psr/psr-3/), 14 | [PSR-4](http://www.php-fig.org/psr/psr-4/) and 15 | [PSR-7](http://www.php-fig.org/psr/psr-7/) compliant. Also uses the 16 | [ADR](https://github.com/pmjones/adr) pattern. You should too! 17 | 18 | To get started, check out [the documentation](https://equipframework.readthedocs.io). 19 | There's also a [sample project](https://github.com/equip/project) that shows 20 | the best way to use this framework and is a quick way to start a new project. 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "equip/framework", 3 | "description": "A tiny framework that can start anything.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Equip Contributors", 9 | "homepage": "https://github.com/equip" 10 | } 11 | ], 12 | "minimum-stability": "stable", 13 | "require": { 14 | "php": ">=5.5", 15 | "equip/adr": "^2.0", 16 | "equip/config": "^1.0.2", 17 | "equip/structure": "^1.0", 18 | "filp/whoops": "^2.0", 19 | "lukasoppermann/http-status": "^2", 20 | "nikic/fast-route": "^1.0", 21 | "psr/log": "^1.0", 22 | "rdlowrey/auryn": "^1.1", 23 | "relay/relay": "^1.1", 24 | "relay/middleware": "^1.0", 25 | "willdurand/negotiation": "^2.0", 26 | "psr/http-message": "^1.0" 27 | }, 28 | "require-dev": { 29 | "josegonzalez/dotenv": "^2.0", 30 | "league/plates": "^3.1", 31 | "monolog/monolog": "^1.19", 32 | "phpunit/phpunit": "^4.8|^5.0", 33 | "zendframework/zend-diactoros": "^1.0.4" 34 | }, 35 | "suggest": { 36 | "josegonzalez/dotenv": "For environment based configuration loading", 37 | "league/plates": "For simple HTML templates written in PHP", 38 | "monolog/monolog": "For a simple logging implementation of PSR-3" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Equip\\": "src/" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "EquipTests\\": "tests/" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Action.php: -------------------------------------------------------------------------------- 1 | domain = $domain; 40 | 41 | if ($responder) { 42 | $this->responder = $responder; 43 | } 44 | 45 | if ($input) { 46 | $this->input = $input; 47 | } 48 | } 49 | 50 | /** 51 | * Returns the domain specification. 52 | * 53 | * @return DomainInterface 54 | */ 55 | public function getDomain() 56 | { 57 | return $this->domain; 58 | } 59 | 60 | /** 61 | * Returns the responder specification. 62 | * 63 | * @return ResponderInterface 64 | */ 65 | public function getResponder() 66 | { 67 | return $this->responder; 68 | } 69 | 70 | /** 71 | * Returns the input specification. 72 | * 73 | * @return InputInterface 74 | */ 75 | public function getInput() 76 | { 77 | return $this->input; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | injector = $injector ?: new Injector; 61 | $this->configuration = $configuration ?: new ConfigurationSet; 62 | $this->middleware = $middleware ?: new MiddlewareSet; 63 | } 64 | 65 | /** 66 | * Change configuration values 67 | * 68 | * @param array $configuration 69 | * 70 | * @return self 71 | */ 72 | public function setConfiguration(array $configuration) 73 | { 74 | $this->configuration = $this->configuration->withValues($configuration); 75 | return $this; 76 | } 77 | 78 | /** 79 | * Change middleware 80 | * 81 | * @param array $middleware 82 | * 83 | * @return self 84 | */ 85 | public function setMiddleware(array $middleware) 86 | { 87 | $this->middleware = $this->middleware->withValues($middleware); 88 | return $this; 89 | } 90 | 91 | /** 92 | * Change routing 93 | * 94 | * @param callable|string $routing 95 | * 96 | * @return self 97 | */ 98 | public function setRouting($routing) 99 | { 100 | $this->routing = $routing; 101 | return $this; 102 | } 103 | 104 | /** 105 | * Run the application 106 | * 107 | * @param string $runner 108 | * 109 | * @return \Psr\Http\Message\ResponseInterface 110 | */ 111 | public function run($runner = Relay::class) 112 | { 113 | $this->configuration->apply($this->injector); 114 | 115 | return $this->injector 116 | ->share($this->middleware) 117 | ->prepare(Directory::class, $this->routing) 118 | ->execute($runner); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Configuration/AurynConfiguration.php: -------------------------------------------------------------------------------- 1 | alias( 17 | ResolverInterface::class, 18 | AurynResolver::class 19 | ); 20 | 21 | $injector->define(AurynResolver::class, [ 22 | ':injector' => $injector, 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Configuration/DiactorosConfiguration.php: -------------------------------------------------------------------------------- 1 | alias( 21 | RequestInterface::class, 22 | // It should not be necessary to force all requests to be server 23 | // requests, except that Relay uses the wrong type hint: 24 | // https://github.com/relayphp/Relay.Relay/issues/25 25 | // 26 | // 'Zend\Diactoros\Request' 27 | ServerRequest::class 28 | ); 29 | 30 | $injector->alias( 31 | ResponseInterface::class, 32 | Response::class 33 | ); 34 | 35 | $injector->alias( 36 | ServerRequestInterface::class, 37 | ServerRequest::class 38 | ); 39 | 40 | $injector->delegate( 41 | ServerRequest::class, 42 | [ServerRequestFactory::class, 'fromGlobals'] 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Configuration/EnvConfiguration.php: -------------------------------------------------------------------------------- 1 | detectEnvFile(); 26 | } 27 | 28 | if (!is_file($envfile) || !is_readable($envfile)) { 29 | throw EnvException::invalidFile($envfile); 30 | } 31 | 32 | $this->envfile = $envfile; 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function apply(Injector $injector) 39 | { 40 | $injector->define(Loader::class, [ 41 | ':filepaths' => $this->envfile, 42 | ]); 43 | 44 | $injector->share(Env::class); 45 | 46 | $injector->prepare(Env::class, function (Env $env, Injector $injector) { 47 | $loader = $injector->make(Loader::class); 48 | $values = $loader->parse()->toArray(); 49 | return $env->withValues($values); 50 | }); 51 | } 52 | 53 | /** 54 | * Find a .env file by traversing up the filesystem 55 | * 56 | * @return string 57 | * 58 | * @throws EnvException If no file is found 59 | */ 60 | private function detectEnvFile() 61 | { 62 | $env = DIRECTORY_SEPARATOR . '.env'; 63 | $dir = dirname(dirname(__DIR__)); 64 | 65 | do { 66 | if (is_file($dir . $env)) { 67 | return $dir . $env; 68 | } 69 | $dir = dirname($dir); 70 | } while (is_readable($dir) && dirname($dir) !== $dir); 71 | 72 | throw EnvException::detectionFailed(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Configuration/MonologConfiguration.php: -------------------------------------------------------------------------------- 1 | alias( 19 | LoggerInterface::class, 20 | Logger::class 21 | ); 22 | 23 | $injector->share(Logger::class); 24 | 25 | $injector->define(Logger::class, [ 26 | ':name' => $this->env->getValue('LOGGER_NAME', 'equip') 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Configuration/PayloadConfiguration.php: -------------------------------------------------------------------------------- 1 | alias( 17 | PayloadInterface::class, 18 | Payload::class 19 | ); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/Configuration/PlatesConfiguration.php: -------------------------------------------------------------------------------- 1 | define(Engine::class, [ 15 | ':directory' => $this->env['PLATES_DIRECTORY'], 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Configuration/PlatesResponderConfiguration.php: -------------------------------------------------------------------------------- 1 | prepare(FormattedResponder::class, function (FormattedResponder $responder) { 14 | return $responder->withValue(PlatesFormatter::class, 1.0); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Configuration/RedisConfiguration.php: -------------------------------------------------------------------------------- 1 | prepare(Redis::class, function($redis) { 18 | $redis->connect( 19 | $this->env->getValue('REDIS_HOST', '127.0.0.1'), 20 | $this->env->getValue('REDIS_PORT', 6379) 21 | ); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Configuration/RelayConfiguration.php: -------------------------------------------------------------------------------- 1 | define(RelayBuilder::class, [ 19 | 'resolver' => ResolverInterface::class 20 | ]); 21 | 22 | $factory = function (RelayBuilder $builder, MiddlewareSet $queue) { 23 | return $builder->newInstance($queue); 24 | }; 25 | 26 | $injector->delegate(Relay::class, $factory); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Configuration/WhoopsConfiguration.php: -------------------------------------------------------------------------------- 1 | prepare(Whoops::class, [$this, 'prepareWhoops']); 20 | $injector->prepare(JsonResponseHandler::class, [$this, 'prepareJsonHandler']); 21 | $injector->prepare(PlainTextHandler::class, [$this, 'preparePlainTextHandler']); 22 | } 23 | 24 | /** 25 | * @param Whoops $whoops 26 | * 27 | * @return void 28 | */ 29 | public function prepareWhoops(Whoops $whoops) 30 | { 31 | if($this->env->getValue('DEBUG_STACKTRACE', false) == true) { 32 | set_error_handler([$whoops, Whoops::ERROR_HANDLER]); 33 | } 34 | 35 | $whoops->writeToOutput(false); 36 | $whoops->allowQuit(false); 37 | } 38 | 39 | /** 40 | * @param JsonResponseHandler $handler 41 | * 42 | * @return void 43 | */ 44 | public function prepareJsonHandler(JsonResponseHandler $handler) 45 | { 46 | $handler->addTraceToOutput(true); 47 | } 48 | 49 | /** 50 | * @param PlainTextHandler $handler 51 | * 52 | * @return void 53 | */ 54 | public function preparePlainTextHandler(PlainTextHandler $handler) 55 | { 56 | $handler->addTraceToOutput(true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Directory.php: -------------------------------------------------------------------------------- 1 | prefix = '/' . trim($prefix, '/'); 36 | 37 | return $copy; 38 | } 39 | 40 | /** 41 | * Remove the directory path prefix. 42 | * 43 | * @return static 44 | */ 45 | public function withoutPrefix() 46 | { 47 | $copy = clone $this; 48 | $copy->prefix = ''; 49 | 50 | return $copy; 51 | } 52 | 53 | /** 54 | * Add the prefix to a path. 55 | * 56 | * @param string $path 57 | * 58 | * @return string 59 | */ 60 | public function prefix($path) 61 | { 62 | return $this->prefix . $path; 63 | } 64 | 65 | /** 66 | * @param string $path 67 | * @param string|Action $domainOrAction 68 | * 69 | * @return static 70 | */ 71 | public function any($path, $domainOrAction) 72 | { 73 | return $this->action(self::ANY, $path, $domainOrAction); 74 | } 75 | 76 | /** 77 | * @param string $path 78 | * @param string|Action $domainOrAction 79 | * 80 | * @return static 81 | */ 82 | public function get($path, $domainOrAction) 83 | { 84 | return $this->action(self::GET, $path, $domainOrAction); 85 | } 86 | 87 | /** 88 | * @param string $path 89 | * @param string|Action $domainOrAction 90 | * 91 | * @return static 92 | */ 93 | public function post($path, $domainOrAction) 94 | { 95 | return $this->action(self::POST, $path, $domainOrAction); 96 | } 97 | 98 | /** 99 | * @param string $path 100 | * @param string|Action $domainOrAction 101 | * 102 | * @return static 103 | */ 104 | public function put($path, $domainOrAction) 105 | { 106 | return $this->action(self::PUT, $path, $domainOrAction); 107 | } 108 | 109 | /** 110 | * @param string $path 111 | * @param string|Action $domainOrAction 112 | * 113 | * @return static 114 | */ 115 | public function patch($path, $domainOrAction) 116 | { 117 | return $this->action(self::PATCH, $path, $domainOrAction); 118 | } 119 | 120 | /** 121 | * @param string $path 122 | * @param string|Action $domainOrAction 123 | * 124 | * @return static 125 | */ 126 | public function head($path, $domainOrAction) 127 | { 128 | return $this->action(self::HEAD, $path, $domainOrAction); 129 | } 130 | 131 | /** 132 | * @param string $path 133 | * @param string|Action $domainOrAction 134 | * 135 | * @return static 136 | */ 137 | public function delete($path, $domainOrAction) 138 | { 139 | return $this->action(self::DELETE, $path, $domainOrAction); 140 | } 141 | 142 | /** 143 | * @param string $path 144 | * @param string|Action $domainOrAction 145 | * 146 | * @return static 147 | */ 148 | public function options($path, $domainOrAction) 149 | { 150 | return $this->action(self::OPTIONS, $path, $domainOrAction); 151 | } 152 | 153 | /** 154 | * @param string $method 155 | * @param string $path 156 | * @param string|Action $domainOrAction 157 | * 158 | * @return static 159 | */ 160 | public function action($method, $path, $domainOrAction) 161 | { 162 | if ($domainOrAction instanceof Action) { 163 | $action = $domainOrAction; 164 | } else { 165 | $action = new Action($domainOrAction); 166 | } 167 | 168 | return $this->withValue(sprintf('%s %s', $method, $path), $action); 169 | } 170 | 171 | /** 172 | * @inheritDoc 173 | * 174 | * @throws DirectoryException If a value is not an Action instance 175 | */ 176 | protected function assertValid(array $data) 177 | { 178 | parent::assertValid($data); 179 | 180 | foreach ($data as $value) { 181 | if (!is_object($value) || !$value instanceof Action) { 182 | throw DirectoryException::invalidEntry($value); 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/Exception/DirectoryException.php: -------------------------------------------------------------------------------- 1 | allowed = $allowed; 39 | 40 | return $error; 41 | } 42 | 43 | /** 44 | * @param string $message 45 | * 46 | * @return static 47 | */ 48 | public static function badRequest($message) 49 | { 50 | return new static(sprintf( 51 | 'Cannot parse the request: %s', 52 | $message 53 | ), 400); 54 | } 55 | 56 | /** 57 | * @var array 58 | */ 59 | private $allowed = []; 60 | 61 | /** 62 | * @param ResponseInterface $response 63 | * 64 | * @return ResponseInterface 65 | */ 66 | public function withResponse(ResponseInterface $response) 67 | { 68 | if (!empty($this->allowed)) { 69 | $response = $response->withHeader('Allow', implode(',', $this->allowed)); 70 | } 71 | 72 | return $response; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Exception/MiddlewareException.php: -------------------------------------------------------------------------------- 1 | getOutput(), $this->options()); 31 | } 32 | 33 | /** 34 | * @inheritDoc 35 | */ 36 | protected function options() 37 | { 38 | return 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Formatter/PlatesFormatter.php: -------------------------------------------------------------------------------- 1 | engine = $engine; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | public function body(PayloadInterface $payload) 28 | { 29 | return $this->render($payload); 30 | } 31 | 32 | /** 33 | * @param PayloadInterface $payload 34 | * 35 | * @return string 36 | */ 37 | private function render(PayloadInterface $payload) 38 | { 39 | $template = $payload->getSetting('template'); 40 | $output = $payload->getOutput(); 41 | 42 | return $this->engine->render($template, $output); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Handler/ActionHandler.php: -------------------------------------------------------------------------------- 1 | resolver = $resolver; 27 | } 28 | 29 | /** 30 | * @param ServerRequestInterface $request 31 | * @param ResponseInterface $response 32 | * @param callable $next 33 | */ 34 | public function __invoke( 35 | ServerRequestInterface $request, 36 | ResponseInterface $response, 37 | callable $next 38 | ) { 39 | $action = $request->getAttribute(self::ACTION_ATTRIBUTE); 40 | $request = $request->withoutAttribute(self::ACTION_ATTRIBUTE); 41 | 42 | $response = $this->handle($action, $request, $response); 43 | 44 | return $next($request, $response); 45 | } 46 | 47 | /** 48 | * Use the action collaborators to get a response. 49 | * 50 | * @param Action $action 51 | * @param ServerRequestInterface $request 52 | * @param ResponseInterface $response 53 | * 54 | * @return ResponseInterface 55 | */ 56 | private function handle( 57 | Action $action, 58 | ServerRequestInterface $request, 59 | ResponseInterface $response 60 | ) { 61 | $domain = $this->resolve($action->getDomain()); 62 | $input = $this->resolve($action->getInput()); 63 | $responder = $this->resolve($action->getResponder()); 64 | 65 | $payload = $this->payload($domain, $input, $request); 66 | $response = $this->response($responder, $request, $response, $payload); 67 | 68 | return $response; 69 | } 70 | 71 | /** 72 | * Execute the domain to get a payload using input from the request. 73 | * 74 | * @param DomainInterface $domain 75 | * @param InputInterface $input 76 | * @param ServerRequestInterface $request 77 | * 78 | * @return PayloadInterface 79 | */ 80 | private function payload( 81 | DomainInterface $domain, 82 | InputInterface $input, 83 | ServerRequestInterface $request 84 | ) { 85 | return $domain($input($request)); 86 | } 87 | 88 | /** 89 | * Execute the responder to marshall the payload into the response. 90 | * 91 | * @param ResponderInterface $responder 92 | * @param ServerRequestInterface $request 93 | * @param ResponseInterface $response 94 | * @param PayloadInterface $payload 95 | * 96 | * @return ResponseInterface 97 | */ 98 | private function response( 99 | ResponderInterface $responder, 100 | ServerRequestInterface $request, 101 | ResponseInterface $response, 102 | PayloadInterface $payload 103 | ) { 104 | return $responder($request, $response, $payload); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Handler/ContentHandler.php: -------------------------------------------------------------------------------- 1 | directory = $directory; 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | public function __invoke( 33 | ServerRequestInterface $request, 34 | ResponseInterface $response, 35 | callable $next 36 | ) { 37 | /** 38 | * @var $action Equip\Action 39 | */ 40 | list($action, $args) = $this->dispatch( 41 | $this->dispatcher(), 42 | $request->getMethod(), 43 | $request->getUri()->getPath() 44 | ); 45 | 46 | $request = $request->withAttribute(ActionHandler::ACTION_ATTRIBUTE, $action); 47 | 48 | foreach ($args as $key => $value) { 49 | $request = $request->withAttribute($key, $value); 50 | } 51 | 52 | return $next($request, $response); 53 | } 54 | 55 | /** 56 | * @return Dispatcher 57 | */ 58 | protected function dispatcher() 59 | { 60 | return FastRoute\simpleDispatcher(function (RouteCollector $collector) { 61 | foreach ($this->directory as $request => $action) { 62 | list($method, $path) = explode(' ', $request, 2); 63 | 64 | $collector->addRoute( 65 | $method, 66 | $this->directory->prefix($path), 67 | $action 68 | ); 69 | } 70 | }); 71 | } 72 | 73 | /** 74 | * @throws HttpNotFound 75 | * @throws HttpMethodNotAllowed 76 | * 77 | * @param Dispatcher $dispatcher 78 | * @param string $method 79 | * @param string $path 80 | * 81 | * @return array [Action, $arguments] 82 | */ 83 | private function dispatch(Dispatcher $dispatcher, $method, $path) 84 | { 85 | $route = $dispatcher->dispatch($method, $path); 86 | $status = array_shift($route); 87 | 88 | if (Dispatcher::FOUND === $status) { 89 | return $route; 90 | } 91 | 92 | if (Dispatcher::METHOD_NOT_ALLOWED === $status) { 93 | $allowed = array_shift($route); 94 | throw HttpException::methodNotAllowed($path, $method, $allowed); 95 | } 96 | 97 | throw HttpException::notFound($path); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Handler/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | preferences = $preferences; 57 | $this->logger = $logger; 58 | $this->negotiator = $negotiator; 59 | $this->resolver = $resolver; 60 | $this->whoops = $whoops; 61 | } 62 | 63 | /** 64 | * @param ServerRequestInterface $request 65 | * @param ResponseInterface $response 66 | * @param callable $next 67 | * 68 | * @return ResponseInterface 69 | */ 70 | public function __invoke( 71 | ServerRequestInterface $request, 72 | ResponseInterface $response, 73 | callable $next 74 | ) { 75 | try { 76 | return $next($request, $response); 77 | } catch (Exception $e) { 78 | if ($this->logger) { 79 | if ($e instanceof HttpException) { 80 | $this->logger->debug($e->getMessage(), ['exception' => $e]); 81 | } else { 82 | $this->logger->error($e->getMessage(), ['exception' => $e]); 83 | } 84 | } 85 | 86 | $type = $this->type($request); 87 | 88 | $response = $response->withHeader('Content-Type', $type); 89 | 90 | try { 91 | if (method_exists($e, 'getHttpStatus')) { 92 | $code = $e->getHttpStatus(); 93 | } else { 94 | $code = $e->getCode(); 95 | } 96 | $response = $response->withStatus($code); 97 | } catch (InvalidArgumentException $_) { 98 | // Exception did not contain a valid code 99 | $response = $response->withStatus(500); 100 | } 101 | 102 | if ($e instanceof HttpException) { 103 | $response = $e->withResponse($response); 104 | } 105 | 106 | if ($this->preferences->displayDebug()) { 107 | 108 | $handler = $this->handler($type); 109 | $this->whoops->pushHandler($handler); 110 | 111 | $body = $this->whoops->handleException($e); 112 | $response->getBody()->write($body); 113 | 114 | $this->whoops->popHandler(); 115 | } 116 | 117 | return $response; 118 | } 119 | } 120 | 121 | /** 122 | * Determine the preferred content type for the current request 123 | * 124 | * @param ServerRequestInterface $request 125 | * 126 | * @return string 127 | */ 128 | private function type(ServerRequestInterface $request) 129 | { 130 | $accept = $request->getHeaderLine('Accept'); 131 | $priorities = $this->preferences->toArray(); 132 | 133 | if (!empty($accept)) { 134 | $preferred = $this->negotiator->getBest($accept, array_keys($priorities)); 135 | } 136 | 137 | if (!empty($preferred)) { 138 | return $preferred->getValue(); 139 | } 140 | 141 | return key($priorities); 142 | } 143 | 144 | /** 145 | * Retrieve the handler to use for the given type 146 | * 147 | * @param string $type 148 | * 149 | * @return \Whoops\Handler\HandlerInterface 150 | */ 151 | private function handler($type) 152 | { 153 | return call_user_func($this->resolver, $this->preferences[$type]); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Handler/ExceptionHandlerPreferences.php: -------------------------------------------------------------------------------- 1 | PrettyPageHandler::class, 27 | 'application/javascript' => JsonResponseHandler::class, 28 | 'application/json' => JsonResponseHandler::class, 29 | 'application/ld+json' => JsonResponseHandler::class, 30 | 'application/vnd.api+json' => JsonResponseHandler::class, 31 | 'application/vnd.geo+json' => JsonResponseHandler::class, 32 | 'application/xml' => XmlResponseHandler::class, 33 | 'application/atom+xml' => XmlResponseHandler::class, 34 | 'application/rss+xml' => XmlResponseHandler::class, 35 | 'text/plain' => PlainTextHandler::class, 36 | ]; // @codeCoverageIgnore 37 | 38 | $this->debug = (bool) $env->getValue('DEBUG_STACKTRACE', false); 39 | parent::__construct($data); 40 | } 41 | 42 | public function displayDebug( ) 43 | { 44 | return $this->debug; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Handler/FormContentHandler.php: -------------------------------------------------------------------------------- 1 | getAttributes(); 21 | $body = $this->body($request); 22 | $cookies = $request->getCookieParams(); 23 | $query = $request->getQueryParams(); 24 | $uploads = $request->getUploadedFiles(); 25 | 26 | // Order matters here! Important values go last! 27 | return array_replace( 28 | $query, 29 | $body, 30 | $uploads, 31 | $cookies, 32 | $attrs 33 | ); 34 | } 35 | 36 | /** 37 | * @param ServerRequestInterface $request 38 | * 39 | * @return array 40 | */ 41 | private function body(ServerRequestInterface $request) 42 | { 43 | $body = $request->getParsedBody(); 44 | 45 | if (empty($body)) { 46 | return []; 47 | } 48 | 49 | if (is_object($body)) { 50 | // Because the parsed body may also be represented as an object, 51 | // additional parsing is required. This is a bit dirty but works 52 | // very well for anonymous objects. 53 | $body = json_decode(json_encode($body), true); 54 | } 55 | 56 | return $body; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Middleware/MiddlewareSet.php: -------------------------------------------------------------------------------- 1 | status = $status; 41 | 42 | return $copy; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public function getStatus() 49 | { 50 | return $this->status; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function withInput(array $input) 57 | { 58 | $copy = clone $this; 59 | $copy->input = $input; 60 | 61 | return $copy; 62 | } 63 | 64 | /** 65 | * @inheritDoc 66 | */ 67 | public function getInput() 68 | { 69 | return $this->input; 70 | } 71 | 72 | /** 73 | * @inheritDoc 74 | */ 75 | public function withOutput(array $output) 76 | { 77 | $copy = clone $this; 78 | $copy->output = $output; 79 | 80 | return $copy; 81 | } 82 | 83 | /** 84 | * @inheritDoc 85 | */ 86 | public function getOutput() 87 | { 88 | return $this->output; 89 | } 90 | 91 | /** 92 | * @inheritDoc 93 | */ 94 | public function withMessages(array $messages) 95 | { 96 | $copy = clone $this; 97 | $copy->messages = $messages; 98 | 99 | return $copy; 100 | } 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | public function getMessages() 106 | { 107 | return $this->messages; 108 | } 109 | 110 | /** 111 | * @inheritDoc 112 | */ 113 | public function withSetting($name, $value) 114 | { 115 | $copy = clone $this; 116 | $copy->settings[$name] = $value; 117 | 118 | return $copy; 119 | } 120 | 121 | /** 122 | * @inheritDoc 123 | */ 124 | public function withoutSetting($name) 125 | { 126 | $copy = clone $this; 127 | unset($copy->settings[$name]); 128 | 129 | return $copy; 130 | } 131 | 132 | /** 133 | * @inheritDoc 134 | */ 135 | public function getSetting($name) 136 | { 137 | if (isset($this->settings[$name])) { 138 | return $this->settings[$name]; 139 | } 140 | 141 | return null; 142 | } 143 | 144 | /** 145 | * @inheritDoc 146 | */ 147 | public function getSettings() 148 | { 149 | return $this->settings; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Resolver/AurynResolver.php: -------------------------------------------------------------------------------- 1 | injector = $injector; 18 | } 19 | 20 | /** 21 | * Resolve a class spec into an object, if it is not already instantiated. 22 | * 23 | * @param string|object $specOrObject 24 | * 25 | * @return object 26 | */ 27 | public function __invoke($specOrObject) 28 | { 29 | if (is_object($specOrObject)) { 30 | return $specOrObject; 31 | } 32 | 33 | return $this->injector->make($specOrObject); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Resolver/ResolverTrait.php: -------------------------------------------------------------------------------- 1 | resolver, $spec); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Responder/ChainedResponder.php: -------------------------------------------------------------------------------- 1 | resolver = $resolver; 31 | 32 | parent::__construct($responders); 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function __invoke( 39 | ServerRequestInterface $request, 40 | ResponseInterface $response, 41 | PayloadInterface $payload 42 | ) { 43 | foreach ($this as $responder) { 44 | $responder = $this->resolve($responder); 45 | $response = $responder($request, $response, $payload); 46 | } 47 | 48 | return $response; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | * 54 | * @throws ResponderException 55 | * If $classes does not implement the correct interface. 56 | */ 57 | protected function assertValid(array $classes) 58 | { 59 | parent::assertValid($classes); 60 | 61 | foreach ($classes as $responder) { 62 | if (!is_subclass_of($responder, ResponderInterface::class)) { 63 | throw ResponderException::invalidClass($responder); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Responder/FormattedResponder.php: -------------------------------------------------------------------------------- 1 | 1.0, 35 | ] 36 | ) { 37 | $this->negotiator = $negotiator; 38 | $this->resolver = $resolver; 39 | 40 | parent::__construct($formatters); 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | public function __invoke( 47 | ServerRequestInterface $request, 48 | ResponseInterface $response, 49 | PayloadInterface $payload 50 | ) { 51 | if ($this->hasOutput($payload)) { 52 | $response = $this->format($request, $response, $payload); 53 | } 54 | 55 | return $response; 56 | } 57 | 58 | /** 59 | * Determine if the payload has usable output 60 | * 61 | * @param PayloadInterface $payload 62 | * 63 | * @return boolean 64 | */ 65 | protected function hasOutput(PayloadInterface $payload) 66 | { 67 | return (bool) $payload->getOutput(); 68 | } 69 | 70 | /** 71 | * Retrieve a map of accepted priorities with the responsible formatter. 72 | * 73 | * @return array 74 | */ 75 | protected function priorities() 76 | { 77 | $priorities = []; 78 | 79 | foreach ($this as $formatter => $quality) { 80 | foreach ($formatter::accepts() as $type) { 81 | $priorities[$type] = $formatter; 82 | } 83 | } 84 | 85 | return $priorities; 86 | } 87 | 88 | /** 89 | * Retrieve the formatter to use for the current request. 90 | * 91 | * Uses content negotiation to find the best available output format for 92 | * the requested content type. 93 | * 94 | * @param ServerRequestInterface $request 95 | * 96 | * @return FormatterInterface 97 | */ 98 | protected function formatter(ServerRequestInterface $request) 99 | { 100 | $accept = $request->getHeaderLine('Accept'); 101 | $priorities = $this->priorities(); 102 | 103 | if (!empty($accept)) { 104 | $preferred = $this->negotiator->getBest($accept, array_keys($priorities)); 105 | } 106 | 107 | if (!empty($preferred)) { 108 | $formatter = $priorities[$preferred->getValue()]; 109 | } else { 110 | $formatter = array_shift($priorities); 111 | } 112 | 113 | return $this->resolve($formatter); 114 | } 115 | 116 | /** 117 | * Update the response by formatting the payload. 118 | * 119 | * @param ServerRequestInterface $request 120 | * @param ResponseInterface $response 121 | * @param PayloadInterface $payload 122 | * 123 | * @return ResponseInterface 124 | */ 125 | protected function format( 126 | ServerRequestInterface $request, 127 | ResponseInterface $response, 128 | PayloadInterface $payload 129 | ) { 130 | $formatter = $this->formatter($request); 131 | 132 | $response = $response->withHeader('Content-Type', $formatter->type()); 133 | // Overwrite the body instead of making a copy and dealing with the stream. 134 | $response->getBody()->write($formatter->body($payload)); 135 | 136 | return $response; 137 | } 138 | 139 | /** 140 | * @inheritDoc 141 | * 142 | * @throws FormatterException 143 | * If $classes does not implement the correct interface, 144 | * or does not have a quality value. 145 | */ 146 | protected function assertValid(array $classes) 147 | { 148 | parent::assertValid($classes); 149 | 150 | foreach ($classes as $formatter => $quality) { 151 | if (!is_subclass_of($formatter, FormatterInterface::class)) { 152 | throw FormatterException::invalidClass($formatter); 153 | } 154 | 155 | if (!is_float($quality)) { 156 | throw FormatterException::needsQuality($formatter); 157 | } 158 | } 159 | } 160 | 161 | /** 162 | * @inheritDoc 163 | */ 164 | protected function sortValues() 165 | { 166 | arsort($this->values); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/Responder/RedirectResponder.php: -------------------------------------------------------------------------------- 1 | getSetting('redirect'); 21 | 22 | if (!empty($location)) { 23 | $response = $response->withHeader('Location', $location); 24 | } 25 | 26 | return $response; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Responder/StatusResponder.php: -------------------------------------------------------------------------------- 1 | http_status = $http_status; 24 | } 25 | 26 | /** 27 | * @inheritDoc 28 | */ 29 | public function __invoke( 30 | ServerRequestInterface $request, 31 | ResponseInterface $response, 32 | PayloadInterface $payload 33 | ) { 34 | if ($this->hasStatus($payload)) { 35 | $response = $this->status($response, $payload); 36 | } 37 | 38 | return $response; 39 | } 40 | 41 | /** 42 | * Determine if the payload has a status. 43 | * 44 | * @param PayloadInterface $payload 45 | * 46 | * @return boolean 47 | */ 48 | private function hasStatus(PayloadInterface $payload) 49 | { 50 | return (bool) $payload->getStatus(); 51 | } 52 | 53 | /** 54 | * Get the response with the status code from the payload. 55 | * 56 | * @param ResponseInterface $response 57 | * @param PayloadInterface $payload 58 | * 59 | * @return ResponseInterface 60 | */ 61 | private function status( 62 | ResponseInterface $response, 63 | PayloadInterface $payload 64 | ) { 65 | $status = $payload->getStatus(); 66 | $code = $this->http_status->getStatusCode($status); 67 | 68 | return $response->withStatus($code); 69 | } 70 | } 71 | --------------------------------------------------------------------------------