Repository: dingo/api Branch: master Commit: 42b6afa6e20a Files: 140 Total size: 390.2 KB Directory structure: gitextract_9d3mtabi/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── composer.json ├── config/ │ └── api.php ├── phpunit.xml.dist ├── readme.md ├── src/ │ ├── Auth/ │ │ ├── Auth.php │ │ └── Provider/ │ │ ├── Authorization.php │ │ ├── Basic.php │ │ └── JWT.php │ ├── Console/ │ │ └── Command/ │ │ ├── Cache.php │ │ ├── Docs.php │ │ └── Routes.php │ ├── Contract/ │ │ ├── Auth/ │ │ │ └── Provider.php │ │ ├── Debug/ │ │ │ ├── ExceptionHandler.php │ │ │ └── MessageBagErrors.php │ │ ├── Http/ │ │ │ ├── Parser.php │ │ │ ├── RateLimit/ │ │ │ │ ├── HasRateLimiter.php │ │ │ │ └── Throttle.php │ │ │ ├── Request.php │ │ │ └── Validator.php │ │ ├── Routing/ │ │ │ └── Adapter.php │ │ └── Transformer/ │ │ └── Adapter.php │ ├── Dispatcher.php │ ├── Event/ │ │ ├── RequestWasMatched.php │ │ ├── ResponseIsMorphing.php │ │ └── ResponseWasMorphed.php │ ├── Exception/ │ │ ├── DeleteResourceFailedException.php │ │ ├── Handler.php │ │ ├── InternalHttpException.php │ │ ├── RateLimitExceededException.php │ │ ├── ResourceException.php │ │ ├── StoreResourceFailedException.php │ │ ├── UnknownVersionException.php │ │ ├── UpdateResourceFailedException.php │ │ └── ValidationHttpException.php │ ├── Facade/ │ │ ├── API.php │ │ └── Route.php │ ├── Http/ │ │ ├── FormRequest.php │ │ ├── InternalRequest.php │ │ ├── Middleware/ │ │ │ ├── Auth.php │ │ │ ├── PrepareController.php │ │ │ ├── RateLimit.php │ │ │ └── Request.php │ │ ├── Parser/ │ │ │ └── Accept.php │ │ ├── RateLimit/ │ │ │ ├── Handler.php │ │ │ └── Throttle/ │ │ │ ├── Authenticated.php │ │ │ ├── Route.php │ │ │ ├── Throttle.php │ │ │ └── Unauthenticated.php │ │ ├── Request.php │ │ ├── RequestValidator.php │ │ ├── Response/ │ │ │ ├── Factory.php │ │ │ └── Format/ │ │ │ ├── Format.php │ │ │ ├── Json.php │ │ │ ├── JsonOptionalFormatting.php │ │ │ └── Jsonp.php │ │ ├── Response.php │ │ └── Validation/ │ │ ├── Accept.php │ │ ├── Domain.php │ │ └── Prefix.php │ ├── Provider/ │ │ ├── DingoServiceProvider.php │ │ ├── HttpServiceProvider.php │ │ ├── LaravelServiceProvider.php │ │ ├── LumenServiceProvider.php │ │ ├── RoutingServiceProvider.php │ │ └── ServiceProvider.php │ ├── Routing/ │ │ ├── Adapter/ │ │ │ ├── Laravel.php │ │ │ └── Lumen.php │ │ ├── Helpers.php │ │ ├── ResourceRegistrar.php │ │ ├── Route.php │ │ ├── RouteCollection.php │ │ ├── Router.php │ │ └── UrlGenerator.php │ ├── Transformer/ │ │ ├── Adapter/ │ │ │ └── Fractal.php │ │ ├── Binding.php │ │ └── Factory.php │ └── helpers.php └── tests/ ├── Auth/ │ ├── AuthTest.php │ └── Provider/ │ ├── AuthorizationTest.php │ ├── BasicTest.php │ └── JWTTest.php ├── BaseTestCase.php ├── ChecksLaravelVersionTrait.php ├── DispatcherTest.php ├── Exception/ │ └── HandlerTest.php ├── Http/ │ ├── Middleware/ │ │ ├── AuthTest.php │ │ ├── RateLimitTest.php │ │ └── RequestTest.php │ ├── Parser/ │ │ └── AcceptTest.php │ ├── RateLimit/ │ │ ├── HandlerTest.php │ │ └── Throttle/ │ │ ├── AuthenticatedTest.php │ │ └── UnauthenticatedTest.php │ ├── RequestValidatorTest.php │ ├── Response/ │ │ ├── FactoryTest.php │ │ └── Format/ │ │ ├── ExpectedPrettyPrintedJson/ │ │ │ ├── testMorphingArrayWithEightSpacesPrettyPrintIndent.json.php │ │ │ ├── testMorphingArrayWithFourSpacesPrettyPrintIndent.json.php │ │ │ ├── testMorphingArrayWithOneTabPrettyPrintIndent.json.php │ │ │ └── testMorphingArrayWithTwoSpacesPrettyPrintIndent.json.php │ │ ├── ExpectedPrettyPrintedJsonp/ │ │ │ ├── testMorphingArrayWithEightSpacesPrettyPrintIndent.jsonp.php │ │ │ ├── testMorphingArrayWithFourSpacesPrettyPrintIndent.jsonp.php │ │ │ ├── testMorphingArrayWithOneTabPrettyPrintIndent.jsonp.php │ │ │ └── testMorphingArrayWithTwoSpacesPrettyPrintIndent.jsonp.php │ │ ├── JsonTest.php │ │ └── JsonpTest.php │ ├── ResponseTest.php │ └── Validation/ │ ├── AcceptTest.php │ ├── DomainTest.php │ └── PrefixTest.php ├── Routing/ │ ├── Adapter/ │ │ ├── BaseAdapterTest.php │ │ ├── LaravelTest.php │ │ └── LumenTest.php │ ├── RouteTest.php │ └── RouterTest.php ├── Stubs/ │ ├── Application58Stub.php │ ├── Application6Stub.php │ ├── Application7Stub.php │ ├── Application8Stub.php │ ├── ApplicationStub.php │ ├── AuthorizationProviderStub.php │ ├── BasicThrottleStub.php │ ├── EloquentModelStub.php │ ├── HttpValidatorStub.php │ ├── MiddlewareStub.php │ ├── RoutingAdapterStub.php │ ├── RoutingControllerOtherStub.php │ ├── RoutingControllerStub.php │ ├── ThrottleStub.php │ ├── TransformerStub.php │ ├── UserStub.php │ └── UserTransformerStub.php └── Transformer/ ├── Adapter/ │ └── FractalTest.php └── FactoryTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 [*.yml] indent_style = space indent_size = 2 ================================================ FILE: .gitattributes ================================================ /tests export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore /.php_cs export-ignore /.travis.yml export-ignore /phpunit.xml.dist export-ignore /CHANGELOG.md export-ignore /CONTRIBUTING.md export-ignore /README.md export-ignore /.github/ ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ | Q | A | ----------------- | --- | Bug? | no|yes | New Feature? | no|yes | Framework | Laravel|Lumen | Framework version | 5.x.y | Package version | 1.x.y | PHP version | 5.x.y|7.x.y #### Actual Behaviour Describe the behaviour you're experiencing. Do not just copy and paste a random error message and expect help. #### Expected Behaviour Describe the behaviour you're expecting. #### Steps to Reproduce List all the steps needed to reproduce the issue you're having. Make sure to include code (affected models, configurations), any screenshots and/or other resources that may help us understand what's going on. #### Possible Solutions If you have any ideas on how to solve the issue, add them here, otherwise you can omit this part. ================================================ FILE: .gitignore ================================================ .php_cs.cache .idea composer.lock phpunit.xml storage vendor .php_cs ================================================ FILE: .styleci.yml ================================================ preset: laravel disabled: - alpha_ordered_imports enabled: - phpdoc_order - phpdoc_separation - unalign_double_arrow ================================================ FILE: .travis.yml ================================================ language: php sudo: false dist: trusty env: global: - setup=basic - xdebug=true cache: directories: - $HOME/.composer/cache matrix: include: - php: 7.2 env: xdebug=false - php: 7.3 env: xdebug=false - php: 7.4 env: xdebug=false before_install: - if [[ $xdebug = 'true' ]] ; then phpenv config-rm xdebug.ini; fi install: - if [[ $setup = 'basic' ]]; then travis_retry composer install --prefer-dist --no-interaction --no-suggest; fi - if [[ $setup = 'stable' ]]; then travis_retry composer update --prefer-dist --no-interaction --no-suggest --prefer-stable; fi - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --no-suggest --prefer-stable --prefer-lowest; fi script: - vendor/bin/phpunit ================================================ FILE: CONTRIBUTING.md ================================================ # CONTRIBUTING Contributions are welcome, and are accepted via pull requests. Please review these guidelines before submitting any pull requests. ## Guidelines * Please follow the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). * Ensure that the current tests pass, and if you've added something new, add the tests where relevant. * Remember that we follow [SemVer](http://semver.org). If you are changing the behaviour, or the public api, you may need to update the docs. * Send a coherent commit history, making sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash](http://git-scm.com/book/en/Git-Tools-Rewriting-History) them before submitting. * You may also need to [rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) to avoid merge conflicts. ## Running Tests You will need an install of [Composer](https://getcomposer.org) before continuing. First, install the dependencies: ```bash $ composer install ``` Then run phpunit: ```bash $ vendor/bin/phpunit ``` If the test suite passes on your local machine you should be good to go. When you make a pull request, the tests will automatically be run again by [Travis CI](https://travis-ci.org/) on multiple php versions and hhvm. ================================================ FILE: LICENSE ================================================ Copyright (c) 2014-2015, Jason Lewis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Dingo API nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: composer.json ================================================ { "name": "dingo/api", "description": "A RESTful API package for the Laravel and Lumen frameworks.", "keywords": [ "api", "dingo", "laravel", "restful" ], "license": "BSD-3-Clause", "authors": [{ "name": "Jason Lewis", "email": "jason.lewis1991@gmail.com" }], "require": { "php": "^7.2.5|^8.0", "dingo/blueprint": "^0.4", "illuminate/routing": "^7.0|^8.0", "illuminate/support": "^7.0|^8.0", "league/fractal": "^0.19" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2", "illuminate/auth": "^7.0|^8.0", "illuminate/cache": "^7.0|^8.0", "illuminate/console": "^7.0|^8.0", "illuminate/database": "^7.0|^8.0", "illuminate/events": "^7.0|^8.0", "illuminate/filesystem": "^7.0|^8.0", "illuminate/log": "^7.0|^8.0", "illuminate/pagination": "^7.0|^8.0", "laravel/lumen-framework": "^7.0|^8.0", "mockery/mockery": "~1.0", "phpunit/phpunit": "^8.5|^9.0", "squizlabs/php_codesniffer": "~2.0", "tymon/jwt-auth": "1.0.*" }, "suggest": { "tymon/jwt-auth": "Protect your API with JSON Web Tokens." }, "abandoned": "api-ecosystem-for-laravel/dingo-api", "autoload": { "psr-4": { "Dingo\\Api\\": "src/" }, "files": [ "src/helpers.php" ] }, "autoload-dev": { "psr-4": { "Dingo\\Api\\Tests\\": "tests/" } }, "extra": { "branch-alias": { "dev-master": "2.0-dev" }, "laravel": { "providers": [ "Dingo\\Api\\Provider\\LaravelServiceProvider" ], "aliases": { "API": "Dingo\\Api\\Facade\\API" } } }, "config": { "sort-packages": true }, "minimum-stability": "dev", "prefer-stable": true } ================================================ FILE: config/api.php ================================================ env('API_STANDARDS_TREE', 'x'), /* |-------------------------------------------------------------------------- | API Subtype |-------------------------------------------------------------------------- | | Your subtype will follow the standards tree you use when used in the | "Accept" header to negotiate the content type and version. | | For example: Accept: application/x.SUBTYPE.v1+json | */ 'subtype' => env('API_SUBTYPE', ''), /* |-------------------------------------------------------------------------- | Default API Version |-------------------------------------------------------------------------- | | This is the default version when strict mode is disabled and your API | is accessed via a web browser. It's also used as the default version | when generating your APIs documentation. | */ 'version' => env('API_VERSION', 'v1'), /* |-------------------------------------------------------------------------- | Default API Prefix |-------------------------------------------------------------------------- | | A default prefix to use for your API routes so you don't have to | specify it for each group. | */ 'prefix' => env('API_PREFIX', null), /* |-------------------------------------------------------------------------- | Default API Domain |-------------------------------------------------------------------------- | | A default domain to use for your API routes so you don't have to | specify it for each group. | */ 'domain' => env('API_DOMAIN', null), /* |-------------------------------------------------------------------------- | Name |-------------------------------------------------------------------------- | | When documenting your API using the API Blueprint syntax you can | configure a default name to avoid having to manually specify | one when using the command. | */ 'name' => env('API_NAME', null), /* |-------------------------------------------------------------------------- | Conditional Requests |-------------------------------------------------------------------------- | | Globally enable conditional requests so that an ETag header is added to | any successful response. Subsequent requests will perform a check and | will return a 304 Not Modified. This can also be enabled or disabled | on certain groups or routes. | */ 'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true), /* |-------------------------------------------------------------------------- | Strict Mode |-------------------------------------------------------------------------- | | Enabling strict mode will require clients to send a valid Accept header | with every request. This also voids the default API version, meaning | your API will not be browsable via a web browser. | */ 'strict' => env('API_STRICT', false), /* |-------------------------------------------------------------------------- | Debug Mode |-------------------------------------------------------------------------- | | Enabling debug mode will result in error responses caused by thrown | exceptions to have a "debug" key that will be populated with | more detailed information on the exception. | */ 'debug' => env('API_DEBUG', false), /* |-------------------------------------------------------------------------- | Generic Error Format |-------------------------------------------------------------------------- | | When some HTTP exceptions are not caught and dealt with the API will | generate a generic error response in the format provided. Any | keys that aren't replaced with corresponding values will be | removed from the final response. | */ 'errorFormat' => [ 'message' => ':message', 'errors' => ':errors', 'code' => ':code', 'status_code' => ':status_code', 'debug' => ':debug', ], /* |-------------------------------------------------------------------------- | API Middleware |-------------------------------------------------------------------------- | | Middleware that will be applied globally to all API requests. | */ 'middleware' => [ ], /* |-------------------------------------------------------------------------- | Authentication Providers |-------------------------------------------------------------------------- | | The authentication providers that should be used when attempting to | authenticate an incoming API request. | */ 'auth' => [ ], /* |-------------------------------------------------------------------------- | Throttling / Rate Limiting |-------------------------------------------------------------------------- | | Consumers of your API can be limited to the amount of requests they can | make. You can create your own throttles or simply change the default | throttles. | */ 'throttling' => [ ], /* |-------------------------------------------------------------------------- | Response Transformer |-------------------------------------------------------------------------- | | Responses can be transformed so that they are easier to format. By | default a Fractal transformer will be used to transform any | responses prior to formatting. You can easily replace | this with your own transformer. | */ 'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class), /* |-------------------------------------------------------------------------- | Response Formats |-------------------------------------------------------------------------- | | Responses can be returned in multiple formats by registering different | response formatters. You can also customize an existing response | formatter with a number of options to configure its output. | */ 'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'), 'formats' => [ 'json' => Dingo\Api\Http\Response\Format\Json::class, ], 'formatsOptions' => [ 'json' => [ 'pretty_print' => env('API_JSON_FORMAT_PRETTY_PRINT_ENABLED', false), 'indent_style' => env('API_JSON_FORMAT_INDENT_STYLE', 'space'), 'indent_size' => env('API_JSON_FORMAT_INDENT_SIZE', 2), ], ], ]; ================================================ FILE: phpunit.xml.dist ================================================ ./tests ./tests/Routing/Adapter/LumenTest.php ./src ================================================ FILE: readme.md ================================================ # Move repositories notice Unfortunately this package cannot be maintained at this location anymore due to broken CI integrations, and travis-ci likely can't be used much longer either due to their change to paid plans. This project is still being actively maintained, we ask you to please switch to the following repository: https://github.com/api-ecosystem-for-laravel/dingo-api --- ![](https://cloud.githubusercontent.com/assets/829059/9216039/82be51cc-40f6-11e5-88f5-f0cbd07bcc39.png) The Dingo API package is meant to provide you, the developer, with a set of tools to help you easily and quickly build your own API. While the goal of this package is to remain as flexible as possible it still won't cover all situations and solve all problems. [![Build Status](https://img.shields.io/travis/dingo/api/master.svg?style=flat-square)](https://travis-ci.org/dingo/api) [![License](https://img.shields.io/packagist/l/dingo/api.svg?style=flat-square)](LICENSE) [![Development Version](https://img.shields.io/packagist/vpre/dingo/api.svg?style=flat-square)](https://packagist.org/packages/dingo/api) [![Monthly Installs](https://img.shields.io/packagist/dm/dingo/api.svg?style=flat-square)](https://packagist.org/packages/dingo/api) [![StyleCI](https://styleci.io/repos/18673522/shield)](https://styleci.io/repos/18673522) ## Features This package provides tools for the following, and more: - Content Negotiation - Multiple Authentication Adapters - API Versioning - Rate Limiting - Response Transformers and Formatters - Error and Exception Handling - Internal Requests - API Blueprint Documentation ## Documentation Please refer to our extensive [Wiki documentation](https://github.com/dingo/api/wiki) for more information. ## API Boilerplate If you are looking to start a new project from scratch, consider using the [Laravel API Boilerplate](https://github.com/specialtactics/laravel-api-boilerplate), which builds on top of the dingo-api package, and adds a lot of great features. ## Support For answers you may not find in the Wiki, avoid posting issues. Feel free to ask for support on the dedicated [Slack](https://larachat.slack.com/messages/api/) room. Make sure to mention **specialtactics** so he is notified. ## License This package is licensed under the [BSD 3-Clause license](http://opensource.org/licenses/BSD-3-Clause). ================================================ FILE: src/Auth/Auth.php ================================================ router = $router; $this->container = $container; $this->providers = $providers; } /** * Authenticate the current request. * * @param array $providers * * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException * * @return mixed */ public function authenticate(array $providers = []) { $exceptionStack = []; // Spin through each of the registered authentication providers and attempt to // authenticate through one of them. This allows a developer to implement // and allow a number of different authentication mechanisms. foreach ($this->filterProviders($providers) as $provider) { try { $user = $provider->authenticate($this->router->getCurrentRequest(), $this->router->getCurrentRoute()); $this->providerUsed = $provider; return $this->user = $user; } catch (UnauthorizedHttpException $exception) { $exceptionStack[] = $exception; } catch (BadRequestHttpException $exception) { // We won't add this exception to the stack as it's thrown when the provider // is unable to authenticate due to the correct authorization header not // being set. We will throw an exception for this below. } } $this->throwUnauthorizedException($exceptionStack); } /** * Throw the first exception from the exception stack. * * @param array $exceptionStack * * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException * * @return void */ protected function throwUnauthorizedException(array $exceptionStack) { $exception = array_shift($exceptionStack); if ($exception === null) { $exception = new UnauthorizedHttpException('dingo', 'Failed to authenticate because of bad credentials or an invalid authorization header.'); } throw $exception; } /** * Filter the requested providers from the available providers. * * @param array $providers * * @return array */ protected function filterProviders(array $providers) { if (empty($providers)) { return $this->providers; } return array_intersect_key($this->providers, array_flip($providers)); } /** * Get the authenticated user. * * @param bool $authenticate * * @return \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model|null */ public function getUser($authenticate = true) { if ($this->user) { return $this->user; } elseif (! $authenticate) { return; } try { return $this->user = $this->authenticate(); } catch (Exception $exception) { return; } } /** * Alias for getUser. * * @param bool $authenticate * * @return \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model */ public function user($authenticate = true) { return $this->getUser($authenticate); } /** * Set the authenticated user. * * @param \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model $user * * @return \Dingo\Api\Auth\Auth */ public function setUser($user) { $this->user = $user; return $this; } /** * Check if a user has authenticated with the API. * * @param bool $authenticate * * @return bool */ public function check($authenticate = false) { return ! is_null($this->user($authenticate)); } /** * Get the provider used for authentication. * * @return \Dingo\Api\Contract\Auth\Provider */ public function getProviderUsed() { return $this->providerUsed; } /** * Extend the authentication layer with a custom provider. * * @param string $key * @param object|callable $provider * * @return void */ public function extend($key, $provider) { if (is_callable($provider)) { $provider = call_user_func($provider, $this->container); } $this->providers[$key] = $provider; } } ================================================ FILE: src/Auth/Provider/Authorization.php ================================================ headers->get('authorization')), $this->getAuthorizationMethod())) { return true; } throw new BadRequestHttpException; } /** * Get the providers authorization method. * * @return string */ abstract public function getAuthorizationMethod(); } ================================================ FILE: src/Auth/Provider/Basic.php ================================================ auth = $auth; $this->identifier = $identifier; } /** * Authenticate request with Basic. * * @param \Illuminate\Http\Request $request * @param \Dingo\Api\Routing\Route $route * * @return mixed */ public function authenticate(Request $request, Route $route) { $this->validateAuthorizationHeader($request); if (($response = $this->auth->onceBasic($this->identifier)) && $response->getStatusCode() === 401) { throw new UnauthorizedHttpException('Basic', 'Invalid authentication credentials.'); } return $this->auth->user(); } /** * Get the providers authorization method. * * @return string */ public function getAuthorizationMethod() { return 'basic'; } } ================================================ FILE: src/Auth/Provider/JWT.php ================================================ auth = $auth; } /** * Authenticate request with a JWT. * * @param \Illuminate\Http\Request $request * @param \Dingo\Api\Routing\Route $route * * @return mixed */ public function authenticate(Request $request, Route $route) { $token = $this->getToken($request); try { if (! $user = $this->auth->setToken($token)->authenticate()) { throw new UnauthorizedHttpException('JWTAuth', 'Unable to authenticate with invalid token.'); } } catch (JWTException $exception) { throw new UnauthorizedHttpException('JWTAuth', $exception->getMessage(), $exception); } return $user; } /** * Get the JWT from the request. * * @param \Illuminate\Http\Request $request * * @throws \Exception * * @return string */ protected function getToken(Request $request) { try { $this->validateAuthorizationHeader($request); $token = $this->parseAuthorizationHeader($request); } catch (Exception $exception) { if (! $token = $request->query('token', false)) { throw $exception; } } return $token; } /** * Parse JWT from the authorization header. * * @param \Illuminate\Http\Request $request * * @return string */ protected function parseAuthorizationHeader(Request $request) { return trim(str_ireplace($this->getAuthorizationMethod(), '', $request->header('authorization'))); } /** * Get the providers authorization method. * * @return string */ public function getAuthorizationMethod() { return 'bearer'; } } ================================================ FILE: src/Console/Command/Cache.php ================================================ files = $files; $this->router = $router; $this->adapter = $adapter; parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { $this->callSilent('route:clear'); $app = $this->getFreshApplication(); $this->call('route:cache'); $routes = $app['api.router']->getAdapterRoutes(); foreach ($routes as $collection) { foreach ($collection as $route) { $app['api.router.adapter']->prepareRouteForSerialization($route); } } $stub = "app('api.router')->setAdapterRoutes(unserialize(base64_decode('{{routes}}')));"; $path = $this->laravel->getCachedRoutesPath(); if (! $this->files->exists($path)) { $stub = "files->append( $path, str_replace('{{routes}}', base64_encode(serialize($routes)), $stub) ); } /** * Get a fresh application instance. * * @return \Illuminate\Contracts\Container\Container */ protected function getFreshApplication() { if (method_exists($this->laravel, 'bootstrapPath')) { $app = require $this->laravel->bootstrapPath().'/app.php'; } else { $app = require $this->laravel->basePath().'/bootstrap/app.php'; } $app->make(Kernel::class)->bootstrap(); return $app; } } ================================================ FILE: src/Console/Command/Docs.php ================================================ router = $router; $this->blueprint = $blueprint; $this->writer = $writer; $this->name = $name; $this->version = $version; } /** * Execute the console command. * * @return mixed */ public function handle() { $contents = $this->blueprint->generate($this->getControllers(), $this->getDocName(), $this->getVersion(), $this->getIncludePath()); if ($file = $this->option('output-file')) { $this->writer->write($contents, $file); return $this->info('Documentation was generated successfully.'); } return $this->line($contents); } /** * Get the documentation name. * * @return string */ protected function getDocName() { $name = $this->option('name') ?: $this->name; if (! $name) { $this->comment('A name for the documentation was not supplied. Use the --name option or set a default in the configuration.'); exit; } return $name; } /** * Get the include path for documentation files. * * @return string */ protected function getIncludePath() { return base_path($this->option('include-path')); } /** * Get the documentation version. * * @return string */ protected function getVersion() { $version = $this->option('use-version') ?: $this->version; if (! $version) { $this->comment('A version for the documentation was not supplied. Use the --use-version option or set a default in the configuration.'); exit; } return $version; } /** * Get all the controller instances. * * @return array */ protected function getControllers() { $controllers = new Collection; if ($controller = $this->option('use-controller')) { $this->addControllerIfNotExists($controllers, app($controller)); return $controllers; } foreach ($this->router->getRoutes() as $collections) { foreach ($collections as $route) { if ($controller = $route->getControllerInstance()) { $this->addControllerIfNotExists($controllers, $controller); } } } return $controllers; } /** * Add a controller to the collection if it does not exist. If the * controller implements an interface suffixed with "Docs" it * will be used instead of the controller. * * @param \Illuminate\Support\Collection $controllers * @param object $controller * * @return void */ protected function addControllerIfNotExists(Collection $controllers, $controller) { $class = get_class($controller); if ($controllers->has($class)) { return; } $reflection = new ReflectionClass($controller); $interface = Arr::first($reflection->getInterfaces(), function ($key, $value) { return Str::endsWith($key, 'Docs'); }); if ($interface) { $controller = $interface; } $controllers->put($class, $controller); } } ================================================ FILE: src/Console/Command/Routes.php ================================================ router = $router; } /** * Execute the console command. * * @return void */ public function fire() { $this->routes = $this->router->getRoutes(); parent::fire(); } /** * Execute the console command. * * @return void */ public function handle() { $this->routes = $this->router->getRoutes(); parent::handle(); } /** * Compile the routes into a displayable format. * * @return array */ protected function getRoutes() { $routes = []; foreach ($this->router->getRoutes() as $collection) { foreach ($collection->getRoutes() as $route) { $routes[] = $this->filterRoute([ 'host' => $route->domain(), 'method' => implode('|', $route->methods()), 'uri' => $route->uri(), 'name' => $route->getName(), 'action' => $route->getActionName(), 'protected' => $route->isProtected() ? 'Yes' : 'No', 'versions' => implode(', ', $route->versions()), 'scopes' => implode(', ', $route->scopes()), 'rate' => $this->routeRateLimit($route), ]); } } if ($sort = $this->option('sort')) { $routes = Arr::sort($routes, function ($value) use ($sort) { return $value[$sort]; }); } if ($this->option('reverse')) { $routes = array_reverse($routes); } if ($this->option('short')) { $this->headers = ['Method', 'URI', 'Name', 'Version(s)']; $routes = array_map(function ($item) { return Arr::only($item, ['method', 'uri', 'name', 'versions']); }, $routes); } return array_filter(array_unique($routes, SORT_REGULAR)); } /** * Display the routes rate limiting requests per second. This takes the limit * and divides it by the expiration time in seconds to give you a rough * idea of how many requests you'd be able to fire off per second * on the route. * * @param \Dingo\Api\Routing\Route $route * * @return null|string */ protected function routeRateLimit($route) { [$limit, $expires] = [$route->getRateLimit(), $route->getRateLimitExpiration()]; if ($limit && $expires) { return sprintf('%s req/s', round($limit / ($expires * 60), 2)); } } /** * Filter the route by URI, Version, Scopes and / or name. * * @param array $route * * @return array|null */ protected function filterRoute(array $route) { $filters = ['name', 'path', 'protected', 'unprotected', 'versions', 'scopes']; foreach ($filters as $filter) { if ($this->option($filter) && ! $this->{'filterBy'.ucfirst($filter)}($route)) { return; } } return $route; } /** * Get the console command options. * * @return array */ protected function getOptions() { $options = parent::getOptions(); foreach ($options as $key => $option) { if ($option[0] == 'sort') { unset($options[$key]); } } return array_merge( $options, [ ['sort', null, InputOption::VALUE_OPTIONAL, 'The column (domain, method, uri, name, action) to sort by'], ['versions', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Filter the routes by version'], ['scopes', 'S', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Filter the routes by scopes'], ['protected', null, InputOption::VALUE_NONE, 'Filter the protected routes'], ['unprotected', null, InputOption::VALUE_NONE, 'Filter the unprotected routes'], ['short', null, InputOption::VALUE_NONE, 'Get an abridged version of the routes'], ] ); } /** * Filter the route by its path. * * @param array $route * * @return bool */ protected function filterByPath(array $route) { return Str::contains($route['uri'], $this->option('path')); } /** * Filter the route by whether or not it is protected. * * @param array $route * * @return bool */ protected function filterByProtected(array $route) { return $this->option('protected') && $route['protected'] == 'Yes'; } /** * Filter the route by whether or not it is unprotected. * * @param array $route * * @return bool */ protected function filterByUnprotected(array $route) { return $this->option('unprotected') && $route['protected'] == 'No'; } /** * Filter the route by its versions. * * @param array $route * * @return bool */ protected function filterByVersions(array $route) { foreach ($this->option('versions') as $version) { if (Str::contains($route['versions'], $version)) { return true; } } return false; } /** * Filter the route by its name. * * @param array $route * * @return bool */ protected function filterByName(array $route) { return Str::contains($route['name'], $this->option('name')); } /** * Filter the route by its scopes. * * @param array $route * * @return bool */ protected function filterByScopes(array $route) { foreach ($this->option('scopes') as $scope) { if (Str::contains($route['scopes'], $scope)) { return true; } } return false; } } ================================================ FILE: src/Contract/Auth/Provider.php ================================================ container = $container; $this->files = $files; $this->router = $router; $this->auth = $auth; $this->setupRequestStack(); } /** * Setup the request stack by grabbing the initial request. * * @return void */ protected function setupRequestStack() { $this->requestStack[] = $this->container['request']; } /** * Attach files to be uploaded. * * @param array $files * * @return \Dingo\Api\Dispatcher */ public function attach(array $files) { foreach ($files as $key => $file) { if (is_array($file)) { $file = new UploadedFile($file['path'], basename($file['path']), $file['mime'], $file['size']); } elseif (is_string($file)) { $finfo = finfo_open(FILEINFO_MIME_TYPE); $file = new UploadedFile($file, basename($file), finfo_file($finfo, $file), $this->files->size($file)); } elseif (! $file instanceof UploadedFile) { continue; } $this->uploads[$key] = $file; } return $this; } /** * Internal request will be authenticated as the given user. * * @param mixed $user * * @return \Dingo\Api\Dispatcher */ public function be($user) { $this->auth->setUser($user); return $this; } /** * Send a JSON payload in the request body. * * @param string|array $content * * @return \Dingo\Api\Dispatcher */ public function json($content) { if (is_array($content)) { $content = json_encode($content); } $this->content = $content; return $this->header('Content-Type', 'application/json'); } /** * Sets the domain to be used for the request. * * @param string $domain * * @return \Dingo\Api\Dispatcher */ public function on($domain) { $this->domain = $domain; return $this; } /** * Return the raw response object once request is dispatched. * * @return \Dingo\Api\Dispatcher */ public function raw() { $this->raw = true; return $this; } /** * Only authenticate with the given user for a single request. * * @return \Dingo\Api\Dispatcher */ public function once() { $this->persistAuthentication = false; return $this; } /** * Set the version of the API for the next request. * * @param string $version * * @return \Dingo\Api\Dispatcher */ public function version($version) { $this->version = $version; return $this; } /** * Set the parameters to be sent on the next API request. * * @param string|array $parameters * * @return \Dingo\Api\Dispatcher */ public function with($parameters) { $this->parameters = array_merge($this->parameters, is_array($parameters) ? $parameters : func_get_args()); return $this; } /** * Set a header to be sent on the next API request. * * @param string $key * @param string $value * * @return \Dingo\Api\Dispatcher */ public function header($key, $value) { $this->headers[$key] = $value; return $this; } /** * Set a cookie to be sent on the next API request. * * @param \Symfony\Component\HttpFoundation\Cookie $cookie * * @return \Dingo\Api\Dispatcher */ public function cookie(Cookie $cookie) { $this->cookies[] = $cookie; return $this; } /** * Perform API GET request. * * @param string $uri * @param string|array $parameters * * @return mixed */ public function get($uri, $parameters = []) { return $this->queueRequest('get', $uri, $parameters); } /** * Perform API POST request. * * @param string $uri * @param string|array $parameters * @param string $content * * @return mixed */ public function post($uri, $parameters = [], $content = '') { return $this->queueRequest('post', $uri, $parameters, $content); } /** * Perform API PUT request. * * @param string $uri * @param string|array $parameters * @param string $content * * @return mixed */ public function put($uri, $parameters = [], $content = '') { return $this->queueRequest('put', $uri, $parameters, $content); } /** * Perform API PATCH request. * * @param string $uri * @param string|array $parameters * @param string $content * * @return mixed */ public function patch($uri, $parameters = [], $content = '') { return $this->queueRequest('patch', $uri, $parameters, $content); } /** * Perform API DELETE request. * * @param string $uri * @param string|array $parameters * @param string $content * * @return mixed */ public function delete($uri, $parameters = [], $content = '') { return $this->queueRequest('delete', $uri, $parameters, $content); } /** * Queue up and dispatch a new request. * * @param string $verb * @param string $uri * @param string|array $parameters * @param string $content * * @return mixed */ protected function queueRequest($verb, $uri, $parameters, $content = '') { if (! empty($content)) { $this->content = $content; } // Sometimes after setting the initial request another request might be made prior to // internally dispatching an API request. We need to capture this request as well // and add it to the request stack as it has become the new parent request to // this internal request. This will generally occur during tests when // using the crawler to navigate pages that also make internal // requests. if (end($this->requestStack) != $this->container['request']) { $this->requestStack[] = $this->container['request']; } $this->requestStack[] = $request = $this->createRequest($verb, $uri, $parameters); return $this->dispatch($request); } /** * Create a new internal request from an HTTP verb and URI. * * @param string $verb * @param string $uri * @param string|array $parameters * * @return \Dingo\Api\Http\InternalRequest */ protected function createRequest($verb, $uri, $parameters) { $parameters = array_merge($this->parameters, (array) $parameters); $uri = $this->addPrefixToUri($uri); // If the URI does not have a scheme then we can assume that there it is not an // absolute URI, in this case we'll prefix the root requests path to the URI. $rootUrl = $this->getRootRequest()->root(); if ((! parse_url($uri, PHP_URL_SCHEME)) && parse_url($rootUrl) !== false) { $uri = rtrim($rootUrl, '/').'/'.ltrim($uri, '/'); } $request = InternalRequest::create( $uri, $verb, $parameters, $this->cookies, $this->uploads, $this->container['request']->server->all(), $this->content ); $request->headers->set('host', $this->getDomain()); foreach ($this->headers as $header => $value) { $request->headers->set($header, $value); } $request->headers->set('accept', $this->getAcceptHeader()); return $request; } /** * Add the prefix to the URI. * * @param string $uri * * @return string */ protected function addPrefixToUri($uri) { if (! isset($this->prefix)) { return $uri; } $uri = trim($uri, '/'); if (Str::startsWith($uri, $this->prefix)) { return $uri; } return rtrim('/'.trim($this->prefix, '/').'/'.$uri, '/'); } /** * Build the "Accept" header. * * @return string */ protected function getAcceptHeader() { return sprintf('application/%s.%s.%s+%s', $this->getStandardsTree(), $this->getSubtype(), $this->getVersion(), $this->getFormat()); } /** * Attempt to dispatch an internal request. * * @param \Dingo\Api\Http\InternalRequest $request * * @throws \Exception|\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface * * @return mixed */ protected function dispatch(InternalRequest $request) { $this->routeStack[] = $this->router->getCurrentRoute(); $this->clearCachedFacadeInstance(); try { $this->container->instance('request', $request); $response = $this->router->dispatch($request); if (! $response->isSuccessful() && ! $response->isRedirection()) { throw new InternalHttpException($response); } if (! $this->raw) { $response = $response->getOriginalContent(); } } catch (HttpExceptionInterface $exception) { $this->refreshRequestStack(); throw $exception; } $this->refreshRequestStack(); return $response; } /** * Refresh the request stack. * * This is done by resetting the authentication, popping * the last request from the stack, replacing the input, * and resetting the version and parameters. * * @return void */ protected function refreshRequestStack() { if (! $this->persistAuthentication) { $this->auth->setUser(null); $this->persistAuthentication = true; } if ($route = array_pop($this->routeStack)) { $this->router->setCurrentRoute($route); } $this->replaceRequestInstance(); $this->clearCachedFacadeInstance(); $this->raw = false; $this->version = $this->domain = $this->content = null; $this->parameters = $this->uploads = []; } /** * Replace the request instance with the previous request instance. * * @return void */ protected function replaceRequestInstance() { array_pop($this->requestStack); $this->container->instance('request', end($this->requestStack)); } /** * Clear the cached facade instance. * * @return void */ protected function clearCachedFacadeInstance() { // Facades cache the resolved instance so we need to clear out the // request instance that may have been cached. Otherwise we'll // may get unexpected results. RequestFacade::clearResolvedInstance('request'); } /** * Get the root request instance. * * @return \Illuminate\Http\Request */ protected function getRootRequest() { return reset($this->requestStack); } /** * Get the domain. * * @return string */ public function getDomain() { return $this->domain ?: $this->defaultDomain; } /** * Get the version. * * @return string */ public function getVersion() { return $this->version ?: $this->defaultVersion; } /** * Get the format. * * @return string */ public function getFormat() { return $this->defaultFormat; } /** * Get the subtype. * * @return string */ public function getSubtype() { return $this->subtype; } /** * Set the subtype. * * @param string $subtype * * @return void */ public function setSubtype($subtype) { $this->subtype = $subtype; } /** * Get the standards tree. * * @return string */ public function getStandardsTree() { return $this->standardsTree; } /** * Set the standards tree. * * @param string $standardsTree * * @return void */ public function setStandardsTree($standardsTree) { $this->standardsTree = $standardsTree; } /** * Set the prefix. * * @param string $prefix * * @return void */ public function setPrefix($prefix) { $this->prefix = $prefix; } /** * Set the default version. * * @param string $version * * @return void */ public function setDefaultVersion($version) { $this->defaultVersion = $version; } /** * Set the default domain. * * @param string $domain * * @return void */ public function setDefaultDomain($domain) { $this->defaultDomain = $domain; } /** * Set the default format. * * @param string $format * * @return void */ public function setDefaultFormat($format) { $this->defaultFormat = $format; } } ================================================ FILE: src/Event/RequestWasMatched.php ================================================ request = $request; $this->app = $app; } } ================================================ FILE: src/Event/ResponseIsMorphing.php ================================================ response = $response; $this->content = &$content; } } ================================================ FILE: src/Event/ResponseWasMorphed.php ================================================ parentHandler = $parentHandler; $this->format = $format; $this->debug = $debug; } /** * Report or log an exception. * * @param Throwable $exception * * @return void */ public function report(Throwable $throwable) { $this->parentHandler->report($throwable); } /** * Determine if the exception should be reported. * * @param Throwable $e * * @return bool */ public function shouldReport(Throwable $e) { return true; } /** * Render an exception into an HTTP response. * * @param Request $request * @param Throwable $exception * * @throws Exception * * @return mixed */ public function render($request, Throwable $exception) { return $this->handle($exception); } /** * Render an exception to the console. * * @param OutputInterface $output * @param Throwable $exception * * @return mixed */ public function renderForConsole($output, Throwable $exception) { return $this->parentHandler->renderForConsole($output, $exception); } /** * Register a new exception handler. * * @param callable $callback * * @return void */ public function register(callable $callback) { $hint = $this->handlerHint($callback); $this->handlers[$hint] = $callback; } /** * Handle an exception if it has an existing handler. * * @param Throwable|Exception $exception * * @return Response */ public function handle($exception) { // Convert Eloquent's 500 ModelNotFoundException into a 404 NotFoundHttpException if ($exception instanceof ModelNotFoundException) { $exception = new NotFoundHttpException($exception->getMessage(), $exception); } foreach ($this->handlers as $hint => $handler) { if (! $exception instanceof $hint) { continue; } if ($response = $handler($exception)) { if (! $response instanceof BaseResponse) { $response = new Response($response, $this->getExceptionStatusCode($exception)); } return $response->withException($exception); } } return $this->genericResponse($exception)->withException($exception); } /** * Handle a generic error response if there is no handler available. * * @param Throwable $exception * * @throws Throwable * * @return Response */ protected function genericResponse(Throwable $exception) { $replacements = $this->prepareReplacements($exception); $response = $this->newResponseArray(); array_walk_recursive($response, function (&$value, $key) use ($replacements) { if (Str::startsWith($value, ':') && isset($replacements[$value])) { $value = $replacements[$value]; } }); $response = $this->recursivelyRemoveEmptyReplacements($response); return new Response($response, $this->getStatusCode($exception), $this->getHeaders($exception)); } /** * Get the status code from the exception. * * @param Throwable $exception * * @return int */ protected function getStatusCode(Throwable $exception) { $statusCode = null; if ($exception instanceof ValidationException) { $statusCode = $exception->status; } elseif ($exception instanceof HttpExceptionInterface) { $statusCode = $exception->getStatusCode(); } else { // By default throw 500 $statusCode = 500; } // Be extra defensive if ($statusCode < 100 || $statusCode > 599) { $statusCode = 500; } return $statusCode; } /** * Get the headers from the exception. * * @param Throwable $exception * * @return array */ protected function getHeaders(Throwable $exception) { return $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : []; } /** * Prepare the replacements array by gathering the keys and values. * * @param Throwable $exception * * @return array */ protected function prepareReplacements(Throwable $exception) { $statusCode = $this->getStatusCode($exception); if (! $message = $exception->getMessage()) { $message = sprintf('%d %s', $statusCode, Response::$statusTexts[$statusCode]); } $replacements = [ ':message' => $message, ':status_code' => $statusCode, ]; if ($exception instanceof MessageBagErrors && $exception->hasErrors()) { $replacements[':errors'] = $exception->getErrors(); } if ($exception instanceof ValidationException) { $replacements[':errors'] = $exception->errors(); $replacements[':status_code'] = $exception->status; } if ($code = $exception->getCode()) { $replacements[':code'] = $code; } if ($this->runningInDebugMode()) { $replacements[':debug'] = [ 'line' => $exception->getLine(), 'file' => $exception->getFile(), 'class' => get_class($exception), 'trace' => explode("\n", $exception->getTraceAsString()), ]; // Attach trace of previous exception, if exists if (! is_null($exception->getPrevious())) { $currentTrace = $replacements[':debug']['trace']; $replacements[':debug']['trace'] = [ 'previous' => explode("\n", $exception->getPrevious()->getTraceAsString()), 'current' => $currentTrace, ]; } } return array_merge($replacements, $this->replacements); } /** * Set user defined replacements. * * @param array $replacements * * @return void */ public function setReplacements(array $replacements) { $this->replacements = $replacements; } /** * Recursively remove any empty replacement values in the response array. * * @param array $input * * @return array */ protected function recursivelyRemoveEmptyReplacements(array $input) { foreach ($input as &$value) { if (is_array($value)) { $value = $this->recursivelyRemoveEmptyReplacements($value); } } return array_filter($input, function ($value) { if (is_string($value)) { return ! Str::startsWith($value, ':'); } return true; }); } /** * Create a new response array with replacement values. * * @return array */ protected function newResponseArray() { return $this->format; } /** * Get the exception status code. * * @param Exception $exception * @param int $defaultStatusCode * * @return int */ protected function getExceptionStatusCode(Exception $exception, $defaultStatusCode = 500) { return ($exception instanceof HttpExceptionInterface) ? $exception->getStatusCode() : $defaultStatusCode; } /** * Determines if we are running in debug mode. * * @return bool */ protected function runningInDebugMode() { return $this->debug; } /** * Get the hint for an exception handler. * * @param callable $callback * * @return string */ protected function handlerHint(callable $callback) { $reflection = new ReflectionFunction($callback); $exception = $reflection->getParameters()[0]; $reflectionType = $exception->getType(); if ($reflectionType && ! $reflectionType->isBuiltin()) { if ($reflectionType instanceof \ReflectionNamedType) { return $reflectionType->getName(); } } return ''; } /** * Get the exception handlers. * * @return array */ public function getHandlers() { return $this->handlers; } /** * Set the error format array. * * @param array $format * * @return void */ public function setErrorFormat(array $format) { $this->format = $format; } /** * Set the debug mode. * * @param bool $debug * * @return void */ public function setDebug($debug) { $this->debug = $debug; } } ================================================ FILE: src/Exception/InternalHttpException.php ================================================ response = $response; parent::__construct($response->getStatusCode(), $message, $previous, $headers, $code); } /** * Get the response of the internal request. * * @return \Illuminate\Http\Response */ public function getResponse() { return $this->response; } } ================================================ FILE: src/Exception/RateLimitExceededException.php ================================================ errors = new MessageBag; } else { $this->errors = is_array($errors) ? new MessageBag($errors) : $errors; } parent::__construct(422, $message, $previous, $headers, $code); } /** * Get the errors message bag. * * @return \Illuminate\Support\MessageBag */ public function getErrors() { return $this->errors; } /** * Determine if message bag has any errors. * * @return bool */ public function hasErrors() { return ! $this->errors->isEmpty(); } } ================================================ FILE: src/Exception/StoreResourceFailedException.php ================================================ register($callback); } /** * Register a class transformer. * * @param string $class * @param string|\Closure $transformer * * @return \Dingo\Api\Transformer\Binding */ public static function transform($class, $transformer) { return static::$app['api.transformer']->register($class, $transformer); } /** * Get the authenticator. * * @return \Dingo\Api\Auth\Auth */ public static function auth() { return static::$app['api.auth']; } /** * Get the authenticated user. * * @return \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model */ public static function user() { return static::$app['api.auth']->user(); } /** * Determine if a request is internal. * * @return bool */ public static function internal() { return static::$app['api.router']->getCurrentRequest() instanceof InternalRequest; } /** * Get the response factory to begin building a response. * * @return \Dingo\Api\Http\Response\Factory */ public static function response() { return static::$app['api.http.response']; } /** * Get the API router instance. * * @return \Dingo\Api\Routing\Router */ public static function router() { return static::$app['api.router']; } /** * Get the API route of the given name, and optionally specify the API version. * * @param string $routeName * @param string $apiVersion * * @return string */ public static function route($routeName, $apiVersion = 'v1') { return static::$app['api.url']->version($apiVersion)->route($routeName); } } ================================================ FILE: src/Facade/Route.php ================================================ authorize() === false) { throw new AccessDeniedHttpException(); } $validator = app('validator')->make($this->all(), $this->rules(), $this->messages()); if ($validator->fails()) { throw new ValidationHttpException($validator->errors()); } } /** * Get the validator instance for the request. * * @return \Illuminate\Contracts\Validation\Validator * * @SuppressWarnings(PHPMD.ElseExpression) */ protected function getValidatorInstance() { $factory = $this->container->make(ValidationFactory::class); if (method_exists($this, 'validator')) { $validator = $this->container->call([$this, 'validator'], compact('factory')); } else { $validator = $this->createDefaultValidator($factory); } if (method_exists($this, 'withValidator')) { $this->withValidator($validator); } return $validator; } /** * Create the default validator instance. * * @param \Illuminate\Contracts\Validation\Factory $factory * * @return \Illuminate\Contracts\Validation\Validator */ protected function createDefaultValidator(ValidationFactory $factory) { return $factory->make( $this->validationData(), $this->container->call([$this, 'rules']), $this->messages(), $this->attributes() ); } /** * Get data to be validated from the request. * * @return array */ protected function validationData() { return $this->all(); } /** * Handle a failed validation attempt. * * @param \Illuminate\Contracts\Validation\Validator $validator * * @return void */ protected function failedValidation(Validator $validator) { if ($this->container['request'] instanceof Request) { throw new ValidationHttpException($validator->errors()); } parent::failedValidation($validator); } /** * Get the proper failed validation response for the request. * * @param array $errors * * @return \Symfony\Component\HttpFoundation\Response */ public function response(array $errors) { if ($this->expectsJson()) { return new JsonResponse($errors, 422); } return $this->redirector->to($this->getRedirectUrl()) ->withInput($this->except($this->dontFlash)) ->withErrors($errors, $this->errorBag); } /** * Format the errors from the given Validator instance. * * @param \Illuminate\Contracts\Validation\Validator $validator * * @return array */ protected function formatErrors(Validator $validator) { return $validator->getMessageBag()->toArray(); } /** * Get the URL to redirect to on a validation error. * * @return string */ protected function getRedirectUrl() { $url = $this->redirector->getUrlGenerator(); if ($this->redirect) { return $url->to($this->redirect); } elseif ($this->redirectRoute) { return $url->route($this->redirectRoute); } elseif ($this->redirectAction) { return $url->action($this->redirectAction); } return $url->previous(); } /** * Determine if the request passes the authorization check. * * @return bool */ protected function passesAuthorization() { if (method_exists($this, 'authorize')) { return $this->container->call([$this, 'authorize']); } return false; } /** * Handle a failed authorization attempt. * * @return void */ protected function failedAuthorization() { if ($this->container['request'] instanceof Request) { throw new HttpException(403); } parent::failedAuthorization(); } /** * Get custom messages for validator errors. * * @return array */ public function messages() { return []; } /** * Get custom attributes for validator errors. * * @return array */ public function attributes() { return []; } /** * Set the Redirector instance. * * @param \Laravel\Lumen\Http\Redirector|\Illuminate\Routing\Redirector $redirector * * @return $this */ public function setRedirector($redirector) { $this->redirector = $redirector; return $this; } /** * Set the container implementation. * * @param \Illuminate\Contracts\Container\Container $container * * @return $this */ public function setContainer(Container $container) { $this->container = $container; return $this; } } ================================================ FILE: src/Http/InternalRequest.php ================================================ input() if ($this->isJson() && isset($this->request)) { $this->setJson($this->request); } } } ================================================ FILE: src/Http/Middleware/Auth.php ================================================ router = $router; $this->auth = $auth; } /** * Perform authentication before a request is executed. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { $route = $this->router->getCurrentRoute(); if (! $this->auth->check(false)) { $this->auth->authenticate($route->getAuthenticationProviders()); } return $next($request); } } ================================================ FILE: src/Http/Middleware/PrepareController.php ================================================ router = $router; } /** * Handle the request. * * @param \Dingo\Api\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { // To prepare the controller all we need to do is call the current method on the router to fetch // the current route. This will create a new Dingo\Api\Routing\Route instance and prepare the // controller by binding it as a singleton in the container. This will result in the // controller only be instantiated once per request. $this->router->current(); return $next($request); } } ================================================ FILE: src/Http/Middleware/RateLimit.php ================================================ router = $router; $this->handler = $handler; } /** * Perform rate limiting before a request is executed. * * @param \Dingo\Api\Http\Request $request * @param \Closure $next * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return mixed */ public function handle($request, Closure $next) { if ($request instanceof InternalRequest) { return $next($request); } $route = $this->router->getCurrentRoute(); if ($route->hasThrottle()) { $this->handler->setThrottle($route->getThrottle()); } $this->handler->rateLimitRequest($request, $route->getRateLimit(), $route->getRateLimitExpiration()); if ($this->handler->exceededRateLimit()) { throw new RateLimitExceededException('You have exceeded your rate limit.', null, $this->getHeaders()); } $response = $next($request); if ($this->handler->requestWasRateLimited()) { return $this->responseWithHeaders($response); } return $response; } /** * Send the response with the rate limit headers. * * @param \Dingo\Api\Http\Response $response * * @return \Dingo\Api\Http\Response */ protected function responseWithHeaders($response) { foreach ($this->getHeaders() as $key => $value) { $response->headers->set($key, $value); } return $response; } /** * Get the headers for the response. * * @return array */ protected function getHeaders() { return [ 'X-RateLimit-Limit' => $this->handler->getThrottleLimit(), 'X-RateLimit-Remaining' => $this->handler->getRemainingLimit(), 'X-RateLimit-Reset' => $this->handler->getRateLimitReset(), ]; } } ================================================ FILE: src/Http/Middleware/Request.php ================================================ app = $app; $this->exception = $exception; $this->router = $router; $this->validator = $validator; $this->events = $events; } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { try { if ($this->validator->validateRequest($request)) { $this->app->singleton(LaravelExceptionHandler::class, function ($app) { return $app[ExceptionHandler::class]; }); $request = $this->app->make(RequestContract::class)->createFromIlluminate($request); $this->events->dispatch(new RequestWasMatched($request, $this->app)); return $this->sendRequestThroughRouter($request); } } catch (Exception $exception) { $this->exception->report($exception); return $this->exception->handle($exception); } return $next($request); } /** * Send the request through the Dingo router. * * @param \Dingo\Api\Http\Request $request * * @return \Dingo\Api\Http\Response */ protected function sendRequestThroughRouter(HttpRequest $request) { $this->app->instance('request', $request); return (new Pipeline($this->app))->send($request)->through($this->middleware)->then(function ($request) { return $this->router->dispatch($request); }); } /** * Call the terminate method on middlewares. * * @return void */ public function terminate($request, $response) { if (! ($request = $this->app['request']) instanceof HttpRequest) { return; } // Laravel's route middlewares can be terminated just like application // middleware, so we'll gather all the route middleware here. // On Lumen this will simply be an empty array as it does // not implement terminable route middleware. $middlewares = $this->gatherRouteMiddlewares($request); // Because of how middleware is executed on Lumen we'll need to merge in the // application middlewares now so that we can terminate them. Laravel does // not need this as it handles things a little more gracefully so it // can terminate the application ones itself. if (class_exists(Application::class, false)) { $middlewares = array_merge($middlewares, $this->middleware); } foreach ($middlewares as $middleware) { if ($middleware instanceof Closure) { continue; } [$name, $parameters] = $this->parseMiddleware($middleware); $instance = $this->app->make($name); if (method_exists($instance, 'terminate')) { $instance->terminate($request, $response); } } } /** * Parse a middleware string to get the name and parameters. * * @author Taylor Otwell * * @param string $middleware * * @return array */ protected function parseMiddleware($middleware) { [$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []); if (is_string($parameters)) { $parameters = explode(',', $parameters); } return [$name, $parameters]; } /** * Gather the middlewares for the route. * * @param \Dingo\Api\Http\Request $request * * @return array */ protected function gatherRouteMiddlewares($request) { if ($route = $request->route()) { return $this->router->gatherRouteMiddlewares($route); } return []; } /** * Set the middlewares. * * @param array $middleware * * @return void */ public function setMiddlewares(array $middleware) { $this->middleware = $middleware; } /** * Merge new middlewares onto the existing middlewares. * * @param array $middleware * * @return void */ public function mergeMiddlewares(array $middleware) { $this->middleware = array_merge($this->middleware, $middleware); } } ================================================ FILE: src/Http/Parser/Accept.php ================================================ standardsTree = $standardsTree; $this->subtype = $subtype; $this->version = $version; $this->format = $format; } /** * Parse the accept header on the incoming request. If strict is enabled * then the accept header must be available and must be a valid match. * * @param \Illuminate\Http\Request $request * @param bool $strict * * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException * * @return array */ public function parse(Request $request, $strict = false) { $pattern = '/application\/'.$this->standardsTree.'\.('.$this->subtype.')\.([\w\d\.\-]+)\+([\w]+)/'; if (! preg_match($pattern, $request->header('accept'), $matches)) { if ($strict) { throw new BadRequestHttpException('Accept header could not be properly parsed because of a strict matching process.'); } $default = 'application/'.$this->standardsTree.'.'.$this->subtype.'.'.$this->version.'+'.$this->format; preg_match($pattern, $default, $matches); } return array_combine(['subtype', 'version', 'format'], array_slice($matches, 1)); } } ================================================ FILE: src/Http/RateLimit/Handler.php ================================================ cache = $cache; $this->container = $container; $this->throttles = new Collection($throttles); } /** * Execute the rate limiting for the given request. * * @param \Dingo\Api\Http\Request $request * @param int $limit * @param int $expires * * @return void */ public function rateLimitRequest(Request $request, $limit = 0, $expires = 0) { $this->request = $request; // If the throttle instance is already set then we'll just carry on as // per usual. if ($this->throttle instanceof Throttle) { // If the developer specified a certain amount of requests or expiration // time on a specific route then we'll always use the route specific // throttle with the given values. } elseif ($limit > 0 || $expires > 0) { $this->throttle = new Route(['limit' => $limit, 'expires' => $expires]); $this->keyPrefix = sha1($request->path()); // Otherwise we'll use the throttle that gives the consumer the largest // amount of requests. If no matching throttle is found then rate // limiting will not be imposed for the request. } else { $this->throttle = $this->getMatchingThrottles()->sort(function ($a, $b) { return $a->getLimit() < $b->getLimit(); })->first(); } if (is_null($this->throttle)) { return; } if ($this->throttle instanceof HasRateLimiter) { $this->setRateLimiter([$this->throttle, 'getRateLimiter']); } $this->prepareCacheStore(); $this->cache('requests', 0, $this->throttle->getExpires()); $this->cache('expires', $this->throttle->getExpires(), $this->throttle->getExpires()); $this->cache('reset', time() + ($this->throttle->getExpires() * 60), $this->throttle->getExpires()); $this->increment('requests'); } /** * Prepare the cache store. * * @return void */ protected function prepareCacheStore() { if ($this->retrieve('expires') != $this->throttle->getExpires()) { $this->forget('requests'); $this->forget('expires'); $this->forget('reset'); } } /** * Determine if the rate limit has been exceeded. * * @return bool */ public function exceededRateLimit() { return $this->requestWasRateLimited() ? $this->retrieve('requests') > $this->throttle->getLimit() : false; } /** * Get matching throttles after executing the condition of each throttle. * * @return \Illuminate\Support\Collection */ protected function getMatchingThrottles() { return $this->throttles->filter(function ($throttle) { return $throttle->match($this->container); }); } /** * Namespace a cache key. * * @param string $key * * @return string */ protected function key($key) { return sprintf('dingo.api.%s.%s', $key, $this->getRateLimiter()); } /** * Cache a value under a given key for a certain amount of minutes. * * @param string $key * @param mixed $value * @param int $minutes * * @return void */ protected function cache($key, $value, $minutes) { $this->cache->add($this->key($key), $value, Carbon::now()->addMinutes($minutes)); } /** * Retrieve a value from the cache store. * * @param string $key * * @return mixed */ protected function retrieve($key) { return $this->cache->get($this->key($key)); } /** * Increment a key in the cache. * * @param string $key * * @return void */ protected function increment($key) { $this->cache->increment($this->key($key)); } /** * Forget a key in the cache. * * @param string $key * * @return void */ protected function forget($key) { $this->cache->forget($this->key($key)); } /** * Determine if the request was rate limited. * * @return bool */ public function requestWasRateLimited() { return ! is_null($this->throttle); } /** * Get the rate limiter. * * @return string */ public function getRateLimiter() { return call_user_func($this->limiter ?: function ($container, $request) { return $request->getClientIp(); }, $this->container, $this->request); } /** * Set the rate limiter. * * @param callable $limiter * * @return void */ public function setRateLimiter(callable $limiter) { $this->limiter = $limiter; } /** * Set the throttle to use for rate limiting. * * @param string|\Dingo\Api\Contract\Http\RateLimit\Throttle $throttle * * @return void */ public function setThrottle($throttle) { if (is_string($throttle)) { $throttle = $this->container->make($throttle); } $this->throttle = $throttle; } /** * Get the throttle used to rate limit the request. * * @return \Dingo\Api\Contract\Http\RateLimit\Throttle */ public function getThrottle() { return $this->throttle; } /** * Get the limit of the throttle used. * * @return int */ public function getThrottleLimit() { return $this->throttle->getLimit(); } /** * Get the remaining limit before the consumer is rate limited. * * @return int */ public function getRemainingLimit() { $remaining = $this->throttle->getLimit() - $this->retrieve('requests'); return $remaining > 0 ? $remaining : 0; } /** * Get the timestamp for when the current rate limiting will expire. * * @return int */ public function getRateLimitReset() { return $this->retrieve('reset'); } /** * Extend the rate limiter by adding a new throttle. * * @param callable|\Dingo\Api\Http\RateLimit\Throttle $throttle * * @return void */ public function extend($throttle) { if (is_callable($throttle)) { $throttle = call_user_func($throttle, $this->container); } $this->throttles->push($throttle); } } ================================================ FILE: src/Http/RateLimit/Throttle/Authenticated.php ================================================ check(); } } ================================================ FILE: src/Http/RateLimit/Throttle/Route.php ================================================ 60, 'expires' => 60]; /** * Create a new throttle instance. * * @param array $options * * @return void */ public function __construct(array $options = []) { $this->options = array_merge($this->options, $options); } /** * Get the throttles request limit. * * @return int */ public function getLimit() { return $this->options['limit']; } /** * Get the time in minutes that the throttles request limit will expire. * * @return int */ public function getExpires() { return $this->options['expires']; } } ================================================ FILE: src/Http/RateLimit/Throttle/Unauthenticated.php ================================================ check(); } } ================================================ FILE: src/Http/Request.php ================================================ query->all(), $old->request->all(), $old->attributes->all(), $old->cookies->all(), $old->files->all(), $old->server->all(), $old->content ); if ($session = $old->getSession()) { $new->setLaravelSession($old->getSession()); } $new->setRouteResolver($old->getRouteResolver()); $new->setUserResolver($old->getUserResolver()); return $new; } /** * Get the defined version. * * @return string */ public function version() { $this->parseAcceptHeader(); return $this->accept['version']; } /** * Get the defined subtype. * * @return string */ public function subtype() { $this->parseAcceptHeader(); return $this->accept['subtype']; } /** * Get the expected format type. * * @return string */ public function format($default = 'html') { $this->parseAcceptHeader(); return $this->accept['format'] ?: parent::format($default); } /** * Parse the accept header. * * @return void */ protected function parseAcceptHeader() { if ($this->accept) { return; } $this->accept = static::$acceptParser->parse($this); } /** * Set the accept parser instance. * * @param \Dingo\Api\Http\Parser\Accept $acceptParser * * @return void */ public static function setAcceptParser(Accept $acceptParser) { static::$acceptParser = $acceptParser; } /** * Get the accept parser instance. * * @return \Dingo\Api\Http\Parser\Accept */ public static function getAcceptParser() { return static::$acceptParser; } } ================================================ FILE: src/Http/RequestValidator.php ================================================ container = $container; } /** * Replace the validators. * * @param array $validators * * @return void */ public function replace(array $validators) { $this->validators = $validators; } /** * Merge an array of validators. * * @param array $validators * * @return void */ public function merge(array $validators) { $this->validators = array_merge($this->validators, $validators); } /** * Extend the validators. * * @param string|\Dingo\Api\Http\Validator $validator * * @return void */ public function extend($validator) { $this->validators[] = $validator; } /** * Validate a request. * * @param \Illuminate\Http\Request $request * * @return bool */ public function validateRequest(IlluminateRequest $request) { $passed = false; foreach ($this->validators as $validator) { $validator = $this->container->make($validator); if ($validator instanceof Validator && $validator->validate($request)) { $passed = true; } } // The accept validator will always be run once any of the previous validators have // been run. This ensures that we only run the accept validator once we know we // have a request that is targeting the API. if ($passed) { $this->container->make(Accept::class)->validate($request); } return $passed; } } ================================================ FILE: src/Http/Response/Factory.php ================================================ transformer = $transformer; } /** * Respond with a created response and associate a location if provided. * * @param null|string $location * * @return \Dingo\Api\Http\Response */ public function created($location = null, $content = null) { $response = new Response($content); $response->setStatusCode(201); if (! is_null($location)) { $response->header('Location', $location); } return $response; } /** * Respond with an accepted response and associate a location and/or content if provided. * * @param null|string $location * @param mixed $content * * @return \Dingo\Api\Http\Response */ public function accepted($location = null, $content = null) { $response = new Response($content); $response->setStatusCode(202); if (! is_null($location)) { $response->header('Location', $location); } return $response; } /** * Respond with a no content response. * * @return \Dingo\Api\Http\Response */ public function noContent() { $response = new Response(null); return $response->setStatusCode(204); } /** * Bind a collection to a transformer and start building a response. * * @param \Illuminate\Support\Collection $collection * @param string|callable|object $transformer * @param array|\Closure $parameters * @param \Closure|null $after * * @return \Dingo\Api\Http\Response */ public function collection(Collection $collection, $transformer = null, $parameters = [], Closure $after = null) { if ($collection->isEmpty()) { $class = get_class($collection); } else { $class = get_class($collection->first()); } if ($parameters instanceof \Closure) { $after = $parameters; $parameters = []; } if ($transformer !== null) { $binding = $this->transformer->register($class, $transformer, $parameters, $after); } else { $binding = $this->transformer->getBinding($collection); } return new Response($collection, 200, [], $binding); } /** * Bind an item to a transformer and start building a response. * * @param object $item * @param null|string|callable|object $transformer * @param array $parameters * @param \Closure $after * * @return \Dingo\Api\Http\Response */ public function item($item, $transformer = null, $parameters = [], Closure $after = null) { // Check for $item being null if (! is_null($item)) { $class = get_class($item); } else { $class = \StdClass::class; } if ($parameters instanceof \Closure) { $after = $parameters; $parameters = []; } if ($transformer !== null) { $binding = $this->transformer->register($class, $transformer, $parameters, $after); } else { $binding = $this->transformer->getBinding($item); } return new Response($item, 200, [], $binding); } /** * Bind an arbitrary array to a transformer and start building a response. * * @param array $array * @param $transformer * @param array $parameters * @param Closure|null $after * * @return Response */ public function array(array $array, $transformer = null, $parameters = [], Closure $after = null) { if ($parameters instanceof \Closure) { $after = $parameters; $parameters = []; } // For backwards compatibility, allow no transformer if ($transformer) { // Use the PHP stdClass for this purpose, as a work-around, since we need to register a class binding $class = 'stdClass'; // This will convert the array into an stdClass $array = (object) $array; $binding = $this->transformer->register($class, $transformer, $parameters, $after); } else { $binding = null; } return new Response($array, 200, [], $binding); } /** * Bind a paginator to a transformer and start building a response. * * @param \Illuminate\Contracts\Pagination\Paginator $paginator * @param null|string|callable|object $transformer * @param array $parameters * @param \Closure $after * * @return \Dingo\Api\Http\Response */ public function paginator(Paginator $paginator, $transformer = null, array $parameters = [], Closure $after = null) { if ($paginator->isEmpty()) { $class = get_class($paginator); } else { $class = get_class($paginator->first()); } if ($transformer !== null) { $binding = $this->transformer->register($class, $transformer, $parameters, $after); } else { $binding = $this->transformer->getBinding($paginator->first()); } return new Response($paginator, 200, [], $binding); } /** * Return an error response. * * @param string $message * @param int $statusCode * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function error($message, $statusCode) { throw new HttpException($statusCode, $message); } /** * Return a 404 not found error. * * @param string $message * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function errorNotFound($message = 'Not Found') { $this->error($message, 404); } /** * Return a 400 bad request error. * * @param string $message * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function errorBadRequest($message = 'Bad Request') { $this->error($message, 400); } /** * Return a 403 forbidden error. * * @param string $message * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function errorForbidden($message = 'Forbidden') { $this->error($message, 403); } /** * Return a 500 internal server error. * * @param string $message * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function errorInternal($message = 'Internal Error') { $this->error($message, 500); } /** * Return a 401 unauthorized error. * * @param string $message * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function errorUnauthorized($message = 'Unauthorized') { $this->error($message, 401); } /** * Return a 405 method not allowed error. * * @param string $message * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * * @return void */ public function errorMethodNotAllowed($message = 'Method Not Allowed') { $this->error($message, 405); } /** * Call magic methods beginning with "with". * * @param string $method * @param array $parameters * * @throws \ErrorException * * @return mixed */ public function __call($method, $parameters) { if (Str::startsWith($method, 'with')) { return call_user_func_array([$this, Str::camel(substr($method, 4))], $parameters); // Because PHP won't let us name the method "array" we'll simply watch for it // in here and return the new binding. Gross. This is now DEPRECATED and // should not be used. Just return an array or a new response instance. } elseif ($method == 'array') { return new Response($parameters[0]); } throw new ErrorException('Undefined method '.get_class($this).'::'.$method); } } ================================================ FILE: src/Http/Response/Format/Format.php ================================================ request = $request; return $this; } /** * Set the response instance. * * @param \Illuminate\Http\Response $response * * @return \Dingo\Api\Http\Response\Format\Format */ public function setResponse($response) { $this->response = $response; return $this; } /** * Set the formats' options. * * @param array $options * * @return \Dingo\Api\Http\Response\Format\Format */ public function setOptions(array $options) { $this->options = $options; return $this; } /** * Format an Eloquent model. * * @param \Illuminate\Database\Eloquent\Model $model * * @return string */ abstract public function formatEloquentModel($model); /** * Format an Eloquent collection. * * @param \Illuminate\Database\Eloquent\Collection $collection * * @return string */ abstract public function formatEloquentCollection($collection); /** * Format an array or instance implementing Arrayable. * * @param array|\Illuminate\Contracts\Support\Arrayable $content * * @return string */ abstract public function formatArray($content); /** * Get the response content type. * * @return string */ abstract public function getContentType(); } ================================================ FILE: src/Http/Response/Format/Json.php ================================================ getTable()); if (! $model::$snakeAttributes) { $key = Str::camel($key); } return $this->encode([$key => $model->toArray()]); } /** * Format an Eloquent collection. * * @param \Illuminate\Database\Eloquent\Collection $collection * * @return string */ public function formatEloquentCollection($collection) { if ($collection->isEmpty()) { return $this->encode([]); } $model = $collection->first(); $key = Str::plural($model->getTable()); if (! $model::$snakeAttributes) { $key = Str::camel($key); } return $this->encode([$key => $collection->toArray()]); } /** * Format an array or instance implementing Arrayable. * * @param array|\Illuminate\Contracts\Support\Arrayable $content * * @return string */ public function formatArray($content) { $content = $this->morphToArray($content); array_walk_recursive($content, function (&$value) { $value = $this->morphToArray($value); }); return $this->encode($content); } /** * Get the response content type. * * @return string */ public function getContentType() { return 'application/json'; } /** * Morph a value to an array. * * @param array|\Illuminate\Contracts\Support\Arrayable $value * * @return array */ protected function morphToArray($value) { return $value instanceof Arrayable ? $value->toArray() : $value; } /** * Encode the content to its JSON representation. * * @param mixed $content * * @return string */ protected function encode($content) { $jsonEncodeOptions = []; // Here is a place, where any available JSON encoding options, that // deal with users' requirements to JSON response formatting and // structure, can be conveniently applied to tweak the output. if ($this->isJsonPrettyPrintEnabled()) { $jsonEncodeOptions[] = JSON_PRETTY_PRINT; $jsonEncodeOptions[] = JSON_UNESCAPED_UNICODE; } $encodedString = $this->performJsonEncoding($content, $jsonEncodeOptions); if ($this->isCustomIndentStyleRequired()) { $encodedString = $this->indentPrettyPrintedJson( $encodedString, $this->options['indent_style'] ); } return $encodedString; } } ================================================ FILE: src/Http/Response/Format/JsonOptionalFormatting.php ================================================ "\t", 'space' => ' ', ]; /* * JSON constants, that are allowed to be used as options while encoding. * Whitelist can be extended by other options in the future. * * @see http://php.net/manual/ru/json.constants.php * * @var array */ protected $jsonEncodeOptionsWhitelist = [ JSON_PRETTY_PRINT, JSON_UNESCAPED_UNICODE, ]; /** * Determine if JSON pretty print option is set to true. * * @return bool */ protected function isJsonPrettyPrintEnabled() { return isset($this->options['pretty_print']) && $this->options['pretty_print'] === true; } /** * Determine if JSON custom indent style is set. * * @return bool */ protected function isCustomIndentStyleRequired() { return $this->isJsonPrettyPrintEnabled() && isset($this->options['indent_style']) && in_array($this->options['indent_style'], $this->indentStyles); } /** * Perform JSON encode. * * @param string $content * @param array $jsonEncodeOptions * * @return string */ protected function performJsonEncoding($content, array $jsonEncodeOptions = []) { $jsonEncodeOptions = $this->filterJsonEncodeOptions($jsonEncodeOptions); $optionsBitmask = $this->calucateJsonEncodeOptionsBitmask($jsonEncodeOptions); if (($encodedString = json_encode($content, $optionsBitmask)) === false) { throw new \ErrorException('Error encoding data in JSON format: '.json_last_error()); } return $encodedString; } /** * Filter JSON encode options array against the whitelist array. * * @param array $jsonEncodeOptions * * @return array */ protected function filterJsonEncodeOptions(array $jsonEncodeOptions) { return array_intersect($jsonEncodeOptions, $this->jsonEncodeOptionsWhitelist); } /** * Sweep JSON encode options together to get options' bitmask. * * @param array $jsonEncodeOptions * * @return int */ protected function calucateJsonEncodeOptionsBitmask(array $jsonEncodeOptions) { return array_sum($jsonEncodeOptions); } /** * Indent pretty printed JSON string, using given indent style. * * @param string $jsonString * @param string $indentStyle * @param int $defaultIndentSize * * @return string */ protected function indentPrettyPrintedJson($jsonString, $indentStyle, $defaultIndentSize = 2) { $indentChar = $this->getIndentCharForIndentStyle($indentStyle); $indentSize = $this->getPrettyPrintIndentSize() ?: $defaultIndentSize; // If the given indentation style is allowed to have various indent size // (number of chars, that are used to indent one level in each line), // indent the JSON string with given (or default) indent size. if ($this->hasVariousIndentSize($indentStyle)) { return $this->peformIndentation($jsonString, $indentChar, $indentSize); } // Otherwise following the convention, that indent styles, that does not // allowed to have various indent size (e.g. tab) are indented using // one tabulation character per one indent level in each line. return $this->peformIndentation($jsonString, $indentChar); } /** * Get indent char for given indent style. * * @param string $indentStyle * * @return string */ protected function getIndentCharForIndentStyle($indentStyle) { return $this->indentChars[$indentStyle]; } /** * Get indent size for pretty printed JSON string. * * @return int|null */ protected function getPrettyPrintIndentSize() { return isset($this->options['indent_size']) ? (int) $this->options['indent_size'] : null; } /** * Determine if indent style is allowed to have various indent size. * * @param string $indentStyle * * @return bool */ protected function hasVariousIndentSize($indentStyle) { return in_array($indentStyle, $this->hasVariousIndentSize); } /** * Perform indentation for pretty printed JSON string with a given * indent char, repeated N times, as determined by indent size. * * @param string $jsonString JSON string, which must be indented * @param string $indentChar Char, used for indent (default is tab) * @param int $indentSize Number of times to repeat indent char per one indent level * @param int $defaultSpaces Default number of indent spaces after json_encode() * * @return string */ protected function peformIndentation($jsonString, $indentChar = "\t", $indentSize = 1, $defaultSpaces = 4) { $pattern = '/(^|\G) {'.$defaultSpaces.'}/m'; $replacement = str_repeat($indentChar, $indentSize).'$1'; return preg_replace($pattern, $replacement, $jsonString); } } ================================================ FILE: src/Http/Response/Format/Jsonp.php ================================================ callbackName = $callbackName; } /** * Determine if a callback is valid. * * @return bool */ protected function hasValidCallback() { return $this->request->query->has($this->callbackName); } /** * Get the callback from the query string. * * @return string */ protected function getCallback() { return $this->request->query->get($this->callbackName); } /** * Get the response content type. * * @return string */ public function getContentType() { if ($this->hasValidCallback()) { return 'application/javascript'; } return parent::getContentType(); } /** * Encode the content to its JSONP representation. * * @param mixed $content * * @return string */ protected function encode($content) { $jsonString = parent::encode($content); if ($this->hasValidCallback()) { return sprintf('%s(%s);', $this->getCallback(), $jsonString); } return $jsonString; } } ================================================ FILE: src/Http/Response.php ================================================ binding = $binding; } /** * Make an API response from an existing Illuminate response. * * @param \Illuminate\Http\Response $old * * @return \Dingo\Api\Http\Response */ public static function makeFromExisting(IlluminateResponse $old) { $new = new static($old->getOriginalContent(), $old->getStatusCode()); $new->headers = $old->headers; return $new; } /** * Make an API response from an existing JSON response. * * @param \Illuminate\Http\JsonResponse $json * * @return \Dingo\Api\Http\Response */ public static function makeFromJson(JsonResponse $json) { $content = $json->getContent(); // If the contents of the JsonResponse does not starts with /**/ (typical laravel jsonp response) // we assume that it is a valid json response that can be decoded, or we just use the raw jsonp // contents for building the response if (! Str::startsWith($json->getContent(), '/**/')) { $content = json_decode($json->getContent(), true); } $new = new static($content, $json->getStatusCode()); $new->headers = $json->headers; return $new; } /** * Morph the API response to the appropriate format. * * @param string $format * * @return \Dingo\Api\Http\Response */ public function morph($format = 'json') { $this->content = $this->getOriginalContent(); $this->fireMorphingEvent(); if (isset(static::$transformer) && static::$transformer->transformableResponse($this->content)) { $this->content = static::$transformer->transform($this->content); } $formatter = static::getFormatter($format); $formatter->setOptions(static::getFormatsOptions($format)); $defaultContentType = $this->headers->get('Content-Type'); // If we have no content, we don't want to set this header, as it will be blank $contentType = $formatter->getContentType(); if (! empty($contentType)) { $this->headers->set('Content-Type', $formatter->getContentType()); } $this->fireMorphedEvent(); if ($this->content instanceof EloquentModel) { $this->content = $formatter->formatEloquentModel($this->content); } elseif ($this->content instanceof EloquentCollection) { $this->content = $formatter->formatEloquentCollection($this->content); } elseif (is_array($this->content) || $this->content instanceof ArrayObject || $this->content instanceof Arrayable) { $this->content = $formatter->formatArray($this->content); } else { if (! empty($defaultContentType)) { $this->headers->set('Content-Type', $defaultContentType); } } return $this; } /** * Fire the morphed event. * * @return void */ protected function fireMorphedEvent() { if (! static::$events) { return; } static::$events->dispatch(new ResponseWasMorphed($this, $this->content)); } /** * Fire the morphing event. * * @return void */ protected function fireMorphingEvent() { if (! static::$events) { return; } static::$events->dispatch(new ResponseIsMorphing($this, $this->content)); } /** * {@inheritdoc} */ public function setContent($content) { // Attempt to set the content string, if we encounter an unexpected value // then we most likely have an object that cannot be type cast. In that // case we'll simply leave the content as null and set the original // content value and continue. if (! empty($content) && is_object($content) && ! $this->shouldBeJson($content)) { $this->original = $content; return $this; } try { return parent::setContent($content); } catch (UnexpectedValueException $exception) { $this->original = $content; return $this; } } /** * Set the event dispatcher instance. * * @param \Illuminate\Contracts\Events\Dispatcher $events * * @return void */ public static function setEventDispatcher(EventDispatcher $events) { static::$events = $events; } /** * Get the formatter based on the requested format type. * * @param string $format * * @throws \RuntimeException * * @return \Dingo\Api\Http\Response\Format\Format */ public static function getFormatter($format) { if (! static::hasFormatter($format)) { throw new NotAcceptableHttpException('Unable to format response according to Accept header.'); } return static::$formatters[$format]; } /** * Determine if a response formatter has been registered. * * @param string $format * * @return bool */ public static function hasFormatter($format) { return isset(static::$formatters[$format]); } /** * Set the response formatters. * * @param array $formatters * * @return void */ public static function setFormatters(array $formatters) { static::$formatters = $formatters; } /** * Set the formats' options. * * @param array $formatsOptions * * @return void */ public static function setFormatsOptions(array $formatsOptions) { static::$formatsOptions = $formatsOptions; } /** * Get the format's options. * * @param string $format * * @return array */ public static function getFormatsOptions($format) { if (! static::hasOptionsForFormat($format)) { return []; } return static::$formatsOptions[$format]; } /** * Determine if any format's options were set. * * @param string $format * * @return bool */ public static function hasOptionsForFormat($format) { return isset(static::$formatsOptions[$format]); } /** * Add a response formatter. * * @param string $key * @param \Dingo\Api\Http\Response\Format\Format $formatter * * @return void */ public static function addFormatter($key, $formatter) { static::$formatters[$key] = $formatter; } /** * Set the transformer factory instance. * * @param \Dingo\Api\Transformer\Factory $transformer * * @return void */ public static function setTransformer(TransformerFactory $transformer) { static::$transformer = $transformer; } /** * Get the transformer instance. * * @return \Dingo\Api\Transformer\Factory */ public static function getTransformer() { return static::$transformer; } /** * Add a meta key and value pair. * * @param string $key * @param mixed $value * * @return \Dingo\Api\Http\Response */ public function addMeta($key, $value) { $this->binding->addMeta($key, $value); return $this; } /** * Add a meta key and value pair. * * @param string $key * @param mixed $value * * @return \Dingo\Api\Http\Response */ public function meta($key, $value) { return $this->addMeta($key, $value); } /** * Set the meta data for the response. * * @param array $meta * * @return \Dingo\Api\Http\Response */ public function setMeta(array $meta) { $this->binding->setMeta($meta); return $this; } /** * Get the meta data for the response. * * @return array */ public function getMeta() { return $this->binding->getMeta(); } /** * Add a cookie to the response. * * @param \Symfony\Component\HttpFoundation\Cookie|mixed $cookie * * @return \Dingo\Api\Http\Response */ public function cookie($cookie) { return $this->withCookie($cookie); } /** * Add a header to the response. * * @param string $key * @param string $value * @param bool $replace * * @return \Dingo\Api\Http\Response */ public function withHeader($key, $value, $replace = true) { return $this->header($key, $value, $replace); } /** * Set the response status code. * * @param int $statusCode * * @return \Dingo\Api\Http\Response */ public function statusCode($statusCode) { return $this->setStatusCode($statusCode); } } ================================================ FILE: src/Http/Validation/Accept.php ================================================ accept = $accept; $this->strict = $strict; } /** * Validate the accept header on the request. If this fails it will throw * an HTTP exception that will be caught by the middleware. This * validator should always be run last and must not return * a success boolean. * * @param \Illuminate\Http\Request $request * * @throws \Exception|\Symfony\Component\HttpKernel\Exception\BadRequestHttpException * * @return bool */ public function validate(Request $request) { try { $this->accept->parse($request, $this->strict); } catch (BadRequestHttpException $exception) { if ($request->getMethod() === 'OPTIONS') { return true; } throw $exception; } } } ================================================ FILE: src/Http/Validation/Domain.php ================================================ domain = $domain; } /** * Validate that the request domain matches the configured domain. * * @param \Illuminate\Http\Request $request * * @return bool */ public function validate(Request $request) { return ! is_null($this->domain) && $request->getHost() === $this->getStrippedDomain(); } /** * Strip the protocol from a domain. * * @param string $domain * * @return string */ protected function stripProtocol($domain) { if (Str::contains($domain, '://')) { $domain = substr($domain, strpos($domain, '://') + 3); } return $domain; } /** * Strip the port from a domain. * * @param $domain * * @return mixed */ protected function stripPort($domain) { if ($domainStripped = preg_replace(self::PATTERN_STRIP_PROTOCOL, null, $domain)) { return $domainStripped; } return $domain; } /** * Get the domain stripped from protocol and port. * * @return mixed */ protected function getStrippedDomain() { return $this->stripPort($this->stripProtocol($this->domain)); } } ================================================ FILE: src/Http/Validation/Prefix.php ================================================ prefix = $prefix; } /** * Validate the request has a prefix and if it matches the configured * API prefix. * * @param \Illuminate\Http\Request $request * * @return bool */ public function validate(Request $request) { $prefix = $this->filterAndExplode($this->prefix); $path = $this->filterAndExplode($request->getPathInfo()); return ! is_null($this->prefix) && $prefix == array_slice($path, 0, count($prefix)); } /** * Explode array on slash and remove empty values. * * @param array $array * * @return array */ protected function filterAndExplode($array) { return array_filter(explode('/', $array)); } } ================================================ FILE: src/Provider/DingoServiceProvider.php ================================================ setResponseStaticInstances(); Request::setAcceptParser($this->app[\Dingo\Api\Http\Parser\Accept::class]); $this->app->rebinding('api.routes', function ($app, $routes) { $app['api.url']->setRouteCollections($routes); }); } protected function setResponseStaticInstances() { Response::setFormatters($this->config('formats')); Response::setFormatsOptions($this->config('formatsOptions')); Response::setTransformer($this->app['api.transformer']); Response::setEventDispatcher($this->app['events']); } /** * Register the service provider. * * @return void */ public function register() { $this->registerConfig(); $this->registerClassAliases(); $this->app->register(RoutingServiceProvider::class); $this->app->register(HttpServiceProvider::class); $this->registerExceptionHandler(); $this->registerDispatcher(); $this->registerAuth(); $this->registerTransformer(); $this->registerDocsCommand(); if (class_exists('Illuminate\Foundation\Application', false)) { $this->commands([ \Dingo\Api\Console\Command\Cache::class, \Dingo\Api\Console\Command\Routes::class, ]); } } /** * Register the configuration. * * @return void */ protected function registerConfig() { $this->mergeConfigFrom(realpath(__DIR__.'/../../config/api.php'), 'api'); if (! $this->app->runningInConsole() && empty($this->config('prefix')) && empty($this->config('domain'))) { throw new RuntimeException('Unable to boot ApiServiceProvider, configure an API domain or prefix.'); } } /** * Register the class aliases. * * @return void */ protected function registerClassAliases() { $serviceAliases = [ \Dingo\Api\Http\Request::class => \Dingo\Api\Contract\Http\Request::class, 'api.dispatcher' => \Dingo\Api\Dispatcher::class, 'api.http.validator' => \Dingo\Api\Http\RequestValidator::class, 'api.http.response' => \Dingo\Api\Http\Response\Factory::class, 'api.router' => \Dingo\Api\Routing\Router::class, 'api.router.adapter' => \Dingo\Api\Contract\Routing\Adapter::class, 'api.auth' => \Dingo\Api\Auth\Auth::class, 'api.limiting' => \Dingo\Api\Http\RateLimit\Handler::class, 'api.transformer' => \Dingo\Api\Transformer\Factory::class, 'api.url' => \Dingo\Api\Routing\UrlGenerator::class, 'api.exception' => [\Dingo\Api\Exception\Handler::class, \Dingo\Api\Contract\Debug\ExceptionHandler::class], ]; foreach ($serviceAliases as $key => $aliases) { foreach ((array) $aliases as $alias) { $this->app->alias($key, $alias); } } } /** * Register the exception handler. * * @return void */ protected function registerExceptionHandler() { $this->app->singleton('api.exception', function ($app) { return new ExceptionHandler($app['Illuminate\Contracts\Debug\ExceptionHandler'], $this->config('errorFormat'), $this->config('debug')); }); } /** * Register the internal dispatcher. * * @return void */ public function registerDispatcher() { $this->app->singleton('api.dispatcher', function ($app) { $dispatcher = new Dispatcher($app, $app['files'], $app[\Dingo\Api\Routing\Router::class], $app[\Dingo\Api\Auth\Auth::class]); $dispatcher->setSubtype($this->config('subtype')); $dispatcher->setStandardsTree($this->config('standardsTree')); $dispatcher->setPrefix($this->config('prefix')); $dispatcher->setDefaultVersion($this->config('version')); $dispatcher->setDefaultDomain($this->config('domain')); $dispatcher->setDefaultFormat($this->config('defaultFormat')); return $dispatcher; }); } /** * Register the auth. * * @return void */ protected function registerAuth() { $this->app->singleton('api.auth', function ($app) { return new Auth($app[\Dingo\Api\Routing\Router::class], $app, $this->config('auth')); }); } /** * Register the transformer factory. * * @return void */ protected function registerTransformer() { $this->app->singleton('api.transformer', function ($app) { return new TransformerFactory($app, $this->config('transformer')); }); } /** * Register the documentation command. * * @return void */ protected function registerDocsCommand() { $this->app->singleton(\Dingo\Api\Console\Command\Docs::class, function ($app) { return new Command\Docs( $app[\Dingo\Api\Routing\Router::class], $app[\Dingo\Blueprint\Blueprint::class], $app[\Dingo\Blueprint\Writer::class], $this->config('name'), $this->config('version') ); }); $this->commands([\Dingo\Api\Console\Command\Docs::class]); } } ================================================ FILE: src/Provider/HttpServiceProvider.php ================================================ registerRateLimiting(); $this->registerHttpValidation(); $this->registerHttpParsers(); $this->registerResponseFactory(); $this->registerMiddleware(); } /** * Register the rate limiting. * * @return void */ protected function registerRateLimiting() { $this->app->singleton('api.limiting', function ($app) { return new RateLimitHandler($app, $app['cache'], $this->config('throttling')); }); } /** * Register the HTTP validation. * * @return void */ protected function registerHttpValidation() { $this->app->singleton('api.http.validator', function ($app) { return new RequestValidator($app); }); $this->app->singleton(Domain::class, function ($app) { return new Validation\Domain($this->config('domain')); }); $this->app->singleton(Prefix::class, function ($app) { return new Validation\Prefix($this->config('prefix')); }); $this->app->singleton(Accept::class, function ($app) { return new Validation\Accept( $this->app[AcceptParser::class], $this->config('strict') ); }); } /** * Register the HTTP parsers. * * @return void */ protected function registerHttpParsers() { $this->app->singleton(AcceptParser::class, function ($app) { return new AcceptParser( $this->config('standardsTree'), $this->config('subtype'), $this->config('version'), $this->config('defaultFormat') ); }); } /** * Register the response factory. * * @return void */ protected function registerResponseFactory() { $this->app->singleton('api.http.response', function ($app) { return new ResponseFactory($app[Factory::class]); }); } /** * Register the middleware. * * @return void */ protected function registerMiddleware() { $this->app->singleton(Request::class, function ($app) { $middleware = new Middleware\Request( $app, $app[ExceptionHandler::class], $app[Router::class], $app[RequestValidator::class], $app['events'] ); $middleware->setMiddlewares($this->config('middleware', false)); return $middleware; }); $this->app->singleton(AuthMiddleware::class, function ($app) { return new Middleware\Auth($app[Router::class], $app[Auth::class]); }); $this->app->singleton(RateLimit::class, function ($app) { return new Middleware\RateLimit($app[Router::class], $app[Handler::class]); }); $this->app->singleton(PrepareController::class, function ($app) { return new Middleware\PrepareController($app[Router::class]); }); } } ================================================ FILE: src/Provider/LaravelServiceProvider.php ================================================ publishes([realpath(__DIR__.'/../../config/api.php') => config_path('api.php')]); $kernel = $this->app->make(Kernel::class); $this->app[Request::class]->mergeMiddlewares( $this->gatherAppMiddleware($kernel) ); $this->addRequestMiddlewareToBeginning($kernel); $this->app['events']->listen(RequestWasMatched::class, function (RequestWasMatched $event) { $this->replaceRouteDispatcher(); $this->updateRouterBindings(); }); // Originally Validate FormRequest after resolving /* This casues the prepareForValidation() function to be called twice, and seemingly has no other benefit, see discussion at https://github.com/dingo/api/issues/1668 This is already done by laravel service provider, and works with Dingo router $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) { $resolved->validateResolved(); }); */ $this->app->resolving(FormRequest::class, function (FormRequest $request, Application $app) { $this->initializeRequest($request, $app['request']); $request->setContainer($app)->setRedirector($app->make(Redirector::class)); }); $this->addMiddlewareAlias('api.auth', Auth::class); $this->addMiddlewareAlias('api.throttle', RateLimit::class); $this->addMiddlewareAlias('api.controllers', PrepareController::class); } /** * Replace the route dispatcher. * * @return void */ protected function replaceRouteDispatcher() { $this->app->singleton('illuminate.route.dispatcher', function ($app) { return new ControllerDispatcher($app['api.router.adapter']->getRouter(), $app); }); } /** * Grab the bindings from the Laravel router and set them on the adapters * router. * * @return void */ protected function updateRouterBindings() { foreach ($this->getRouterBindings() as $key => $binding) { $this->app['api.router.adapter']->getRouter()->bind($key, $binding); } } /** * Get the Laravel routers bindings. * * @return array */ protected function getRouterBindings() { $property = (new ReflectionClass($this->app['router']))->getProperty('binders'); $property->setAccessible(true); return $property->getValue($this->app['router']); } /** * Register the service provider. * * @return void */ public function register() { parent::register(); $this->registerRouterAdapter(); } /** * Register the router adapter. * * @return void */ protected function registerRouterAdapter() { $this->app->singleton('api.router.adapter', function ($app) { return new LaravelAdapter($app['router']); }); } /** * Add the request middleware to the beginning of the kernel. * * @param \Illuminate\Contracts\Http\Kernel $kernel * * @return void */ protected function addRequestMiddlewareToBeginning(Kernel $kernel) { $kernel->prependMiddleware(Request::class); } /** * Register a short-hand name for a middleware. For compatibility * with Laravel < 5.4 check if aliasMiddleware exists since this * method has been renamed. * * @param string $name * @param string $class * * @return void */ protected function addMiddlewareAlias($name, $class) { $router = $this->app['router']; if (method_exists($router, 'aliasMiddleware')) { return $router->aliasMiddleware($name, $class); } return $router->middleware($name, $class); } /** * Gather the application middleware besides this one so that we can send * our request through them, exactly how the developer wanted. * * @param \Illuminate\Contracts\Http\Kernel $kernel * * @return array */ protected function gatherAppMiddleware(Kernel $kernel) { $property = (new ReflectionClass($kernel))->getProperty('middleware'); $property->setAccessible(true); return $property->getValue($kernel); } /** * Initialize the form request with data from the given request. * * @param FormRequest $form * @param IlluminateRequest $current * * @return void */ protected function initializeRequest(FormRequest $form, IlluminateRequest $current) { $files = $current->files->all(); $files = is_array($files) ? array_filter($files) : $files; $form->initialize( $current->query->all(), $current->request->all(), $current->attributes->all(), $current->cookies->all(), $files, $current->server->all(), $current->getContent() ); $form->setJson($current->json()); if ($session = $current->getSession()) { $form->setLaravelSession($session); } $form->setUserResolver($current->getUserResolver()); $form->setRouteResolver($current->getRouteResolver()); } } ================================================ FILE: src/Provider/LumenServiceProvider.php ================================================ app->configure('api'); $reflection = new ReflectionClass($this->app); $this->app[Request::class]->mergeMiddlewares( $this->gatherAppMiddleware($reflection) ); $this->addRequestMiddlewareToBeginning($reflection); // Because Lumen sets the route resolver at a very weird point we're going to // have to use reflection whenever the request instance is rebound to // set the route resolver to get the current route. $this->app->rebinding(IlluminateRequest::class, function ($app, $request) { $request->setRouteResolver(function () use ($app) { $reflection = new ReflectionClass($app); $property = $reflection->getProperty('currentRoute'); $property->setAccessible(true); return $property->getValue($app); }); }); // Validate FormRequest after resolving $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) { $resolved->validateResolved(); }); $this->app->resolving(FormRequest::class, function (FormRequest $request, Application $app) { $this->initializeRequest($request, $app['request']); $request->setContainer($app)->setRedirector($app->make(Redirector::class)); }); $this->app->routeMiddleware([ 'api.auth' => Auth::class, 'api.throttle' => RateLimit::class, 'api.controllers' => PrepareController::class, ]); } /** * Setup the configuration. * * @return void */ protected function setupConfig() { $this->app->configure('api'); parent::setupConfig(); } /** * Register the service provider. * * @return void */ public function register() { parent::register(); $this->app->singleton('api.router.adapter', function ($app) { return new LumenAdapter($app, new StdRouteParser, new GcbDataGenerator, $this->getDispatcherResolver()); }); } /** * Get the dispatcher resolver callback. * * @return \Closure */ protected function getDispatcherResolver() { return function ($routeCollector) { return new GroupCountBased($routeCollector->getData()); }; } /** * Add the request middleware to the beginning of the middleware stack on the * Lumen application instance. * * @param \ReflectionClass $reflection * * @return void */ protected function addRequestMiddlewareToBeginning(ReflectionClass $reflection) { $property = $reflection->getProperty('middleware'); $property->setAccessible(true); $middleware = $property->getValue($this->app); array_unshift($middleware, Request::class); $property->setValue($this->app, $middleware); $property->setAccessible(false); } /** * Gather the application middleware besides this one so that we can send * our request through them, exactly how the developer wanted. * * @param \ReflectionClass $reflection * * @return array */ protected function gatherAppMiddleware(ReflectionClass $reflection) { $property = $reflection->getProperty('middleware'); $property->setAccessible(true); $middleware = $property->getValue($this->app); return $middleware; } /** * Initialize the form request with data from the given request. * * @param FormRequest $form * @param IlluminateRequest $current * * @return void */ protected function initializeRequest(FormRequest $form, IlluminateRequest $current) { $files = $current->files->all(); $files = is_array($files) ? array_filter($files) : $files; $form->initialize( $current->query->all(), $current->request->all(), $current->attributes->all(), $current->cookies->all(), $files, $current->server->all(), $current->getContent() ); $form->setJson($current->json()); if ($session = $current->getSession()) { $form->setLaravelSession($session); } $form->setUserResolver($current->getUserResolver()); $form->setRouteResolver($current->getRouteResolver()); } } ================================================ FILE: src/Provider/RoutingServiceProvider.php ================================================ registerRouter(); $this->registerUrlGenerator(); } /** * Register the router. */ protected function registerRouter() { $this->app->singleton('api.router', function ($app) { $router = new Router( $app[Adapter::class], $app[ExceptionHandler::class], $app, $this->config('domain'), $this->config('prefix') ); $router->setConditionalRequest($this->config('conditionalRequest')); return $router; }); $this->app->singleton(ResourceRegistrar::class, function ($app) { return new ResourceRegistrar($app[Router::class]); }); } /** * Register the URL generator. */ protected function registerUrlGenerator() { $this->app->singleton('api.url', function ($app) { $url = new UrlGenerator($app['request']); $url->setRouteCollections($app[Router::class]->getRoutes()); $url->setKeyResolver(function () { return $this->app->make('config')->get('app.key'); }); return $url; }); } /** * Get the URL generator request rebinder. * * @return \Closure */ private function requestRebinder() { return function ($app, $request) { $app['api.url']->setRequest($request); }; } } ================================================ FILE: src/Provider/ServiceProvider.php ================================================ app['config']->get('api.'.$item); if (is_array($value)) { return $instantiate ? $this->instantiateConfigValues($item, $value) : $value; } return $instantiate ? $this->instantiateConfigValue($item, $value) : $value; } /** * Instantiate an array of instantiable configuration values. * * @param string $item * @param array $values * * @return array */ protected function instantiateConfigValues($item, array $values) { foreach ($values as $key => $value) { $values[$key] = $this->instantiateConfigValue($item, $value); } return $values; } /** * Instantiate an instantiable configuration value. * * @param string $item * @param mixed $value * * @return mixed */ protected function instantiateConfigValue($item, $value) { if (is_string($value) && in_array($item, $this->instantiable)) { return $this->app->make($value); } return $value; } } ================================================ FILE: src/Routing/Adapter/Laravel.php ================================================ router = $router; } /** * Dispatch a request. * * @param \Illuminate\Http\Request $request * @param string $version * * @return mixed */ public function dispatch(Request $request, $version) { if (! isset($this->routes[$version])) { throw new UnknownVersionException; } $routes = $this->mergeOldRoutes($version); $this->router->setRoutes($routes); $router = clone $this->router; $response = $router->dispatch($request); unset($router); return $response; } /** * Merge the old application routes with the API routes. * * @param string $version * * @return array */ protected function mergeOldRoutes($version) { if (! isset($this->oldRoutes)) { $this->oldRoutes = $this->router->getRoutes(); } if (! isset($this->mergedRoutes[$version])) { $this->mergedRoutes[$version] = $this->routes[$version]; foreach ($this->oldRoutes as $route) { $this->mergedRoutes[$version]->add($route); } $this->mergedRoutes[$version]->refreshNameLookups(); $this->mergedRoutes[$version]->refreshActionLookups(); } return $this->mergedRoutes[$version]; } /** * Get the URI, methods, and action from the route. * * @param mixed $route * @param \Illuminate\Http\Request $request * * @return array */ public function getRouteProperties($route, Request $request) { if (method_exists($route, 'uri') && method_exists($route, 'methods')) { return [$route->uri(), $route->methods(), $route->getAction()]; } return [$route->getUri(), $route->getMethods(), $route->getAction()]; } /** * Add a route to the appropriate route collection. * * @param array $methods * @param array $versions * @param string $uri * @param mixed $action * * @return \Illuminate\Routing\Route */ public function addRoute(array $methods, array $versions, $uri, $action) { $this->createRouteCollections($versions); // Add where-patterns from original laravel router $action['where'] = array_merge($this->router->getPatterns(), $action['where'] ?? []); $route = new Route($methods, $uri, $action); $route->where($action['where']); foreach ($versions as $version) { $this->routes[$version]->add($route); } return $route; } /** * Create the route collections for the versions. * * @param array $versions * * @return void */ protected function createRouteCollections(array $versions) { foreach ($versions as $version) { if (! isset($this->routes[$version])) { $this->routes[$version] = new RouteCollection; } } } /** * Get all routes or only for a specific version. * * @param string $version * * @return mixed */ public function getRoutes($version = null) { if (! is_null($version)) { return $this->routes[$version]; } return $this->routes; } /** * Get a normalized iterable set of routes. * * @param string $version * * @return mixed */ public function getIterableRoutes($version = null) { return $this->getRoutes($version); } /** * Set the routes on the adapter. * * @param array $routes * * @return void */ public function setRoutes(array $routes) { $this->routes = $routes; } /** * Prepare a route for serialization. * * @param mixed $route * * @return mixed */ public function prepareRouteForSerialization($route) { $route->prepareForSerialization(); return $route; } /** * Gather the route middlewares. * * @param \Illuminate\Routing\Route $route * * @return array */ public function gatherRouteMiddlewares($route) { if (method_exists($this->router, 'gatherRouteMiddleware')) { return $this->router->gatherRouteMiddleware($route); } return $this->router->gatherRouteMiddlewares($route); } /** * Get the Laravel router instance. * * @return \Illuminate\Routing\Router */ public function getRouter() { return $this->router; } /** * Get the global "where" patterns. * * @return array */ public function getPatterns() { return $this->patterns; } } ================================================ FILE: src/Routing/Adapter/Lumen.php ================================================ app = $app; $this->parser = $parser; $this->generator = $generator; $this->dispatcherResolver = $dispatcherResolver; } /** * Dispatch a request. * * @param \Illuminate\Http\Request $request * @param string $version * * @return mixed */ public function dispatch(Request $request, $version) { if (! isset($this->routes[$version])) { throw new UnknownVersionException; } $this->removeMiddlewareFromApp(); $routeCollector = $this->mergeOldRoutes($version); $dispatcher = call_user_func($this->dispatcherResolver, $routeCollector); $this->app->setDispatcher($dispatcher); $this->normalizeRequestUri($request); return $this->app->dispatch($request); } /** * Merge the old application routes with the API routes. * * @param string $version * * @return array */ protected function mergeOldRoutes($version) { if (! isset($this->oldRoutes)) { $this->oldRoutes = $this->app->router->getRoutes(); } if (! isset($this->mergedRoutes[$version])) { $this->mergedRoutes[$version] = $this->routes[$version]; foreach ($this->oldRoutes as $route) { $this->mergedRoutes[$version]->addRoute($route['method'], $route['uri'], $route['action']); } } return $this->mergedRoutes[$version]; } /** * Normalize the request URI so that Lumen can properly dispatch it. * * @param \Illuminate\Http\Request $request * * @return void */ protected function normalizeRequestUri(Request $request) { $query = $request->server->get('QUERY_STRING'); $uri = '/'.trim(str_replace('?'.$query, '', $request->server->get('REQUEST_URI')), '/').($query ? '?'.$query : ''); $request->server->set('REQUEST_URI', $uri); } /** * Get the URI, methods, and action from the route. * * @param mixed $route * @param \Illuminate\Http\Request $request * * @return array */ public function getRouteProperties($route, Request $request) { $uri = ltrim(isset($route['uri']) ? $route['uri'] : $request->getRequestUri(), '/'); $methods = isset($route['methods']) ? $route['methods'] : (array) $request->getMethod(); $action = (isset($route[1]) && is_array($route[1])) ? $route[1] : $route; if (in_array('GET', $methods) && ! in_array('HEAD', $methods)) { $methods[] = 'HEAD'; } return [$uri, $methods, $action]; } /** * Add a route to the appropriate route collection. * * @param array $methods * @param array $versions * @param string $uri * @param mixed $action * * @return void */ public function addRoute(array $methods, array $versions, $uri, $action) { $this->createRouteCollections($versions); foreach ($versions as $version) { foreach ($this->breakUriSegments($uri) as $uri) { $this->routes[$version]->addRoute($methods, $uri, $action); } } } /** * Break a URI that has optional segments into individual URIs. * * @param string $uri * * @return array */ protected function breakUriSegments($uri) { if (! Str::contains($uri, '?}')) { return (array) $uri; } $segments = preg_split( '/\/(\{.*?\})/', preg_replace('/\{(.*?)\?\}/', '{$1}', $uri), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); $uris = []; while ($segments) { $uris[] = implode('/', $segments); array_pop($segments); } return $uris; } /** * Create the route collections for the versions. * * @param array $versions * * @return void */ protected function createRouteCollections(array $versions) { foreach ($versions as $version) { if (! isset($this->routes[$version])) { $this->routes[$version] = new RouteCollector($this->parser, clone $this->generator); } } } /** * Remove the global application middleware as it's run from this packages * Request middleware. Lumen runs middleware later in its life cycle * which results in some middleware being executed twice. * * @return void */ protected function removeMiddlewareFromApp() { if ($this->middlewareRemoved) { return; } $this->middlewareRemoved = true; $reflection = new ReflectionClass($this->app); $property = $reflection->getProperty('middleware'); $property->setAccessible(true); $oldMiddlewares = $property->getValue($this->app); $newMiddlewares = []; foreach ($oldMiddlewares as $middle) { if ((new ReflectionClass($middle))->hasMethod('terminate') && $middle != 'Dingo\Api\Http\Middleware\Request') { $newMiddlewares = array_merge($newMiddlewares, [$middle]); } } $property->setValue($this->app, $newMiddlewares); $property->setAccessible(false); } /** * Get all routes or only for a specific version. * * @param string $version * * @return mixed */ public function getRoutes($version = null) { if (! is_null($version)) { return $this->routes[$version]; } return $this->routes; } /** * Get routes in an iterable form. * * @param string $version * * @return \ArrayIterator */ public function getIterableRoutes($version = null) { $iterable = []; foreach ($this->getRoutes($version) as $version => $collector) { $routeData = $collector->getData(); // The first element in the array are the static routes that do not have any parameters. foreach ($this->normalizeStaticRoutes($routeData[0]) as $method => $routes) { if ($method === 'HEAD') { continue; } foreach ($routes as $route) { $route['methods'] = $this->setRouteMethods($route, $method); $iterable[$version][] = $route; } } // The second element is the more complicated regex routes that have parameters. foreach ($routeData[1] as $method => $routes) { if ($method === 'HEAD') { continue; } foreach ($routes as $data) { foreach ($data['routeMap'] as [$route, $parameters]) { $route['methods'] = $this->setRouteMethods($route, $method); $iterable[$version][] = $route; } } } } return new ArrayIterator($iterable); } /** * Normalize the FastRoute static routes so they're the same across multiple versions. * * @param array $routes * * @return array */ protected function normalizeStaticRoutes(array $routes) { foreach (array_keys($routes) as $key) { // If any of the keys are an HTTP method then we are running on a newer version of // Lumen and FastRoute which means we can leave the routes as they are. if ($this->stringIsHttpMethod($key)) { return $routes; } } $normalized = []; // To normalize the routes we'll take the inner array which contains the routes method as the // key and make that the parent element on the array. We'll then add all routes for a // particular HTTP method as children of it by keying them to their URI. foreach ($routes as $uri => $value) { foreach ($value as $method => $route) { $normalized[$method][$uri] = $route; } } return $normalized; } /** * Determine if a string is an HTTP method. * * @param string $string * * @return bool */ protected function stringIsHttpMethod($string) { $methods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; return in_array($string, $methods, true); } /** * Set the routes on the adapter. * * @param array $routes * * @return void */ public function setRoutes(array $routes) { // Route caching is not implemented for Lumen. } /** * Prepare a route for serialization. * * @param mixed $route * * @return mixed */ public function prepareRouteForSerialization($route) { // Route caching is not implemented for Lumen. } /** * Gather the route middlewares. * * @param array $route * * @return array */ public function gatherRouteMiddlewares($route) { // Route middleware in Lumen is not terminated. return []; } /** * Append given method to the current route methods. * * @param array $route * @param string $method * * @return array */ private function setRouteMethods($route, $method) { return isset($route['methods']) ? array_push($route['methods'], $method) : [$method]; } } ================================================ FILE: src/Routing/Helpers.php ================================================ throttles[] = compact('class', 'options'); } /** * Rate limit controller methods. * * @param int $limit * @param int $expires * @param array $options * * @return void */ protected function rateLimit($limit, $expires, array $options = []) { $this->rateLimit[] = compact('limit', 'expires', 'options'); } /** * Add scopes to controller methods. * * @param string|array $scopes * @param array $options * * @return void */ protected function scopes($scopes, array $options = []) { $scopes = $this->getPropertyValue($scopes); $this->scopes[] = compact('scopes', 'options'); } /** * Authenticate with certain providers on controller methods. * * @param string|array $providers * @param array $options * * @return void */ protected function authenticateWith($providers, array $options = []) { $providers = $this->getPropertyValue($providers); $this->authenticationProviders[] = compact('providers', 'options'); } /** * Prepare a property value. * * @param string|array $value * * @return array */ protected function getPropertyValue($value) { return is_string($value) ? explode('|', $value) : $value; } /** * Get the controllers rate limiting throttles. * * @return array */ public function getThrottles() { return $this->throttles; } /** * Get the controllers rate limit and expiration. * * @return array */ public function getRateLimit() { return $this->rateLimit; } /** * Get the controllers scopes. * * @return array */ public function getScopes() { return $this->scopes; } /** * Get the controllers authentication providers. * * @return array */ public function getAuthenticationProviders() { return $this->authenticationProviders; } /** * Get the internal dispatcher instance. * * @return \Dingo\Api\Dispatcher */ public function api() { return app(Dispatcher::class); } /** * Get the authenticated user. * * @return mixed */ protected function user() { return app(Auth::class)->user(); } /** * Get the auth instance. * * @return \Dingo\Api\Auth\Auth */ protected function auth() { return app(Auth::class); } /** * Get the response factory instance. * * @return \Dingo\Api\Http\Response\Factory */ protected function response() { return app(Factory::class); } /** * Magically handle calls to certain properties. * * @param string $key * * @throws \ErrorException * * @return mixed */ public function __get($key) { $callable = [ 'api', 'user', 'auth', 'response', ]; if (in_array($key, $callable) && method_exists($this, $key)) { return $this->$key(); } throw new ErrorException('Undefined property '.get_class($this).'::'.$key); } /** * Magically handle calls to certain methods on the response factory. * * @param string $method * @param array $parameters * * @throws \ErrorException * * @return \Dingo\Api\Http\Response */ public function __call($method, $parameters) { if (method_exists($this->response(), $method) || $method == 'array') { return call_user_func_array([$this->response(), $method], $parameters); } throw new ErrorException('Undefined method '.get_class($this).'::'.$method); } } ================================================ FILE: src/Routing/ResourceRegistrar.php ================================================ router = $router; } /** * Route a resource to a controller. * * @param string $name * @param string $controller * @param array $options * * @return void */ public function register($name, $controller, array $options = []) { if (isset($options['parameters']) && ! isset($this->parameters)) { $this->parameters = $options['parameters']; } // If the resource name contains a slash, we will assume the developer wishes to // register these resource routes with a prefix so we will set that up out of // the box so they don't have to mess with it. Otherwise, we will continue. if (Str::contains($name, '/')) { $this->prefixedResource($name, $controller, $options); return; } // We need to extract the base resource from the resource name. Nested resources // are supported in the framework, but we need to know what name to use for a // place-holder on the route parameters, which should be the base resources. $base = $this->getResourceWildcard(last(explode('.', $name))); $defaults = $this->resourceDefaults; foreach ($this->getResourceMethods($defaults, $options) as $m) { $this->{'addResource'.ucfirst($m)}($name, $base, $controller, $options); } } } ================================================ FILE: src/Routing/Route.php ================================================ adapter = $adapter; $this->container = $container; $this->route = $route; $this->setupRouteProperties($request, $route); parent::__construct($this->methods, $this->uri, $this->action); } /** * Setup the route properties. * * @param Request $request * @param $route * * @return void */ protected function setupRouteProperties(Request $request, $route) { [$this->uri, $this->methods, $this->action] = $this->adapter->getRouteProperties($route, $request); $this->versions = Arr::pull($this->action, 'version'); $this->conditionalRequest = Arr::pull($this->action, 'conditionalRequest', true); $this->middleware = (array) Arr::pull($this->action, 'middleware', []); $this->throttle = Arr::pull($this->action, 'throttle'); $this->scopes = Arr::pull($this->action, 'scopes', []); $this->authenticationProviders = Arr::pull($this->action, 'providers', []); $this->rateLimit = Arr::pull($this->action, 'limit', 0); $this->rateExpiration = Arr::pull($this->action, 'expires', 0); // Now that the default route properties have been set we'll go ahead and merge // any controller properties to fully configure the route. $this->mergeControllerProperties(); // If we have a string based throttle then we'll new up an instance of the // throttle through the container. if (is_string($this->throttle)) { $this->throttle = $this->container->make($this->throttle); } } /** * Merge the controller properties onto the route properties. */ protected function mergeControllerProperties() { if (isset($this->action['uses']) && is_string($this->action['uses']) && Str::contains($this->action['uses'], '@')) { $this->action['controller'] = $this->action['uses']; $this->makeControllerInstance(); } if (! $this->controllerUsesHelpersTrait()) { return; } $controller = $this->getControllerInstance(); $controllerMiddleware = []; if (method_exists($controller, 'getMiddleware')) { $controllerMiddleware = $controller->getMiddleware(); } elseif (method_exists($controller, 'getMiddlewareForMethod')) { $controllerMiddleware = $controller->getMiddlewareForMethod($this->controllerMethod); } $this->middleware = array_merge($this->middleware, $controllerMiddleware); if ($property = $this->findControllerPropertyOptions('throttles')) { $this->throttle = $property['class']; } if ($property = $this->findControllerPropertyOptions('scopes')) { $this->scopes = array_merge($this->scopes, $property['scopes']); } if ($property = $this->findControllerPropertyOptions('authenticationProviders')) { $this->authenticationProviders = array_merge($this->authenticationProviders, $property['providers']); } if ($property = $this->findControllerPropertyOptions('rateLimit')) { $this->rateLimit = $property['limit']; $this->rateExpiration = $property['expires']; } } /** * Find the controller options and whether or not it will apply to this routes controller method. * * @param string $name * * @return array */ protected function findControllerPropertyOptions($name) { $properties = []; foreach ($this->getControllerInstance()->{'get'.ucfirst($name)}() as $property) { if (isset($property['options']) && ! $this->optionsApplyToControllerMethod($property['options'])) { continue; } unset($property['options']); $properties = array_merge_recursive($properties, $property); } return $properties; } /** * Determine if a controller method is in an array of options. * * @param array $options * * @return bool */ protected function optionsApplyToControllerMethod(array $options) { if (empty($options)) { return true; } elseif (isset($options['only']) && in_array($this->controllerMethod, $this->explodeOnPipes($options['only']))) { return true; } elseif (isset($options['except'])) { return ! in_array($this->controllerMethod, $this->explodeOnPipes($options['except'])); } elseif (in_array($this->controllerMethod, $this->explodeOnPipes($options))) { return true; } return false; } /** * Explode a value on a pipe delimiter. * * @param string|array $value * * @return array */ protected function explodeOnPipes($value) { return is_string($value) ? explode('|', $value) : $value; } /** * Determine if the controller instance uses the helpers trait. * * @return bool */ protected function controllerUsesHelpersTrait() { if (! $controller = $this->getControllerInstance()) { return false; } $traits = []; do { $traits = array_merge(class_uses($controller, false), $traits); } while ($controller = get_parent_class($controller)); foreach ($traits as $trait => $same) { $traits = array_merge(class_uses($trait, false), $traits); } return isset($traits[Helpers::class]); } /** * Get the routes controller instance. * * @return null|\Illuminate\Routing\Controller|\Laravel\Lumen\Routing\Controller */ public function getControllerInstance() { return $this->controller; } /** * Make a new controller instance through the container. * * @return \Illuminate\Routing\Controller|\Laravel\Lumen\Routing\Controller */ protected function makeControllerInstance() { [$this->controllerClass, $this->controllerMethod] = explode('@', $this->action['uses']); $this->container->instance($this->controllerClass, $this->controller = $this->container->make($this->controllerClass)); return $this->controller; } /** * Determine if the route is protected. * * @return bool */ public function isProtected() { if (isset($this->middleware['api.auth']) || in_array('api.auth', $this->middleware)) { if ($this->controller && isset($this->middleware['api.auth'])) { return $this->optionsApplyToControllerMethod($this->middleware['api.auth']); } return true; } return false; } /** * Determine if the route has a throttle. * * @return bool */ public function hasThrottle() { return ! is_null($this->throttle); } /** * Get the route throttle. * * @return string|\Dingo\Api\Http\RateLimit\Throttle\Throttle */ public function throttle() { return $this->throttle; } /** * Get the route throttle. * * @return string|\Dingo\Api\Http\RateLimit\Throttle\Throttle */ public function getThrottle() { return $this->throttle; } /** * Get the route scopes. * * @return array */ public function scopes() { return $this->scopes; } /** * Get the route scopes. * * @return array */ public function getScopes() { return $this->scopes; } /** * Check if route requires all scopes or any scope to be valid. * * @return bool */ public function scopeStrict() { return Arr::get($this->action, 'scopeStrict', false); } /** * Get the route authentication providers. * * @return array */ public function authenticationProviders() { return $this->authenticationProviders; } /** * Get the route authentication providers. * * @return array */ public function getAuthenticationProviders() { return $this->authenticationProviders; } /** * Get the rate limit for this route. * * @return int */ public function rateLimit() { return $this->rateLimit; } /** * Get the rate limit for this route. * * @return int */ public function getRateLimit() { return $this->rateLimit; } /** * Get the rate limit expiration time for this route. * * @return int */ public function rateLimitExpiration() { return $this->rateExpiration; } /** * Get the rate limit expiration time for this route. * * @return int */ public function getRateLimitExpiration() { return $this->rateExpiration; } /** * Get the name of the route. * * @return string */ public function getName() { return Arr::get($this->action, 'as', null); } /** * Determine if the request is conditional. * * @return bool */ public function requestIsConditional() { return $this->conditionalRequest === true; } /** * Get the versions for the route. * * @return array */ public function getVersions() { return $this->versions; } /** * Get the versions for the route. * * @return array */ public function versions() { return $this->getVersions(); } /** * Get the URI associated with the route. * * @return string */ public function getPath() { return $this->uri(); } /** * Get the URI associated with the route. * * @return string */ public function uri() { return $this->uri; } /** * Get the HTTP verbs the route responds to. * * @return array */ public function getMethods() { return $this->methods(); } /** * Get the HTTP verbs the route responds to. * * @return array */ public function methods() { return $this->methods; } /** * Determine if the route only responds to HTTP requests. * * @return bool */ public function httpOnly() { return in_array('http', $this->action, true) || (array_key_exists('http', $this->action) && $this->action['http']); } /** * Determine if the route only responds to HTTPS requests. * * @return bool */ public function httpsOnly() { return $this->secure(); } /** * Determine if the route only responds to HTTPS requests. * * @return bool */ public function secure() { return in_array('https', $this->action, true) || (array_key_exists('https', $this->action) && $this->action['https']); } /** * Return the middlewares for this route. * * @return array */ public function getMiddleware() { return $this->middleware; } } ================================================ FILE: src/Routing/RouteCollection.php ================================================ routes[] = $route; $this->addLookups($route); return $route; } /** * Add route lookups. * * @param \Dingo\Api\Routing\Route $route * * @return void */ protected function addLookups(Route $route) { $action = $route->getAction(); if (isset($action['as'])) { $this->names[$action['as']] = $route; } if (isset($action['controller'])) { $this->actions[$action['controller']] = $route; } } /** * Get a route by name. * * @param string $name * * @return \Dingo\Api\Routing\Route|null */ public function getByName($name) { return isset($this->names[$name]) ? $this->names[$name] : null; } /** * Get a route by action. * * @param string $action * * @return \Dingo\Api\Routing\Route|null */ public function getByAction($action) { return isset($this->actions[$action]) ? $this->actions[$action] : null; } /** * Get all routes. * * @return array */ public function getRoutes() { return $this->routes; } /** * Get an iterator for the items. * * @return \ArrayIterator */ public function getIterator() { return new ArrayIterator($this->getRoutes()); } /** * Count the number of items in the collection. * * @return int */ public function count() { return count($this->getRoutes()); } } ================================================ FILE: src/Routing/Router.php ================================================ adapter = $adapter; $this->exception = $exception; $this->container = $container; $this->domain = $domain; $this->prefix = $prefix; } /** * An alias for calling the group method, allows a more fluent API * for registering a new API version group with optional * attributes and a required callback. * * This method can be called without the third parameter, however, * the callback should always be the last parameter. * * @param array|string $version * @param array|callable $second * @param callable $third * * @return void */ public function version($version, $second, $third = null) { if (func_num_args() == 2) { [$version, $callback, $attributes] = array_merge(func_get_args(), [[]]); } else { [$version, $attributes, $callback] = func_get_args(); } $attributes = array_merge($attributes, ['version' => $version]); $this->group($attributes, $callback); } /** * Create a new route group. * * @param array $attributes * @param callable $callback * * @return void */ public function group(array $attributes, $callback) { if (! isset($attributes['conditionalRequest'])) { $attributes['conditionalRequest'] = $this->conditionalRequest; } $attributes = $this->mergeLastGroupAttributes($attributes); if (! isset($attributes['version'])) { throw new RuntimeException('A version is required for an API group definition.'); } else { $attributes['version'] = (array) $attributes['version']; } if ((! isset($attributes['prefix']) || empty($attributes['prefix'])) && isset($this->prefix)) { $attributes['prefix'] = $this->prefix; } if ((! isset($attributes['domain']) || empty($attributes['domain'])) && isset($this->domain)) { $attributes['domain'] = $this->domain; } $this->groupStack[] = $attributes; call_user_func($callback, $this); array_pop($this->groupStack); } /** * Create a new GET route. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function get($uri, $action) { return $this->addRoute(['GET', 'HEAD'], $uri, $action); } /** * Create a new POST route. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function post($uri, $action) { return $this->addRoute('POST', $uri, $action); } /** * Create a new PUT route. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function put($uri, $action) { return $this->addRoute('PUT', $uri, $action); } /** * Create a new PATCH route. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function patch($uri, $action) { return $this->addRoute('PATCH', $uri, $action); } /** * Create a new DELETE route. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function delete($uri, $action) { return $this->addRoute('DELETE', $uri, $action); } /** * Create a new OPTIONS route. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function options($uri, $action) { return $this->addRoute('OPTIONS', $uri, $action); } /** * Create a new route that responding to all verbs. * * @param string $uri * @param array|string|callable $action * * @return mixed */ public function any($uri, $action) { $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; return $this->addRoute($verbs, $uri, $action); } /** * Create a new route with the given verbs. * * @param array|string $methods * @param string $uri * @param array|string|callable $action * * @return mixed */ public function match($methods, $uri, $action) { return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); } /** * Register an array of resources. * * @param array $resources * * @return void */ public function resources(array $resources) { foreach ($resources as $name => $resource) { $options = []; if (is_array($resource)) { [$resource, $options] = $resource; } $this->resource($name, $resource, $options); } } /** * Register a resource controller. * * @param string $name * @param string $controller * @param array $options * * @return void */ public function resource($name, $controller, array $options = []) { if ($this->container->bound(ResourceRegistrar::class)) { $registrar = $this->container->make(ResourceRegistrar::class); } else { $registrar = new ResourceRegistrar($this); } $registrar->register($name, $controller, $options); } /** * Add a route to the routing adapter. * * @param string|array $methods * @param string $uri * @param string|array|callable $action * * @return mixed */ public function addRoute($methods, $uri, $action) { if (is_string($action)) { $action = ['uses' => $action, 'controller' => $action]; } elseif ($action instanceof Closure) { $action = [$action]; } elseif (is_array($action)) { // For this sort of syntax $api->post('login', [LoginController::class, 'login']); if (is_string(Arr::first($action)) && class_exists(Arr::first($action)) && count($action) == 2) { $action = implode('@', $action); $action = ['uses' => $action, 'controller' => $action]; } } $action = $this->mergeLastGroupAttributes($action); $action = $this->addControllerMiddlewareToRouteAction($action); $uri = $uri === '/' ? $uri : '/'.trim($uri, '/'); if (! empty($action['prefix'])) { $uri = '/'.ltrim(rtrim(trim($action['prefix'], '/').'/'.trim($uri, '/'), '/'), '/'); unset($action['prefix']); } $action['uri'] = $uri; return $this->adapter->addRoute((array) $methods, $action['version'], $uri, $action); } /** * Add the controller preparation middleware to the beginning of the routes middleware. * * @param array $action * * @return array */ protected function addControllerMiddlewareToRouteAction(array $action) { array_unshift($action['middleware'], 'api.controllers'); return $action; } /** * Merge the last groups attributes. * * @param array $attributes * * @return array */ protected function mergeLastGroupAttributes(array $attributes) { if (empty($this->groupStack)) { return $this->mergeGroup($attributes, []); } return $this->mergeGroup($attributes, end($this->groupStack)); } /** * Merge the given group attributes. * * @param array $new * @param array $old * * @return array */ protected function mergeGroup(array $new, array $old) { $new['namespace'] = $this->formatNamespace($new, $old); $new['prefix'] = $this->formatPrefix($new, $old); foreach (['middleware', 'providers', 'scopes', 'before', 'after'] as $option) { $new[$option] = $this->formatArrayBasedOption($option, $new); } if (isset($new['domain'])) { unset($old['domain']); } if (isset($new['conditionalRequest'])) { unset($old['conditionalRequest']); } if (isset($new['uses'])) { $new['uses'] = $this->formatUses($new, $old); } $new['where'] = array_merge(Arr::get($old, 'where', []), Arr::get($new, 'where', [])); if (isset($old['as'])) { $new['as'] = trim($old['as'].'.'.Arr::get($new, 'as', ''), '.'); } return array_merge_recursive(Arr::except($old, ['namespace', 'prefix', 'where', 'as']), $new); } /** * Format an array based option in a route action. * * @param string $option * @param array $new * * @return array */ protected function formatArrayBasedOption($option, array $new) { $value = Arr::get($new, $option, []); return is_string($value) ? explode('|', $value) : $value; } /** * Format the uses key in a route action. * * @param array $new * @param array $old * * @return string */ protected function formatUses(array $new, array $old) { if (isset($old['namespace']) && is_string($new['uses']) && strpos($new['uses'], '\\') !== 0) { return $old['namespace'].'\\'.$new['uses']; } return $new['uses']; } /** * Format the namespace for the new group attributes. * * @param array $new * @param array $old * * @return string */ protected function formatNamespace(array $new, array $old) { if (isset($new['namespace']) && isset($old['namespace'])) { return trim($old['namespace'], '\\').'\\'.trim($new['namespace'], '\\'); } elseif (isset($new['namespace'])) { return trim($new['namespace'], '\\'); } return Arr::get($old, 'namespace'); } /** * Format the prefix for the new group attributes. * * @param array $new * @param array $old * * @return string */ protected function formatPrefix($new, $old) { if (isset($new['prefix'])) { return trim(Arr::get($old, 'prefix'), '/').'/'.trim($new['prefix'], '/'); } return Arr::get($old, 'prefix', ''); } /** * Dispatch a request via the adapter. * * @param \Dingo\Api\Http\Request $request * * @throws \Exception * * @return \Dingo\Api\Http\Response */ public function dispatch(Request $request) { $this->currentRoute = null; $this->container->instance(Request::class, $request); $this->routesDispatched++; try { $response = $this->adapter->dispatch($request, $request->version()); } catch (Exception $exception) { if ($request instanceof InternalRequest) { throw $exception; } $this->exception->report($exception); $response = $this->exception->handle($exception); } return $this->prepareResponse($response, $request, $request->format()); } /** * Prepare a response by transforming and formatting it correctly. * * @param mixed $response * @param \Dingo\Api\Http\Request $request * @param string $format * * @return \Dingo\Api\Http\Response */ protected function prepareResponse($response, Request $request, $format) { if ($response instanceof IlluminateResponse) { $response = Response::makeFromExisting($response); } elseif ($response instanceof JsonResponse) { $response = Response::makeFromJson($response); } if ($response instanceof Response) { // If we try and get a formatter that does not exist we'll let the exception // handler deal with it. At worst we'll get a generic JSON response that // a consumer can hopefully deal with. Ideally they won't be using // an unsupported format. try { $response->getFormatter($format)->setResponse($response)->setRequest($request); } catch (NotAcceptableHttpException $exception) { return $this->exception->handle($exception); } $response = $response->morph($format); } if ($response->isSuccessful() && $this->requestIsConditional()) { if (! $response->headers->has('ETag')) { $response->setEtag(sha1($response->getContent() ?: '')); } $response->isNotModified($request); } return $response; } /** * Gather the middleware for the given route. * * @param mixed $route * * @return array */ public function gatherRouteMiddlewares($route) { return $this->adapter->gatherRouteMiddlewares($route); } /** * Determine if the request is conditional. * * @return bool */ protected function requestIsConditional() { return $this->getCurrentRoute()->requestIsConditional(); } /** * Set the conditional request. * * @param bool $conditionalRequest * * @return void */ public function setConditionalRequest($conditionalRequest) { $this->conditionalRequest = $conditionalRequest; } /** * Get the current request instance. * * @return \Dingo\Api\Http\Request */ public function getCurrentRequest() { return $this->container['request']; } /** * Get the current route instance. * * @return \Dingo\Api\Routing\Route */ public function getCurrentRoute() { if (isset($this->currentRoute)) { return $this->currentRoute; } elseif (! $this->hasDispatchedRoutes() || ! $route = $this->container['request']->route()) { return; } return $this->currentRoute = $this->createRoute($route); } /** * Get the currently dispatched route instance. * * @return \Illuminate\Routing\Route */ public function current() { return $this->getCurrentRoute(); } /** * Create a new route instance from an adapter route. * * @param array|\Illuminate\Routing\Route $route * * @return \Dingo\Api\Routing\Route */ public function createRoute($route) { return new Route($this->adapter, $this->container, $this->container['request'], $route); } /** * Set the current route instance. * * @param \Dingo\Api\Routing\Route $route * * @return void */ public function setCurrentRoute(Route $route) { $this->currentRoute = $route; } /** * Determine if the router has a group stack. * * @return bool */ public function hasGroupStack() { return ! empty($this->groupStack); } /** * Get the prefix from the last group on the stack. * * @return string */ public function getLastGroupPrefix() { if (empty($this->groupStack)) { return ''; } $group = end($this->groupStack); return $group['prefix']; } /** * Get all routes registered on the adapter. * * @param string $version * * @return mixed */ public function getRoutes($version = null) { $routes = $this->adapter->getIterableRoutes($version); if (! is_null($version)) { $routes = [$version => $routes]; } $collections = []; foreach ($routes as $key => $value) { $collections[$key] = new RouteCollection($this->container['request']); foreach ($value as $route) { $route = $this->createRoute($route); $collections[$key]->add($route); } } return is_null($version) ? $collections : $collections[$version]; } /** * Get the raw adapter routes. * * @return array */ public function getAdapterRoutes() { return $this->adapter->getRoutes(); } /** * Set the raw adapter routes. * * @param array $routes * * @return void */ public function setAdapterRoutes(array $routes) { $this->adapter->setRoutes($routes); $this->container->instance('api.routes', $this->getRoutes()); } /** * Get the number of routes dispatched. * * @return int */ public function getRoutesDispatched() { return $this->routesDispatched; } /** * Determine if the router has dispatched any routes. * * @return bool */ public function hasDispatchedRoutes() { return $this->routesDispatched > 0; } /** * Get the current route name. * * @return string|null */ public function currentRouteName() { return $this->current() ? $this->current()->getName() : null; } /** * Alias for the "currentRouteNamed" method. * * @param mixed string * * @return bool */ public function is() { foreach (func_get_args() as $pattern) { if (Str::is($pattern, $this->currentRouteName())) { return true; } } return false; } /** * Determine if the current route matches a given name. * * @param string $name * * @return bool */ public function currentRouteNamed($name) { return $this->current() ? $this->current()->getName() == $name : false; } /** * Get the current route action. * * @return string|null */ public function currentRouteAction() { if (! $route = $this->current()) { return; } $action = $route->getAction(); return is_string($action['uses']) ? $action['uses'] : null; } /** * Alias for the "currentRouteUses" method. * * @param mixed string * * @return bool */ public function uses() { foreach (func_get_args() as $pattern) { if (Str::is($pattern, $this->currentRouteAction())) { return true; } } return false; } /** * Determine if the current route action matches a given action. * * @param string $action * * @return bool */ public function currentRouteUses($action) { return $this->currentRouteAction() == $action; } /** * Flush the router's middleware groups. * * @return $this */ public function flushMiddlewareGroups() { return $this; } } ================================================ FILE: src/Routing/UrlGenerator.php ================================================ setRequest($request); } /** * Set the routes to use from the version. * * @param string $version * * @return \Dingo\Api\Routing\UrlGenerator */ public function version($version) { $this->routes = $this->collections[$version]; return $this; } /** * Set the route collection instance. * * @param array $collections */ public function setRouteCollections(array $collections) { $this->collections = $collections; } } ================================================ FILE: src/Transformer/Adapter/Fractal.php ================================================ fractal = $fractal; $this->includeKey = $includeKey; $this->includeSeparator = $includeSeparator; $this->eagerLoading = $eagerLoading; } /** * Transform a response with a transformer. * * @param mixed $response * @param League\Fractal\TransformerAbstract|object $transformer * @param \Dingo\Api\Transformer\Binding $binding * @param \Dingo\Api\Http\Request $request * * @return array */ public function transform($response, $transformer, Binding $binding, Request $request) { $this->parseFractalIncludes($request); $resource = $this->createResource($response, $transformer, $parameters = $binding->getParameters()); // If the response is a paginator then we'll create a new paginator // adapter for Laravel and set the paginator instance on our // collection resource. if ($response instanceof IlluminatePaginator) { $paginator = $this->createPaginatorAdapter($response); $resource->setPaginator($paginator); } if ($this->shouldEagerLoad($response)) { $eagerLoads = $this->mergeEagerLoads($transformer, $this->fractal->getRequestedIncludes()); if ($transformer instanceof TransformerAbstract) { // Only eager load the items in available includes $eagerLoads = array_intersect($eagerLoads, $transformer->getAvailableIncludes()); } $response->load($eagerLoads); } foreach ($binding->getMeta() as $key => $value) { $resource->setMetaValue($key, $value); } $binding->fireCallback($resource, $this->fractal); $identifier = isset($parameters['identifier']) ? $parameters['identifier'] : null; return $this->fractal->createData($resource, $identifier)->toArray(); } /** * Eager loading is only performed when the response is or contains an * Eloquent collection and eager loading is enabled. * * @param mixed $response * * @return bool */ protected function shouldEagerLoad($response) { if ($response instanceof IlluminatePaginator) { $response = $response->getCollection(); } return $response instanceof EloquentCollection && $this->eagerLoading; } /** * Create the Fractal paginator adapter. * * @param \Illuminate\Contracts\Pagination\Paginator $paginator * * @return \League\Fractal\Pagination\IlluminatePaginatorAdapter */ protected function createPaginatorAdapter(IlluminatePaginator $paginator) { return new IlluminatePaginatorAdapter($paginator); } /** * Create a Fractal resource instance. * * @param mixed $response * @param \League\Fractal\TransformerAbstract $transformer * @param array $parameters * * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\Collection */ protected function createResource($response, $transformer, array $parameters) { $key = isset($parameters['key']) ? $parameters['key'] : null; if ($response instanceof IlluminatePaginator || $response instanceof IlluminateCollection) { return new FractalCollection($response, $transformer, $key); } return new FractalItem($response, $transformer, $key); } /** * Parse the includes. * * @param \Dingo\Api\Http\Request $request * * @return void */ public function parseFractalIncludes(Request $request) { $includes = $request->input($this->includeKey); if (! is_array($includes)) { $includes = array_map('trim', array_filter(explode($this->includeSeparator, $includes))); } $this->fractal->parseIncludes($includes); } /** * Get the underlying Fractal instance. * * @return \League\Fractal\Manager */ public function getFractal() { return $this->fractal; } /** * Get includes as their array keys for eager loading. * * @param \League\Fractal\TransformerAbstract $transformer * @param string|array $requestedIncludes * * @return array */ protected function mergeEagerLoads($transformer, $requestedIncludes) { $includes = array_merge($requestedIncludes, $transformer->getDefaultIncludes()); $eagerLoads = []; foreach ($includes as $key => $value) { $eagerLoads[] = is_string($key) ? $key : $value; } if (property_exists($transformer, 'lazyLoadedIncludes')) { $eagerLoads = array_diff($eagerLoads, $transformer->lazyLoadedIncludes); } return $eagerLoads; } /** * Disable eager loading. * * @return \Dingo\Api\Transformer\Adapter\Fractal */ public function disableEagerLoading() { $this->eagerLoading = false; return $this; } /** * Enable eager loading. * * @return \Dingo\Api\Transformer\Adapter\Fractal */ public function enableEagerLoading() { $this->eagerLoading = true; return $this; } } ================================================ FILE: src/Transformer/Binding.php ================================================ container = $container; $this->resolver = $resolver; $this->parameters = $parameters; $this->callback = $callback; } /** * Resolve a transformer binding instance. * * @throws \RuntimeException * * @return object */ public function resolveTransformer() { if (is_string($this->resolver)) { return $this->container->make($this->resolver); } elseif (is_callable($this->resolver)) { return call_user_func($this->resolver, $this->container); } elseif (is_object($this->resolver)) { return $this->resolver; } throw new RuntimeException('Unable to resolve transformer binding.'); } /** * Fire the binding callback. * * @param string|array $parameters * * @return void */ public function fireCallback($parameters = null) { if (is_callable($this->callback)) { call_user_func_array($this->callback, func_get_args()); } } /** * Get the binding parameters. * * @return array */ public function getParameters() { return $this->parameters; } /** * Set the meta data for the binding. * * @param array $meta * * @return void */ public function setMeta(array $meta) { $this->meta = $meta; } /** * Add a meta data key/value pair. * * @param string $key * @param mixed $value * * @return void */ public function addMeta($key, $value) { $this->meta[$key] = $value; } /** * Get the binding meta data. * * @return array */ public function getMeta() { return $this->meta; } } ================================================ FILE: src/Transformer/Factory.php ================================================ container = $container; $this->adapter = $adapter; } /** * Register a transformer binding resolver for a class. * * @param $class * @param $resolver * @param array $parameters * @param \Closure|null $after * * @return \Dingo\Api\Transformer\Binding */ public function register($class, $resolver, array $parameters = [], Closure $after = null) { return $this->bindings[$class] = $this->createBinding($resolver, $parameters, $after); } /** * Transform a response. * * @param string|object $response * * @return mixed */ public function transform($response) { $binding = $this->getBinding($response); return $this->adapter->transform($response, $binding->resolveTransformer(), $binding, $this->getRequest()); } /** * Determine if a response is transformable. * * @param mixed $response * * @return bool */ public function transformableResponse($response) { return $this->transformableType($response) && $this->hasBinding($response); } /** * Determine if a value is of a transformable type. * * @param mixed $value * * @return bool */ public function transformableType($value) { return is_object($value) || is_string($value); } /** * Get a registered transformer binding. * * @param string|object $class * * @throws \RuntimeException * * @return \Dingo\Api\Transformer\Binding */ public function getBinding($class) { if ($this->isCollection($class) && ! $class->isEmpty()) { return $this->getBindingFromCollection($class); } $class = is_object($class) ? get_class($class) : $class; if (! $this->hasBinding($class)) { throw new RuntimeException('Unable to find bound transformer for "'.$class.'" class.'); } return $this->bindings[$class]; } /** * Create a new binding instance. * * @param string|callable|object $resolver * @param array $parameters * @param \Closure $callback * * @return \Dingo\Api\Transformer\Binding */ protected function createBinding($resolver, array $parameters = [], Closure $callback = null) { return new Binding($this->container, $resolver, $parameters, $callback); } /** * Get a registered transformer binding from a collection of items. * * @param \Illuminate\Support\Collection $collection * * @return null|string|callable */ protected function getBindingFromCollection($collection) { return $this->getBinding($collection->first()); } /** * Determine if a class has a transformer binding. * * @param string|object $class * * @return bool */ protected function hasBinding($class) { if ($this->isCollection($class) && ! $class->isEmpty()) { $class = $class->first(); } $class = is_object($class) ? get_class($class) : $class; return isset($this->bindings[$class]); } /** * Determine if the instance is a collection. * * @param object $instance * * @return bool */ protected function isCollection($instance) { return $instance instanceof Collection || $instance instanceof Paginator; } /** * Get the array of registered transformer bindings. * * @return array */ public function getTransformerBindings() { return $this->bindings; } /** * Set the transformation layer at runtime. * * @param \Closure|\Dingo\Api\Contract\Transformer\Adapter $adapter * * @return void */ public function setAdapter($adapter) { if (is_callable($adapter)) { $adapter = call_user_func($adapter, $this->container); } $this->adapter = $adapter; } /** * Get the transformation layer adapter. * * @return \Dingo\Api\Contract\Transformer\Adapter */ public function getAdapter() { return $this->adapter; } /** * Get the request from the container. * * @return \Dingo\Api\Http\Request */ public function getRequest() { $request = $this->container['request']; if ($request instanceof IlluminateRequest && ! $request instanceof Request) { $request = (new Request())->createFromIlluminate($request); } return $request; } /** * Pass unknown method calls through to the adapter. * * @param string $method * @param array $parameters * * @return mixed */ public function __call($method, $parameters) { return call_user_func_array([$this->adapter, $method], $parameters); } } ================================================ FILE: src/helpers.php ================================================ version($version); } } ================================================ FILE: tests/Auth/AuthTest.php ================================================ container = new Container; $this->router = m::mock(Router::class); $this->auth = new Auth($this->router, $this->container, []); } public function testExceptionThrownWhenAuthorizationHeaderNotSet() { $this->expectException(UnauthorizedHttpException::class); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route = m::mock(Route::class)); $this->router->shouldReceive('getCurrentRequest')->once()->andReturn($request = Request::create('foo', 'GET')); $provider = m::mock(Provider::class); $provider->shouldReceive('authenticate')->once()->with($request, $route)->andThrow(new BadRequestHttpException); $this->auth->extend('provider', $provider); $this->auth->authenticate(); } public function testExceptionThrownWhenProviderFailsToAuthenticate() { $this->expectException(UnauthorizedHttpException::class); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route = m::mock(Route::class)); $this->router->shouldReceive('getCurrentRequest')->once()->andReturn($request = Request::create('foo', 'GET')); $provider = m::mock(Provider::class); $provider->shouldReceive('authenticate')->once()->with($request, $route)->andThrow(new UnauthorizedHttpException('foo')); $this->auth->extend('provider', $provider); $this->auth->authenticate(); } public function testAuthenticationIsSuccessfulAndUserIsSet() { $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route = m::mock(Route::class)); $this->router->shouldReceive('getCurrentRequest')->once()->andReturn($request = Request::create('foo', 'GET')); $provider = m::mock(Provider::class); $provider->shouldReceive('authenticate')->once()->with($request, $route)->andReturn((object) ['id' => 1]); $this->auth->extend('provider', $provider); $user = $this->auth->authenticate(); $this->assertSame(1, $user->id); } public function testProvidersAreFilteredWhenSpecificProviderIsRequested() { $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route = m::mock(Route::class)); $this->router->shouldReceive('getCurrentRequest')->once()->andReturn($request = Request::create('foo', 'GET')); $provider = m::mock(Provider::class); $provider->shouldReceive('authenticate')->once()->with($request, $route)->andReturn((object) ['id' => 1]); $this->auth->extend('one', m::mock(Provider::class)); $this->auth->extend('two', $provider); $this->auth->authenticate(['two']); $this->assertSame($provider, $this->auth->getProviderUsed()); } public function testGettingUserWhenNotAuthenticatedAttemptsToAuthenticateAndReturnsNull() { $this->router->shouldReceive('getCurrentRoute')->once()->andReturn(m::mock(Route::class)); $this->router->shouldReceive('getCurrentRequest')->once()->andReturn(Request::create('foo', 'GET')); $this->auth->extend('provider', m::mock(Provider::class)); $this->assertNull($this->auth->user()); } public function testGettingUserWhenAlreadyAuthenticatedReturnsUser() { $this->auth->setUser((object) ['id' => 1]); $this->assertSame(1, $this->auth->user()->id); $this->assertTrue($this->auth->check()); } } ================================================ FILE: tests/Auth/Provider/AuthorizationTest.php ================================================ expectException(BadRequestHttpException::class); $request = Request::create('GET', '/'); (new AuthorizationProviderStub)->authenticate($request, m::mock(Route::class)); } } ================================================ FILE: tests/Auth/Provider/BasicTest.php ================================================ auth = m::mock('Illuminate\Auth\AuthManager'); $this->provider = new Basic($this->auth); } public function testInvalidBasicCredentialsThrowsException() { $this->expectException(UnauthorizedHttpException::class); $request = Request::create('GET', '/', [], [], [], ['HTTP_AUTHORIZATION' => 'Basic 12345']); $this->auth->shouldReceive('onceBasic')->once()->with('email')->andReturn(new Response('', 401)); $this->provider->authenticate($request, m::mock(Route::class)); } public function testValidCredentialsReturnsUser() { $request = Request::create('GET', '/', [], [], [], ['HTTP_AUTHORIZATION' => 'Basic 12345']); $this->auth->shouldReceive('onceBasic')->once()->with('email')->andReturn(null); $this->auth->shouldReceive('user')->once()->andReturn('foo'); $this->assertSame('foo', $this->provider->authenticate($request, m::mock(Route::class))); } } ================================================ FILE: tests/Auth/Provider/JWTTest.php ================================================ auth = m::mock('Tymon\JWTAuth\JWTAuth'); $this->provider = new JWT($this->auth); } public function testValidatingAuthorizationHeaderFailsAndThrowsException() { $this->expectException(BadRequestHttpException::class); $request = Request::create('foo', 'GET'); $this->provider->authenticate($request, m::mock(Route::class)); } public function testAuthenticatingFailsAndThrowsException() { $this->expectException(UnauthorizedHttpException::class); $request = Request::create('foo', 'GET'); $request->headers->set('authorization', 'Bearer foo'); $this->auth->shouldReceive('setToken')->with('foo')->andReturn(m::self()); $this->auth->shouldReceive('authenticate')->once()->andThrow(new JWTException('foo')); $this->provider->authenticate($request, m::mock(Route::class)); } public function testAuthenticatingSucceedsAndReturnsUserObject() { $request = Request::create('foo', 'GET'); $request->headers->set('authorization', 'Bearer foo'); $this->auth->shouldReceive('setToken')->with('foo')->andReturn(m::self()); $this->auth->shouldReceive('authenticate')->once()->andReturn((object) ['id' => 1]); $this->assertSame(1, $this->provider->authenticate($request, m::mock(Route::class))->id); } public function testAuthenticatingWithQueryStringSucceedsAndReturnsUserObject() { $request = Request::create('foo', 'GET', ['token' => 'foo']); $this->auth->shouldReceive('setToken')->with('foo')->andReturn(m::self()); $this->auth->shouldReceive('authenticate')->once()->andReturn((object) ['id' => 1]); $this->assertSame(1, $this->provider->authenticate($request, m::mock(Route::class))->id); } } ================================================ FILE: tests/BaseTestCase.php ================================================ installed_file_path); $parsed_data = json_decode($contents, true); // Changed array format in newer versions of composer (v2?) if (array_key_exists('packages', $parsed_data)) { $parsed_data = $parsed_data['packages']; } // Find laravel/framework or lumen package $just_laravel = array_filter($parsed_data, function ($composerPackageData) { if (is_array($composerPackageData) && array_key_exists('name', $composerPackageData)) { if ('laravel/framework' === $composerPackageData['name'] || 'laravel/lumen-framework' === $composerPackageData['name']) { return true; } } }); if (empty($just_laravel)) { exit(PHP_EOL.'No Laravel version detected, please do a "composer require laravel/framework:x" prior to testing'.PHP_EOL); } $laravelVersion = array_map(function ($val) { return $val['version']; }, array_values($just_laravel))[0]; return $laravelVersion; } private function getApplicationStub() { $version = $this->getFrameworkVersion(); if ('dev-master' === $version) { $version = $this->current_release; } // Remove the 'v' in for example 'v5.8.3' $version = str_replace('v', '', $version); // Return the version stub for the right version if (version_compare($version, '8.0.0', '>=')) { return new Application8Stub; } elseif (version_compare($version, '7.0.0', '>=')) { return new Application7Stub; } elseif (version_compare($version, '6.0.0', '>=')) { return new Application6Stub; } elseif (version_compare($version, '5.8.0', '>=')) { return new Application58Stub; } else { return new ApplicationStub; } } } ================================================ FILE: tests/DispatcherTest.php ================================================ container = new Container; $this->container['request'] = Request::create('/', 'GET'); $this->container['api.auth'] = new MiddlewareStub; $this->container['api.limiting'] = new MiddlewareStub; Http\Request::setAcceptParser(new Http\Parser\Accept('vnd', 'api', 'v1', 'json')); $this->transformerFactory = new TransformerFactory($this->container, new TransformerStub); $this->adapter = new RoutingAdapterStub; $this->exception = m::mock(Handler::class); $this->router = new Router($this->adapter, $this->exception, $this->container, null, null); $this->auth = new Auth($this->router, $this->container, []); $this->dispatcher = new Dispatcher($this->container, new Filesystem, $this->router, $this->auth); app()->instance(\Illuminate\Routing\Router::class, $this->adapter); $this->dispatcher->setSubtype('api'); $this->dispatcher->setStandardsTree('vnd'); $this->dispatcher->setDefaultVersion('v1'); $this->dispatcher->setDefaultFormat('json'); Response::setFormatters(['json' => new Http\Response\Format\Json]); Response::setTransformer($this->transformerFactory); } public function testInternalRequests() { $this->router->version('v1', function () { $this->router->get('test', function () { return 'foo'; }); $this->router->post('test', function () { return 'bar'; }); $this->router->put('test', function () { return 'baz'; }); $this->router->patch('test', function () { return 'yin'; }); $this->router->delete('test', function () { return 'yang'; }); }); $this->assertSame('foo', $this->dispatcher->get('test')); $this->assertSame('bar', $this->dispatcher->post('test')); $this->assertSame('baz', $this->dispatcher->put('test')); $this->assertSame('yin', $this->dispatcher->patch('test')); $this->assertSame('yang', $this->dispatcher->delete('test')); } public function testInternalRequestWithVersionAndParameters() { $this->router->version('v1', function () { $this->router->get('test', function () { return 'test'; }); }); $this->assertSame('test', $this->dispatcher->version('v1')->with(['foo' => 'bar'])->get('test')); } public function testInternalRequestWithPrefix() { $this->router->version('v1', ['prefix' => 'baz'], function () { $this->router->get('test', function () { return 'test'; }); }); $this->assertSame('test', $this->dispatcher->get('baz/test')); $this->dispatcher->setPrefix('baz'); $this->assertSame('test', $this->dispatcher->get('test')); } public function testInternalRequestWithDomain() { $this->router->version('v1', ['domain' => 'foo.bar'], function () { $this->router->get('test', function () { return 'test'; }); }); $this->assertSame('test', $this->dispatcher->get('http://foo.bar/test')); $this->dispatcher->setDefaultDomain('foo.bar'); $this->assertSame('test', $this->dispatcher->get('test')); } public function testInternalRequestThrowsExceptionWhenResponseIsNotOkay() { $this->expectException(InternalHttpException::class); $this->router->version('v1', function () { $this->router->get('test', function () { return new \Illuminate\Http\Response('test', 403); }); }); $this->dispatcher->get('test'); } public function testInternalExceptionContainsResponseObject() { $this->router->version('v1', function () { $this->router->get('test', function () { return new \Illuminate\Http\Response('test', 401); }); }); try { $this->dispatcher->get('test'); } catch (InternalHttpException $exception) { $this->assertInstanceOf(\Illuminate\Http\Response::class, $exception->getResponse()); $this->assertSame('test', $exception->getResponse()->getContent()); } } public function testThrowingHttpExceptionFallsThroughRouter() { $this->router->version('v1', function () { $this->router->get('test', function () { throw new GoneHttpException; }); }); $passed = false; try { $this->dispatcher->get('test'); } catch (GoneHttpException $exception) { $passed = true; } $this->assertTrue($passed); } public function testPretendingToBeUserForSingleRequest() { $user = m::mock('Illuminate\Database\Eloquent\Model'); $this->router->version('v1', function () use ($user) { $this->router->get('test', function () use ($user) { $this->assertSame($user, $this->auth->user()); }); }); $this->dispatcher->be($user)->once()->get('test'); } public function testInternalRequestWithMultipleVersionsCallsCorrectVersion() { $this->router->version('v1', function () { $this->router->get('foo', function () { return 'foo'; }); }); $this->router->version(['v2', 'v3'], function () { $this->router->get('foo', function () { return 'bar'; }); }); $this->assertSame('foo', $this->dispatcher->version('v1')->get('foo')); $this->assertSame('bar', $this->dispatcher->version('v2')->get('foo')); $this->assertSame('bar', $this->dispatcher->version('v3')->get('foo')); } public function testInternalRequestWithNestedInternalRequest() { $this->router->version('v1', function () { $this->router->get('foo', function () { return 'foo'.$this->dispatcher->version('v2')->get('foo'); }); }); $this->router->version('v2', function () { $this->router->get('foo', function () { return 'bar'.$this->dispatcher->version('v3')->get('foo'); }); }); $this->router->version('v3', function () { $this->router->get('foo', function () { return 'baz'; }); }); $this->assertSame('foobarbaz', $this->dispatcher->get('foo')); } public function testRequestStackIsMaintained() { $this->router->version('v1', ['prefix' => 'api'], function () { $this->router->post('foo', function () { $this->assertSame('bar', $this->router->getCurrentRequest()->input('foo')); $this->dispatcher->with(['foo' => 'baz'])->post('api/bar'); $this->assertSame('bar', $this->router->getCurrentRequest()->input('foo')); }); $this->router->post('bar', function () { $this->assertSame('baz', $this->router->getCurrentRequest()->input('foo')); $this->dispatcher->with(['foo' => 'bazinga'])->post('api/baz'); $this->assertSame('baz', $this->router->getCurrentRequest()->input('foo')); }); $this->router->post('baz', function () { $this->assertSame('bazinga', $this->router->getCurrentRequest()->input('foo')); }); }); $this->dispatcher->with(['foo' => 'bar'])->post('api/foo'); } public function testRouteStackIsMaintained() { $this->router->version('v1', function () { $this->router->post('foo', ['as' => 'foo', function () { $this->assertSame('foo', $this->router->getCurrentRoute()->getName()); $this->dispatcher->post('bar'); $this->assertSame('foo', $this->router->getCurrentRoute()->getName()); }]); $this->router->post('bar', ['as' => 'bar', function () { $this->assertSame('bar', $this->router->getCurrentRoute()->getName()); $this->dispatcher->post('baz'); $this->assertSame('bar', $this->router->getCurrentRoute()->getName()); }]); $this->router->post('baz', ['as' => 'bazinga', function () { $this->assertSame('bazinga', $this->router->getCurrentRoute()->getName()); }]); }); $this->dispatcher->post('foo'); } public function testSendingJsonPayload() { $this->router->version('v1', function () { $this->router->post('foo', function () { $this->assertSame('jason', $this->router->getCurrentRequest()->json('username')); }); $this->router->post('bar', function () { $this->assertSame('mat', $this->router->getCurrentRequest()->json('username')); }); }); $this->dispatcher->json(['username' => 'jason'])->post('foo'); $this->dispatcher->json('{"username":"mat"}')->post('bar'); } public function testInternalRequestsToDifferentDomains() { $this->router->version(['v1', 'v2'], ['domain' => 'foo.bar'], function () { $this->router->get('foo', function () { return 'v1 and v2 on domain foo.bar'; }); }); $this->router->version('v1', ['domain' => 'foo.baz'], function () { $this->router->get('foo', function () { return 'v1 on domain foo.baz'; }); }); $this->router->version('v2', ['domain' => 'foo.baz'], function () { $this->router->get('foo', function () { return 'v2 on domain foo.baz'; }); }); $this->assertSame('v1 and v2 on domain foo.bar', $this->dispatcher->on('foo.bar')->version('v2')->get('foo')); $this->assertSame('v1 on domain foo.baz', $this->dispatcher->on('foo.baz')->get('foo')); $this->assertSame('v2 on domain foo.baz', $this->dispatcher->on('foo.baz')->version('v2')->get('foo')); } public function testRequestingRawResponse() { $this->router->version('v1', function () { $this->router->get('foo', function () { return ['foo' => 'bar']; }); }); $response = $this->dispatcher->raw()->get('foo'); $this->assertInstanceOf(Response::class, $response); $this->assertSame('{"foo":"bar"}', $response->getContent()); $this->assertSame(['foo' => 'bar'], $response->getOriginalContent()); } public function testRequestingRawResponseWithTransformers() { $instance = null; $this->router->version('v1', function () use (&$instance) { $this->router->get('foo', function () use (&$instance) { return $instance = new UserStub('Jason'); }); }); $this->transformerFactory->register(UserStub::class, UserTransformerStub::class); $response = $this->dispatcher->raw()->get('foo'); $this->assertInstanceOf(Response::class, $response); $this->assertSame('{"name":"Jason"}', $response->getContent()); $this->assertSame($instance, $response->getOriginalContent()); } public function testUsingRequestFacadeDoesNotCacheRequestInstance() { RequestFacade::setFacadeApplication($this->container); $this->router->version('v1', function () { $this->router->get('foo', function () { return RequestFacade::input('foo'); }); }); $this->assertNull(RequestFacade::input('foo')); $response = $this->dispatcher->with(['foo' => 'bar'])->get('foo'); $this->assertSame('bar', $response); $this->assertNull(RequestFacade::input('foo')); } public function testRedirectResponseThrowsException() { $this->router->version('v1', function () { $this->router->get('redirect', function () { return new RedirectResponse('redirect-test'); }); }); $response = $this->dispatcher->get('redirect'); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertSame('redirect-test', $response->getTargetUrl()); } public function testNotOkJsonResponseThrowsException() { $this->expectException(InternalHttpException::class); $this->router->version('v1', function () { $this->router->get('json', function () { return new JsonResponse(['is' => 'json'], 422); }); }); $this->dispatcher->get('json'); } public function testFormRequestValidationFailureThrowsValidationException() { $this->expectException(ValidationHttpException::class); $this->router->version('v1', function () { $this->router->get('fail', function () { //Mocking the form validation call is challenging at the moment, so next best thing throw new ValidationHttpException(['foo' => 'bar']); }); }); $this->dispatcher->get('fail'); } } ================================================ FILE: tests/Exception/HandlerTest.php ================================================ parentHandler = m::mock('Illuminate\Contracts\Debug\ExceptionHandler'); $this->exceptionHandler = new Handler($this->parentHandler, [ 'message' => ':message', 'errors' => ':errors', 'code' => ':code', 'status_code' => ':status_code', 'debug' => ':debug', ], false); } public function testRegisterExceptionHandler() { $this->exceptionHandler->register(function (HttpException $e) { // }); $this->assertArrayHasKey(HttpException::class, $this->exceptionHandler->getHandlers()); } public function testExceptionHandlerHandlesException() { $this->exceptionHandler->register(function (HttpException $e) { return new Response('foo', 404); }); $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $this->assertSame('foo', $response->getContent()); $this->assertSame(404, $response->getStatusCode()); $this->assertSame($exception, $response->exception); } public function testExceptionHandlerHandlesExceptionAndCreatesNewResponse() { $this->exceptionHandler->register(function (HttpException $e) { return 'foo'; }); $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(Response::class, $response); $this->assertSame('foo', $response->getContent()); $this->assertSame(404, $response->getStatusCode()); } public function testExceptionHandlerHandlesExceptionWithRedirectResponse() { $this->exceptionHandler->register(function (HttpException $e) { return new RedirectResponse('foo'); }); $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertSame('foo', $response->getTargetUrl()); $this->assertSame(302, $response->getStatusCode()); } public function testExceptionHandlerHandlesExceptionWithJsonResponse() { $this->exceptionHandler->register(function (HttpException $e) { return new JsonResponse(['foo' => 'bar'], 404); }); $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(JsonResponse::class, $response); $this->assertSame('{"foo":"bar"}', $response->getContent()); $this->assertSame(404, $response->getStatusCode()); } public function testExceptionHandlerReturnsGenericWhenNoMatchingHandler() { $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(Response::class, $response); $this->assertSame('{"message":"bar","status_code":404}', $response->getContent()); $this->assertSame(404, $response->getStatusCode()); } public function testUsingMultidimensionalArrayForGenericResponse() { $this->exceptionHandler->setErrorFormat([ 'error' => [ 'message' => ':message', 'errors' => ':errors', 'code' => ':code', 'status_code' => ':status_code', 'debug' => ':debug', ], ]); $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(Response::class, $response); $this->assertSame('{"error":{"message":"bar","status_code":404}}', $response->getContent()); $this->assertSame(404, $response->getStatusCode()); } public function testRegularExceptionsAreHandledByGenericHandler() { $exception = new RuntimeException('Uh oh'); $response = $this->exceptionHandler->handle($exception); $this->assertSame('{"message":"Uh oh","status_code":500}', $response->getContent()); $this->assertSame(500, $response->getStatusCode()); $this->assertSame($exception, $response->exception); } public function testResourceExceptionErrorsAreIncludedInResponse() { $exception = new ResourceException('bar', ['foo' => 'bar'], null, [], 10); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(Response::class, $response); $this->assertSame('{"message":"bar","errors":{"foo":["bar"]},"code":10,"status_code":422}', $response->getContent()); $this->assertSame(422, $response->getStatusCode()); } public function testExceptionTraceIncludedInResponse() { $this->exceptionHandler->setDebug(true); $exception = new HttpException(404, 'bar'); $response = $this->exceptionHandler->handle($exception); $object = json_decode($response->getContent()); $this->assertObjectHasAttribute('debug', $object); } public function testHttpExceptionsWithNoMessageUseStatusCodeMessage() { $exception = new HttpException(404); $response = $this->exceptionHandler->handle($exception); $this->assertInstanceOf(Response::class, $response); $this->assertSame('{"message":"404 Not Found","status_code":404}', $response->getContent()); $this->assertSame(404, $response->getStatusCode()); } public function testExceptionsHandledByRenderAreReroutedThroughHandler() { $request = ApiRequest::create('foo', 'GET'); $exception = new HttpException(404); $response = $this->exceptionHandler->render($request, $exception); $this->assertSame('{"message":"404 Not Found","status_code":404}', $response->getContent()); } public function testSettingUserDefinedReplacements() { $this->exceptionHandler->setReplacements([':foo' => 'bar']); $this->exceptionHandler->setErrorFormat(['bing' => ':foo']); $exception = new HttpException(404); $response = $this->exceptionHandler->handle($exception); $this->assertSame('{"bing":"bar"}', $response->getContent()); } } ================================================ FILE: tests/Http/Middleware/AuthTest.php ================================================ container = new Container; $this->adapter = new RoutingAdapterStub; $this->router = m::mock(Router::class); $this->auth = m::mock(Auth::class); $this->middleware = new AuthMiddleware($this->router, $this->auth); } public function testProtectedRouteFiresAuthenticationAndPasses() { $request = Request::create('test', 'GET'); $route = new Route($this->adapter, $this->container, $request, new IlluminateRoute('GET', '/test', [ 'providers' => [], ])); $this->auth->shouldReceive('check')->once()->with(false)->andReturn(false); $this->auth->shouldReceive('authenticate')->once()->with([])->andReturn(null); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $this->middleware->handle($request, function ($handledRequest) use ($request) { $this->assertSame($handledRequest, $request); }); } public function testProtectedRouteAlreadyLoggedIn() { $request = Request::create('test', 'GET'); $route = new Route($this->adapter, $this->container, $request, new IlluminateRoute('GET', '/test', [ 'providers' => [], ])); $this->auth->shouldReceive('check')->once()->with(false)->andReturn(true); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $this->middleware->handle($request, function ($handledRequest) use ($request) { $this->assertSame($handledRequest, $request); }); } public function testAuthenticationFailsAndExceptionIsThrown() { $this->expectException(UnauthorizedHttpException::class); $exception = new UnauthorizedHttpException('test'); $request = Request::create('test', 'GET'); $route = new Route($this->adapter, $this->container, $request, new IlluminateRoute('GET', '/test', [ 'providers' => [], ])); $this->auth->shouldReceive('check')->once()->with(false)->andReturn(false); $this->auth->shouldReceive('authenticate')->once()->with([])->andThrow($exception); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $this->middleware->handle($request, function () { // }); } } ================================================ FILE: tests/Http/Middleware/RateLimitTest.php ================================================ container = new Container; $this->container['config'] = ['cache.default' => 'array', 'cache.stores.array' => ['driver' => 'array']]; $this->router = m::mock(Router::class); $this->cache = new CacheManager($this->container); $this->handler = new Handler($this->container, $this->cache, []); $this->middleware = new RateLimit($this->router, $this->handler); $this->handler->setRateLimiter(function ($container, $request) { return $request->getClientIp(); }); } public function testMiddlewareBypassesRequestsWithNoRateLimiting() { $request = Request::create('test', 'GET'); $route = m::mock(Route::class); $route->shouldReceive('hasThrottle')->once()->andReturn(false); $route->shouldReceive('getRateLimit')->once()->andReturn(0); $route->shouldReceive('getRateLimitExpiration')->once()->andReturn(0); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $this->handler->extend(new ThrottleStub([], false)); $response = $this->middleware->handle($request, function ($request) { return new Response('foo'); }); $this->assertSame('foo', $response->getContent()); $this->assertArrayNotHasKey('x-ratelimit-limit', $response->headers->all()); $this->assertArrayNotHasKey('x-ratelimit-remaining', $response->headers->all()); $this->assertArrayNotHasKey('x-ratelimit-reset', $response->headers->all()); } public function testMiddlewareBypassesInternalRequest() { $request = InternalRequest::create('test', 'GET'); $route = m::mock(Route::class); $route->shouldReceive('hasThrottle')->never(); $route->shouldReceive('getRateLimit')->never(); $route->shouldReceive('getRateLimitExpiration')->never(); $this->router->shouldReceive('getCurrentRoute')->never(); $this->handler->extend(new ThrottleStub([], false)); $response = $this->middleware->handle($request, function ($request) { return new Response('foo'); }); $this->assertSame('foo', $response->getContent()); $this->assertArrayNotHasKey('x-ratelimit-limit', $response->headers->all()); $this->assertArrayNotHasKey('x-ratelimit-remaining', $response->headers->all()); $this->assertArrayNotHasKey('x-ratelimit-reset', $response->headers->all()); } public function testRateLimitingPassesAndResponseHeadersAreSet() { $request = Request::create('test', 'GET'); $route = m::mock(Route::class); $route->shouldReceive('hasThrottle')->once()->andReturn(false); $route->shouldReceive('getRateLimit')->once()->andReturn(0); $route->shouldReceive('getRateLimitExpiration')->once()->andReturn(0); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $this->handler->extend(new ThrottleStub(['limit' => 1])); $response = $this->middleware->handle($request, function ($request) { return new Response('foo'); }); $this->assertSame('foo', $response->getContent()); $this->assertArrayHasKey('x-ratelimit-limit', $response->headers->all()); $this->assertArrayHasKey('x-ratelimit-remaining', $response->headers->all()); $this->assertArrayHasKey('x-ratelimit-reset', $response->headers->all()); } public function testRateLimitingFailsAndHeadersAreSetOnException() { $request = Request::create('test', 'GET'); $route = m::mock(Route::class); $route->shouldReceive('hasThrottle')->once()->andReturn(false); $route->shouldReceive('getRateLimit')->once()->andReturn(0); $route->shouldReceive('getRateLimitExpiration')->once()->andReturn(0); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $this->handler->extend(new ThrottleStub(['limit' => 0])); try { $this->middleware->handle($request, function ($request) { return new Response('foo'); }); } catch (HttpException $exception) { $this->assertInstanceOf(RateLimitExceededException::class, $exception); $this->assertSame(429, $exception->getStatusCode()); $this->assertSame('You have exceeded your rate limit.', $exception->getMessage()); $headers = $exception->getHeaders(); $this->assertSame($headers['X-RateLimit-Reset'] - time(), $headers['Retry-After']); $this->assertArrayHasKey('X-RateLimit-Limit', $headers); $this->assertArrayHasKey('X-RateLimit-Remaining', $headers); $this->assertArrayHasKey('X-RateLimit-Reset', $headers); } } public function testRateLimitingWithLimitsSetOnRoute() { $request = Request::create('test', 'GET'); $route = m::mock(Route::class); $route->shouldReceive('hasThrottle')->once()->andReturn(false); $route->shouldReceive('getRateLimit')->once()->andReturn(5); $route->shouldReceive('getRateLimitExpiration')->once()->andReturn(10); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $response = $this->middleware->handle($request, function ($request) { return new Response('foo'); }); $this->assertArrayHasKey('x-ratelimit-limit', $response->headers->all()); $this->assertArrayHasKey('x-ratelimit-remaining', $response->headers->all()); $this->assertArrayHasKey('x-ratelimit-reset', $response->headers->all()); $this->assertEquals(4, $response->headers->get('x-ratelimit-remaining')); $this->assertEquals(5, $response->headers->get('x-ratelimit-limit')); } public function testRateLimitingWithRouteThrottle() { $request = Request::create('test', 'GET'); $route = m::mock(Route::class); $route->shouldReceive('hasThrottle')->once()->andReturn(true); $route->shouldReceive('getThrottle')->once()->andReturn(new ThrottleStub(['limit' => 10, 'expires' => 20])); $route->shouldReceive('getRateLimit')->once()->andReturn(0); $route->shouldReceive('getRateLimitExpiration')->once()->andReturn(0); $this->router->shouldReceive('getCurrentRoute')->once()->andReturn($route); $response = $this->middleware->handle($request, function ($request) { return new Response('foo'); }); $this->assertArrayHasKey('x-ratelimit-limit', $response->headers->all()); $this->assertArrayHasKey('x-ratelimit-remaining', $response->headers->all()); $this->assertArrayHasKey('x-ratelimit-reset', $response->headers->all()); $this->assertEquals(9, $response->headers->get('x-ratelimit-remaining')); $this->assertEquals(10, $response->headers->get('x-ratelimit-limit')); } } ================================================ FILE: tests/Http/Middleware/RequestTest.php ================================================ app = $this->getApplicationStub(); $this->router = m::mock(Router::class); $this->validator = new RequestValidator($this->app); $this->handler = m::mock(Handler::class); $this->events = new EventDispatcher($this->app); $this->app->alias(Request::class, RequestContract::class); $this->middleware = new RequestMiddleware($this->app, $this->handler, $this->router, $this->validator, $this->events); } public function testNoPrefixOrDomainDoesNotMatch() { $this->app[Domain::class] = new Validation\Domain(null); $this->app[Prefix::class] = new Validation\Prefix(null); $this->app[Accept::class] = new Validation\Accept(new AcceptParser('vnd', 'api', 'v1', 'json')); $request = Request::create('foo', 'GET'); $this->middleware->handle($request, function ($handled) use ($request) { $this->assertSame($handled, $request); }); } public function testPrefixMatchesAndSendsRequestThroughRouter() { $this->app[Domain::class] = new Validation\Domain(null); $this->app[Prefix::class] = new Validation\Prefix('/'); $this->app[Accept::class] = new Validation\Accept(new AcceptParser('vnd', 'api', 'v1', 'json')); $request = IlluminateRequest::create('foo', 'GET'); $this->router->shouldReceive('dispatch')->once(); $this->middleware->handle($request, function () { // }); $this->app[Domain::class] = new Validation\Domain(null); $this->app[Prefix::class] = new Validation\Prefix('bar'); $this->app[Accept::class] = new Validation\Accept(new AcceptParser('vnd', 'api', 'v1', 'json')); $request = IlluminateRequest::create('bar/foo', 'GET'); $this->router->shouldReceive('dispatch')->once(); $this->middleware->handle($request, function () { // }); $request = IlluminateRequest::create('bing/bar/foo', 'GET'); $this->middleware->handle($request, function ($handled) use ($request) { $this->assertSame($handled, $request); }); } public function testDomainMatchesAndSendsRequestThroughRouter() { $this->app[Domain::class] = new Validation\Domain('foo.bar'); $this->app[Prefix::class] = new Validation\Prefix(null); $this->app[Accept::class] = new Validation\Accept(new AcceptParser('vnd', 'api', 'v1', 'json')); $request = IlluminateRequest::create('http://foo.bar/baz', 'GET'); $this->router->shouldReceive('dispatch')->once(); $this->middleware->handle($request, function () { // }); $request = IlluminateRequest::create('http://bing.foo.bar/baz', 'GET'); $this->middleware->handle($request, function ($handled) use ($request) { $this->assertSame($handled, $request); }); } } ================================================ FILE: tests/Http/Parser/AcceptTest.php ================================================ parse($this->createRequest('foo', 'GET', ['accept' => 'application/vnd.foo.v2+xml'])); $this->assertSame('api', $accept['subtype']); $this->assertSame('v1', $accept['version']); $this->assertSame('json', $accept['format']); } public function testStrictlyParsingInvalidAcceptHeaderThrowsException() { $this->expectException(BadRequestHttpException::class); $this->expectExceptionMessage('Accept header could not be properly parsed because of a strict matching process.'); $parser = new Accept('vnd', 'api', 'v1', 'json'); $parser->parse($this->createRequest('foo', 'GET', ['accept' => 'application/vnd.foo.v2+xml']), true); } public function testParsingValidAcceptReturnsHeaderValues() { $parser = new Accept('vnd', 'api', 'v1', 'json'); $accept = $parser->parse($this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v2+xml'])); $this->assertSame('api', $accept['subtype']); $this->assertSame('v2', $accept['version']); $this->assertSame('xml', $accept['format']); } public function testApiVersionWithoutVSuffix() { $parser = new Accept('vnd', 'api', '1.0', 'json'); $accept = $parser->parse($this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.1.0+xml'])); $this->assertSame('api', $accept['subtype']); $this->assertSame('1.0', $accept['version']); $this->assertSame('xml', $accept['format']); } public function testApiVersionWithHyphen() { $parser = new Accept('vnd', 'api', '1.0-beta', 'json'); $accept = $parser->parse($this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.1.0-beta+xml'])); $this->assertSame('api', $accept['subtype']); $this->assertSame('1.0-beta', $accept['version']); $this->assertSame('xml', $accept['format']); } protected function createRequest($uri, $method, array $headers = []) { $request = Request::create($uri, $method); foreach ($headers as $key => $value) { $request->headers->set($key, $value); } return $request; } } ================================================ FILE: tests/Http/RateLimit/HandlerTest.php ================================================ container = new Container; $this->container['config'] = ['cache.default' => 'array', 'cache.stores.array' => ['driver' => 'array']]; $this->cache = new CacheManager($this->container); $this->limiter = new Handler($this->container, $this->cache, []); $this->limiter->setRateLimiter(function ($container, $request) { return $request->getClientIp(); }); } public function testSettingSpecificLimitsOnRouteUsesRouteSpecificThrottle() { $this->limiter->rateLimitRequest(Request::create('test', 'GET'), 100, 100); $throttle = $this->limiter->getThrottle(); $this->assertInstanceOf(Route::class, $throttle); $this->assertSame(100, $throttle->getLimit()); $this->assertSame(100, $throttle->getExpires()); } public function testThrottleWithHighestAmountOfRequestsIsUsedWhenMoreThanOneMatchingThrottle() { $this->limiter->extend($first = new ThrottleStub(['limit' => 100, 'expires' => 200])); $this->limiter->extend($second = new ThrottleStub(['limit' => 99, 'expires' => 400])); $this->limiter->rateLimitRequest(Request::create('test', 'GET')); $this->assertSame($first, $this->limiter->getThrottle()); } public function testExceedingOfRateLimit() { $request = Request::create('test', 'GET'); $this->limiter->rateLimitRequest($request); $this->assertFalse($this->limiter->exceededRateLimit()); $this->limiter->extend(new ThrottleStub(['limit' => 1, 'expires' => 200])); $this->limiter->rateLimitRequest($request); $this->assertFalse($this->limiter->exceededRateLimit()); $this->limiter->rateLimitRequest($request); $this->assertTrue($this->limiter->exceededRateLimit()); } public function testGettingTheRemainingLimit() { $this->limiter->extend(new ThrottleStub(['limit' => 10, 'expires' => 200])); $this->limiter->rateLimitRequest(Request::create('test', 'GET')); $this->assertSame(9, $this->limiter->getRemainingLimit()); } } ================================================ FILE: tests/Http/RateLimit/Throttle/AuthenticatedTest.php ================================================ shouldReceive('check')->once()->andReturn(true)->getMock(); $container = new Container; $container['api.auth'] = $auth; $this->assertTrue((new Authenticated)->match($container)); } } ================================================ FILE: tests/Http/RateLimit/Throttle/UnauthenticatedTest.php ================================================ shouldReceive('check')->once()->andReturn(true)->getMock(); $container = new Container; $container['api.auth'] = $auth; $this->assertFalse((new Unauthenticated)->match($container)); } } ================================================ FILE: tests/Http/RequestValidatorTest.php ================================================ container = new Container; $this->container->instance(AcceptParser::class, new AcceptParser('vnd', 'test', 'v1', 'json')); $this->validator = new RequestValidator($this->container); } public function testValidationFailsWithNoValidators() { $this->validator->replace([]); $this->assertFalse($this->validator->validateRequest(Request::create('foo', 'GET')), 'Validation passed when there were no validators.'); } public function testValidationFails() { $this->validator->replace([HttpValidatorStub::class]); $this->assertFalse($this->validator->validateRequest(Request::create('foo', 'GET')), 'Validation passed when given a GET request.'); } public function testValidationPasses() { $this->validator->replace([HttpValidatorStub::class]); $this->assertTrue($this->validator->validateRequest(Request::create('foo', 'POST')), 'Validation failed when given a POST request.'); } } ================================================ FILE: tests/Http/Response/FactoryTest.php ================================================ transformer = Mockery::mock(TransformerFactory::class); $this->factory = new Factory($this->transformer); } public function testMakingACreatedResponse() { $response = $this->factory->created(); $responseWithLocation = $this->factory->created('test'); $this->assertSame($response->getStatusCode(), 201); $this->assertFalse($response->headers->has('Location')); $this->assertSame($responseWithLocation->getStatusCode(), 201); $this->assertTrue($responseWithLocation->headers->has('Location')); $this->assertSame($responseWithLocation->headers->get('Location'), 'test'); } public function testMakingAnAcceptedResponse() { $response = $this->factory->accepted(); $responseWithLocation = $this->factory->accepted('testHeader'); $responseWithContent = $this->factory->accepted(null, 'testContent'); $responseWithBoth = $this->factory->accepted('testHeader', 'testContent'); $this->assertSame($response->getStatusCode(), 202); $this->assertFalse($response->headers->has('Location')); $this->assertSame('', $response->getContent()); $this->assertSame($responseWithLocation->getStatusCode(), 202); $this->assertTrue($responseWithLocation->headers->has('Location')); $this->assertSame($responseWithLocation->headers->get('Location'), 'testHeader'); $this->assertSame('', $responseWithLocation->getContent()); $this->assertSame($responseWithContent->getStatusCode(), 202); $this->assertFalse($responseWithContent->headers->has('Location')); $this->assertSame('testContent', $responseWithContent->getContent()); $this->assertSame($responseWithBoth->getStatusCode(), 202); $this->assertTrue($responseWithBoth->headers->has('Location')); $this->assertSame($responseWithBoth->headers->get('Location'), 'testHeader'); $this->assertSame('testContent', $responseWithBoth->getContent()); } public function testMakingANoContentResponse() { $response = $this->factory->noContent(); $this->assertSame(204, $response->getStatusCode()); $this->assertSame('', $response->getContent()); $this->assertNull($response->headers->get('Content-Type')); } public function testMakingCollectionRegistersUnderlyingClassWithTransformer() { $this->transformer->shouldReceive('register')->twice()->with(UserStub::class, 'test', [], null); $this->assertInstanceOf(Collection::class, $this->factory->collection(new Collection([new UserStub('Jason')]), 'test')->getOriginalContent()); $this->assertInstanceOf(Collection::class, $this->factory->withCollection(new Collection([new UserStub('Jason')]), 'test')->getOriginalContent()); } public function testMakingCollectionResponseWithThreeParameters() { $this->transformer->shouldReceive('register')->twice()->with(UserStub::class, 'test', [], Mockery::on(function ($param) { return $param instanceof Closure; })); $this->assertInstanceOf(Collection::class, $this->factory->collection(new Collection([new UserStub('Jason')]), 'test', function ($resource, $fractal) { $this->assertInstanceOf(\League\Fractal\Resource\Collection::class, $resource); $this->assertInstanceOf(Manager::class, $fractal); })->getOriginalContent()); $this->assertInstanceOf(Collection::class, $this->factory->withCollection(new Collection([new UserStub('Jason')]), 'test', function ($resource, $fractal) { $this->assertInstanceOf(\League\Fractal\Resource\Collection::class, $resource); $this->assertInstanceOf(Manager::class, $fractal); })->getOriginalContent()); } public function testMakingItemsRegistersClassWithTransformer() { $this->transformer->shouldReceive('register')->twice()->with(UserStub::class, 'test', [], null); $this->assertInstanceOf(UserStub::class, $this->factory->item(new UserStub('Jason'), 'test')->getOriginalContent()); $this->assertInstanceOf(UserStub::class, $this->factory->withItem(new UserStub('Jason'), 'test')->getOriginalContent()); } public function testMakingItemResponseWithThreeParameters() { $this->transformer->shouldReceive('register')->twice()->with(UserStub::class, 'test', [], Mockery::on(function ($param) { return $param instanceof Closure; })); $this->assertInstanceOf(UserStub::class, $this->factory->item(new UserStub('Jason'), 'test', function ($resource, $fractal) { $this->assertInstanceOf(Item::class, $resource); $this->assertInstanceOf(Manager::class, $fractal); })->getOriginalContent()); $this->assertInstanceOf(UserStub::class, $this->factory->withItem(new UserStub('Jason'), 'test', function ($resource, $fractal) { $this->assertInstanceOf(Item::class, $resource); $this->assertInstanceOf(Manager::class, $fractal); })->getOriginalContent()); } public function testMakingPaginatorRegistersUnderlyingClassWithTransformer() { $this->transformer->shouldReceive('register')->twice()->with(UserStub::class, 'test', [], null); $this->assertInstanceOf(Paginator::class, $this->factory->paginator(new Paginator([new UserStub('Jason')], 1), 'test')->getOriginalContent()); $this->assertInstanceOf(Paginator::class, $this->factory->withPaginator(new Paginator([new UserStub('Jason')], 1), 'test')->getOriginalContent()); } public function testNotFoundThrowsHttpException() { $this->expectException(HttpException::class); $this->factory->errorNotFound(); } public function testBadRequestThrowsHttpException() { $this->expectException(HttpException::class); $this->factory->errorBadRequest(); } public function testForbiddenThrowsHttpException() { $this->expectException(HttpException::class); $this->factory->errorForbidden(); } public function testInternalThrowsHttpException() { $this->expectException(HttpException::class); $this->factory->errorInternal(); } public function testUnauthorizedThrowsHttpException() { $this->expectException(HttpException::class); $this->factory->errorUnauthorized(); } public function testMethodNotAllowedThrowsHttpException() { $this->expectException(HttpException::class); $this->factory->errorMethodNotAllowed(); } public function testMakingArrayResponse() { $response = $this->factory->array(['foo' => 'bar']); $this->assertSame('{"foo":"bar"}', $response->getContent()); } public function testPrefixingWithCallsMethodsCorrectly() { $response = $this->factory->withArray(['foo' => 'bar']); $this->assertSame('{"foo":"bar"}', $response->getContent()); } } ================================================ FILE: tests/Http/Response/Format/ExpectedPrettyPrintedJson/testMorphingArrayWithEightSpacesPrettyPrintIndent.json.php ================================================ new Json]); } public function tearDown(): void { parent::tearDown(); EloquentModelStub::$snakeAttributes = true; } /* * Read expected pretty printed JSON string from external file. * * JSON strings, that are expected for assertion in each test, are placed * in separate files to avoid littering tests and available on demand. * All the filenames are the same as the tests they associated to. * * @return string */ private function getExpectedPrettyPrintedJson($testMethodName) { return require __DIR__.DIRECTORY_SEPARATOR. 'ExpectedPrettyPrintedJson'.DIRECTORY_SEPARATOR. $testMethodName.'.json.php'; } public function testMorphingEloquentModel() { $response = (new Response(new EloquentModelStub))->morph(); $this->assertSame('{"foo_bar":{"foo":"bar"}}', $response->getContent()); } public function testMorphingEloquentCollection() { $response = (new Response(new Collection([new EloquentModelStub, new EloquentModelStub])))->morph(); $this->assertSame('{"foo_bars":[{"foo":"bar"},{"foo":"bar"}]}', $response->getContent()); } public function testMorphingEmptyEloquentCollection() { $response = (new Response(new Collection))->morph(); $this->assertSame('[]', $response->getContent()); } public function testMorphingString() { $response = (new Response('foo'))->morph(); $this->assertSame('foo', $response->getContent()); } public function testMorphingArray() { $messages = new MessageBag(['foo' => 'bar']); $response = (new Response(['foo' => 'bar', 'baz' => $messages]))->morph(); $this->assertSame('{"foo":"bar","baz":{"foo":["bar"]}}', $response->getContent()); } public function testMorphingUnknownType() { $this->assertSame(1, (new Response(1))->morph()->getContent()); } public function testMorphingEloquentModelWithCamelCasing() { EloquentModelStub::$snakeAttributes = false; $response = (new Response(new EloquentModelStub))->morph(); $this->assertSame('{"fooBar":{"foo":"bar"}}', $response->getContent()); } public function testMorphingEloquentCollectionWithCamelCasing() { EloquentModelStub::$snakeAttributes = false; $response = (new Response(new Collection([new EloquentModelStub, new EloquentModelStub])))->morph(); $this->assertSame('{"fooBars":[{"foo":"bar"},{"foo":"bar"}]}', $response->getContent()); } public function testMorphingArrayWithTwoSpacesPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'space', 'indent_size' => 2, ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJson(__FUNCTION__), $response->getContent()); } public function testMorphingArrayWithFourSpacesPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'space', 'indent_size' => 4, ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJson(__FUNCTION__), $response->getContent()); } public function testMorphingArrayWithEightSpacesPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'space', 'indent_size' => 8, ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJson(__FUNCTION__), $response->getContent()); } public function testMorphingArrayWithOneTabPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'tab', ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJson(__FUNCTION__), $response->getContent()); } } ================================================ FILE: tests/Http/Response/Format/JsonpTest.php ================================================ setRequest(Request::create('GET', '/', ['callback' => 'foo'])); Response::setFormatters(['json' => $formatter]); } /* * Read expected pretty printed JSONP string from external file. * * JSONP strings, that are expected for assertion in each test, are placed * in separate files to avoid littering tests and available on demand. * All the filenames are the same as the tests they associated to. * * @return string */ private function getExpectedPrettyPrintedJsonp($testMethodName) { return require __DIR__.DIRECTORY_SEPARATOR. 'ExpectedPrettyPrintedJsonp'.DIRECTORY_SEPARATOR. $testMethodName.'.jsonp.php'; } public function testMorphingEloquentModel() { $response = (new Response(new EloquentModelStub))->morph(); $this->assertSame('foo({"foo_bar":{"foo":"bar"}});', $response->getContent()); } public function testMorphingEloquentCollection() { $response = (new Response(new Collection([new EloquentModelStub, new EloquentModelStub])))->morph(); $this->assertSame('foo({"foo_bars":[{"foo":"bar"},{"foo":"bar"}]});', $response->getContent()); } public function testMorphingEmptyEloquentCollection() { $response = (new Response(new Collection))->morph(); $this->assertSame('foo([]);', $response->getContent()); } public function testMorphingString() { $response = (new Response('foo'))->morph(); $this->assertSame('foo', $response->getContent()); } public function testMorphingArray() { $messages = new MessageBag(['foo' => 'bar']); $response = (new Response(['foo' => 'bar', 'baz' => $messages]))->morph(); $this->assertSame('foo({"foo":"bar","baz":{"foo":["bar"]}});', $response->getContent()); } public function testMorphingUnknownType() { $this->assertSame(1, (new Response(1))->morph()->getContent()); } public function testMorphingArrayWithOneTabPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'tab', ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJsonp(__FUNCTION__), $response->getContent()); } public function testMorphingArrayWithTwoSpacesPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'space', 'indent_size' => 2, ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJsonp(__FUNCTION__), $response->getContent()); } public function testMorphingArrayWithFourSpacesPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'space', 'indent_size' => 4, ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJsonp(__FUNCTION__), $response->getContent()); } public function testMorphingArrayWithEightSpacesPrettyPrintIndent() { $options = [ 'json' => [ 'pretty_print' => true, 'indent_style' => 'space', 'indent_size' => 8, ], ]; Response::setFormatsOptions($options); $array = ['foo' => 'bar', 'baz' => ['foobar' => [42, 0.00042, '', null]]]; $response = (new Response($array))->morph(); $this->assertSame($this->getExpectedPrettyPrintedJsonp(__FUNCTION__), $response->getContent()); } } ================================================ FILE: tests/Http/ResponseTest.php ================================================ events = new EventDispatcher); } public function testGettingInvalidFormatterThrowsException() { $this->expectException(NotAcceptableHttpException::class); $this->expectExceptionMessage('Unable to format response according to Accept header.'); Response::getFormatter('json'); } public function testNonCastableObjectsSetAsOriginalContent() { $object = new StdClass; $object->id = 'test'; $response = new Response($object); $this->assertNull($response->getContent()); $this->assertSame($object, $response->getOriginalContent()); } public function testAddingAndSettingMetaCallsUnderlyingTransformerBinding() { $binding = new Binding(m::mock(Container::class), 'foo'); $response = new Response('test', 200, [], $binding); $response->setMeta(['foo' => 'bar']); $response->meta('bing', 'bang'); $this->assertSame(['foo' => 'bar', 'bing' => 'bang'], $response->getMeta()); } public function testBuildingWithCustomStatusCodeAndHeaders() { $response = new Response('test'); $response->statusCode(302); $response->header('Foo', 'Bar'); $this->assertSame('Bar', $response->headers->get('Foo')); $this->assertSame(302, $response->getStatusCode()); } public function testChangingContentWithEvents() { $this->events->listen(ResponseWasMorphed::class, function ($event) { $event->content['foo'] = 'bam!'; }); Response::addFormatter('json', new Json); $response = new Response(['foo' => 'bar']); $this->assertSame('{"foo":"bam!"}', $response->morph('json')->getContent()); $this->events->forget(ResponseWasMorphed::class); } public function testChangingResponseHeadersWithEvents() { $this->events->listen(ResponseIsMorphing::class, function ($event) { $event->response->headers->set('x-foo', 'bar'); }); Response::addFormatter('json', new Json); $response = new Response(['foo' => 'bar']); $this->assertSame('bar', $response->morph('json')->headers->get('x-foo')); $this->events->forget(ResponseIsMorphing::class); } } ================================================ FILE: tests/Http/Validation/AcceptTest.php ================================================ assertTrue($validator->validate(Request::create('bar', 'OPTIONS')), 'Validation failed when it should have passed with an options request.'); } } ================================================ FILE: tests/Http/Validation/DomainTest.php ================================================ assertFalse($validator->validate(Request::create('http://bar.foo', 'GET')), 'Validation passed when it should have failed with an invalid domain.'); $validator = new Domain(null); $this->assertFalse($validator->validate(Request::create('http://bar.foo', 'GET')), 'Validation passed when it should have failed with a null domain.'); } public function testValidationPasses() { $validator = new Domain('http://foo.bar'); $this->assertTrue($validator->validate(Request::create('http://foo.bar', 'GET')), 'Validation failed when it should have passed with a valid domain.'); } public function testValidationPassesWithDifferentProtocols() { $validator = new Domain('ftp://foo.bar'); $this->assertTrue($validator->validate(Request::create('http://foo.bar', 'GET')), 'Validation failed when it should have passed with a valid domain.'); $validator = new Domain('https://foo.bar'); $this->assertTrue($validator->validate(Request::create('http://foo.bar', 'GET')), 'Validation failed when it should have passed with a valid domain.'); } public function testValidationPassesWithPortOnDomain() { $validator = new Domain('http://foo.bar:8888'); $this->assertTrue($validator->validate(Request::create('http://foo.bar', 'GET')), 'Validation failed when it should have passed with a valid domain.'); } public function testValidationPassesWithPortOnRequest() { $validator = new Domain('http://foo.bar'); $this->assertTrue($validator->validate(Request::create('http://foo.bar:8888', 'GET')), 'Validation failed when it should have passed with a valid domain.'); } public function testValidationPassesWithPortOnDomainAndRequest() { $validator = new Domain('http://foo.bar:8888'); $this->assertTrue($validator->validate(Request::create('http://foo.bar:8888', 'GET')), 'Validation failed when it should have passed with a valid domain.'); } } ================================================ FILE: tests/Http/Validation/PrefixTest.php ================================================ assertFalse($validator->validate(Request::create('bar', 'GET')), 'Validation passed when it should have failed with an invalid prefix.'); $validator = new Prefix(null); $this->assertFalse($validator->validate(Request::create('foo', 'GET')), 'Validation passed when it should have failed with a null prefix.'); } public function testValidationPasses() { $validator = new Prefix('foo'); $this->assertTrue($validator->validate(Request::create('foo', 'GET')), 'Validation failed when it should have passed with a valid prefix.'); $this->assertTrue($validator->validate(Request::create('foo/bar', 'GET')), 'Validation failed when it should have passed with a valid prefix.'); } public function testValidationPassesWithHyphenatedPrefix() { $validator = new Prefix('web-api'); $this->assertTrue($validator->validate(Request::create('web-api', 'GET')), 'Validation failed when it should have passed with a valid prefix.'); $this->assertTrue($validator->validate(Request::create('web-api/bar', 'GET')), 'Validation failed when it should have passed with a valid prefix.'); } } ================================================ FILE: tests/Routing/Adapter/BaseAdapterTest.php ================================================ container = $this->getContainerInstance(); $this->container['Illuminate\Container\Container'] = $this->container; $this->container['api.auth'] = new MiddlewareStub; $this->container['api.limiting'] = new MiddlewareStub; $this->container['api.controllers'] = new MiddlewareStub; $this->container['request'] = new Http\Request; Http\Request::setAcceptParser(new Http\Parser\Accept('vnd', 'api', 'v1', 'json')); $this->adapter = $this->getAdapterInstance(); $this->exception = m::mock(Handler::class); $this->router = new Router($this->adapter, $this->exception, $this->container, null, null); app()->instance(\Illuminate\Routing\Router::class, $this->adapter, true); Http\Response::setFormatters(['json' => new Http\Response\Format\Json]); } /** * @return Container|Application */ abstract public function getContainerInstance(); /** * @return Laravel|Lumen */ abstract public function getAdapterInstance(); protected function createRequest($uri, $method, array $headers = []) { $request = Http\Request::create($uri, $method); foreach ($headers as $key => $value) { $request->headers->set($key, $value); } return $this->container['request'] = $request; } public function testBasicRouteVersions() { $this->router->version('v1', function () { $this->router->get('foo', function () { return 'foo'; }); $this->router->post('foo', function () { return 'posted'; }); $this->router->patch('foo', function () { return 'patched'; }); $this->router->delete('foo', function () { return 'deleted'; }); $this->router->put('foo', function () { return 'put'; }); $this->router->options('foo', function () { return 'options'; }); }); $this->router->group(['version' => 'v2'], function () { $this->router->get('foo', ['version' => 'v3', function () { return 'bar'; }]); }); $this->createRequest('/', 'GET'); $this->assertArrayHasKey('v1', $this->router->getRoutes(), 'No routes were registered for version 1.'); $this->assertArrayHasKey('v2', $this->router->getRoutes(), 'No routes were registered for version 2.'); $this->assertArrayHasKey('v3', $this->router->getRoutes(), 'No routes were registered for version 3.'); $request = $this->createRequest('/foo', 'GET', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('foo', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo/', 'GET', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('foo', $this->router->dispatch($request)->getContent(), 'Could not dispatch request with trailing slash.'); $request = $this->createRequest('/foo', 'GET', ['accept' => 'application/vnd.api.v2+json']); $this->assertSame('bar', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo', 'GET', ['accept' => 'application/vnd.api.v3+json']); $this->assertSame('bar', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo', 'POST', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('posted', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo', 'PATCH', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('patched', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo', 'DELETE', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('deleted', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo', 'PUT', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('put', $this->router->dispatch($request)->getContent()); $request = $this->createRequest('/foo', 'options', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('options', $this->router->dispatch($request)->getContent()); } public function testAdapterDispatchesRequestsThroughRouter() { $this->container['request'] = Http\Request::create('/foo', 'GET'); $this->router->version('v1', function () { $this->router->get('foo', function () { return 'foo'; }); }); $response = $this->router->dispatch($this->container['request']); $this->assertSame('foo', $response->getContent()); } public function testRoutesWithPrefix() { $this->router->version('v1', ['prefix' => 'foo/bar'], function () { $this->router->get('foo', function () { return 'foo'; }); }); $this->router->version('v2', ['prefix' => 'foo/bar'], function () { $this->router->get('foo', function () { return 'bar'; }); }); $request = $this->createRequest('/foo/bar/foo', 'GET', ['accept' => 'application/vnd.api.v2+json']); $this->assertSame('bar', $this->router->dispatch($request)->getContent(), 'Router could not dispatch prefixed routes.'); } public function testRoutesWithDomains() { $this->router->version('v1', ['domain' => 'foo.bar'], function () { $this->router->get('foo', function () { return 'foo'; }); }); $this->router->version('v2', ['domain' => 'foo.bar'], function () { $this->router->get('foo', function () { return 'bar'; }); }); $request = $this->createRequest('http://foo.bar/foo', 'GET', ['accept' => 'application/vnd.api.v2+json']); $this->assertSame('bar', $this->router->dispatch($request)->getContent(), 'Router could not dispatch domain routes.'); } public function testPointReleaseVersions() { $this->router->version('v1.1', function () { $this->router->get('foo', function () { return 'foo'; }); }); $this->router->version('v2.0.1', function () { $this->router->get('bar', function () { return 'bar'; }); }); $request = $this->createRequest('/foo', 'GET', ['accept' => 'application/vnd.api.v1.1+json']); $this->assertSame('foo', $this->router->dispatch($request)->getContent(), 'Router does not support point release versions.'); $request = $this->createRequest('/bar', 'GET', ['accept' => 'application/vnd.api.v2.0.1+json']); $this->assertSame('bar', $this->router->dispatch($request)->getContent(), 'Router does not support point release versions.'); } public function testRoutingResources() { $this->router->version('v1', ['namespace' => '\Dingo\Api\Tests\Stubs'], function () { $this->router->resources([ 'bar' => ['RoutingControllerStub', ['only' => ['index']]], ]); }); $request = $this->createRequest('/bar', 'GET', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('foo', $this->router->dispatch($request)->getContent(), 'Router did not register controller correctly.'); } public function testIterableRoutes() { $this->router->version('v1', ['namespace' => '\Dingo\Api\Tests\Stubs'], function () { $this->router->post('/', ['uses' => 'RoutingControllerStub@index']); $this->router->post('/find', ['uses' => 'RoutingControllerOtherStub@show']); }); $routes = $this->adapter->getIterableRoutes(); $this->assertTrue(array_key_exists('v1', (array) $routes)); $this->assertSame(2, count($routes['v1'])); } } ================================================ FILE: tests/Routing/Adapter/LaravelTest.php ================================================ container)); } public function getContainerInstance() { return new Container; } } ================================================ FILE: tests/Routing/Adapter/LumenTest.php ================================================ container->routeMiddleware([ 'api.auth' => get_class($this->container['api.auth']), 'api.limiting' => get_class($this->container['api.limiting']), ]); // When we rebind the "request" instance during testing we'll pull the route resolver // from the Lumen request instance and set it on our request so we can fetch // the route properly. $this->container->rebinding('request', function ($app, $request) { $request->setRouteResolver($app[Request::class]->getRouteResolver()); }); return new Lumen($this->container, new StdRouteParser, new GcbDataGenerator, function ($routes) { return new GcbDispatcher($routes->getData()); }); } public function getContainerInstance() { return new Application; } public function testRoutesWithDomains() { $this->markTestSkipped('Lumen does not support sub-domain routing.'); } } ================================================ FILE: tests/Routing/RouteTest.php ================================================ adapter = new RoutingAdapterStub; $this->container = new Container; } public function testCreatingNewRoute() { $request = Request::create('foo', 'GET'); $route = new Route($this->adapter, $this->container, $request, new IlluminateRoute(['GET', 'HEAD'], 'foo', [ 'scopes' => ['foo', 'bar'], 'providers' => ['foo'], 'limit' => 5, 'expires' => 10, 'throttle' => BasicThrottleStub::class, 'version' => ['v1'], 'conditionalRequest' => false, 'middleware' => 'foo.bar', ])); $this->assertSame(['foo', 'bar'], $route->scopes(), 'Route did not setup scopes correctly.'); $this->assertSame(['foo'], $route->getAuthenticationProviders(), 'Route did not setup authentication providers correctly.'); $this->assertSame(5, $route->getRateLimit(), 'Route did not setup rate limit correctly.'); $this->assertSame(10, $route->getRateLimitExpiration(), 'Route did not setup rate limit expiration correctly.'); $this->assertTrue($route->hasThrottle(), 'Route did not setup throttle correctly.'); $this->assertInstanceOf(BasicThrottleStub::class, $route->getThrottle(), 'Route did not setup throttle correctly.'); $this->assertFalse($route->requestIsConditional(), 'Route did not setup conditional request correctly.'); } public function testControllerOptionsMergeAndOverrideRouteOptions() { $request = Request::create('foo', 'GET'); $route = new Route($this->adapter, $this->container, $request, new IlluminateRoute(['GET', 'HEAD'], 'foo', [ 'scopes' => ['foo', 'bar'], 'providers' => ['foo'], 'limit' => 5, 'expires' => 10, 'throttle' => ThrottleStub::class, 'version' => ['v1'], 'conditionalRequest' => false, 'uses' => RoutingControllerStub::class.'@index', 'middleware' => 'foo.bar', ])); $this->assertSame(['foo', 'bar', 'baz', 'bing'], $route->scopes(), 'Route did not setup scopes correctly.'); $this->assertSame(['foo', 'red', 'black'], $route->getAuthenticationProviders(), 'Route did not setup authentication providers correctly.'); $this->assertSame(10, $route->getRateLimit(), 'Route did not setup rate limit correctly.'); $this->assertSame(20, $route->getRateLimitExpiration(), 'Route did not setup rate limit expiration correctly.'); $this->assertTrue($route->hasThrottle(), 'Route did not setup throttle correctly.'); $this->assertInstanceOf(BasicThrottleStub::class, $route->getThrottle(), 'Route did not setup throttle correctly.'); $route = new Route($this->adapter, $this->container, $request, new IlluminateRoute(['GET', 'HEAD'], 'foo/bar', [ 'scopes' => ['foo', 'bar'], 'providers' => ['foo'], 'limit' => 5, 'expires' => 10, 'throttle' => ThrottleStub::class, 'version' => ['v1'], 'conditionalRequest' => false, 'uses' => RoutingControllerStub::class.'@show', ])); $this->assertSame(['foo', 'bar', 'baz', 'bing', 'bob'], $route->scopes(), 'Route did not setup scopes correctly.'); $this->assertSame(['foo'], $route->getAuthenticationProviders(), 'Route did not setup authentication providers correctly.'); $this->assertSame(10, $route->getRateLimit(), 'Route did not setup rate limit correctly.'); $this->assertSame(20, $route->getRateLimitExpiration(), 'Route did not setup rate limit expiration correctly.'); $this->assertTrue($route->hasThrottle(), 'Route did not setup throttle correctly.'); $this->assertInstanceOf(BasicThrottleStub::class, $route->getThrottle(), 'Route did not setup throttle correctly.'); } } ================================================ FILE: tests/Routing/RouterTest.php ================================================ container->make(RoutingAdapterStub::class); } public function getContainerInstance() { return new Container; } public function testRouteOptionsMergeCorrectly() { $this->router->version('v1', ['scopes' => 'foo|bar'], function () { $this->router->get('foo', ['scopes' => ['baz'], function () { $this->assertSame( ['foo', 'bar', 'baz'], $this->router->getCurrentRoute()->getScopes(), 'Router did not merge string based group scopes with route based array scopes.' ); }]); $this->router->get('baz', function () { $this->assertSame( ['foo', 'bar'], $this->router->getCurrentRoute()->getScopes(), 'Router did not merge string based group scopes with route.' ); }); }); $request = $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v1+json']); $this->router->dispatch($request); $request = $this->createRequest('baz', 'GET', ['accept' => 'application/vnd.api.v1+json']); $this->router->dispatch($request); $this->router->version('v2', ['providers' => 'foo', 'throttle' => new ThrottleStub(['limit' => 10, 'expires' => 15]), 'namespace' => '\Dingo\Api\Tests'], function () { $this->router->get('foo', 'Stubs\RoutingControllerStub@index'); }); $request = $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v2+json']); $this->router->dispatch($request); $route = $this->router->getCurrentRoute(); $this->assertSame(['baz', 'bing'], $route->scopes()); $this->assertSame(['foo', 'red', 'black'], $route->getAuthenticationProviders()); $this->assertSame(10, $route->getRateLimit()); $this->assertSame(20, $route->getRateLimitExpiration()); $this->assertInstanceOf(BasicThrottleStub::class, $route->getThrottle()); } public function testGroupAsPrefixesRouteAs() { $this->router->version('v1', ['as' => 'api'], function ($api) { $api->get('users', ['as' => 'users', function () { return 'foo'; }]); }); $routes = $this->router->getRoutes('v1'); $this->assertInstanceOf(Route::class, $routes->getByName('api.users')); } public function testNoGroupVersionThrowsException() { $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('A version is required for an API group definition.'); $this->router->group([], function () { // }); } public function testMatchRoutes() { $this->router->version('v1', function ($api) { $api->match(['get', 'post'], 'foo', function () { return 'bar'; }); }); $this->router->setConditionalRequest(false); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'POST', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); } public function testAnyRoutes() { $this->router->version('v1', function ($api) { $api->any('foo', function () { return 'bar'; }); }); $this->router->setConditionalRequest(false); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'POST', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'PATCH', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'DELETE', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); } public function testRouterPreparesNotModifiedResponse() { $this->router->version('v1', function () { $this->router->get('foo', function () { return 'bar'; }); }); $this->router->setConditionalRequest(false); $response = $this->router->dispatch( $request = $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('bar', $response->getContent()); $this->router->setConditionalRequest(true); $response = $this->router->dispatch($request); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('"'.sha1('bar').'"', $response->getETag()); $this->assertSame('bar', $response->getContent()); $request = $this->createRequest('foo', 'GET', [ 'if-none-match' => '"'.sha1('bar').'"', 'accept' => 'application/vnd.api.v1+json', ]); $response = $this->router->dispatch($request); $this->assertSame(304, $response->getStatusCode()); $this->assertSame('"'.sha1('bar').'"', $response->getETag()); $this->assertEmpty($response->getContent()); $request = $this->createRequest('foo', 'GET', [ 'if-none-match' => '123456789', 'accept' => 'application/vnd.api.v1+json', ]); $response = $this->router->dispatch($request); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('"'.sha1('bar').'"', $response->getETag()); $this->assertSame('bar', $response->getContent()); } public function testRouterHandlesExistingEtag() { $this->router->version('v1', ['conditional_request' => true], function () { $this->router->get('foo', function () { $response = new Http\Response('bar'); $response->setEtag('custom-etag'); return $response; }); }); $response = $this->router->dispatch( $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(200, $response->getStatusCode()); $this->assertSame('"custom-etag"', $response->getETag()); $this->assertSame('bar', $response->getContent()); } public function testRouterHandlesCustomEtag() { $this->router->version('v1', ['conditional_request' => true], function () { $this->router->get('foo', function () { $response = new Http\Response('bar'); $response->setEtag('custom-etag'); return $response; }); }); $response = $this->router->dispatch( $this->createRequest('foo', 'GET', [ 'if-none-match' => '"custom-etag"', 'accept' => 'application/vnd.api.v1+json', ]) ); $this->assertSame(304, $response->getStatusCode()); $this->assertSame('"custom-etag"', $response->getETag()); $this->assertEmpty($response->getContent()); } public function testExceptionsAreHandledByExceptionHandler() { $exception = new HttpException(400); $this->router->version('v1', function () use ($exception) { $this->router->get('foo', function () use ($exception) { throw $exception; }); }); $this->exception->shouldReceive('report')->once()->with($exception); $this->exception->shouldReceive('handle')->once()->with($exception)->andReturn(new Http\Response('exception')); $request = $this->createRequest('foo', 'GET', ['accept' => 'application/vnd.api.v1+json']); $this->assertSame('exception', $this->router->dispatch($request)->getContent(), 'Router did not delegate exception handling.'); } public function testNoAcceptHeaderUsesDefaultVersion() { $this->router->version('v1', function () { $this->router->get('foo', function () { return 'foo'; }); }); $this->assertSame('foo', $this->router->dispatch($this->createRequest('foo', 'GET'))->getContent(), 'Router does not default to default version.'); } public function testRoutesAddedToCorrectVersions() { $this->router->version('v1', ['domain' => 'foo.bar'], function () { $this->router->get('foo', function () { return 'bar'; }); }); $this->router->version('v2', ['domain' => 'foo.bar'], function () { $this->router->get('bar', function () { return 'baz'; }); }); $this->createRequest('/', 'GET'); $this->assertCount(1, $this->router->getRoutes()['v1'], 'Routes were not added to the correct versions.'); } public function testUnsuccessfulResponseThrowsHttpException() { $this->router->version('v1', function () { $this->router->get('foo', function () { return new Http\Response('Failed!', 400); }); }); $request = $this->createRequest('foo', 'GET'); $this->exception->shouldReceive('handle')->with(m::type(HttpException::class))->andReturn(new Http\Response('Failed!')); $this->assertSame('Failed!', $this->router->dispatch($request)->getContent(), 'Router did not throw and handle a HttpException.'); } public function testGroupNamespacesAreConcatenated() { $this->router->version('v1', ['namespace' => '\Dingo\Api'], function () { $this->router->group(['namespace' => 'Tests\Stubs'], function () { $this->router->get('foo', 'RoutingControllerStub@getIndex'); }); }); $request = $this->createRequest('foo', 'GET'); $this->assertSame('foo', $this->router->dispatch($request)->getContent(), 'Router did not concatenate controller namespace correctly.'); } public function testCurrentRouteName() { $this->router->version('v1', function () { $this->router->get('foo', ['as' => 'foo', function () { return 'foo'; }]); }); $request = $this->createRequest('foo', 'GET'); $this->router->dispatch($request); $this->assertFalse($this->router->currentRouteNamed('bar')); $this->assertTrue($this->router->currentRouteNamed('foo')); $this->assertTrue($this->router->is('*')); $this->assertFalse($this->router->is('b*')); $this->assertTrue($this->router->is('b*', 'f*')); } public function testCurrentRouteAction() { $this->router->version('v1', ['namespace' => '\Dingo\Api\Tests\Stubs'], function () { $this->router->get('foo', 'RoutingControllerStub@getIndex'); }); $request = $this->createRequest('foo', 'GET'); $this->router->dispatch($request); $this->assertFalse($this->router->currentRouteUses('foo')); $this->assertTrue($this->router->currentRouteUses(RoutingControllerStub::class.'@getIndex')); $this->assertFalse($this->router->uses('foo*')); $this->assertTrue($this->router->uses('*')); $this->assertTrue($this->router->uses(RoutingControllerStub::class.'@*')); } public function testRoutePatternsAreAppliedCorrectly() { $adapter = $this->adapter; $adapter->pattern('bar', '[0-9]+'); $this->router = new Router($adapter, $this->exception, $this->container, null, null); $this->router->version('v1', function ($api) { $api->any('foo/{bar}', function () { return 'bar'; }); }); $this->router->setConditionalRequest(false); $this->exception->shouldReceive('report')->once()->with(HttpException::class); $this->exception->shouldReceive('handle')->with(m::type(HttpException::class))->andReturn(new Http\Response('Not Found!', 404)); $response = $this->router->dispatch( $request = $this->createRequest('foo/abc', 'GET', ['accept' => 'application/vnd.api.v1+json']) ); $this->assertSame(404, $response->getStatusCode()); $this->assertSame('Not Found!', $response->getContent()); } } ================================================ FILE: tests/Stubs/Application58Stub.php ================================================ validateAuthorizationHeader($request); } public function getAuthorizationMethod() { return 'foo'; } } ================================================ FILE: tests/Stubs/BasicThrottleStub.php ================================================ 'bar']; } } ================================================ FILE: tests/Stubs/HttpValidatorStub.php ================================================ getMethod() === 'POST'; } } ================================================ FILE: tests/Stubs/MiddlewareStub.php ================================================ routes[$version]; $route = $this->findRoute($request, $routes); $request->setRouteResolver(function () use ($route) { return $route; }); return (new Pipeline(new Container)) ->send($request) ->through([]) ->then(function ($request) use ($route) { return $this->prepareResponse($request, $route->run($request)); }); } protected function findRouteClosure(array $action) { foreach ($action as $value) { if ($value instanceof Closure) { return $value; } } } protected function prepareResponse($request, $response) { if ($response instanceof IlluminateResponse) { $response = Response::makeFromExisting($response); } elseif ($response instanceof JsonResponse) { $response = Response::makeFromJson($response); } else { $response = new Response($response); } return $response->prepare($request); } protected function findRoute(Request $request, $routeCollection) { return $routeCollection->match($request); } public function getRouteProperties($route, Request $request) { return [$route->uri(), (array) $request->getMethod(), $route->getAction()]; } public function addRoute(array $methods, array $versions, $uri, $action) { $this->createRouteCollections($versions); $route = new IlluminateRoute($methods, $uri, $action); $this->addWhereClausesToRoute($route); foreach ($versions as $version) { $this->routes[$version]->add($route); } return $route; } public function getRoutes($version = null) { if (! is_null($version)) { return $this->routes[$version]; } return $this->routes; } public function getIterableRoutes($version = null) { return $this->getRoutes($version); } public function setRoutes(array $routes) { // } public function prepareRouteForSerialization($route) { // } public function pattern($key, $pattern) { $this->patterns[$key] = $pattern; } public function getPatterns() { return $this->patterns; } protected function createRouteCollections(array $versions) { foreach ($versions as $version) { if (! isset($this->routes[$version])) { $this->routes[$version] = new RouteCollection; } } } protected function addWhereClausesToRoute($route) { $where = isset($route->getAction()['where']) ? $route->getAction()['where'] : []; $route->where(array_merge($this->patterns, $where)); return $route; } } ================================================ FILE: tests/Stubs/RoutingControllerOtherStub.php ================================================ scopes('baz|bing'); $this->scopes('bob', ['except' => ['index']]); $this->authenticateWith('red|black', ['only' => 'index']); $this->rateLimit(10, 20); $this->throttle(BasicThrottleStub::class); } public function index() { return 'foo'; } public function show() { return 'bar'; } public function getIndex() { return 'foo'; } } ================================================ FILE: tests/Stubs/ThrottleStub.php ================================================ 60, 'expires' => 60], $enabled = true) { $this->enabled = $enabled; parent::__construct($options); } public function match(Container $app) { return $this->enabled; } } ================================================ FILE: tests/Stubs/TransformerStub.php ================================================ transform(function ($response) use ($transformer) { return $transformer->transform($response); })->toArray(); } return $transformer->transform($response); } } ================================================ FILE: tests/Stubs/UserStub.php ================================================ name = $name; } } ================================================ FILE: tests/Stubs/UserTransformerStub.php ================================================ $user->name, ]; } } ================================================ FILE: tests/Transformer/Adapter/FractalTest.php ================================================ fractal = new Fractal(new FractalManager()); } public function testParseFractalIncludes() { $request = Request::create('/?include=foo,bar', 'GET'); $this->fractal->parseFractalIncludes($request); $requestedIncludes = $this->fractal->getFractal()->getRequestedIncludes(); $this->assertEquals(['foo', 'bar'], $requestedIncludes); } public function testParseFractalIncludesWithSpaces() { $request = Request::create('/?include=foo, bar', 'GET'); $this->fractal->parseFractalIncludes($request); $requestedIncludes = $this->fractal->getFractal()->getRequestedIncludes(); $this->assertEquals(['foo', 'bar'], $requestedIncludes); } } ================================================ FILE: tests/Transformer/FactoryTest.php ================================================ factory = new Factory($container, new TransformerStub); } public function testResponseIsTransformable() { $this->assertFalse($this->factory->transformableResponse(new UserStub('Jason'), new UserTransformerStub)); $this->factory->register(UserStub::class, new UserTransformerStub); $this->assertTrue($this->factory->transformableResponse(new UserStub('Jason'), new UserTransformerStub)); } public function testRegisterParameterOrder() { // Third parameter is parameters and fourth is callback. $binding = $this->factory->register(UserStub::class, new UserTransformerStub, ['foo' => 'bar'], function ($foo) { $this->assertSame('foo', $foo); }); $binding->fireCallback('foo'); $this->assertSame(['foo' => 'bar'], $binding->getParameters()); // Third parameter is parameters and fourth is null. $binding = $this->factory->register(UserStub::class, new UserTransformerStub, ['foo' => 'bar']); $this->assertSame(['foo' => 'bar'], $binding->getParameters()); // Third parameter is an empty array and fourth is callback. $binding = $this->factory->register(UserStub::class, new UserTransformerStub, [], function ($foo) { $this->assertSame('foo', $foo); }); $binding->fireCallback('foo'); } public function testResponseIsTransformableType() { $this->assertFalse($this->factory->transformableType(['foo' => 'bar'])); $this->assertTrue($this->factory->transformableType('Foo')); $this->assertTrue($this->factory->transformableType((object) ['foo' => 'bar'])); } public function testTransformingResponse() { $this->factory->register(UserStub::class, new UserTransformerStub); $response = $this->factory->transform(new UserStub('Jason')); $this->assertSame(['name' => 'Jason'], $response); } public function testTransformingCollectionResponse() { $this->factory->register(UserStub::class, new UserTransformerStub); $response = $this->factory->transform(new Collection([new UserStub('Jason'), new UserStub('Bob')])); $this->assertSame([['name' => 'Jason'], ['name' => 'Bob']], $response); } public function testTransforingWithIlluminateRequest() { $container = new Container; $container['request'] = new Request(); $factory = new Factory($container, new TransformerStub); $factory->register(UserStub::class, new UserTransformerStub); $response = $factory->transform(new UserStub('Jason')); $this->assertSame(['name' => 'Jason'], $response); } public function testTransformingWithNoTransformerThrowsException() { $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Unable to find bound transformer for "Dingo\Api\Tests\Stubs\UserStub" class'); $this->factory->transform(new UserStub('Jason')); } }