├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src ├── Action │ ├── Action.php │ ├── Command.php │ └── Query.php ├── ActionMapper.php ├── Config.php ├── EnsureException.php ├── ErrorHandler.php ├── Exception │ ├── ExceptionRenderer.php │ └── ExceptionToJsonRenderer.php ├── Factory.php ├── Framework.php ├── JsonArray.php ├── JsonException.php ├── JsonObject.php ├── Monitoring │ ├── MonitoringLocator.php │ ├── NewRelicMonitoring.php │ ├── TransactionMonitoring.php │ ├── TransactionNameMapper.php │ └── VoidTransactionMonitoring.php ├── Request │ ├── Body │ │ ├── Body.php │ │ ├── BodyException.php │ │ ├── EmptyBody.php │ │ ├── FormDataBody.php │ │ ├── JsonBody.php │ │ ├── MissingContentTypeException.php │ │ ├── PdfBody.php │ │ ├── RawBody.php │ │ └── UnsupportedRequestBodyException.php │ ├── DeleteRequest.php │ ├── GetRequest.php │ ├── Header │ │ ├── Header.php │ │ ├── HeaderCollection.php │ │ └── HeaderException.php │ ├── Method │ │ ├── AbstractRequestMethod.php │ │ ├── DeleteRequestMethod.php │ │ ├── GetRequestMethod.php │ │ ├── OptionsRequestMethod.php │ │ ├── PatchRequestMethod.php │ │ ├── PostRequestMethod.php │ │ ├── PutRequestMethod.php │ │ ├── RequestMethod.php │ │ └── UnsupportedRequestMethodException.php │ ├── OptionsRequest.php │ ├── PatchRequest.php │ ├── Pattern.php │ ├── PostRequest.php │ ├── PutRequest.php │ ├── Request.php │ ├── RequestParameterException.php │ ├── UnauthorizedException.php │ ├── UploadedFile │ │ ├── UploadedFile.php │ │ ├── UploadedFilesCollection.php │ │ └── UploadedFilesException.php │ ├── Uri.php │ └── UriException.php ├── ResourceRequest │ ├── AbstractJsonResourceRequest.php │ ├── AbstractResourceRequest.php │ ├── BadRequestException.php │ └── ResourceRequest.php ├── Response │ ├── BadRequestResponse.php │ ├── Content │ │ ├── Content.php │ │ ├── ContentType.php │ │ ├── EncodeException.php │ │ ├── IcsContent.php │ │ ├── IcsContentType.php │ │ ├── JsonContent.php │ │ ├── JsonContentType.php │ │ ├── PdfContent.php │ │ ├── PdfContentType.php │ │ ├── PhpObjectContent.php │ │ ├── PlainContentType.php │ │ └── UnsupportedContentTypeException.php │ ├── ContentResponse.php │ ├── CreatedResponse.php │ ├── HttpHeader.php │ ├── MethodNotAllowedResponse.php │ ├── NoContentResponse.php │ ├── NotFoundResponse.php │ ├── OptionsResponse.php │ ├── Response.php │ ├── ServiceUnavailableResponse.php │ └── UnauthorizedResponse.php ├── RestResource │ ├── RestResource.php │ ├── SupportsDeleteRequests.php │ ├── SupportsGetRequests.php │ ├── SupportsPatchRequests.php │ ├── SupportsPostRequests.php │ └── SupportsPutRequests.php ├── Router │ ├── AbstractResourceRouter.php │ ├── Acl.php │ ├── AclRule.php │ ├── NoMoreRoutersException.php │ ├── ResourceRouter.php │ └── RouterChain.php └── Token.php └── tests ├── Unit ├── ActionMapperTest.php ├── ConfigTest.php ├── ErrorHandlerTest.php ├── FactoryTest.php ├── FrameworkTest.php ├── JsonObjectTest.php ├── Monitoring │ ├── MonitoringLocatorTest.php │ ├── NewRelicMonitoringTest.php │ ├── TransactionNameMapperTest.php │ └── VoidTransactionMonitoringTest.php ├── Request │ ├── Body │ │ ├── BodyTest.php │ │ └── fixtures │ │ │ ├── emptyBody.txt │ │ │ ├── jsonBody.txt │ │ │ └── rawBody.txt │ ├── DeleteRequestTest.php │ ├── GetRequestTest.php │ ├── Method │ │ ├── AbstractRequestMethodTest.php │ │ ├── DeleteRequestMethodTest.php │ │ ├── GetRequestMethodTest.php │ │ ├── OptionsRequestMethodTest.php │ │ ├── PatchRequestMethodTest.php │ │ ├── PostRequestMethodTest.php │ │ └── PutRequestMethodTest.php │ ├── PatchRequestTest.php │ ├── PatternTest.php │ ├── PostRequestTest.php │ ├── PutRequestTest.php │ ├── RequestTest.php │ └── UriTest.php ├── Response │ ├── BadRequestResponseTest.php │ ├── Content │ │ ├── ContentTypeTest.php │ │ ├── IcsContentTest.php │ │ ├── IcsContentTypeTest.php │ │ ├── JsonContentTest.php │ │ ├── JsonContentTypeTest.php │ │ └── PhpObjectContentTest.php │ ├── ContentResponseTest.php │ ├── CreatedResponseTest.php │ ├── HttpHeaderTest.php │ ├── MethodNotAllowedResponseTest.php │ ├── NoContentResponseTest.php │ ├── NotFoundResponseTest.php │ ├── OptionsResponseTest.php │ ├── ServiceUnavailableResponseTest.php │ └── UnauthorizedResponseTest.php ├── RestResource │ └── RestResourceTest.php ├── Router │ ├── AbstractResourceRouterTest.php │ └── RouterChainTest.php ├── Stubs │ ├── RestResourceStubSupportingAllMethods.php │ ├── RestResourceStubSupportingGetAndPost.php │ └── SomeRequestMethod.php └── TokenTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /build 3 | /vendor 4 | /phpunit.xml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | language: php 5 | 6 | before_install: 7 | - composer self-update 8 | 9 | install: 10 | - travis_retry composer install --no-interaction --prefer-source 11 | 12 | script: vendor/bin/phpunit 13 | 14 | php: 15 | - 7.1 16 | - 7.2 17 | 18 | notifications: 19 | email: false 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 4 | 5 | ## [3.0.0] - 2017-07-27 6 | 7 | ### Added 8 | 9 | * add transaction monitoring through NewRelic 10 | * instantiation has been changed: `Framework::createInstance(Config $config)` 11 | 12 | ## [2.1.6] - 2017-11-30 13 | 14 | ### Added 15 | 16 | * introduced `ServiceUnavailableResponse` 17 | 18 | ## [2.1.5] - 2017-11-16 19 | 20 | ### Added 21 | 22 | * introduced `Request::hasBody()` 23 | 24 | ## [2.1.1] - 2017-01-26 25 | 26 | ### Changed 27 | 28 | * `ErrorHandler::register()` now accepts an optional `ExceptionRender`. If no renderer is provided, `JsonExceptionRenderer` will be used to retain backwards compatibility. 29 | 30 | ### Added 31 | 32 | * introduced `JsonBody::has()` 33 | 34 | ## [2.1.0] - 2017-01-19 35 | 36 | ### Changed 37 | 38 | * Allow body in `DELETE` requests 39 | 40 | ## [2.0.0] - 2017-01-17 41 | 42 | ### Changed 43 | 44 | * Classes extending `RestResource` now need to implement the `getUriPattern()` method 45 | 46 | ### Added 47 | 48 | * `PhpObjectContent` has been added to allow sending a response containing a serialised PHP object 49 | 50 | ## [1.0.0] - 2016-12-22 51 | 52 | Initial Release 53 | 54 | ## [Unreleased] 55 | 56 | [Unreleased]: https://github.com/kartenmacherei/rest-framework/compare/3.0.0...HEAD 57 | [1.0.0]: https://github.com/kartenmacherei/rest-framework/releases/tag/1.0.0 58 | [2.0.0]: https://github.com/kartenmacherei/rest-framework/releases/tag/2.0.0 59 | [2.1.0]: https://github.com/kartenmacherei/rest-framework/releases/tag/2.1.0 60 | [2.1.1]: https://github.com/kartenmacherei/rest-framework/releases/tag/2.1.1 61 | [2.1.5]: https://github.com/kartenmacherei/rest-framework/releases/tag/2.1.5 62 | [3.0.0]: https://github.com/kartenmacherei/rest-framework/releases/tag/3.0.0 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 die kartenmacherei GmbH 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 | # RESTful Server Framework 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/kartenmacherei/rest-framework/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/kartenmacherei/rest-framework/?branch=master) 4 | [![Build Status](https://travis-ci.org/kartenmacherei/rest-framework.svg?branch=master)](https://travis-ci.org/kartenmacherei/rest-framework) 5 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/3eb072a0-0f58-4d39-a91d-71662adbdcd7/mini.png)](https://insight.sensiolabs.com/projects/3eb072a0-0f58-4d39-a91d-71662adbdcd7) 6 | 7 | The goal of this framework is to enable us to quickly bootstrap new RESTful Services while sticking to our very strict coding guidelines. This especially means that any kind of magic should be avoided. 8 | 9 | ## Basic Concepts 10 | 11 | ## Components 12 | 13 | ### RestResource 14 | 15 | - Provides a `Pattern` that can be matches against an ```URI``` 16 | - Supports HTTP verbs by implementing interfaces like `SupportsGetRequests` 17 | - Returns `Action` objects through explicit methods like `getPostCommand()` or `getQuery()` 18 | 19 | ### ResourceRouter 20 | 21 | - Holds references to all ```RestResource``` objects it is responsible for 22 | - Determines if it is responsible for routing a given URL in `canRoute()` 23 | - Returns a `RestResource` in `doRoute()` 24 | 25 | ### Command 26 | 27 | - Changes the state of a resource (like creating or updating) 28 | 29 | ### Query 30 | 31 | - Does not change the state of a resource and only returns existing data. 32 | 33 | ## Using the Framework 34 | 35 | ### Requirements 36 | 37 | - Composer 38 | - PHP 7.0+ 39 | 40 | ### Add the Framework to your composer.json: 41 | ``` 42 | "require": { 43 | "kartenmacherei/rest-framework": "dev-master" 44 | } 45 | ``` 46 | 47 | ### Connect your code to the Framework: 48 | ```php 49 | // create a request 50 | $request = Request::fromSuperGlobals(); 51 | 52 | // create config object 53 | // 'app-name' will be used as newrelic appname, if monitoring was enabled 54 | // bool $enableMonitoring if true, framework will set newrelic transaction name based on mapping 55 | // array $transactionMapping, class name to transaction name mapping array for each action. If action was not set, fallback is transaction_name_was_not_set 56 | $config = new Config('app-name', $enableMonitoring, $transactionNamesMapping); 57 | 58 | // create a new instance of the framework 59 | $framework = Framework::createInstance($config); 60 | 61 | // register a RestResource Router 62 | $framework->registerResourceRouter(new BasketResourceRouter()); 63 | 64 | // let the framework process the request 65 | $response = $framework->run($request); 66 | 67 | // send the response to the client 68 | $response->flush(); 69 | ``` 70 | 71 | See https://github.com/kartenmacherei/rest-framework-example for a working example. 72 | 73 | ## License 74 | 75 | This software is licensed under the terms of the [MIT license](https://opensource.org/licenses/MIT). See LICENSE.md for the full license. 76 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kartenmacherei/rest-framework", 3 | "description": "Micro framework for creating RESTful webservices", 4 | "type": "library", 5 | "keywords": ["framework", "rest", "restful", "kartenmacherei"], 6 | "homepage": "https://github.com/kartenmacherei/rest-framework", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Sebastian Heuer", 11 | "email": "sebastian.heuer@kartenmacherei.de" 12 | }, 13 | { 14 | "name": "Lee Wilkins", 15 | "email": "lee.wilkins@kartenmacherei.de" 16 | }, 17 | { 18 | "name": "Victor Sanchez", 19 | "email": "victor.sanchez@kartenmacherei.de" 20 | } 21 | ], 22 | "autoload": { 23 | "psr-4": { 24 | "Kartenmacherei\\RestFramework\\": "src" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Kartenmacherei\\RestFramework\\UnitTests\\": "tests/Unit" 30 | } 31 | }, 32 | "require": { 33 | "php" : ">=7.1.0", 34 | "kartenmacherei/kam-newrelic": "^1.0" 35 | }, 36 | "require-dev": { 37 | "phpunit/phpunit": "^6.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | tests/Unit 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | src 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Action/Action.php: -------------------------------------------------------------------------------- 1 | supports($request->getMethod())) { 35 | throw new UnsupportedRequestMethodException(); 36 | } 37 | 38 | /** @var RestResource|SupportsDeleteRequests|SupportsGetRequests|SupportsPatchRequests|SupportsPostRequests|SupportsPutRequests $resource */ 39 | switch ($request->getMethod()) { 40 | case new DeleteRequestMethod(): 41 | /** @var DeleteRequest $request */ 42 | return $resource->getDeleteCommand($request); 43 | case new GetRequestMethod(): 44 | /** @var GetRequest $request */ 45 | return $resource->getQuery($request); 46 | case new PatchRequestMethod(): 47 | /** @var PatchRequest $request */ 48 | return $resource->getPatchCommand($request); 49 | case new PostRequestMethod(): 50 | /** @var PostRequest $request */ 51 | return $resource->getPostCommand($request); 52 | case new PutRequestMethod(): 53 | /** @var PutRequest $request */ 54 | return $resource->getPutCommand($request); 55 | } 56 | throw new UnsupportedRequestMethodException(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | applicationName = $applicationName; 34 | $this->isTransactionMonitoringEnabled = $isTransactionMonitoringEnabled; 35 | $this->transactionNamesMapping = $transactionNamesMapping; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getApplicationName(): string 42 | { 43 | return $this->applicationName; 44 | } 45 | 46 | /** 47 | * @return bool 48 | */ 49 | public function isTransactionMonitoringEnabled(): bool 50 | { 51 | return $this->isTransactionMonitoringEnabled; 52 | } 53 | 54 | public function getTransactionMapping(): array 55 | { 56 | return $this->transactionNamesMapping; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/EnsureException.php: -------------------------------------------------------------------------------- 1 | exceptionRenderer = $exceptionRenderer; 21 | } 22 | 23 | /** 24 | * @codeCoverageIgnore 25 | * 26 | * @param ExceptionRenderer $exceptionRenderer 27 | */ 28 | public static function register(ExceptionRenderer $exceptionRenderer = null) 29 | { 30 | if (is_null($exceptionRenderer)) { 31 | $exceptionRenderer = new ExceptionToJsonRenderer(); 32 | } 33 | 34 | $self = new self($exceptionRenderer); 35 | set_exception_handler([$self, 'handleException']); 36 | set_error_handler([$self, 'handleError']); 37 | } 38 | 39 | /** 40 | * @param int $errno 41 | * @param string $errstr 42 | * @param string $errfile 43 | * @param int $errline 44 | * @throws \ErrorException 45 | */ 46 | public function handleError($errno, $errstr, $errfile = '', $errline = 0) 47 | { 48 | throw new \ErrorException($errstr, $errno,1, $errfile, $errline); 49 | } 50 | 51 | /** 52 | * @param Throwable $throwable 53 | */ 54 | public function handleException(Throwable $throwable) 55 | { 56 | http_response_code(500); 57 | header('Content-Type: application/json'); 58 | print($this->exceptionRenderer->render($throwable)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Exception/ExceptionRenderer.php: -------------------------------------------------------------------------------- 1 | toJson($throwable); 14 | } 15 | 16 | /** 17 | * @param \Throwable $throwable 18 | * @return string 19 | */ 20 | private function toJson(\Throwable $throwable): string 21 | { 22 | return json_encode( 23 | [ 24 | 'class' => get_class($throwable), 25 | 'message' => $throwable->getMessage(), 26 | 'file' => $throwable->getFile(), 27 | 'line' => $throwable->getLine() 28 | ] 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | config = $config; 25 | } 26 | 27 | /** 28 | * @return RouterChain 29 | */ 30 | public function createRouterChain(): RouterChain 31 | { 32 | return new RouterChain(); 33 | } 34 | 35 | /** 36 | * @return ActionMapper 37 | */ 38 | public function createActionMapper(): ActionMapper 39 | { 40 | return new ActionMapper(); 41 | } 42 | 43 | public function createTransactionMonitoring(): TransactionMonitoring 44 | { 45 | return $this->createMonitoringLocator()->getTransactionMonitoring(); 46 | } 47 | 48 | /** 49 | * @codeCoverageIgnore 50 | */ 51 | public function createConcreteTransactionMonitoring(): TransactionMonitoring 52 | { 53 | $appName = $this->config->getApplicationName(); 54 | $newRelicAgent = $this->createNewRelicFactory()->createNewRelicAgent($appName); 55 | 56 | return new NewRelicMonitoring($newRelicAgent); 57 | } 58 | 59 | public function createVoidTransactionMonitoring(): TransactionMonitoring 60 | { 61 | return new VoidTransactionMonitoring(); 62 | } 63 | 64 | public function createTransactionNameMapper(): TransactionNameMapper 65 | { 66 | return new TransactionNameMapper($this->config->getTransactionMapping()); 67 | } 68 | 69 | private function createMonitoringLocator(): MonitoringLocator 70 | { 71 | return new MonitoringLocator($this->config->isTransactionMonitoringEnabled(), $this); 72 | } 73 | 74 | private function createNewRelicFactory(): NewRelicFactory 75 | { 76 | return new NewRelicFactory(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Framework.php: -------------------------------------------------------------------------------- 1 | routerChain = $routerChain; 54 | $this->actionMapper = $actionMapper; 55 | $this->transactionMonitoring = $transactionMonitoring; 56 | $this->transactionNameMapper = $transactionNameMapper; 57 | } 58 | 59 | /** 60 | * @param Config $config 61 | * @return Framework 62 | */ 63 | public static function createInstance(Config $config): Framework 64 | { 65 | $factory = new Factory($config); 66 | 67 | return new self( 68 | $factory->createRouterChain(), 69 | $factory->createActionMapper(), 70 | $factory->createTransactionMonitoring(), 71 | $factory->createTransactionNameMapper() 72 | ); 73 | } 74 | 75 | /** 76 | * @param ResourceRouter $router 77 | */ 78 | public function registerResourceRouter(ResourceRouter $router) 79 | { 80 | $this->routerChain->addRouter($router); 81 | } 82 | 83 | /** 84 | * @param Request $request 85 | * @return Response 86 | * @throws UnsupportedRequestMethodException 87 | */ 88 | public function run(Request $request): Response 89 | { 90 | try { 91 | $resource = $this->routerChain->route($request); 92 | if ($request->isOptionsRequest()) { 93 | return new OptionsResponse($resource->getSupportedMethods()); 94 | } 95 | 96 | $action = $this->actionMapper->getAction($request, $resource); 97 | $this->setTransactionName($action); 98 | 99 | return $action->execute(); 100 | } catch (NoMoreRoutersException $e) { 101 | return new NotFoundResponse(); 102 | } catch (UnauthorizedException $e) { 103 | return new UnauthorizedResponse(); 104 | } catch (BadRequestException $e) { 105 | return new BadRequestResponse($e); 106 | } 107 | } 108 | 109 | private function setTransactionName(Action $action) 110 | { 111 | $transactionName = $this->transactionNameMapper->getTransactionName(get_class($action)); 112 | $this->transactionMonitoring->nameTransaction($transactionName); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/JsonArray.php: -------------------------------------------------------------------------------- 1 | data = $data; 17 | } 18 | 19 | public function current(): mixed 20 | { 21 | $current = current($this->data); 22 | if (is_array($current)) { 23 | return new JsonArray($current); 24 | } 25 | if (is_object($current)) { 26 | return new JsonObject($current); 27 | } 28 | return $current; 29 | } 30 | 31 | public function next(): void 32 | { 33 | next($this->data); 34 | } 35 | 36 | public function key(): mixed 37 | { 38 | return key($this->data); 39 | } 40 | 41 | /** 42 | * @return bool 43 | */ 44 | public function valid():bool 45 | { 46 | return array_key_exists($this->key(), $this->data); 47 | } 48 | 49 | public function rewind(): void 50 | { 51 | reset($this->data); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/JsonException.php: -------------------------------------------------------------------------------- 1 | data = $data; 17 | } 18 | 19 | /** 20 | * @param string $selector 21 | * @return bool 22 | */ 23 | public function has(string $selector): bool 24 | { 25 | return property_exists($this->data, $selector); 26 | } 27 | 28 | /** 29 | * @param string $selector 30 | * @return JsonArray|JsonObject 31 | * @throws JsonException 32 | */ 33 | public function query(string $selector) 34 | { 35 | if (!$this->has($selector)) { 36 | throw new JsonException(sprintf('element %s not found', $selector)); 37 | } 38 | $value = $this->data->{$selector}; 39 | if (is_array($value)) { 40 | return new JsonArray($value); 41 | } elseif (is_object($value)) { 42 | return new JsonObject($value); 43 | } 44 | return $value; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Monitoring/MonitoringLocator.php: -------------------------------------------------------------------------------- 1 | isTransactionMonitoringEnabled = $isTransactionMonitoringEnabled; 27 | $this->factory = $factory; 28 | } 29 | 30 | public function getTransactionMonitoring(): TransactionMonitoring 31 | { 32 | if ($this->isTransactionMonitoringEnabled) { 33 | return $this->factory->createConcreteTransactionMonitoring(); 34 | } 35 | 36 | return $this->factory->createVoidTransactionMonitoring(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Monitoring/NewRelicMonitoring.php: -------------------------------------------------------------------------------- 1 | newRelic = $newRelic; 21 | } 22 | 23 | public function nameTransaction(string $transactionName) 24 | { 25 | $this->newRelic->nameTransaction($transactionName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Monitoring/TransactionMonitoring.php: -------------------------------------------------------------------------------- 1 | mapping = $mapping; 21 | } 22 | 23 | /** 24 | * @param string $className 25 | * @return string 26 | */ 27 | public function getTransactionName(string $className): string 28 | { 29 | if (!array_key_exists($className, $this->mapping)) { 30 | return self::FALLBACK_TRANSACTION_NAME; 31 | } 32 | 33 | return $this->mapping[$className]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Monitoring/VoidTransactionMonitoring.php: -------------------------------------------------------------------------------- 1 | data = $data; 17 | } 18 | 19 | /** 20 | * @param string $name 21 | * @return bool 22 | */ 23 | public function has(string $name): bool 24 | { 25 | return array_key_exists($name, $this->data); 26 | } 27 | 28 | /** 29 | * @param string $name 30 | * @return mixed 31 | * @throws BodyException 32 | */ 33 | public function get(string $name) 34 | { 35 | if (!$this->has($name)) { 36 | throw new BodyException(sprintf('Form field %s not found', $name)); 37 | } 38 | return $this->data[$name]; 39 | } 40 | 41 | /** 42 | * @return bool 43 | */ 44 | public function isJson(): bool 45 | { 46 | return false; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Request/Body/JsonBody.php: -------------------------------------------------------------------------------- 1 | json = $this->decode($jsonString); 25 | $this->jsonString = $jsonString; 26 | } 27 | 28 | /** 29 | * @return bool 30 | */ 31 | public function isJson(): bool 32 | { 33 | return true; 34 | } 35 | 36 | /** 37 | * @param string $selector 38 | * @return bool 39 | */ 40 | public function has(string $selector): bool 41 | { 42 | return $this->json->has($selector); 43 | } 44 | 45 | /** 46 | * @param string $selector 47 | * @return \Kartenmacherei\RestFramework\JsonArray|JsonObject 48 | */ 49 | public function query(string $selector) 50 | { 51 | return $this->json->query($selector); 52 | } 53 | 54 | /** 55 | * @return JsonObject 56 | */ 57 | public function getJson(): JsonObject 58 | { 59 | return $this->json; 60 | } 61 | 62 | /** 63 | * @return string 64 | */ 65 | public function getEncodedString(): string 66 | { 67 | return $this->jsonString; 68 | } 69 | 70 | /** 71 | * @param string $jsonString 72 | * @return JsonObject 73 | * @throws EnsureException 74 | */ 75 | private function decode(string $jsonString): JsonObject 76 | { 77 | $decoded = json_decode($jsonString, false); 78 | if (json_last_error() !== JSON_ERROR_NONE) { 79 | throw new EnsureException(sprintf('JSON body could not be decoded: %s', json_last_error_msg())); 80 | } 81 | return new JsonObject($decoded); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/Request/Body/MissingContentTypeException.php: -------------------------------------------------------------------------------- 1 | content = $content; 17 | } 18 | 19 | /** 20 | * @return bool 21 | */ 22 | public function isJson(): bool 23 | { 24 | return false; 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getContent(): string 31 | { 32 | return $this->content; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Request/Body/RawBody.php: -------------------------------------------------------------------------------- 1 | content = $content; 17 | } 18 | 19 | /** 20 | * @return string 21 | */ 22 | public function getContent(): string 23 | { 24 | return $this->content; 25 | } 26 | 27 | /** 28 | * @return bool 29 | */ 30 | public function isJson(): bool 31 | { 32 | return false; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Request/Body/UnsupportedRequestBodyException.php: -------------------------------------------------------------------------------- 1 | parameters = $parameters; 31 | $this->body = $body; 32 | } 33 | 34 | /** 35 | * @param $name 36 | * @return bool 37 | */ 38 | public function hasParameter($name): bool 39 | { 40 | return array_key_exists($name, $this->parameters); 41 | } 42 | 43 | /** 44 | * @param $name 45 | * @return mixed 46 | * @throws RequestParameterException 47 | */ 48 | public function getParameter($name) 49 | { 50 | if (!$this->hasParameter($name)) { 51 | throw new RequestParameterException(sprintf('Parameter %s not found')); 52 | } 53 | return $this->parameters[$name]; 54 | } 55 | 56 | /** 57 | * @return Body 58 | */ 59 | public function getBody(): Body 60 | { 61 | return $this->body; 62 | } 63 | 64 | /** 65 | * @return RequestMethod 66 | */ 67 | public function getMethod(): RequestMethod 68 | { 69 | return new DeleteRequestMethod(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Request/GetRequest.php: -------------------------------------------------------------------------------- 1 | parameters = $parameters; 24 | } 25 | 26 | /** 27 | * @param $name 28 | * @return bool 29 | */ 30 | public function hasParameter($name): bool 31 | { 32 | return array_key_exists($name, $this->parameters); 33 | } 34 | 35 | /** 36 | * @param $name 37 | * @return mixed 38 | * @throws RequestParameterException 39 | */ 40 | public function getParameter($name) 41 | { 42 | if (!$this->hasParameter($name)) { 43 | throw new RequestParameterException(sprintf('Parameter %s not found', $name)); 44 | } 45 | return $this->parameters[$name]; 46 | } 47 | 48 | /** 49 | * @return RequestMethod 50 | */ 51 | public function getMethod(): RequestMethod 52 | { 53 | return new GetRequestMethod(); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Request/Header/Header.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | $this->value = $value; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getName(): string 30 | { 31 | return $this->name; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getValue(): string 38 | { 39 | return $this->value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Request/Header/HeaderCollection.php: -------------------------------------------------------------------------------- 1 | headers = $headers; 17 | } 18 | 19 | /** 20 | * @return HeaderCollection 21 | */ 22 | public static function fromSuperGlobals(): HeaderCollection 23 | { 24 | $headers = []; 25 | 26 | foreach ($_SERVER as $name => $value) { 27 | if (strpos($name, 'HTTP_') === false) { 28 | continue; 29 | } 30 | $headers[$name] = new Header($name, $value); 31 | } 32 | 33 | return new self($headers); 34 | } 35 | 36 | /** 37 | * @param string $name 38 | * @return bool 39 | */ 40 | public function has(string $name) 41 | { 42 | return array_key_exists($name, $this->headers); 43 | } 44 | 45 | /** 46 | * @param string $name 47 | * @return Header 48 | * @throws HeaderException 49 | */ 50 | public function get(string $name) 51 | { 52 | if (!$this->has($name)) { 53 | throw new HeaderException(sprintf('Header %s not found', $name)); 54 | } 55 | return $this->headers[$name]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Request/Header/HeaderException.php: -------------------------------------------------------------------------------- 1 | value = str_replace(self::REGEX_DELIMITER, '\\' . self::REGEX_DELIMITER, $value); 19 | } 20 | 21 | public function asString(): string 22 | { 23 | return sprintf('%s%s%s', self::REGEX_DELIMITER, $this->value, self::REGEX_DELIMITER); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Request/PostRequest.php: -------------------------------------------------------------------------------- 1 | body = $body; 33 | $this->uploadedFiles = $uploadedFiles; 34 | } 35 | 36 | /** 37 | * @return Body 38 | */ 39 | public function getBody(): Body 40 | { 41 | return $this->body; 42 | } 43 | 44 | /** 45 | * @return UploadedFilesCollection 46 | */ 47 | public function getUploadedFiles(): UploadedFilesCollection 48 | { 49 | return $this->uploadedFiles; 50 | } 51 | 52 | /** 53 | * @return RequestMethod 54 | */ 55 | public function getMethod(): RequestMethod 56 | { 57 | return new PostRequestMethod(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/Request/PutRequest.php: -------------------------------------------------------------------------------- 1 | uri = $uri; 34 | $this->headers = $headers; 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function isOptionsRequest(): bool 41 | { 42 | return false; 43 | } 44 | 45 | /** 46 | * @return RequestMethod 47 | */ 48 | abstract public function getMethod(): RequestMethod; 49 | 50 | /** 51 | * @return Request 52 | * @throws UnsupportedRequestMethodException 53 | */ 54 | public static function fromSuperGlobals(): Request 55 | { 56 | $method = strtoupper($_SERVER['REQUEST_METHOD']); 57 | $uri = new Uri($_SERVER['REQUEST_URI']); 58 | 59 | $body = Body::fromSuperGlobals(); 60 | $headers = HeaderCollection::fromSuperGlobals(); 61 | $uploadedFiles = UploadedFilesCollection::fromSuperGlobals(); 62 | 63 | switch ($method) { 64 | case RequestMethod::OPTIONS: 65 | return new OptionsRequest($uri, $headers); 66 | case RequestMethod::DELETE: 67 | return new DeleteRequest($uri, $headers, $_GET, $body); 68 | case RequestMethod::GET: 69 | return new GetRequest($uri, $headers, $_GET); 70 | case RequestMethod::PATCH: 71 | return new PatchRequest($uri, $headers, $body, $uploadedFiles); 72 | case RequestMethod::POST: 73 | return new PostRequest($uri, $headers, $body, $uploadedFiles); 74 | case RequestMethod::PUT: 75 | return new PutRequest($uri, $headers, $body, $uploadedFiles); 76 | } 77 | 78 | throw new UnsupportedRequestMethodException(sprintf('Unsupported method %s', $method)); 79 | } 80 | 81 | /** 82 | * @return Uri 83 | */ 84 | public function getUri(): Uri 85 | { 86 | return $this->uri; 87 | } 88 | 89 | /** 90 | * @param string $name 91 | * @return bool 92 | */ 93 | public function hasHeader(string $name): bool 94 | { 95 | return $this->headers->has($name); 96 | } 97 | 98 | /** 99 | * @param string $name 100 | * @return Header 101 | */ 102 | public function getHeader(string $name): Header 103 | { 104 | return $this->headers->get($name); 105 | } 106 | 107 | /** 108 | * @return Token 109 | * @throws BadRequestException 110 | */ 111 | public function getAuthorizationToken(): Token 112 | { 113 | if (!$this->hasHeader(self::AUTHORIZATION_HEADER_NAME)) { 114 | throw new BadRequestException('Missing Authorization Header'); 115 | } 116 | $headerValue = $this->getHeader(self::AUTHORIZATION_HEADER_NAME)->getValue(); 117 | if (!preg_match('/^Bearer\s(.*)/', $headerValue, $matches)) { 118 | throw new BadRequestException('Invalid Authorization Header Value'); 119 | } 120 | return new Token($matches[1]); 121 | } 122 | 123 | /** 124 | * @return bool 125 | */ 126 | public function hasBody(): bool 127 | { 128 | return !$this instanceof GetRequest && !$this instanceof OptionsRequest; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Request/RequestParameterException.php: -------------------------------------------------------------------------------- 1 | originalFilename = $originalFilename; 35 | $this->mimeType = $mimeType; 36 | $this->size = $size; 37 | $this->temporaryFilename = $temporaryFilename; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getOriginalFilename(): string 44 | { 45 | return $this->originalFilename; 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getMimeType(): string 52 | { 53 | return $this->mimeType; 54 | } 55 | 56 | /** 57 | * @return int 58 | */ 59 | public function getSize(): int 60 | { 61 | return $this->size; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getTemporaryFilename(): string 68 | { 69 | return $this->temporaryFilename; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Request/UploadedFile/UploadedFilesCollection.php: -------------------------------------------------------------------------------- 1 | files = $files; 17 | } 18 | 19 | /** 20 | * @param string $name 21 | * @return bool 22 | */ 23 | public function hasFile(string $name): bool 24 | { 25 | return array_key_exists($name, $this->files); 26 | } 27 | 28 | /** 29 | * @param string $name 30 | * @return UploadedFile 31 | * @throws UploadedFilesException 32 | */ 33 | public function getFile(string $name): UploadedFile 34 | { 35 | if (!$this->hasFile($name)) { 36 | throw new UploadedFilesException(sprintf('Uploaded file %s not found', $name)); 37 | } 38 | return $this->files[$name]; 39 | } 40 | 41 | /** 42 | * @return UploadedFilesCollection 43 | */ 44 | public static function fromSuperGlobals(): UploadedFilesCollection 45 | { 46 | $files = []; 47 | foreach ($_FILES as $name => $data) { 48 | $files[$name] = new UploadedFile( 49 | $data['name'], 50 | $data['type'], 51 | $data['size'], 52 | $data['tmp_name'] 53 | ); 54 | } 55 | return new self($files); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Request/UploadedFile/UploadedFilesException.php: -------------------------------------------------------------------------------- 1 | value = rtrim($value, '/'); 16 | } 17 | 18 | /** 19 | * @return string 20 | */ 21 | public function asString(): string 22 | { 23 | return $this->value; 24 | } 25 | 26 | /** 27 | * @param Uri $uri 28 | * @return bool 29 | */ 30 | public function equals(Uri $uri): bool 31 | { 32 | $path = parse_url($this->asString(), PHP_URL_PATH); 33 | $otherPath = parse_url($uri->asString(), PHP_URL_PATH); 34 | return $path === $otherPath; 35 | } 36 | 37 | /** 38 | * @param Pattern $pattern 39 | * @return bool 40 | */ 41 | public function matches(Pattern $pattern): bool 42 | { 43 | return preg_match($pattern->asString(), $this->value) === 1; 44 | } 45 | 46 | /** 47 | * @param int $index 48 | * @return string 49 | * @throws EnsureException 50 | * @throws UriException 51 | */ 52 | public function getPathSegment(int $index): string 53 | { 54 | if ($index < 0) { 55 | throw new EnsureException('Index must not be negative'); 56 | } 57 | $path = parse_url($this->value, PHP_URL_PATH); 58 | $parts = explode('/', trim($path, '/')); 59 | if (count($parts) <= $index) { 60 | throw new UriException(sprintf('URI does not have %d segments', $index)); 61 | } 62 | return $parts[$index]; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Request/UriException.php: -------------------------------------------------------------------------------- 1 | body = $body; 19 | } 20 | 21 | /** 22 | * @param string $selector 23 | * @return \Kartenmacherei\RestFramework\JsonArray|\Kartenmacherei\RestFramework\JsonObject 24 | */ 25 | public function getFromJsonInBody(string $selector) 26 | { 27 | return $this->body->query($selector); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ResourceRequest/AbstractResourceRequest.php: -------------------------------------------------------------------------------- 1 | throwable = $throwable; 17 | } 18 | 19 | public function flush() 20 | { 21 | http_response_code(400); 22 | header('Content-Type: application/json'); 23 | print( 24 | json_encode( 25 | [ 26 | 'class' => get_class($this->throwable), 27 | 'message' => $this->throwable->getMessage(), 28 | 'file' => $this->throwable->getFile(), 29 | 'line' => $this->throwable->getLine() 30 | ] 31 | ) 32 | ); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Response/Content/Content.php: -------------------------------------------------------------------------------- 1 | value = $value; 18 | } 19 | 20 | /** 21 | * @return string 22 | */ 23 | public function asString(): string 24 | { 25 | return $this->value; 26 | } 27 | 28 | /** 29 | * @return ContentType 30 | */ 31 | public function getContentType(): ContentType 32 | { 33 | return new IcsContentType(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Response/Content/IcsContentType.php: -------------------------------------------------------------------------------- 1 | jsonString = $encodedData; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function asString(): string 30 | { 31 | return $this->jsonString; 32 | } 33 | 34 | /** 35 | * @return ContentType 36 | */ 37 | public function getContentType(): ContentType 38 | { 39 | return new JsonContentType(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Response/Content/JsonContentType.php: -------------------------------------------------------------------------------- 1 | value = $value; 17 | } 18 | 19 | /** 20 | * @return string 21 | */ 22 | public function asString(): string 23 | { 24 | return $this->value; 25 | } 26 | 27 | /** 28 | * @return ContentType 29 | */ 30 | public function getContentType(): ContentType 31 | { 32 | return new PdfContentType(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Response/Content/PdfContentType.php: -------------------------------------------------------------------------------- 1 | ensureIsObject($object); 19 | $this->object = $object; 20 | } 21 | 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function asString(): string 27 | { 28 | return serialize($this->object); 29 | } 30 | 31 | /** 32 | * @return ContentType 33 | */ 34 | public function getContentType(): ContentType 35 | { 36 | return new PlainContentType(); 37 | } 38 | 39 | /** 40 | * @param $value 41 | * @throws EnsureException 42 | */ 43 | private function ensureIsObject($value) 44 | { 45 | if (!is_object($value)) { 46 | throw new EnsureException('Value must be an object'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Response/Content/PlainContentType.php: -------------------------------------------------------------------------------- 1 | content = $content; 19 | } 20 | 21 | public function flush() 22 | { 23 | http_response_code($this->getResponseCode()); 24 | header((new HttpHeader('Content-Type', $this->content->getContentType()->asString()))->asString()); 25 | 26 | print($this->content->asString()); 27 | } 28 | 29 | /** 30 | * @return int 31 | */ 32 | protected function getResponseCode(): int 33 | { 34 | return 200; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Response/CreatedResponse.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | $this->value = $value; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function asString(): string 30 | { 31 | return sprintf('%s: %s', $this->name, $this->value); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Response/MethodNotAllowedResponse.php: -------------------------------------------------------------------------------- 1 | getResponseCode()); 9 | } 10 | 11 | /** 12 | * @return int 13 | */ 14 | protected function getResponseCode(): int 15 | { 16 | return 204; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Response/NotFoundResponse.php: -------------------------------------------------------------------------------- 1 | supportedMethods = $supportedMethods; 19 | } 20 | 21 | public function flush() 22 | { 23 | $methods = []; 24 | foreach ($this->supportedMethods as $requestMethod) { 25 | $methods[] = $requestMethod->asString(); 26 | } 27 | $headerValue = implode(',', $methods); 28 | 29 | http_response_code(200); 30 | header((new HttpHeader('Allow', $headerValue))->asString()); 31 | header((new HttpHeader('Access-Control-Allow-Methods', $headerValue))->asString()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Response/Response.php: -------------------------------------------------------------------------------- 1 | matches($this->getUriPattern()); 22 | } 23 | 24 | /** 25 | * @param RequestMethod $method 26 | * @return bool 27 | */ 28 | public function supports(RequestMethod $method): bool 29 | { 30 | return in_array($method, $this->getSupportedMethods()); 31 | } 32 | 33 | /** 34 | * @return RequestMethod[] 35 | */ 36 | public function getSupportedMethods(): array 37 | { 38 | $implementedInterfaces = class_implements($this); 39 | return array_values(array_intersect_key($this->getMethodMap(), $implementedInterfaces)); 40 | } 41 | 42 | /** 43 | * @return Pattern 44 | */ 45 | abstract public function getUriPattern(): Pattern; 46 | 47 | /** 48 | * @return array 49 | */ 50 | private function getMethodMap(): array 51 | { 52 | return [ 53 | SupportsDeleteRequests::class => new DeleteRequestMethod(), 54 | SupportsGetRequests::class => new GetRequestMethod(), 55 | SupportsPatchRequests::class => new PatchRequestMethod(), 56 | SupportsPostRequests::class => new PostRequestMethod(), 57 | SupportsPutRequests::class => new PutRequestMethod() 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/RestResource/SupportsDeleteRequests.php: -------------------------------------------------------------------------------- 1 | acl = $acl; 31 | } 32 | 33 | /** 34 | * @param RestResource $resource 35 | */ 36 | public function addResource(RestResource $resource) 37 | { 38 | $this->resources[] = $resource; 39 | } 40 | 41 | /** 42 | * @param Request $request 43 | * @return RestResource 44 | * @throws NoMoreRoutersException 45 | */ 46 | public function route(Request $request): RestResource 47 | { 48 | if ($this->canRoute($request)) { 49 | $this->protect($request); 50 | return $this->doRoute($request); 51 | } 52 | if (null !== $this->next) { 53 | return $this->next->route($request); 54 | } 55 | throw new NoMoreRoutersException(); 56 | } 57 | 58 | /** 59 | * @param Request $request 60 | * @throws UnauthorizedException 61 | */ 62 | private function protect(Request $request) 63 | { 64 | if (null === $this->acl || $request->isOptionsRequest()) { 65 | return; 66 | } 67 | 68 | if (!$this->acl->complies($request)) { 69 | throw new UnauthorizedException(); 70 | } 71 | } 72 | 73 | /** 74 | * @return RestResource[] 75 | */ 76 | protected function getResources() 77 | { 78 | return $this->resources; 79 | } 80 | 81 | /** 82 | * @param ResourceRouter $router 83 | */ 84 | public function setNext(ResourceRouter $router) 85 | { 86 | $this->next = $router; 87 | } 88 | 89 | /** 90 | * @param Request $request 91 | * @return bool 92 | */ 93 | abstract protected function canRoute(Request $request): bool; 94 | 95 | /** 96 | * @param Request $request 97 | * @return RestResource 98 | * @throws NoMoreRoutersException 99 | */ 100 | protected function doRoute(Request $request): RestResource 101 | { 102 | foreach ($this->getResources() as $resource) { 103 | if ($resource->isIdentifiedBy($request->getUri())) { 104 | return $resource; 105 | } 106 | } 107 | throw new NoMoreRoutersException(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Router/Acl.php: -------------------------------------------------------------------------------- 1 | rules[] = $rule; 19 | } 20 | 21 | /** 22 | * @param Request $request 23 | * @return bool 24 | */ 25 | public function complies(Request $request): bool 26 | { 27 | foreach ($this->rules as $rule) { 28 | if (!$rule->complies($request)) { 29 | return false; 30 | } 31 | } 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Router/AclRule.php: -------------------------------------------------------------------------------- 1 | first) { 26 | $this->first = $router; 27 | } 28 | if (null !== $this->last) { 29 | $this->last->setNext($router); 30 | } 31 | $this->last = $router; 32 | } 33 | 34 | /** 35 | * @param Request $request 36 | * @return RestResource 37 | * @throws NoMoreRoutersException 38 | */ 39 | public function route(Request $request): RestResource 40 | { 41 | if (null === $this->first) { 42 | throw new NoMoreRoutersException(); 43 | } 44 | return $this->first->route($request); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Token.php: -------------------------------------------------------------------------------- 1 | value = $value; 18 | } else { 19 | $this->setValue(); 20 | } 21 | } 22 | 23 | /** 24 | * 25 | */ 26 | private function setValue() 27 | { 28 | $this->value = bin2hex(random_bytes(16)); 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function asString(): string 35 | { 36 | return $this->value; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /tests/Unit/ActionMapperTest.php: -------------------------------------------------------------------------------- 1 | getRestResourceMock(); 39 | $action = $this->createMock($actionClassname); 40 | 41 | $resource->method('supports')->willReturn(true); 42 | $resource->expects($this->once())->method($expectedMethod)->willReturn($action); 43 | 44 | $request = $this->getRequestMock($requestMethod); 45 | $request->method('getMethod')->willReturn($requestMethod); 46 | 47 | $mapper = new ActionMapper(); 48 | $actualAction = $mapper->getAction($request, $resource); 49 | 50 | $this->assertSame($action, $actualAction); 51 | } 52 | 53 | /** 54 | * @param RequestMethod $requestMethod 55 | * 56 | * @return \PHPUnit_Framework_MockObject_MockObject|PatchRequest 57 | */ 58 | private function getRequestMock(RequestMethod $requestMethod) 59 | { 60 | switch (true) 61 | { 62 | case $requestMethod instanceof PatchRequestMethod: 63 | return $this->getPatchRequestMock(); 64 | case $requestMethod instanceof GetRequestMethod: 65 | return $this->getGetRequestMock(); 66 | case $requestMethod instanceof PutRequestMethod: 67 | return $this->getPutRequestMock(); 68 | case $requestMethod instanceof DeleteRequestMethod: 69 | return $this->getDeleteRequestMock(); 70 | case $requestMethod instanceof PostRequestMethod: 71 | return $this->getPostRequestMock(); 72 | } 73 | throw new \RuntimeException('Invalid Request Method'); 74 | } 75 | 76 | /** 77 | * @return array 78 | */ 79 | public static function mapTestDataProvider(): array 80 | { 81 | return [ 82 | [new DeleteRequestMethod(), 'getDeleteCommand', Command::class], 83 | [new GetRequestMethod(), 'getQuery', Query::class], 84 | [new PatchRequestMethod(), 'getPatchCommand', Command::class], 85 | [new PostRequestMethod(), 'getPostCommand', Command::class], 86 | [new PutRequestMethod(), 'getPutCommand', Command::class] 87 | ]; 88 | } 89 | 90 | /** 91 | * @dataProvider requestMethodProvider 92 | * 93 | * @param RequestMethod $requestMethod 94 | */ 95 | public function testThrowsExceptionIfMethodIsNotSupported(RequestMethod $requestMethod) 96 | { 97 | $resource = $this->getRestResourceMock(); 98 | $mapper = new ActionMapper(); 99 | 100 | $request = $this->getRequestMock($requestMethod); 101 | $request->method('getMethod')->willReturn($requestMethod); 102 | 103 | $this->expectException(UnsupportedRequestMethodException::class); 104 | 105 | $mapper->getAction($request, $resource); 106 | } 107 | 108 | public function testThrowsExceptionIfMethodIsUnknown() 109 | { 110 | $resource = $this->getRestResourceMock(); 111 | $mapper = new ActionMapper(); 112 | $this->expectException(UnsupportedRequestMethodException::class); 113 | 114 | $request = $this->getGetRequestMock(); 115 | $request->method('getMethod')->willReturn(new SomeRequestMethod()); 116 | 117 | 118 | $mapper->getAction($request, $resource); 119 | } 120 | 121 | /** 122 | * @return array 123 | */ 124 | public static function requestMethodProvider(): array 125 | { 126 | return [ 127 | [new DeleteRequestMethod()], 128 | [new GetRequestMethod()], 129 | [new PatchRequestMethod()], 130 | [new PostRequestMethod()], 131 | [new PutRequestMethod()] 132 | ]; 133 | } 134 | 135 | /** 136 | * @return \PHPUnit_Framework_MockObject_MockObject|RestResource 137 | */ 138 | private function getRestResourceMock() 139 | { 140 | return $this->createMock(RestResourceStubSupportingAllMethods::class); 141 | } 142 | 143 | /** 144 | * @return \PHPUnit_Framework_MockObject_MockObject|GetRequest 145 | */ 146 | private function getGetRequestMock() 147 | { 148 | return $this->createMock(GetRequest::class); 149 | } 150 | 151 | /** 152 | * @return \PHPUnit_Framework_MockObject_MockObject|DeleteRequest 153 | */ 154 | private function getDeleteRequestMock() 155 | { 156 | return $this->createMock(DeleteRequest::class); 157 | } 158 | 159 | /** 160 | * @return \PHPUnit_Framework_MockObject_MockObject|PostRequest 161 | */ 162 | private function getPostRequestMock() 163 | { 164 | return $this->createMock(PostRequest::class); 165 | } 166 | 167 | /** 168 | * @return \PHPUnit_Framework_MockObject_MockObject|PutRequest 169 | */ 170 | private function getPutRequestMock() 171 | { 172 | return $this->createMock(PutRequest::class); 173 | } 174 | 175 | /** 176 | * @return \PHPUnit_Framework_MockObject_MockObject|PatchRequest 177 | */ 178 | private function getPatchRequestMock() 179 | { 180 | return $this->createMock(PatchRequest::class); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /tests/Unit/ConfigTest.php: -------------------------------------------------------------------------------- 1 | 'bar', 'baz' => 'qux']; 16 | const MONITORING_ENABLED = true; 17 | 18 | public function testGetters() 19 | { 20 | $config = new Config( 21 | self::APPLICATION_NAME, 22 | self::MONITORING_ENABLED, 23 | self::TRANSACTION_MAPPING 24 | ); 25 | 26 | $this->assertSame(self::APPLICATION_NAME, $config->getApplicationName()); 27 | $this->assertSame(self::MONITORING_ENABLED, $config->isTransactionMonitoringEnabled()); 28 | $this->assertSame(self::TRANSACTION_MAPPING, $config->getTransactionMapping()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Unit/ErrorHandlerTest.php: -------------------------------------------------------------------------------- 1 | expectException(ErrorException::class); 18 | $this->expectExceptionMessage('Some Error'); 19 | $this->expectExceptionCode(E_NOTICE); 20 | 21 | $handler->handleError(E_NOTICE, 'Some Error', 'somefile.php', 23); 22 | } 23 | 24 | /** 25 | * @runInSeparateProcess 26 | */ 27 | public function testOutputsExceptionAsJson() 28 | { 29 | if (!extension_loaded('xdebug')) { 30 | $this->markTestSkipped('XDebug extension needed'); 31 | } 32 | 33 | $handler = new ErrorHandler(new ExceptionToJsonRenderer()); 34 | ob_start(); 35 | $handler->handleException(new ErrorException('Something went wrong', 0, 1, 'somefile.php', 23)); 36 | 37 | $actualOutput = ob_get_clean(); 38 | $expectedOutput = '{ 39 | "class": "ErrorException", "message": "Something went wrong", "file": "somefile.php", "line": 23 40 | }'; 41 | $this->assertJsonStringEqualsJsonString($expectedOutput, $actualOutput); 42 | 43 | $expectedHeaders = ['Content-Type: application/json']; 44 | $actualHeaders = xdebug_get_headers(); 45 | $this->assertEquals($expectedHeaders, $actualHeaders); 46 | 47 | $this->assertSame(500, http_response_code()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Unit/FactoryTest.php: -------------------------------------------------------------------------------- 1 | configMock = $this->getConfigMock(); 36 | 37 | $this->factory = new Factory($this->configMock); 38 | } 39 | 40 | public function testCreateRouterChain() 41 | { 42 | $this->assertInstanceOf(RouterChain::class, $this->factory->createRouterChain()); 43 | } 44 | 45 | public function testCreateActionMapper() 46 | { 47 | $this->assertInstanceOf(ActionMapper::class, $this->factory->createActionMapper()); 48 | } 49 | 50 | public function testCreateTransactionMonitoring() 51 | { 52 | $this->assertInstanceOf(TransactionMonitoring::class, $this->factory->createTransactionMonitoring()); 53 | } 54 | 55 | public function testCreateConcreteTransactionMonitoring() 56 | { 57 | $this->expectException(EnsureNotEmptyStringException::class); 58 | $this->expectExceptionMessage('Expected "application name" to not be empty'); 59 | 60 | $this->factory->createConcreteTransactionMonitoring(); 61 | } 62 | 63 | public function testCreateTransactionNameMapper() 64 | { 65 | $this->configMock->expects($this->once()) 66 | ->method('getTransactionMapping') 67 | ->willReturn([]); 68 | 69 | $this->assertInstanceOf(TransactionNameMapper::class, $this->factory->createTransactionNameMapper()); 70 | } 71 | 72 | /** 73 | * @return PHPUnit_Framework_MockObject_MockObject|Config 74 | */ 75 | private function getConfigMock() 76 | { 77 | return $this->createMock(Config::class); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Unit/FrameworkTest.php: -------------------------------------------------------------------------------- 1 | routerChainMock = $this->getRouterChainMock(); 68 | $this->actionMapperMock = $this->getActionMapperMock(); 69 | $this->transactionMonitoring = $this->getTransactionMonitoringMock(); 70 | $this->transactionNameMapperMock = $this->getTransactionNameMapperMock(); 71 | 72 | $this->framework = new Framework( 73 | $this->routerChainMock, 74 | $this->actionMapperMock, 75 | $this->transactionMonitoring, 76 | $this->transactionNameMapperMock 77 | ); 78 | } 79 | 80 | public function testCreateInstanceReturnsExpectedObject() 81 | { 82 | $this->assertInstanceOf(Framework::class, Framework::createInstance($this->getConfigMock())); 83 | } 84 | 85 | public function testReturnsNotFoundResponseWhenNoMoreRoutersExceptionIsThrown() 86 | { 87 | $this->routerChainMock->method('route')->willThrowException(new NoMoreRoutersException()); 88 | $this->transactionNameMapperMock->expects($this->never()) 89 | ->method('getTransactionName'); 90 | $this->transactionMonitoring->expects($this->never()) 91 | ->method('nameTransaction'); 92 | 93 | $this->assertInstanceOf(NotFoundResponse::class, $this->framework->run($this->getRequestMock())); 94 | } 95 | 96 | public function testReturnsUnauthorizedResponseWhenUnauthorizedExceptionIsThrown() 97 | { 98 | $this->routerChainMock->method('route')->willThrowException(new UnauthorizedException()); 99 | $this->transactionNameMapperMock->expects($this->never()) 100 | ->method('getTransactionName'); 101 | $this->transactionMonitoring->expects($this->never()) 102 | ->method('nameTransaction'); 103 | 104 | $this->assertInstanceOf(UnauthorizedResponse::class, $this->framework->run($this->getRequestMock())); 105 | } 106 | 107 | public function testReturnsBadRequestResponseIfBadRequestExceptionIsThrown() 108 | { 109 | $resource = $this->getRestResourceMock(); 110 | 111 | $this->routerChainMock->method('route')->willReturn($resource); 112 | $this->actionMapperMock->method('getAction')->willThrowException(new BadRequestException()); 113 | $this->transactionNameMapperMock->expects($this->never()) 114 | ->method('getTransactionName'); 115 | $this->transactionMonitoring->expects($this->never()) 116 | ->method('nameTransaction'); 117 | 118 | $this->assertInstanceOf(BadRequestResponse::class, $this->framework->run($this->getRequestMock())); 119 | } 120 | 121 | public function testRegisterResourceRouterAddsRouter() 122 | { 123 | $router = $this->getRouterMock(); 124 | 125 | $this->routerChainMock->expects($this->once())->method('addRouter')->with($this->identicalTo($router)); 126 | 127 | $this->framework->registerResourceRouter($router); 128 | } 129 | 130 | public function testReturnsOptionsResponse() 131 | { 132 | $supportedMethods = [new GetRequestMethod(), new PostRequestMethod()]; 133 | 134 | $resource = $this->getRestResourceMock(); 135 | $resource->method('getSupportedMethods')->willReturn($supportedMethods); 136 | $this->routerChainMock->method('route')->willReturn($resource); 137 | 138 | $request = $this->getRequestMock(); 139 | $request->method('isOptionsRequest')->willReturn(true); 140 | 141 | $this->transactionNameMapperMock->expects($this->never()) 142 | ->method('getTransactionName'); 143 | $this->transactionMonitoring->expects($this->never()) 144 | ->method('nameTransaction'); 145 | 146 | $expectedResponse = new OptionsResponse($supportedMethods); 147 | $this->assertEquals($expectedResponse, $this->framework->run($request)); 148 | } 149 | 150 | public function testReturnsExpectedResponseFromAction() 151 | { 152 | $mappedTransactionName = 'mapped-transaction-name'; 153 | $resource = $this->getRestResourceMock(); 154 | $response = $this->getResponseMock(); 155 | 156 | $action = $this->getActionMock(); 157 | $action->method('execute')->willReturn($response); 158 | 159 | $this->routerChainMock->method('route')->willReturn($resource); 160 | $this->actionMapperMock->method('getAction')->willReturn($action); 161 | 162 | $this->transactionNameMapperMock->expects($this->once()) 163 | ->method('getTransactionName') 164 | ->with(get_class($action)) 165 | ->willReturn($mappedTransactionName); 166 | $this->transactionMonitoring->expects($this->once()) 167 | ->method('nameTransaction') 168 | ->with($mappedTransactionName); 169 | 170 | $this->assertSame($response, $this->framework->run($this->getRequestMock())); 171 | } 172 | 173 | /** 174 | * @return PHPUnit_Framework_MockObject_MockObject|Action 175 | */ 176 | private function getActionMock() 177 | { 178 | return $this->createMock(Action::class); 179 | } 180 | 181 | /** 182 | * @return PHPUnit_Framework_MockObject_MockObject|RestResource 183 | */ 184 | private function getRestResourceMock() 185 | { 186 | return $this->createMock(RestResource::class); 187 | } 188 | 189 | /** 190 | * @return PHPUnit_Framework_MockObject_MockObject|ResourceRouter 191 | */ 192 | private function getRouterMock() 193 | { 194 | return $this->createMock(ResourceRouter::class); 195 | } 196 | 197 | /** 198 | * @return PHPUnit_Framework_MockObject_MockObject|Resource 199 | */ 200 | private function getResponseMock() 201 | { 202 | return $this->createMock(Response::class); 203 | } 204 | 205 | /** 206 | * @return PHPUnit_Framework_MockObject_MockObject|RouterChain 207 | */ 208 | private function getRouterChainMock() 209 | { 210 | return $this->createMock(RouterChain::class); 211 | } 212 | 213 | /** 214 | * @return PHPUnit_Framework_MockObject_MockObject|Request 215 | */ 216 | private function getRequestMock() 217 | { 218 | return $this->createMock(Request::class); 219 | } 220 | 221 | /** 222 | * @return PHPUnit_Framework_MockObject_MockObject|ActionMapper 223 | */ 224 | private function getActionMapperMock() 225 | { 226 | return $this->createMock(ActionMapper::class); 227 | } 228 | 229 | /** 230 | * @return PHPUnit_Framework_MockObject_MockObject|Config 231 | */ 232 | private function getConfigMock() 233 | { 234 | return $this->createMock(Config::class); 235 | } 236 | 237 | /** 238 | * @return PHPUnit_Framework_MockObject_MockObject|TransactionMonitoring 239 | */ 240 | private function getTransactionMonitoringMock() 241 | { 242 | return $this->createMock(TransactionMonitoring::class); 243 | } 244 | 245 | /** 246 | * @return PHPUnit_Framework_MockObject_MockObject|TransactionNameMapper 247 | */ 248 | private function getTransactionNameMapperMock() 249 | { 250 | return $this->createMock(TransactionNameMapper::class); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /tests/Unit/JsonObjectTest.php: -------------------------------------------------------------------------------- 1 | query('foo'); 26 | 27 | $this->assertEquals($expected, $actual); 28 | } 29 | 30 | public function testThrowsExceptionIfSelectedPropertyDoesNotExist() 31 | { 32 | $json = new JsonObject(json_decode('{"foo": "bar"}')); 33 | $this->expectException(JsonException::class); 34 | $json->query('baz'); 35 | } 36 | 37 | public function dataProvider() 38 | { 39 | $stdClass = new \stdClass(); 40 | $stdClass->bar = 'baz'; 41 | 42 | $stdClass2 = new \stdClass(); 43 | $stdClass2->bar = 'foobar'; 44 | 45 | return [ 46 | [ 47 | '{"foo": "bar"}', 'bar' 48 | ], 49 | [ 50 | '{"foo": {"bar": "baz"}}', new JsonObject($stdClass) 51 | ], 52 | [ 53 | '{"foo": [{"bar":"baz"}, {"bar": "foobar"}]}', 54 | new JsonArray( 55 | [ 56 | $stdClass, 57 | $stdClass2 58 | ] 59 | ) 60 | ] 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Unit/Monitoring/MonitoringLocatorTest.php: -------------------------------------------------------------------------------- 1 | createMock(NewRelicMonitoring::class); 24 | 25 | $factoryMock = $this->getFactoryMock(); 26 | $factoryMock->expects($this->once()) 27 | ->method('createConcreteTransactionMonitoring') 28 | ->willReturn($expected); 29 | 30 | $monitoringLocator = new MonitoringLocator(self::MONITORING_ENABLED, $factoryMock); 31 | 32 | $this->assertSame($expected, $monitoringLocator->getTransactionMonitoring()); 33 | } 34 | 35 | public function testGetTransactionMonitoringReturnsVoidTransactionMonitoring() 36 | { 37 | $expected = $this->createMock(VoidTransactionMonitoring::class); 38 | 39 | $factoryMock = $this->getFactoryMock(); 40 | $factoryMock->expects($this->once()) 41 | ->method('createVoidTransactionMonitoring') 42 | ->willReturn($expected); 43 | 44 | $monitoringLocator = new MonitoringLocator(self::MONITORING_DISABLED, $factoryMock); 45 | 46 | $this->assertSame($expected, $monitoringLocator->getTransactionMonitoring()); 47 | } 48 | 49 | /** 50 | * @return PHPUnit_Framework_MockObject_MockObject|Factory 51 | */ 52 | private function getFactoryMock() 53 | { 54 | return $this->createMock(Factory::class); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Unit/Monitoring/NewRelicMonitoringTest.php: -------------------------------------------------------------------------------- 1 | createMock(NewRelic::class); 22 | $newRelicMock->expects($this->once()) 23 | ->method('nameTransaction') 24 | ->with(self::TRANSACTION_NAME); 25 | 26 | $newRelicMonitoring = new NewRelicMonitoring($newRelicMock); 27 | $newRelicMonitoring->nameTransaction(self::TRANSACTION_NAME); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Unit/Monitoring/TransactionNameMapperTest.php: -------------------------------------------------------------------------------- 1 | 'bar', 16 | 'baz' => 'qux', 17 | ]; 18 | 19 | public function testGetTransactionName() 20 | { 21 | $transactionNameMapper = new TransactionNameMapper(self::MAPPING); 22 | 23 | $this->assertSame('bar', $transactionNameMapper->getTransactionName('foo')); 24 | $this->assertSame('qux', $transactionNameMapper->getTransactionName('baz')); 25 | $this->assertSame('transaction_name_was_not_set', $transactionNameMapper->getTransactionName('foobarbaz')); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Unit/Monitoring/VoidTransactionMonitoringTest.php: -------------------------------------------------------------------------------- 1 | assertNull($voidTransactionMonitoring->nameTransaction('foo')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/Request/Body/BodyTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(RawBody::class, Body::fromSuperGlobals(__DIR__ . '/fixtures/rawBody.txt')); 27 | } 28 | 29 | public function testCreatesRawBodyIfContentTypeIsMissing() 30 | { 31 | unset($_SERVER['CONTENT_TYPE']); 32 | $this->assertInstanceOf(RawBody::class, Body::fromSuperGlobals(__DIR__ . '/fixtures/rawBody.txt')); 33 | } 34 | 35 | public function testCreatesEmptyBodyIfInputStreamIsEmpty() 36 | { 37 | $_SERVER['CONTENT_TYPE'] = ''; 38 | $this->assertInstanceOf(EmptyBody::class, Body::fromSuperGlobals(__DIR__ . '/fixtures/emptyBody.txt')); 39 | } 40 | 41 | public function testCreatesJsonBody() 42 | { 43 | $_SERVER['CONTENT_TYPE'] = ContentType::JSON; 44 | $this->assertInstanceOf(JsonBody::class, Body::fromSuperGlobals(__DIR__ . '/fixtures/jsonBody.txt')); 45 | } 46 | 47 | public function testCreatesJsonBodyWithExtendedContentType() 48 | { 49 | $_SERVER['CONTENT_TYPE'] = ContentType::JSON. '; charset=utf-8'; 50 | $this->assertInstanceOf(JsonBody::class, Body::fromSuperGlobals(__DIR__ . '/fixtures/jsonBody.txt')); 51 | } 52 | 53 | public function testCreatesFormDataBody() 54 | { 55 | $_SERVER['CONTENT_TYPE'] = ContentType::MULTIPART_FORMDATA; 56 | $_POST = ['foo' => 'bar']; 57 | $this->assertInstanceOf(FormDataBody::class, Body::fromSuperGlobals()); 58 | } 59 | 60 | public function testThrowsExceptionIfContentTypeIsNotSupported() 61 | { 62 | $_SERVER['CONTENT_TYPE'] = 'foo'; 63 | $this->expectException(UnsupportedRequestBodyException::class); 64 | Body::fromSuperGlobals(__DIR__ . '/fixtures/rawBody.txt'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Unit/Request/Body/fixtures/emptyBody.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kartenmacherei/rest-framework/02786550f56e50102aa41c978c72748e61cd1680/tests/Unit/Request/Body/fixtures/emptyBody.txt -------------------------------------------------------------------------------- /tests/Unit/Request/Body/fixtures/jsonBody.txt: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar" 3 | } 4 | -------------------------------------------------------------------------------- /tests/Unit/Request/Body/fixtures/rawBody.txt: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | -------------------------------------------------------------------------------- /tests/Unit/Request/DeleteRequestTest.php: -------------------------------------------------------------------------------- 1 | getUriMock(), 21 | $this->getHeaderCollectionMock(), 22 | [], 23 | $this->getBodyMock() 24 | ); 25 | 26 | $this->assertTrue($request->hasBody()); 27 | } 28 | 29 | /** 30 | * @return PHPUnit_Framework_MockObject_MockObject|Body 31 | */ 32 | private function getBodyMock() 33 | { 34 | return $this->createMock(Body::class); 35 | } 36 | 37 | /** 38 | * @return PHPUnit_Framework_MockObject_MockObject|HeaderCollection 39 | */ 40 | private function getHeaderCollectionMock() 41 | { 42 | return $this->createMock(HeaderCollection::class); 43 | } 44 | 45 | /** 46 | * @return PHPUnit_Framework_MockObject_MockObject|Uri 47 | */ 48 | private function getUriMock() 49 | { 50 | return $this->createMock(Uri::class); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Unit/Request/GetRequestTest.php: -------------------------------------------------------------------------------- 1 | getUriMock(), $this->getHeaderCollectionMock(), []); 19 | $this->assertFalse($request->hasBody()); 20 | } 21 | 22 | /** 23 | * @return PHPUnit_Framework_MockObject_MockObject|HeaderCollection 24 | */ 25 | private function getHeaderCollectionMock() 26 | { 27 | return $this->createMock(HeaderCollection::class); 28 | } 29 | 30 | /** 31 | * @return PHPUnit_Framework_MockObject_MockObject|Uri 32 | */ 33 | private function getUriMock() 34 | { 35 | return $this->createMock(Uri::class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/AbstractRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | getAbstractRequestMethod(); 17 | 18 | $this->assertTrue($requestMethod->equals($this->getAbstractRequestMethod())); 19 | } 20 | 21 | public function testEqualsReturnsFalse() 22 | { 23 | $requestMethod = $this->getAbstractRequestMethod(); 24 | /** @var GetRequestMethod|PHPUnit_Framework_MockObject_MockObject $otherRequestMethod */ 25 | $otherRequestMethod = $this->createMock(GetRequestMethod::class); 26 | 27 | $this->assertFalse($requestMethod->equals($otherRequestMethod)); 28 | } 29 | 30 | public function testIsOptionsRequest() 31 | { 32 | $requestMethod = $this->getAbstractRequestMethod(); 33 | $this->assertFalse($requestMethod->isOptionsMethod()); 34 | } 35 | 36 | /** 37 | * @return PHPUnit_Framework_MockObject_MockObject|AbstractRequestMethod 38 | */ 39 | private function getAbstractRequestMethod() 40 | { 41 | return $this->getMockForAbstractClass(AbstractRequestMethod::class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/DeleteRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | assertSame(RequestMethod::DELETE, $requestMethod->asString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/GetRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | assertSame(RequestMethod::GET, $requestMethod->asString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/OptionsRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | assertSame(RequestMethod::OPTIONS, $requestMethod->asString()); 17 | } 18 | 19 | public function testIsOptionsMethod() 20 | { 21 | $method = new OptionsRequestMethod(); 22 | $this->assertTrue($method->isOptionsMethod()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/PatchRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | assertSame(RequestMethod::PATCH, $requestMethod->asString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/PostRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | assertSame(RequestMethod::POST, $requestMethod->asString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/Request/Method/PutRequestMethodTest.php: -------------------------------------------------------------------------------- 1 | assertSame(RequestMethod::PUT, $requestMethod->asString()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/Request/PatchRequestTest.php: -------------------------------------------------------------------------------- 1 | getUriMock(), 23 | $this->getHeaderCollectionMock(), 24 | $this->getBodyMock(), 25 | $this->getUploadedFilesCollectionMock() 26 | ); 27 | 28 | $this->assertTrue($request->hasBody()); 29 | } 30 | 31 | /** 32 | * @return PHPUnit_Framework_MockObject_MockObject|UploadedFilesCollection 33 | */ 34 | private function getUploadedFilesCollectionMock() 35 | { 36 | return $this->createMock(UploadedFilesCollection::class); 37 | } 38 | 39 | /** 40 | * @return PHPUnit_Framework_MockObject_MockObject|Body 41 | */ 42 | private function getBodyMock() 43 | { 44 | return $this->createMock(Body::class); 45 | } 46 | 47 | /** 48 | * @return PHPUnit_Framework_MockObject_MockObject|HeaderCollection 49 | */ 50 | private function getHeaderCollectionMock() 51 | { 52 | return $this->createMock(HeaderCollection::class); 53 | } 54 | 55 | /** 56 | * @return PHPUnit_Framework_MockObject_MockObject|Uri 57 | */ 58 | private function getUriMock() 59 | { 60 | return $this->createMock(Uri::class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Unit/Request/PatternTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expectedString, $pattern->asString()); 22 | } 23 | 24 | /** 25 | * @return array 26 | */ 27 | public static function patternValueProvider() 28 | { 29 | return [ 30 | ['foo/bar', '/foo\/bar/'], 31 | ['/baskets/\w+$', '/\/baskets\/\w+$/'] 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Unit/Request/PostRequestTest.php: -------------------------------------------------------------------------------- 1 | getUriMock(), 22 | $this->getHeaderCollectionMock(), 23 | $this->getBodyMock(), 24 | $this->getUploadedFilesCollectionMock() 25 | ); 26 | 27 | $this->assertTrue($request->hasBody()); 28 | } 29 | 30 | /** 31 | * @return PHPUnit_Framework_MockObject_MockObject|UploadedFilesCollection 32 | */ 33 | private function getUploadedFilesCollectionMock() 34 | { 35 | return $this->createMock(UploadedFilesCollection::class); 36 | } 37 | 38 | /** 39 | * @return PHPUnit_Framework_MockObject_MockObject|Body 40 | */ 41 | private function getBodyMock() 42 | { 43 | return $this->createMock(Body::class); 44 | } 45 | 46 | /** 47 | * @return PHPUnit_Framework_MockObject_MockObject|HeaderCollection 48 | */ 49 | private function getHeaderCollectionMock() 50 | { 51 | return $this->createMock(HeaderCollection::class); 52 | } 53 | 54 | /** 55 | * @return PHPUnit_Framework_MockObject_MockObject|Uri 56 | */ 57 | private function getUriMock() 58 | { 59 | return $this->createMock(Uri::class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Unit/Request/PutRequestTest.php: -------------------------------------------------------------------------------- 1 | getUriMock(), 23 | $this->getHeaderCollectionMock(), 24 | $this->getBodyMock(), 25 | $this->getUploadedFilesCollectionMock() 26 | ); 27 | 28 | $this->assertTrue($request->hasBody()); 29 | } 30 | 31 | /** 32 | * @return PHPUnit_Framework_MockObject_MockObject|UploadedFilesCollection 33 | */ 34 | private function getUploadedFilesCollectionMock() 35 | { 36 | return $this->createMock(UploadedFilesCollection::class); 37 | } 38 | 39 | /** 40 | * @return PHPUnit_Framework_MockObject_MockObject|Body 41 | */ 42 | private function getBodyMock() 43 | { 44 | return $this->createMock(Body::class); 45 | } 46 | 47 | /** 48 | * @return PHPUnit_Framework_MockObject_MockObject|HeaderCollection 49 | */ 50 | private function getHeaderCollectionMock() 51 | { 52 | return $this->createMock(HeaderCollection::class); 53 | } 54 | 55 | /** 56 | * @return PHPUnit_Framework_MockObject_MockObject|Uri 57 | */ 58 | private function getUriMock() 59 | { 60 | return $this->createMock(Uri::class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Unit/Request/RequestTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf($expectedClass, $request); 46 | } 47 | 48 | public function testFromSuperGlobalsThrowsExceptionWhenRequestMethodIsNotSupported() 49 | { 50 | $_SERVER['REQUEST_URI'] = '/foo'; 51 | $_SERVER['REQUEST_METHOD'] = 'foo'; 52 | 53 | $this->expectException(UnsupportedRequestMethodException::class); 54 | Request::fromSuperGlobals(); 55 | } 56 | 57 | public function testGetUri() 58 | { 59 | $uri = new Uri('/foo'); 60 | $request = $this->getAbstractRequest($uri); 61 | 62 | $this->assertSame($uri, $request->getUri()); 63 | } 64 | 65 | public function testProperlyDeterminesIfHeaderExists() 66 | { 67 | $uri = new Uri('/foo'); 68 | $request = $this->getAbstractRequest($uri); 69 | 70 | $this->assertTrue($request->hasHeader('TEST_HEADER')); 71 | } 72 | 73 | public function testProperlyDeterminesIfHeaderDoesNotExist() 74 | { 75 | $uri = new Uri('/foo'); 76 | $request = $this->getAbstractRequest($uri, false); 77 | 78 | $this->assertFalse($request->hasHeader('BAD_TEST_HEADER')); 79 | } 80 | 81 | public function testCanGetHeader() 82 | { 83 | $uri = new Uri('/foo'); 84 | $request = $this->getAbstractRequest($uri); 85 | 86 | $this->assertInstanceOf(Header::class, $request->getHeader('TEST_HEADER')); 87 | } 88 | 89 | public function testThrowsExceptionIfHeaderDoesNotExist() 90 | { 91 | $uri = new Uri('/foo'); 92 | $request = $this->getAbstractRequest($uri, false); 93 | 94 | $this->expectException(HeaderException::class); 95 | 96 | $request->getHeader('BAD_TEST_HEADER'); 97 | } 98 | 99 | /** 100 | * @return array 101 | */ 102 | public static function methodProvider() 103 | { 104 | return [ 105 | [RequestMethod::OPTIONS, OptionsRequest::class], 106 | [RequestMethod::GET, GetRequest::class], 107 | [RequestMethod::DELETE, DeleteRequest::class], 108 | [RequestMethod::PATCH, PatchRequest::class], 109 | [RequestMethod::POST, PostRequest::class], 110 | [RequestMethod::PUT, PutRequest::class] 111 | ]; 112 | } 113 | 114 | /** 115 | * @param Uri $uri 116 | * @param bool $hasHeader 117 | * 118 | * @return Request|PHPUnit_Framework_MockObject_MockObject 119 | */ 120 | private function getAbstractRequest(Uri $uri, bool $hasHeader = true) 121 | { 122 | return $this->getMockForAbstractClass(Request::class, [$uri, $this->getHeaderCollectionMock($hasHeader)]); 123 | } 124 | 125 | /** 126 | * @param bool $hasHeader 127 | * 128 | * @return HeaderCollection|PHPUnit_Framework_MockObject_MockObject 129 | */ 130 | private function getHeaderCollectionMock(bool $hasHeader = true) 131 | { 132 | $headerCollection = $this->createMock(HeaderCollection::class); 133 | $headerCollection->method('has')->willReturn($hasHeader); 134 | if ($hasHeader) { 135 | $headerCollection->method('get')->willReturn($this->createMock(Header::class)); 136 | } else { 137 | $headerCollection->method('get')->willThrowException(new HeaderException); 138 | } 139 | return $headerCollection; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/Unit/Request/UriTest.php: -------------------------------------------------------------------------------- 1 | assertSame('/foo/bar', $uri->asString()); 20 | } 21 | 22 | public function testGetPathSegment() 23 | { 24 | $uri = new Uri('/foo/bar/baz'); 25 | $this->assertSame('foo', $uri->getPathSegment(0)); 26 | $this->assertSame('bar', $uri->getPathSegment(1)); 27 | $this->assertSame('baz', $uri->getPathSegment(2)); 28 | } 29 | 30 | /** 31 | * @dataProvider uriComparisonProvider 32 | * 33 | * @param string $uriValue 34 | * @param string $otherUriValue 35 | * @param bool $expectedResult 36 | */ 37 | public function testEquals(string $uriValue, string $otherUriValue, bool $expectedResult) 38 | { 39 | $uri = new Uri($uriValue); 40 | $otherUri = new Uri($otherUriValue); 41 | 42 | $this->assertSame($expectedResult, $uri->equals($otherUri)); 43 | } 44 | 45 | public static function uriComparisonProvider() 46 | { 47 | return [ 48 | ['/foo', '/bar', false], 49 | ['/foo', '/foo', true], 50 | ['/foo?bar=baz', '/foo', true], 51 | ]; 52 | } 53 | 54 | public function testGetPathSegmentsThrowsExceptionIfIndexIsOutOfBounds() 55 | { 56 | $uri = new Uri('/foo/bar'); 57 | $this->expectException(UriException::class); 58 | $uri->getPathSegment(2); 59 | } 60 | 61 | public function testGetPathSegmentsThrowsExceptionIfIndexIsNegative() 62 | { 63 | $uri = new Uri('/foo/bar'); 64 | $this->expectException(EnsureException::class); 65 | $uri->getPathSegment(-1); 66 | } 67 | 68 | /** 69 | * @dataProvider patternProvider 70 | * 71 | * @param string $uriValue 72 | * @param string $patternValue 73 | * @param bool $expectedResult 74 | */ 75 | public function testMatches($uriValue, $patternValue, $expectedResult) 76 | { 77 | $uri = new Uri($uriValue); 78 | $pattern = $this->getPatternMock(); 79 | $pattern->method('asString')->willReturn($patternValue); 80 | 81 | $this->assertSame($expectedResult, $uri->matches($pattern)); 82 | } 83 | 84 | /** 85 | * @return array 86 | */ 87 | public static function patternProvider() 88 | { 89 | return [ 90 | ['/foo/bar', '/\/foo\/bar/', true], 91 | ['/foo/bar', '/\/foo\/baz/', false] 92 | ]; 93 | } 94 | 95 | /** 96 | * @return PHPUnit_Framework_MockObject_MockObject|Pattern 97 | */ 98 | private function getPatternMock() 99 | { 100 | return $this->createMock(Pattern::class); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /tests/Unit/Response/BadRequestResponseTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('XDebug extension needed'); 20 | } 21 | 22 | $throwable = new BadRequestException('Something went wrong'); 23 | $response = new BadRequestResponse($throwable); 24 | 25 | ob_start(); 26 | $response->flush(); 27 | $output = ob_get_clean(); 28 | 29 | $expectedHeaders = ['Content-Type: application/json']; 30 | $actualHeaders = xdebug_get_headers(); 31 | $this->assertEquals($expectedHeaders, $actualHeaders); 32 | 33 | $this->assertSame(400, http_response_code()); 34 | 35 | $actualBodyData = json_decode($output, true); 36 | $this->assertSame(BadRequestException::class, $actualBodyData['class']); 37 | $this->assertSame('Something went wrong', $actualBodyData['message']); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Unit/Response/Content/ContentTypeTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(JsonContentType::class, $actual); 20 | } 21 | 22 | public function testThrowsExceptionIfContentTypeIsNotSupported() 23 | { 24 | $this->expectException(UnsupportedContentTypeException::class); 25 | ContentType::fromString('foo'); 26 | } 27 | public function testReturnsIcsContentType() 28 | { 29 | $actual = ContentType::fromString(ContentType::ICS); 30 | $this->assertInstanceOf(IcsContentType::class, $actual); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tests/Unit/Response/Content/IcsContentTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(IcsContentType::class, (new IcsContent(''))->getContentType()); 14 | } 15 | 16 | public function testAsString() 17 | { 18 | $content = new IcsContent('Test'); 19 | $this->assertSame('Test', $content->asString()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Unit/Response/Content/IcsContentTypeTest.php: -------------------------------------------------------------------------------- 1 | assertSame('text/calendar; charset=utf-8', (new IcsContentType())->asString()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Unit/Response/Content/JsonContentTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(JsonContentType::class, (new JsonContent(''))->getContentType()); 17 | } 18 | 19 | public function testThrowsExceptionIfDataCannotBeEncodedToJson() 20 | { 21 | $this->expectException(EncodeException::class); 22 | new JsonContent(fopen('php://memory', 'r')); 23 | } 24 | 25 | /** 26 | * @dataProvider jsonDataProvider 27 | * 28 | * @param mixed $data 29 | * @param string $expectedJsonString 30 | */ 31 | public function testAsString($data, string $expectedJsonString) 32 | { 33 | $content = new JsonContent($data); 34 | $this->assertJsonStringEqualsJsonString($expectedJsonString, $content->asString()); 35 | } 36 | 37 | public static function jsonDataProvider() 38 | { 39 | return [ 40 | ['foo', '"foo"'], 41 | [[0,1,2], '[0,1,2]'], 42 | [['foo' => 1, 'bar' => 2], '{"foo":1,"bar":2}'], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Unit/Response/Content/JsonContentTypeTest.php: -------------------------------------------------------------------------------- 1 | assertSame('application/json; charset=UTF-8', (new JsonContentType())->asString()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Unit/Response/Content/PhpObjectContentTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(PlainContentType::class, $content->getContentType()); 19 | } 20 | 21 | public function testReturnsExpectedString() 22 | { 23 | $object = new stdClass(); 24 | $object->foo = "bar"; 25 | $object->bar = "foo"; 26 | 27 | $content = new PhpObjectContent($object); 28 | 29 | $expected = 'O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"foo";}'; 30 | $actual = $content->asString(); 31 | 32 | $this->assertSame($expected, $actual); 33 | } 34 | 35 | /** 36 | * @dataProvider invalidValueProvider 37 | * 38 | * @param mixed $invalidValue 39 | */ 40 | public function testThrowsExceptionIfUsedWithInvalidValue($invalidValue) 41 | { 42 | $this->expectException(EnsureException::class); 43 | new PhpObjectContent($invalidValue); 44 | } 45 | 46 | public static function invalidValueProvider() 47 | { 48 | return [ 49 | ['foo'], 50 | [12], 51 | [12.1], 52 | [['foo' => 'bar']] 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Unit/Response/ContentResponseTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('XDebug extension needed'); 17 | } 18 | $content = new JsonContent('Foo'); 19 | $response = new ContentResponse($content); 20 | 21 | ob_start(); 22 | $response->flush(); 23 | $output = ob_get_clean(); 24 | 25 | $expectedHeaders = ['Content-Type: application/json; charset=UTF-8']; 26 | $actualHeaders = xdebug_get_headers(); 27 | $this->assertEquals($expectedHeaders, $actualHeaders); 28 | 29 | $this->assertSame(200, http_response_code()); 30 | 31 | $this->assertEquals('"Foo"', $output); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Unit/Response/CreatedResponseTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('XDebug extension needed'); 20 | } 21 | $content = new JsonContent('Foo'); 22 | $response = new CreatedResponse($content); 23 | 24 | ob_start(); 25 | $response->flush(); 26 | $output = ob_get_clean(); 27 | 28 | $expectedHeaders = ['Content-Type: application/json; charset=UTF-8']; 29 | $actualHeaders = xdebug_get_headers(); 30 | $this->assertEquals($expectedHeaders, $actualHeaders); 31 | 32 | $this->assertSame(201, http_response_code()); 33 | 34 | $this->assertEquals('"Foo"', $output); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Unit/Response/HttpHeaderTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expectedString, $header->asString()); 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public static function headerDataProvider(): array 29 | { 30 | return [ 31 | ['Allow', 'GET,POST', 'Allow: GET,POST'], 32 | ['Foo', 'bar', 'Foo: bar'] 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Unit/Response/MethodNotAllowedResponseTest.php: -------------------------------------------------------------------------------- 1 | flush(); 15 | $this->assertSame(405, http_response_code()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Unit/Response/NoContentResponseTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('XDebug extension needed'); 16 | } 17 | $response = new NoContentResponse(); 18 | 19 | ob_start(); 20 | $response->flush(); 21 | $output = ob_get_clean(); 22 | 23 | $expectedHeaders = []; 24 | $actualHeaders = xdebug_get_headers(); 25 | $this->assertEquals($expectedHeaders, $actualHeaders); 26 | 27 | $this->assertSame(204, http_response_code()); 28 | $this->assertEmpty($output); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Unit/Response/NotFoundResponseTest.php: -------------------------------------------------------------------------------- 1 | flush(); 15 | $this->assertSame(404, http_response_code()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Unit/Response/OptionsResponseTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('XDebug extension needed'); 18 | } 19 | $supportedMethods = [new GetRequestMethod(), new PutRequestMethod()]; 20 | 21 | $response = new OptionsResponse($supportedMethods); 22 | 23 | $response->flush(); 24 | 25 | $expectedHeaders = [ 26 | 'Allow: GET,PUT', 27 | 'Access-Control-Allow-Methods: GET,PUT' 28 | ]; 29 | 30 | $actualHeaders = xdebug_get_headers(); 31 | $this->assertEquals($expectedHeaders, $actualHeaders); 32 | 33 | $this->assertSame(200, http_response_code()); 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Unit/Response/ServiceUnavailableResponseTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('XDebug extension needed'); 20 | } 21 | $content = new JsonContent('Foo'); 22 | $response = new ServiceUnavailableResponse($content); 23 | 24 | ob_start(); 25 | $response->flush(); 26 | $output = ob_get_clean(); 27 | 28 | $expectedHeaders = ['Content-Type: application/json; charset=UTF-8']; 29 | $actualHeaders = xdebug_get_headers(); 30 | $this->assertEquals($expectedHeaders, $actualHeaders); 31 | 32 | $this->assertSame(503, http_response_code()); 33 | 34 | $this->assertEquals('"Foo"', $output); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Unit/Response/UnauthorizedResponseTest.php: -------------------------------------------------------------------------------- 1 | flush(); 15 | $this->assertSame(401, http_response_code()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Unit/RestResource/RestResourceTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($resource->supports(new DeleteRequestMethod())); 23 | $this->assertTrue($resource->supports(new GetRequestMethod())); 24 | $this->assertTrue($resource->supports(new PatchRequestMethod())); 25 | $this->assertTrue($resource->supports(new PostRequestMethod())); 26 | $this->assertTrue($resource->supports(new PutRequestMethod())); 27 | 28 | $resource = new RestResourceStubSupportingGetAndPost(); 29 | $this->assertFalse($resource->supports(new DeleteRequestMethod())); 30 | $this->assertTrue($resource->supports(new GetRequestMethod())); 31 | $this->assertFalse($resource->supports(new PatchRequestMethod())); 32 | $this->assertTrue($resource->supports(new PostRequestMethod())); 33 | $this->assertFalse($resource->supports(new PutRequestMethod())); 34 | } 35 | 36 | public function testReturnsExpectedSupportedMethods() 37 | { 38 | $resource = new RestResourceStubSupportingAllMethods(); 39 | $expected = [ 40 | new DeleteRequestMethod(), 41 | new GetRequestMethod(), 42 | new PatchRequestMethod(), 43 | new PostRequestMethod(), 44 | new PutRequestMethod() 45 | ]; 46 | 47 | $this->assertEquals($expected, $resource->getSupportedMethods()); 48 | } 49 | 50 | public function testReturnsGetAndPostMethods() 51 | { 52 | $resource = new RestResourceStubSupportingGetAndPost(); 53 | $expected = [ 54 | new GetRequestMethod(), 55 | new PostRequestMethod() 56 | ]; 57 | 58 | $this->assertEquals($expected, $resource->getSupportedMethods()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Unit/Router/AbstractResourceRouterTest.php: -------------------------------------------------------------------------------- 1 | getAbstractRouter(); 21 | $this->expectException(NoMoreRoutersException::class); 22 | $router->route($this->getRequestMock()); 23 | } 24 | 25 | public function testReturnsResourceRequest() 26 | { 27 | $restResource = $this->getRestResourceMock(); 28 | $restResource->method('isIdentifiedBy')->willReturn(true); 29 | 30 | $router = $this->getAbstractRouter(); 31 | $router->addResource($restResource); 32 | $router->method('canRoute')->willReturn(true); 33 | 34 | $this->assertSame($restResource, $router->route($this->getRequestMock())); 35 | } 36 | 37 | public function testHandsRequestToNextRouterAndReturnsResult() 38 | { 39 | $restResource = $this->getRestResourceMock(); 40 | 41 | $request = $this->getRequestMock(); 42 | 43 | $nextRouter = $this->getRouterMock(); 44 | $nextRouter->expects($this->once()) 45 | ->method('route') 46 | ->with($request) 47 | ->willReturn($restResource); 48 | 49 | $router = $this->getAbstractRouter(); 50 | $router->method('canRoute')->willReturn(false); 51 | $router->setNext($nextRouter); 52 | 53 | $this->assertSame($restResource, $router->route($request)); 54 | } 55 | 56 | /** 57 | * @return PHPUnit_Framework_MockObject_MockObject|ResourceRouter 58 | */ 59 | private function getRouterMock() 60 | { 61 | return $this->createMock(ResourceRouter::class); 62 | } 63 | 64 | /** 65 | * @return PHPUnit_Framework_MockObject_MockObject|RestResource 66 | */ 67 | private function getRestResourceMock() 68 | { 69 | return $this->createMock(RestResource::class); 70 | } 71 | 72 | /** 73 | * @return PHPUnit_Framework_MockObject_MockObject|Request 74 | */ 75 | private function getRequestMock() 76 | { 77 | return $this->createMock(Request::class); 78 | } 79 | 80 | /** 81 | * @return PHPUnit_Framework_MockObject_MockObject|AbstractResourceRouter 82 | */ 83 | private function getAbstractRouter() 84 | { 85 | return $this->getMockForAbstractClass(AbstractResourceRouter::class); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Unit/Router/RouterChainTest.php: -------------------------------------------------------------------------------- 1 | expectException(NoMoreRoutersException::class); 21 | $chain->route($this->getRequestMock()); 22 | } 23 | 24 | public function testHandsRequestToFirstRouter() 25 | { 26 | $resource = $this->getRestResourceMock(); 27 | 28 | $request = $this->getRequestMock(); 29 | $router = $this->getRouterMock(); 30 | $router->expects($this->once()) 31 | ->method('route') 32 | ->with($request) 33 | ->willReturn($resource); 34 | 35 | $chain = new RouterChain(); 36 | $chain->addRouter($router); 37 | 38 | $this->assertSame($resource, $chain->route($request)); 39 | } 40 | 41 | public function testAddsRouterToPreviousRouterInChain() 42 | { 43 | $router2 = $this->getRouterMock(); 44 | $router2->expects($this->never())->method('setNext'); 45 | $router1 = $this->getRouterMock(); 46 | $router1->expects($this->once())->method('setNext')->with($router2); 47 | 48 | $chain = new RouterChain(); 49 | $chain->addRouter($router1); 50 | $chain->addRouter($router2); 51 | } 52 | 53 | /** 54 | * @return PHPUnit_Framework_MockObject_MockObject|ResourceRouter 55 | */ 56 | private function getRouterMock() 57 | { 58 | return $this->createMock(ResourceRouter::class); 59 | } 60 | 61 | /** 62 | * @return PHPUnit_Framework_MockObject_MockObject|Request 63 | */ 64 | private function getRequestMock() 65 | { 66 | return $this->createMock(Request::class); 67 | } 68 | 69 | /** 70 | * @return PHPUnit_Framework_MockObject_MockObject|RestResource 71 | */ 72 | private function getRestResourceMock() 73 | { 74 | return $this->createMock(RestResource::class); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Unit/Stubs/RestResourceStubSupportingAllMethods.php: -------------------------------------------------------------------------------- 1 | assertSame('foo', $token->asString()); 16 | } 17 | 18 | public function testCreatesRandomValue() 19 | { 20 | $token1 = new Token(); 21 | $this->assertSame(32, strlen($token1->asString())); 22 | 23 | $token2 = new Token(); 24 | $this->assertSame(32, strlen($token2->asString())); 25 | 26 | $this->assertNotEquals($token1->asString(), $token2->asString()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |