├── .github └── ISSUE_TEMPLATE.md ├── .styleci.yml ├── LICENSE ├── composer.json ├── config └── api.php ├── 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.php ├── Response │ ├── Factory.php │ └── Format │ │ ├── Format.php │ │ ├── Json.php │ │ ├── JsonOptionalFormatting.php │ │ └── Jsonp.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 /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | | Q | A 2 | | ----------------- | --- 3 | | Bug? | no|yes 4 | | New Feature? | no|yes 5 | | Framework | Laravel|Lumen 6 | | Framework version | 5.x.y 7 | | Package version | 1.x.y 8 | | PHP version | 5.x.y|7.x.y 9 | 10 | #### Actual Behaviour 11 | 12 | Describe the behaviour you're experiencing. Do not just copy and paste a random error message and expect help. 13 | 14 | 15 | #### Expected Behaviour 16 | 17 | Describe the behaviour you're expecting. 18 | 19 | 20 | #### Steps to Reproduce 21 | 22 | List all the steps needed to reproduce the issue you're having. Make sure to include code (affected models, configurations), 23 | any screenshots and/or other resources that may help us understand what's going on. 24 | 25 | 26 | #### Possible Solutions 27 | 28 | If you have any ideas on how to solve the issue, add them here, otherwise you can omit this part. 29 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | disabled: 4 | - alpha_ordered_imports 5 | 6 | enabled: 7 | - phpdoc_order 8 | - phpdoc_separation 9 | - unalign_double_arrow 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015, Jason Lewis 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Dingo API nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dingo/api", 3 | "description": "A RESTful API package for the Laravel and Lumen frameworks.", 4 | "keywords": [ 5 | "api", 6 | "dingo", 7 | "laravel", 8 | "restful" 9 | ], 10 | "license": "BSD-3-Clause", 11 | "authors": [{ 12 | "name": "Jason Lewis", 13 | "email": "jason.lewis1991@gmail.com" 14 | }], 15 | "require": { 16 | "php": "^7.2.5|^8.0", 17 | "dingo/blueprint": "^0.4", 18 | "illuminate/routing": "^7.0|^8.0", 19 | "illuminate/support": "^7.0|^8.0", 20 | "league/fractal": "^0.19" 21 | }, 22 | "require-dev": { 23 | "friendsofphp/php-cs-fixer": "~2", 24 | "illuminate/auth": "^7.0|^8.0", 25 | "illuminate/cache": "^7.0|^8.0", 26 | "illuminate/console": "^7.0|^8.0", 27 | "illuminate/database": "^7.0|^8.0", 28 | "illuminate/events": "^7.0|^8.0", 29 | "illuminate/filesystem": "^7.0|^8.0", 30 | "illuminate/log": "^7.0|^8.0", 31 | "illuminate/pagination": "^7.0|^8.0", 32 | "laravel/lumen-framework": "^7.0|^8.0", 33 | "mockery/mockery": "~1.0", 34 | "phpunit/phpunit": "^8.5|^9.0", 35 | "squizlabs/php_codesniffer": "~2.0", 36 | "tymon/jwt-auth": "1.0.*" 37 | }, 38 | "suggest": { 39 | "tymon/jwt-auth": "Protect your API with JSON Web Tokens." 40 | }, 41 | "abandoned": "api-ecosystem-for-laravel/dingo-api", 42 | "autoload": { 43 | "psr-4": { 44 | "Dingo\\Api\\": "src/" 45 | }, 46 | "files": [ 47 | "src/helpers.php" 48 | ] 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "Dingo\\Api\\Tests\\": "tests/" 53 | } 54 | }, 55 | "extra": { 56 | "branch-alias": { 57 | "dev-master": "2.0-dev" 58 | }, 59 | "laravel": { 60 | "providers": [ 61 | "Dingo\\Api\\Provider\\LaravelServiceProvider" 62 | ], 63 | "aliases": { 64 | "API": "Dingo\\Api\\Facade\\API" 65 | } 66 | } 67 | }, 68 | "config": { 69 | "sort-packages": true 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true 73 | } 74 | -------------------------------------------------------------------------------- /config/api.php: -------------------------------------------------------------------------------- 1 | env('API_STANDARDS_TREE', 'x'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | API Subtype 26 | |-------------------------------------------------------------------------- 27 | | 28 | | Your subtype will follow the standards tree you use when used in the 29 | | "Accept" header to negotiate the content type and version. 30 | | 31 | | For example: Accept: application/x.SUBTYPE.v1+json 32 | | 33 | */ 34 | 35 | 'subtype' => env('API_SUBTYPE', ''), 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Default API Version 40 | |-------------------------------------------------------------------------- 41 | | 42 | | This is the default version when strict mode is disabled and your API 43 | | is accessed via a web browser. It's also used as the default version 44 | | when generating your APIs documentation. 45 | | 46 | */ 47 | 48 | 'version' => env('API_VERSION', 'v1'), 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Default API Prefix 53 | |-------------------------------------------------------------------------- 54 | | 55 | | A default prefix to use for your API routes so you don't have to 56 | | specify it for each group. 57 | | 58 | */ 59 | 60 | 'prefix' => env('API_PREFIX', null), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Default API Domain 65 | |-------------------------------------------------------------------------- 66 | | 67 | | A default domain to use for your API routes so you don't have to 68 | | specify it for each group. 69 | | 70 | */ 71 | 72 | 'domain' => env('API_DOMAIN', null), 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Name 77 | |-------------------------------------------------------------------------- 78 | | 79 | | When documenting your API using the API Blueprint syntax you can 80 | | configure a default name to avoid having to manually specify 81 | | one when using the command. 82 | | 83 | */ 84 | 85 | 'name' => env('API_NAME', null), 86 | 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Conditional Requests 90 | |-------------------------------------------------------------------------- 91 | | 92 | | Globally enable conditional requests so that an ETag header is added to 93 | | any successful response. Subsequent requests will perform a check and 94 | | will return a 304 Not Modified. This can also be enabled or disabled 95 | | on certain groups or routes. 96 | | 97 | */ 98 | 99 | 'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true), 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Strict Mode 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Enabling strict mode will require clients to send a valid Accept header 107 | | with every request. This also voids the default API version, meaning 108 | | your API will not be browsable via a web browser. 109 | | 110 | */ 111 | 112 | 'strict' => env('API_STRICT', false), 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Debug Mode 117 | |-------------------------------------------------------------------------- 118 | | 119 | | Enabling debug mode will result in error responses caused by thrown 120 | | exceptions to have a "debug" key that will be populated with 121 | | more detailed information on the exception. 122 | | 123 | */ 124 | 125 | 'debug' => env('API_DEBUG', false), 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Generic Error Format 130 | |-------------------------------------------------------------------------- 131 | | 132 | | When some HTTP exceptions are not caught and dealt with the API will 133 | | generate a generic error response in the format provided. Any 134 | | keys that aren't replaced with corresponding values will be 135 | | removed from the final response. 136 | | 137 | */ 138 | 139 | 'errorFormat' => [ 140 | 'message' => ':message', 141 | 'errors' => ':errors', 142 | 'code' => ':code', 143 | 'status_code' => ':status_code', 144 | 'debug' => ':debug', 145 | ], 146 | 147 | /* 148 | |-------------------------------------------------------------------------- 149 | | API Middleware 150 | |-------------------------------------------------------------------------- 151 | | 152 | | Middleware that will be applied globally to all API requests. 153 | | 154 | */ 155 | 156 | 'middleware' => [ 157 | 158 | ], 159 | 160 | /* 161 | |-------------------------------------------------------------------------- 162 | | Authentication Providers 163 | |-------------------------------------------------------------------------- 164 | | 165 | | The authentication providers that should be used when attempting to 166 | | authenticate an incoming API request. 167 | | 168 | */ 169 | 170 | 'auth' => [ 171 | 172 | ], 173 | 174 | /* 175 | |-------------------------------------------------------------------------- 176 | | Throttling / Rate Limiting 177 | |-------------------------------------------------------------------------- 178 | | 179 | | Consumers of your API can be limited to the amount of requests they can 180 | | make. You can create your own throttles or simply change the default 181 | | throttles. 182 | | 183 | */ 184 | 185 | 'throttling' => [ 186 | 187 | ], 188 | 189 | /* 190 | |-------------------------------------------------------------------------- 191 | | Response Transformer 192 | |-------------------------------------------------------------------------- 193 | | 194 | | Responses can be transformed so that they are easier to format. By 195 | | default a Fractal transformer will be used to transform any 196 | | responses prior to formatting. You can easily replace 197 | | this with your own transformer. 198 | | 199 | */ 200 | 201 | 'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class), 202 | 203 | /* 204 | |-------------------------------------------------------------------------- 205 | | Response Formats 206 | |-------------------------------------------------------------------------- 207 | | 208 | | Responses can be returned in multiple formats by registering different 209 | | response formatters. You can also customize an existing response 210 | | formatter with a number of options to configure its output. 211 | | 212 | */ 213 | 214 | 'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'), 215 | 216 | 'formats' => [ 217 | 218 | 'json' => Dingo\Api\Http\Response\Format\Json::class, 219 | 220 | ], 221 | 222 | 'formatsOptions' => [ 223 | 224 | 'json' => [ 225 | 'pretty_print' => env('API_JSON_FORMAT_PRETTY_PRINT_ENABLED', false), 226 | 'indent_style' => env('API_JSON_FORMAT_INDENT_STYLE', 'space'), 227 | 'indent_size' => env('API_JSON_FORMAT_INDENT_SIZE', 2), 228 | ], 229 | 230 | ], 231 | 232 | ]; 233 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Move repositories notice 2 | 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 3 | 4 | --- 5 | 6 | ![](https://cloud.githubusercontent.com/assets/829059/9216039/82be51cc-40f6-11e5-88f5-f0cbd07bcc39.png) 7 | 8 | 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. 9 | 10 | [![Build Status](https://img.shields.io/travis/dingo/api/master.svg?style=flat-square)](https://travis-ci.org/dingo/api) 11 | [![License](https://img.shields.io/packagist/l/dingo/api.svg?style=flat-square)](LICENSE) 12 | [![Development Version](https://img.shields.io/packagist/vpre/dingo/api.svg?style=flat-square)](https://packagist.org/packages/dingo/api) 13 | [![Monthly Installs](https://img.shields.io/packagist/dm/dingo/api.svg?style=flat-square)](https://packagist.org/packages/dingo/api) 14 | [![StyleCI](https://styleci.io/repos/18673522/shield)](https://styleci.io/repos/18673522) 15 | 16 | ## Features 17 | 18 | This package provides tools for the following, and more: 19 | 20 | - Content Negotiation 21 | - Multiple Authentication Adapters 22 | - API Versioning 23 | - Rate Limiting 24 | - Response Transformers and Formatters 25 | - Error and Exception Handling 26 | - Internal Requests 27 | - API Blueprint Documentation 28 | 29 | ## Documentation 30 | 31 | Please refer to our extensive [Wiki documentation](https://github.com/dingo/api/wiki) for more information. 32 | 33 | ## API Boilerplate 34 | 35 | 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. 36 | 37 | ## Support 38 | 39 | 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. 40 | 41 | ## License 42 | 43 | This package is licensed under the [BSD 3-Clause license](http://opensource.org/licenses/BSD-3-Clause). 44 | -------------------------------------------------------------------------------- /src/Auth/Auth.php: -------------------------------------------------------------------------------- 1 | router = $router; 60 | $this->container = $container; 61 | $this->providers = $providers; 62 | } 63 | 64 | /** 65 | * Authenticate the current request. 66 | * 67 | * @param array $providers 68 | * 69 | * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException 70 | * 71 | * @return mixed 72 | */ 73 | public function authenticate(array $providers = []) 74 | { 75 | $exceptionStack = []; 76 | 77 | // Spin through each of the registered authentication providers and attempt to 78 | // authenticate through one of them. This allows a developer to implement 79 | // and allow a number of different authentication mechanisms. 80 | foreach ($this->filterProviders($providers) as $provider) { 81 | try { 82 | $user = $provider->authenticate($this->router->getCurrentRequest(), $this->router->getCurrentRoute()); 83 | 84 | $this->providerUsed = $provider; 85 | 86 | return $this->user = $user; 87 | } catch (UnauthorizedHttpException $exception) { 88 | $exceptionStack[] = $exception; 89 | } catch (BadRequestHttpException $exception) { 90 | // We won't add this exception to the stack as it's thrown when the provider 91 | // is unable to authenticate due to the correct authorization header not 92 | // being set. We will throw an exception for this below. 93 | } 94 | } 95 | 96 | $this->throwUnauthorizedException($exceptionStack); 97 | } 98 | 99 | /** 100 | * Throw the first exception from the exception stack. 101 | * 102 | * @param array $exceptionStack 103 | * 104 | * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException 105 | * 106 | * @return void 107 | */ 108 | protected function throwUnauthorizedException(array $exceptionStack) 109 | { 110 | $exception = array_shift($exceptionStack); 111 | 112 | if ($exception === null) { 113 | $exception = new UnauthorizedHttpException('dingo', 'Failed to authenticate because of bad credentials or an invalid authorization header.'); 114 | } 115 | 116 | throw $exception; 117 | } 118 | 119 | /** 120 | * Filter the requested providers from the available providers. 121 | * 122 | * @param array $providers 123 | * 124 | * @return array 125 | */ 126 | protected function filterProviders(array $providers) 127 | { 128 | if (empty($providers)) { 129 | return $this->providers; 130 | } 131 | 132 | return array_intersect_key($this->providers, array_flip($providers)); 133 | } 134 | 135 | /** 136 | * Get the authenticated user. 137 | * 138 | * @param bool $authenticate 139 | * 140 | * @return \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model|null 141 | */ 142 | public function getUser($authenticate = true) 143 | { 144 | if ($this->user) { 145 | return $this->user; 146 | } elseif (! $authenticate) { 147 | return; 148 | } 149 | 150 | try { 151 | return $this->user = $this->authenticate(); 152 | } catch (Exception $exception) { 153 | return; 154 | } 155 | } 156 | 157 | /** 158 | * Alias for getUser. 159 | * 160 | * @param bool $authenticate 161 | * 162 | * @return \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model 163 | */ 164 | public function user($authenticate = true) 165 | { 166 | return $this->getUser($authenticate); 167 | } 168 | 169 | /** 170 | * Set the authenticated user. 171 | * 172 | * @param \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model $user 173 | * 174 | * @return \Dingo\Api\Auth\Auth 175 | */ 176 | public function setUser($user) 177 | { 178 | $this->user = $user; 179 | 180 | return $this; 181 | } 182 | 183 | /** 184 | * Check if a user has authenticated with the API. 185 | * 186 | * @param bool $authenticate 187 | * 188 | * @return bool 189 | */ 190 | public function check($authenticate = false) 191 | { 192 | return ! is_null($this->user($authenticate)); 193 | } 194 | 195 | /** 196 | * Get the provider used for authentication. 197 | * 198 | * @return \Dingo\Api\Contract\Auth\Provider 199 | */ 200 | public function getProviderUsed() 201 | { 202 | return $this->providerUsed; 203 | } 204 | 205 | /** 206 | * Extend the authentication layer with a custom provider. 207 | * 208 | * @param string $key 209 | * @param object|callable $provider 210 | * 211 | * @return void 212 | */ 213 | public function extend($key, $provider) 214 | { 215 | if (is_callable($provider)) { 216 | $provider = call_user_func($provider, $this->container); 217 | } 218 | 219 | $this->providers[$key] = $provider; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/Auth/Provider/Authorization.php: -------------------------------------------------------------------------------- 1 | headers->get('authorization')), $this->getAuthorizationMethod())) { 30 | return true; 31 | } 32 | 33 | throw new BadRequestHttpException; 34 | } 35 | 36 | /** 37 | * Get the providers authorization method. 38 | * 39 | * @return string 40 | */ 41 | abstract public function getAuthorizationMethod(); 42 | } 43 | -------------------------------------------------------------------------------- /src/Auth/Provider/Basic.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 37 | $this->identifier = $identifier; 38 | } 39 | 40 | /** 41 | * Authenticate request with Basic. 42 | * 43 | * @param \Illuminate\Http\Request $request 44 | * @param \Dingo\Api\Routing\Route $route 45 | * 46 | * @return mixed 47 | */ 48 | public function authenticate(Request $request, Route $route) 49 | { 50 | $this->validateAuthorizationHeader($request); 51 | 52 | if (($response = $this->auth->onceBasic($this->identifier)) && $response->getStatusCode() === 401) { 53 | throw new UnauthorizedHttpException('Basic', 'Invalid authentication credentials.'); 54 | } 55 | 56 | return $this->auth->user(); 57 | } 58 | 59 | /** 60 | * Get the providers authorization method. 61 | * 62 | * @return string 63 | */ 64 | public function getAuthorizationMethod() 65 | { 66 | return 'basic'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Auth/Provider/JWT.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 31 | } 32 | 33 | /** 34 | * Authenticate request with a JWT. 35 | * 36 | * @param \Illuminate\Http\Request $request 37 | * @param \Dingo\Api\Routing\Route $route 38 | * 39 | * @return mixed 40 | */ 41 | public function authenticate(Request $request, Route $route) 42 | { 43 | $token = $this->getToken($request); 44 | 45 | try { 46 | if (! $user = $this->auth->setToken($token)->authenticate()) { 47 | throw new UnauthorizedHttpException('JWTAuth', 'Unable to authenticate with invalid token.'); 48 | } 49 | } catch (JWTException $exception) { 50 | throw new UnauthorizedHttpException('JWTAuth', $exception->getMessage(), $exception); 51 | } 52 | 53 | return $user; 54 | } 55 | 56 | /** 57 | * Get the JWT from the request. 58 | * 59 | * @param \Illuminate\Http\Request $request 60 | * 61 | * @throws \Exception 62 | * 63 | * @return string 64 | */ 65 | protected function getToken(Request $request) 66 | { 67 | try { 68 | $this->validateAuthorizationHeader($request); 69 | 70 | $token = $this->parseAuthorizationHeader($request); 71 | } catch (Exception $exception) { 72 | if (! $token = $request->query('token', false)) { 73 | throw $exception; 74 | } 75 | } 76 | 77 | return $token; 78 | } 79 | 80 | /** 81 | * Parse JWT from the authorization header. 82 | * 83 | * @param \Illuminate\Http\Request $request 84 | * 85 | * @return string 86 | */ 87 | protected function parseAuthorizationHeader(Request $request) 88 | { 89 | return trim(str_ireplace($this->getAuthorizationMethod(), '', $request->header('authorization'))); 90 | } 91 | 92 | /** 93 | * Get the providers authorization method. 94 | * 95 | * @return string 96 | */ 97 | public function getAuthorizationMethod() 98 | { 99 | return 'bearer'; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Console/Command/Cache.php: -------------------------------------------------------------------------------- 1 | files = $files; 60 | $this->router = $router; 61 | $this->adapter = $adapter; 62 | 63 | parent::__construct(); 64 | } 65 | 66 | /** 67 | * Execute the console command. 68 | * 69 | * @return mixed 70 | */ 71 | public function handle() 72 | { 73 | $this->callSilent('route:clear'); 74 | 75 | $app = $this->getFreshApplication(); 76 | 77 | $this->call('route:cache'); 78 | 79 | $routes = $app['api.router']->getAdapterRoutes(); 80 | 81 | foreach ($routes as $collection) { 82 | foreach ($collection as $route) { 83 | $app['api.router.adapter']->prepareRouteForSerialization($route); 84 | } 85 | } 86 | 87 | $stub = "app('api.router')->setAdapterRoutes(unserialize(base64_decode('{{routes}}')));"; 88 | $path = $this->laravel->getCachedRoutesPath(); 89 | 90 | if (! $this->files->exists($path)) { 91 | $stub = "files->append( 95 | $path, 96 | str_replace('{{routes}}', base64_encode(serialize($routes)), $stub) 97 | ); 98 | } 99 | 100 | /** 101 | * Get a fresh application instance. 102 | * 103 | * @return \Illuminate\Contracts\Container\Container 104 | */ 105 | protected function getFreshApplication() 106 | { 107 | if (method_exists($this->laravel, 'bootstrapPath')) { 108 | $app = require $this->laravel->bootstrapPath().'/app.php'; 109 | } else { 110 | $app = require $this->laravel->basePath().'/bootstrap/app.php'; 111 | } 112 | 113 | $app->make(Kernel::class)->bootstrap(); 114 | 115 | return $app; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Console/Command/Docs.php: -------------------------------------------------------------------------------- 1 | router = $router; 92 | $this->blueprint = $blueprint; 93 | $this->writer = $writer; 94 | $this->name = $name; 95 | $this->version = $version; 96 | } 97 | 98 | /** 99 | * Execute the console command. 100 | * 101 | * @return mixed 102 | */ 103 | public function handle() 104 | { 105 | $contents = $this->blueprint->generate($this->getControllers(), $this->getDocName(), $this->getVersion(), $this->getIncludePath()); 106 | 107 | if ($file = $this->option('output-file')) { 108 | $this->writer->write($contents, $file); 109 | 110 | return $this->info('Documentation was generated successfully.'); 111 | } 112 | 113 | return $this->line($contents); 114 | } 115 | 116 | /** 117 | * Get the documentation name. 118 | * 119 | * @return string 120 | */ 121 | protected function getDocName() 122 | { 123 | $name = $this->option('name') ?: $this->name; 124 | 125 | if (! $name) { 126 | $this->comment('A name for the documentation was not supplied. Use the --name option or set a default in the configuration.'); 127 | 128 | exit; 129 | } 130 | 131 | return $name; 132 | } 133 | 134 | /** 135 | * Get the include path for documentation files. 136 | * 137 | * @return string 138 | */ 139 | protected function getIncludePath() 140 | { 141 | return base_path($this->option('include-path')); 142 | } 143 | 144 | /** 145 | * Get the documentation version. 146 | * 147 | * @return string 148 | */ 149 | protected function getVersion() 150 | { 151 | $version = $this->option('use-version') ?: $this->version; 152 | 153 | if (! $version) { 154 | $this->comment('A version for the documentation was not supplied. Use the --use-version option or set a default in the configuration.'); 155 | 156 | exit; 157 | } 158 | 159 | return $version; 160 | } 161 | 162 | /** 163 | * Get all the controller instances. 164 | * 165 | * @return array 166 | */ 167 | protected function getControllers() 168 | { 169 | $controllers = new Collection; 170 | 171 | if ($controller = $this->option('use-controller')) { 172 | $this->addControllerIfNotExists($controllers, app($controller)); 173 | 174 | return $controllers; 175 | } 176 | 177 | foreach ($this->router->getRoutes() as $collections) { 178 | foreach ($collections as $route) { 179 | if ($controller = $route->getControllerInstance()) { 180 | $this->addControllerIfNotExists($controllers, $controller); 181 | } 182 | } 183 | } 184 | 185 | return $controllers; 186 | } 187 | 188 | /** 189 | * Add a controller to the collection if it does not exist. If the 190 | * controller implements an interface suffixed with "Docs" it 191 | * will be used instead of the controller. 192 | * 193 | * @param \Illuminate\Support\Collection $controllers 194 | * @param object $controller 195 | * 196 | * @return void 197 | */ 198 | protected function addControllerIfNotExists(Collection $controllers, $controller) 199 | { 200 | $class = get_class($controller); 201 | 202 | if ($controllers->has($class)) { 203 | return; 204 | } 205 | 206 | $reflection = new ReflectionClass($controller); 207 | 208 | $interface = Arr::first($reflection->getInterfaces(), function ($key, $value) { 209 | return Str::endsWith($key, 'Docs'); 210 | }); 211 | 212 | if ($interface) { 213 | $controller = $interface; 214 | } 215 | 216 | $controllers->put($class, $controller); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Console/Command/Routes.php: -------------------------------------------------------------------------------- 1 | router = $router; 63 | } 64 | 65 | /** 66 | * Execute the console command. 67 | * 68 | * @return void 69 | */ 70 | public function fire() 71 | { 72 | $this->routes = $this->router->getRoutes(); 73 | 74 | parent::fire(); 75 | } 76 | 77 | /** 78 | * Execute the console command. 79 | * 80 | * @return void 81 | */ 82 | public function handle() 83 | { 84 | $this->routes = $this->router->getRoutes(); 85 | 86 | parent::handle(); 87 | } 88 | 89 | /** 90 | * Compile the routes into a displayable format. 91 | * 92 | * @return array 93 | */ 94 | protected function getRoutes() 95 | { 96 | $routes = []; 97 | 98 | foreach ($this->router->getRoutes() as $collection) { 99 | foreach ($collection->getRoutes() as $route) { 100 | $routes[] = $this->filterRoute([ 101 | 'host' => $route->domain(), 102 | 'method' => implode('|', $route->methods()), 103 | 'uri' => $route->uri(), 104 | 'name' => $route->getName(), 105 | 'action' => $route->getActionName(), 106 | 'protected' => $route->isProtected() ? 'Yes' : 'No', 107 | 'versions' => implode(', ', $route->versions()), 108 | 'scopes' => implode(', ', $route->scopes()), 109 | 'rate' => $this->routeRateLimit($route), 110 | ]); 111 | } 112 | } 113 | 114 | if ($sort = $this->option('sort')) { 115 | $routes = Arr::sort($routes, function ($value) use ($sort) { 116 | return $value[$sort]; 117 | }); 118 | } 119 | 120 | if ($this->option('reverse')) { 121 | $routes = array_reverse($routes); 122 | } 123 | 124 | if ($this->option('short')) { 125 | $this->headers = ['Method', 'URI', 'Name', 'Version(s)']; 126 | 127 | $routes = array_map(function ($item) { 128 | return Arr::only($item, ['method', 'uri', 'name', 'versions']); 129 | }, $routes); 130 | } 131 | 132 | return array_filter(array_unique($routes, SORT_REGULAR)); 133 | } 134 | 135 | /** 136 | * Display the routes rate limiting requests per second. This takes the limit 137 | * and divides it by the expiration time in seconds to give you a rough 138 | * idea of how many requests you'd be able to fire off per second 139 | * on the route. 140 | * 141 | * @param \Dingo\Api\Routing\Route $route 142 | * 143 | * @return null|string 144 | */ 145 | protected function routeRateLimit($route) 146 | { 147 | [$limit, $expires] = [$route->getRateLimit(), $route->getRateLimitExpiration()]; 148 | 149 | if ($limit && $expires) { 150 | return sprintf('%s req/s', round($limit / ($expires * 60), 2)); 151 | } 152 | } 153 | 154 | /** 155 | * Filter the route by URI, Version, Scopes and / or name. 156 | * 157 | * @param array $route 158 | * 159 | * @return array|null 160 | */ 161 | protected function filterRoute(array $route) 162 | { 163 | $filters = ['name', 'path', 'protected', 'unprotected', 'versions', 'scopes']; 164 | 165 | foreach ($filters as $filter) { 166 | if ($this->option($filter) && ! $this->{'filterBy'.ucfirst($filter)}($route)) { 167 | return; 168 | } 169 | } 170 | 171 | return $route; 172 | } 173 | 174 | /** 175 | * Get the console command options. 176 | * 177 | * @return array 178 | */ 179 | protected function getOptions() 180 | { 181 | $options = parent::getOptions(); 182 | 183 | foreach ($options as $key => $option) { 184 | if ($option[0] == 'sort') { 185 | unset($options[$key]); 186 | } 187 | } 188 | 189 | return array_merge( 190 | $options, 191 | [ 192 | ['sort', null, InputOption::VALUE_OPTIONAL, 'The column (domain, method, uri, name, action) to sort by'], 193 | ['versions', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Filter the routes by version'], 194 | ['scopes', 'S', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Filter the routes by scopes'], 195 | ['protected', null, InputOption::VALUE_NONE, 'Filter the protected routes'], 196 | ['unprotected', null, InputOption::VALUE_NONE, 'Filter the unprotected routes'], 197 | ['short', null, InputOption::VALUE_NONE, 'Get an abridged version of the routes'], 198 | ] 199 | ); 200 | } 201 | 202 | /** 203 | * Filter the route by its path. 204 | * 205 | * @param array $route 206 | * 207 | * @return bool 208 | */ 209 | protected function filterByPath(array $route) 210 | { 211 | return Str::contains($route['uri'], $this->option('path')); 212 | } 213 | 214 | /** 215 | * Filter the route by whether or not it is protected. 216 | * 217 | * @param array $route 218 | * 219 | * @return bool 220 | */ 221 | protected function filterByProtected(array $route) 222 | { 223 | return $this->option('protected') && $route['protected'] == 'Yes'; 224 | } 225 | 226 | /** 227 | * Filter the route by whether or not it is unprotected. 228 | * 229 | * @param array $route 230 | * 231 | * @return bool 232 | */ 233 | protected function filterByUnprotected(array $route) 234 | { 235 | return $this->option('unprotected') && $route['protected'] == 'No'; 236 | } 237 | 238 | /** 239 | * Filter the route by its versions. 240 | * 241 | * @param array $route 242 | * 243 | * @return bool 244 | */ 245 | protected function filterByVersions(array $route) 246 | { 247 | foreach ($this->option('versions') as $version) { 248 | if (Str::contains($route['versions'], $version)) { 249 | return true; 250 | } 251 | } 252 | 253 | return false; 254 | } 255 | 256 | /** 257 | * Filter the route by its name. 258 | * 259 | * @param array $route 260 | * 261 | * @return bool 262 | */ 263 | protected function filterByName(array $route) 264 | { 265 | return Str::contains($route['name'], $this->option('name')); 266 | } 267 | 268 | /** 269 | * Filter the route by its scopes. 270 | * 271 | * @param array $route 272 | * 273 | * @return bool 274 | */ 275 | protected function filterByScopes(array $route) 276 | { 277 | foreach ($this->option('scopes') as $scope) { 278 | if (Str::contains($route['scopes'], $scope)) { 279 | return true; 280 | } 281 | } 282 | 283 | return false; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/Contract/Auth/Provider.php: -------------------------------------------------------------------------------- 1 | request = $request; 35 | $this->app = $app; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Event/ResponseIsMorphing.php: -------------------------------------------------------------------------------- 1 | response = $response; 35 | $this->content = &$content; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Event/ResponseWasMorphed.php: -------------------------------------------------------------------------------- 1 | parentHandler = $parentHandler; 70 | $this->format = $format; 71 | $this->debug = $debug; 72 | } 73 | 74 | /** 75 | * Report or log an exception. 76 | * 77 | * @param Throwable $exception 78 | * 79 | * @return void 80 | */ 81 | public function report(Throwable $throwable) 82 | { 83 | $this->parentHandler->report($throwable); 84 | } 85 | 86 | /** 87 | * Determine if the exception should be reported. 88 | * 89 | * @param Throwable $e 90 | * 91 | * @return bool 92 | */ 93 | public function shouldReport(Throwable $e) 94 | { 95 | return true; 96 | } 97 | 98 | /** 99 | * Render an exception into an HTTP response. 100 | * 101 | * @param Request $request 102 | * @param Throwable $exception 103 | * 104 | * @throws Exception 105 | * 106 | * @return mixed 107 | */ 108 | public function render($request, Throwable $exception) 109 | { 110 | return $this->handle($exception); 111 | } 112 | 113 | /** 114 | * Render an exception to the console. 115 | * 116 | * @param OutputInterface $output 117 | * @param Throwable $exception 118 | * 119 | * @return mixed 120 | */ 121 | public function renderForConsole($output, Throwable $exception) 122 | { 123 | return $this->parentHandler->renderForConsole($output, $exception); 124 | } 125 | 126 | /** 127 | * Register a new exception handler. 128 | * 129 | * @param callable $callback 130 | * 131 | * @return void 132 | */ 133 | public function register(callable $callback) 134 | { 135 | $hint = $this->handlerHint($callback); 136 | 137 | $this->handlers[$hint] = $callback; 138 | } 139 | 140 | /** 141 | * Handle an exception if it has an existing handler. 142 | * 143 | * @param Throwable|Exception $exception 144 | * 145 | * @return Response 146 | */ 147 | public function handle($exception) 148 | { 149 | // Convert Eloquent's 500 ModelNotFoundException into a 404 NotFoundHttpException 150 | if ($exception instanceof ModelNotFoundException) { 151 | $exception = new NotFoundHttpException($exception->getMessage(), $exception); 152 | } 153 | 154 | foreach ($this->handlers as $hint => $handler) { 155 | if (! $exception instanceof $hint) { 156 | continue; 157 | } 158 | 159 | if ($response = $handler($exception)) { 160 | if (! $response instanceof BaseResponse) { 161 | $response = new Response($response, $this->getExceptionStatusCode($exception)); 162 | } 163 | 164 | return $response->withException($exception); 165 | } 166 | } 167 | 168 | return $this->genericResponse($exception)->withException($exception); 169 | } 170 | 171 | /** 172 | * Handle a generic error response if there is no handler available. 173 | * 174 | * @param Throwable $exception 175 | * 176 | * @throws Throwable 177 | * 178 | * @return Response 179 | */ 180 | protected function genericResponse(Throwable $exception) 181 | { 182 | $replacements = $this->prepareReplacements($exception); 183 | 184 | $response = $this->newResponseArray(); 185 | 186 | array_walk_recursive($response, function (&$value, $key) use ($replacements) { 187 | if (Str::startsWith($value, ':') && isset($replacements[$value])) { 188 | $value = $replacements[$value]; 189 | } 190 | }); 191 | 192 | $response = $this->recursivelyRemoveEmptyReplacements($response); 193 | 194 | return new Response($response, $this->getStatusCode($exception), $this->getHeaders($exception)); 195 | } 196 | 197 | /** 198 | * Get the status code from the exception. 199 | * 200 | * @param Throwable $exception 201 | * 202 | * @return int 203 | */ 204 | protected function getStatusCode(Throwable $exception) 205 | { 206 | $statusCode = null; 207 | 208 | if ($exception instanceof ValidationException) { 209 | $statusCode = $exception->status; 210 | } elseif ($exception instanceof HttpExceptionInterface) { 211 | $statusCode = $exception->getStatusCode(); 212 | } else { 213 | // By default throw 500 214 | $statusCode = 500; 215 | } 216 | 217 | // Be extra defensive 218 | if ($statusCode < 100 || $statusCode > 599) { 219 | $statusCode = 500; 220 | } 221 | 222 | return $statusCode; 223 | } 224 | 225 | /** 226 | * Get the headers from the exception. 227 | * 228 | * @param Throwable $exception 229 | * 230 | * @return array 231 | */ 232 | protected function getHeaders(Throwable $exception) 233 | { 234 | return $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : []; 235 | } 236 | 237 | /** 238 | * Prepare the replacements array by gathering the keys and values. 239 | * 240 | * @param Throwable $exception 241 | * 242 | * @return array 243 | */ 244 | protected function prepareReplacements(Throwable $exception) 245 | { 246 | $statusCode = $this->getStatusCode($exception); 247 | 248 | if (! $message = $exception->getMessage()) { 249 | $message = sprintf('%d %s', $statusCode, Response::$statusTexts[$statusCode]); 250 | } 251 | 252 | $replacements = [ 253 | ':message' => $message, 254 | ':status_code' => $statusCode, 255 | ]; 256 | 257 | if ($exception instanceof MessageBagErrors && $exception->hasErrors()) { 258 | $replacements[':errors'] = $exception->getErrors(); 259 | } 260 | 261 | if ($exception instanceof ValidationException) { 262 | $replacements[':errors'] = $exception->errors(); 263 | $replacements[':status_code'] = $exception->status; 264 | } 265 | 266 | if ($code = $exception->getCode()) { 267 | $replacements[':code'] = $code; 268 | } 269 | 270 | if ($this->runningInDebugMode()) { 271 | $replacements[':debug'] = [ 272 | 'line' => $exception->getLine(), 273 | 'file' => $exception->getFile(), 274 | 'class' => get_class($exception), 275 | 'trace' => explode("\n", $exception->getTraceAsString()), 276 | ]; 277 | 278 | // Attach trace of previous exception, if exists 279 | if (! is_null($exception->getPrevious())) { 280 | $currentTrace = $replacements[':debug']['trace']; 281 | 282 | $replacements[':debug']['trace'] = [ 283 | 'previous' => explode("\n", $exception->getPrevious()->getTraceAsString()), 284 | 'current' => $currentTrace, 285 | ]; 286 | } 287 | } 288 | 289 | return array_merge($replacements, $this->replacements); 290 | } 291 | 292 | /** 293 | * Set user defined replacements. 294 | * 295 | * @param array $replacements 296 | * 297 | * @return void 298 | */ 299 | public function setReplacements(array $replacements) 300 | { 301 | $this->replacements = $replacements; 302 | } 303 | 304 | /** 305 | * Recursively remove any empty replacement values in the response array. 306 | * 307 | * @param array $input 308 | * 309 | * @return array 310 | */ 311 | protected function recursivelyRemoveEmptyReplacements(array $input) 312 | { 313 | foreach ($input as &$value) { 314 | if (is_array($value)) { 315 | $value = $this->recursivelyRemoveEmptyReplacements($value); 316 | } 317 | } 318 | 319 | return array_filter($input, function ($value) { 320 | if (is_string($value)) { 321 | return ! Str::startsWith($value, ':'); 322 | } 323 | 324 | return true; 325 | }); 326 | } 327 | 328 | /** 329 | * Create a new response array with replacement values. 330 | * 331 | * @return array 332 | */ 333 | protected function newResponseArray() 334 | { 335 | return $this->format; 336 | } 337 | 338 | /** 339 | * Get the exception status code. 340 | * 341 | * @param Exception $exception 342 | * @param int $defaultStatusCode 343 | * 344 | * @return int 345 | */ 346 | protected function getExceptionStatusCode(Exception $exception, $defaultStatusCode = 500) 347 | { 348 | return ($exception instanceof HttpExceptionInterface) ? $exception->getStatusCode() : $defaultStatusCode; 349 | } 350 | 351 | /** 352 | * Determines if we are running in debug mode. 353 | * 354 | * @return bool 355 | */ 356 | protected function runningInDebugMode() 357 | { 358 | return $this->debug; 359 | } 360 | 361 | /** 362 | * Get the hint for an exception handler. 363 | * 364 | * @param callable $callback 365 | * 366 | * @return string 367 | */ 368 | protected function handlerHint(callable $callback) 369 | { 370 | $reflection = new ReflectionFunction($callback); 371 | 372 | $exception = $reflection->getParameters()[0]; 373 | $reflectionType = $exception->getType(); 374 | 375 | if ($reflectionType && ! $reflectionType->isBuiltin()) { 376 | if ($reflectionType instanceof \ReflectionNamedType) { 377 | return $reflectionType->getName(); 378 | } 379 | } 380 | 381 | return ''; 382 | } 383 | 384 | /** 385 | * Get the exception handlers. 386 | * 387 | * @return array 388 | */ 389 | public function getHandlers() 390 | { 391 | return $this->handlers; 392 | } 393 | 394 | /** 395 | * Set the error format array. 396 | * 397 | * @param array $format 398 | * 399 | * @return void 400 | */ 401 | public function setErrorFormat(array $format) 402 | { 403 | $this->format = $format; 404 | } 405 | 406 | /** 407 | * Set the debug mode. 408 | * 409 | * @param bool $debug 410 | * 411 | * @return void 412 | */ 413 | public function setDebug($debug) 414 | { 415 | $this->debug = $debug; 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/Exception/InternalHttpException.php: -------------------------------------------------------------------------------- 1 | response = $response; 32 | 33 | parent::__construct($response->getStatusCode(), $message, $previous, $headers, $code); 34 | } 35 | 36 | /** 37 | * Get the response of the internal request. 38 | * 39 | * @return \Illuminate\Http\Response 40 | */ 41 | public function getResponse() 42 | { 43 | return $this->response; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Exception/RateLimitExceededException.php: -------------------------------------------------------------------------------- 1 | errors = new MessageBag; 34 | } else { 35 | $this->errors = is_array($errors) ? new MessageBag($errors) : $errors; 36 | } 37 | 38 | parent::__construct(422, $message, $previous, $headers, $code); 39 | } 40 | 41 | /** 42 | * Get the errors message bag. 43 | * 44 | * @return \Illuminate\Support\MessageBag 45 | */ 46 | public function getErrors() 47 | { 48 | return $this->errors; 49 | } 50 | 51 | /** 52 | * Determine if message bag has any errors. 53 | * 54 | * @return bool 55 | */ 56 | public function hasErrors() 57 | { 58 | return ! $this->errors->isEmpty(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Exception/StoreResourceFailedException.php: -------------------------------------------------------------------------------- 1 | register($callback); 30 | } 31 | 32 | /** 33 | * Register a class transformer. 34 | * 35 | * @param string $class 36 | * @param string|\Closure $transformer 37 | * 38 | * @return \Dingo\Api\Transformer\Binding 39 | */ 40 | public static function transform($class, $transformer) 41 | { 42 | return static::$app['api.transformer']->register($class, $transformer); 43 | } 44 | 45 | /** 46 | * Get the authenticator. 47 | * 48 | * @return \Dingo\Api\Auth\Auth 49 | */ 50 | public static function auth() 51 | { 52 | return static::$app['api.auth']; 53 | } 54 | 55 | /** 56 | * Get the authenticated user. 57 | * 58 | * @return \Illuminate\Auth\GenericUser|\Illuminate\Database\Eloquent\Model 59 | */ 60 | public static function user() 61 | { 62 | return static::$app['api.auth']->user(); 63 | } 64 | 65 | /** 66 | * Determine if a request is internal. 67 | * 68 | * @return bool 69 | */ 70 | public static function internal() 71 | { 72 | return static::$app['api.router']->getCurrentRequest() instanceof InternalRequest; 73 | } 74 | 75 | /** 76 | * Get the response factory to begin building a response. 77 | * 78 | * @return \Dingo\Api\Http\Response\Factory 79 | */ 80 | public static function response() 81 | { 82 | return static::$app['api.http.response']; 83 | } 84 | 85 | /** 86 | * Get the API router instance. 87 | * 88 | * @return \Dingo\Api\Routing\Router 89 | */ 90 | public static function router() 91 | { 92 | return static::$app['api.router']; 93 | } 94 | 95 | /** 96 | * Get the API route of the given name, and optionally specify the API version. 97 | * 98 | * @param string $routeName 99 | * @param string $apiVersion 100 | * 101 | * @return string 102 | */ 103 | public static function route($routeName, $apiVersion = 'v1') 104 | { 105 | return static::$app['api.url']->version($apiVersion)->route($routeName); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Facade/Route.php: -------------------------------------------------------------------------------- 1 | authorize() === false) { 78 | throw new AccessDeniedHttpException(); 79 | } 80 | 81 | $validator = app('validator')->make($this->all(), $this->rules(), $this->messages()); 82 | 83 | if ($validator->fails()) { 84 | throw new ValidationHttpException($validator->errors()); 85 | } 86 | } 87 | 88 | /** 89 | * Get the validator instance for the request. 90 | * 91 | * @return \Illuminate\Contracts\Validation\Validator 92 | * 93 | * @SuppressWarnings(PHPMD.ElseExpression) 94 | */ 95 | protected function getValidatorInstance() 96 | { 97 | $factory = $this->container->make(ValidationFactory::class); 98 | 99 | if (method_exists($this, 'validator')) { 100 | $validator = $this->container->call([$this, 'validator'], compact('factory')); 101 | } else { 102 | $validator = $this->createDefaultValidator($factory); 103 | } 104 | 105 | if (method_exists($this, 'withValidator')) { 106 | $this->withValidator($validator); 107 | } 108 | 109 | return $validator; 110 | } 111 | 112 | /** 113 | * Create the default validator instance. 114 | * 115 | * @param \Illuminate\Contracts\Validation\Factory $factory 116 | * 117 | * @return \Illuminate\Contracts\Validation\Validator 118 | */ 119 | protected function createDefaultValidator(ValidationFactory $factory) 120 | { 121 | return $factory->make( 122 | $this->validationData(), 123 | $this->container->call([$this, 'rules']), 124 | $this->messages(), 125 | $this->attributes() 126 | ); 127 | } 128 | 129 | /** 130 | * Get data to be validated from the request. 131 | * 132 | * @return array 133 | */ 134 | protected function validationData() 135 | { 136 | return $this->all(); 137 | } 138 | 139 | /** 140 | * Handle a failed validation attempt. 141 | * 142 | * @param \Illuminate\Contracts\Validation\Validator $validator 143 | * 144 | * @return void 145 | */ 146 | protected function failedValidation(Validator $validator) 147 | { 148 | if ($this->container['request'] instanceof Request) { 149 | throw new ValidationHttpException($validator->errors()); 150 | } 151 | 152 | parent::failedValidation($validator); 153 | } 154 | 155 | /** 156 | * Get the proper failed validation response for the request. 157 | * 158 | * @param array $errors 159 | * 160 | * @return \Symfony\Component\HttpFoundation\Response 161 | */ 162 | public function response(array $errors) 163 | { 164 | if ($this->expectsJson()) { 165 | return new JsonResponse($errors, 422); 166 | } 167 | 168 | return $this->redirector->to($this->getRedirectUrl()) 169 | ->withInput($this->except($this->dontFlash)) 170 | ->withErrors($errors, $this->errorBag); 171 | } 172 | 173 | /** 174 | * Format the errors from the given Validator instance. 175 | * 176 | * @param \Illuminate\Contracts\Validation\Validator $validator 177 | * 178 | * @return array 179 | */ 180 | protected function formatErrors(Validator $validator) 181 | { 182 | return $validator->getMessageBag()->toArray(); 183 | } 184 | 185 | /** 186 | * Get the URL to redirect to on a validation error. 187 | * 188 | * @return string 189 | */ 190 | protected function getRedirectUrl() 191 | { 192 | $url = $this->redirector->getUrlGenerator(); 193 | 194 | if ($this->redirect) { 195 | return $url->to($this->redirect); 196 | } elseif ($this->redirectRoute) { 197 | return $url->route($this->redirectRoute); 198 | } elseif ($this->redirectAction) { 199 | return $url->action($this->redirectAction); 200 | } 201 | 202 | return $url->previous(); 203 | } 204 | 205 | /** 206 | * Determine if the request passes the authorization check. 207 | * 208 | * @return bool 209 | */ 210 | protected function passesAuthorization() 211 | { 212 | if (method_exists($this, 'authorize')) { 213 | return $this->container->call([$this, 'authorize']); 214 | } 215 | 216 | return false; 217 | } 218 | 219 | /** 220 | * Handle a failed authorization attempt. 221 | * 222 | * @return void 223 | */ 224 | protected function failedAuthorization() 225 | { 226 | if ($this->container['request'] instanceof Request) { 227 | throw new HttpException(403); 228 | } 229 | 230 | parent::failedAuthorization(); 231 | } 232 | 233 | /** 234 | * Get custom messages for validator errors. 235 | * 236 | * @return array 237 | */ 238 | public function messages() 239 | { 240 | return []; 241 | } 242 | 243 | /** 244 | * Get custom attributes for validator errors. 245 | * 246 | * @return array 247 | */ 248 | public function attributes() 249 | { 250 | return []; 251 | } 252 | 253 | /** 254 | * Set the Redirector instance. 255 | * 256 | * @param \Laravel\Lumen\Http\Redirector|\Illuminate\Routing\Redirector $redirector 257 | * 258 | * @return $this 259 | */ 260 | public function setRedirector($redirector) 261 | { 262 | $this->redirector = $redirector; 263 | 264 | return $this; 265 | } 266 | 267 | /** 268 | * Set the container implementation. 269 | * 270 | * @param \Illuminate\Contracts\Container\Container $container 271 | * 272 | * @return $this 273 | */ 274 | public function setContainer(Container $container) 275 | { 276 | $this->container = $container; 277 | 278 | return $this; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/Http/InternalRequest.php: -------------------------------------------------------------------------------- 1 | input() 13 | if ($this->isJson() && isset($this->request)) { 14 | $this->setJson($this->request); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Http/Middleware/Auth.php: -------------------------------------------------------------------------------- 1 | router = $router; 36 | $this->auth = $auth; 37 | } 38 | 39 | /** 40 | * Perform authentication before a request is executed. 41 | * 42 | * @param \Illuminate\Http\Request $request 43 | * @param \Closure $next 44 | * 45 | * @return mixed 46 | */ 47 | public function handle($request, Closure $next) 48 | { 49 | $route = $this->router->getCurrentRoute(); 50 | 51 | if (! $this->auth->check(false)) { 52 | $this->auth->authenticate($route->getAuthenticationProviders()); 53 | } 54 | 55 | return $next($request); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Http/Middleware/PrepareController.php: -------------------------------------------------------------------------------- 1 | router = $router; 27 | } 28 | 29 | /** 30 | * Handle the request. 31 | * 32 | * @param \Dingo\Api\Http\Request $request 33 | * @param \Closure $next 34 | * 35 | * @return mixed 36 | */ 37 | public function handle($request, Closure $next) 38 | { 39 | // To prepare the controller all we need to do is call the current method on the router to fetch 40 | // the current route. This will create a new Dingo\Api\Routing\Route instance and prepare the 41 | // controller by binding it as a singleton in the container. This will result in the 42 | // controller only be instantiated once per request. 43 | $this->router->current(); 44 | 45 | return $next($request); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Http/Middleware/RateLimit.php: -------------------------------------------------------------------------------- 1 | router = $router; 39 | $this->handler = $handler; 40 | } 41 | 42 | /** 43 | * Perform rate limiting before a request is executed. 44 | * 45 | * @param \Dingo\Api\Http\Request $request 46 | * @param \Closure $next 47 | * 48 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 49 | * 50 | * @return mixed 51 | */ 52 | public function handle($request, Closure $next) 53 | { 54 | if ($request instanceof InternalRequest) { 55 | return $next($request); 56 | } 57 | 58 | $route = $this->router->getCurrentRoute(); 59 | 60 | if ($route->hasThrottle()) { 61 | $this->handler->setThrottle($route->getThrottle()); 62 | } 63 | 64 | $this->handler->rateLimitRequest($request, $route->getRateLimit(), $route->getRateLimitExpiration()); 65 | 66 | if ($this->handler->exceededRateLimit()) { 67 | throw new RateLimitExceededException('You have exceeded your rate limit.', null, $this->getHeaders()); 68 | } 69 | 70 | $response = $next($request); 71 | 72 | if ($this->handler->requestWasRateLimited()) { 73 | return $this->responseWithHeaders($response); 74 | } 75 | 76 | return $response; 77 | } 78 | 79 | /** 80 | * Send the response with the rate limit headers. 81 | * 82 | * @param \Dingo\Api\Http\Response $response 83 | * 84 | * @return \Dingo\Api\Http\Response 85 | */ 86 | protected function responseWithHeaders($response) 87 | { 88 | foreach ($this->getHeaders() as $key => $value) { 89 | $response->headers->set($key, $value); 90 | } 91 | 92 | return $response; 93 | } 94 | 95 | /** 96 | * Get the headers for the response. 97 | * 98 | * @return array 99 | */ 100 | protected function getHeaders() 101 | { 102 | return [ 103 | 'X-RateLimit-Limit' => $this->handler->getThrottleLimit(), 104 | 'X-RateLimit-Remaining' => $this->handler->getRemainingLimit(), 105 | 'X-RateLimit-Reset' => $this->handler->getRateLimitReset(), 106 | ]; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Http/Middleware/Request.php: -------------------------------------------------------------------------------- 1 | app = $app; 77 | $this->exception = $exception; 78 | $this->router = $router; 79 | $this->validator = $validator; 80 | $this->events = $events; 81 | } 82 | 83 | /** 84 | * Handle an incoming request. 85 | * 86 | * @param \Illuminate\Http\Request $request 87 | * @param \Closure $next 88 | * 89 | * @return mixed 90 | */ 91 | public function handle($request, Closure $next) 92 | { 93 | try { 94 | if ($this->validator->validateRequest($request)) { 95 | $this->app->singleton(LaravelExceptionHandler::class, function ($app) { 96 | return $app[ExceptionHandler::class]; 97 | }); 98 | 99 | $request = $this->app->make(RequestContract::class)->createFromIlluminate($request); 100 | 101 | $this->events->dispatch(new RequestWasMatched($request, $this->app)); 102 | 103 | return $this->sendRequestThroughRouter($request); 104 | } 105 | } catch (Exception $exception) { 106 | $this->exception->report($exception); 107 | 108 | return $this->exception->handle($exception); 109 | } 110 | 111 | return $next($request); 112 | } 113 | 114 | /** 115 | * Send the request through the Dingo router. 116 | * 117 | * @param \Dingo\Api\Http\Request $request 118 | * 119 | * @return \Dingo\Api\Http\Response 120 | */ 121 | protected function sendRequestThroughRouter(HttpRequest $request) 122 | { 123 | $this->app->instance('request', $request); 124 | 125 | return (new Pipeline($this->app))->send($request)->through($this->middleware)->then(function ($request) { 126 | return $this->router->dispatch($request); 127 | }); 128 | } 129 | 130 | /** 131 | * Call the terminate method on middlewares. 132 | * 133 | * @return void 134 | */ 135 | public function terminate($request, $response) 136 | { 137 | if (! ($request = $this->app['request']) instanceof HttpRequest) { 138 | return; 139 | } 140 | 141 | // Laravel's route middlewares can be terminated just like application 142 | // middleware, so we'll gather all the route middleware here. 143 | // On Lumen this will simply be an empty array as it does 144 | // not implement terminable route middleware. 145 | $middlewares = $this->gatherRouteMiddlewares($request); 146 | 147 | // Because of how middleware is executed on Lumen we'll need to merge in the 148 | // application middlewares now so that we can terminate them. Laravel does 149 | // not need this as it handles things a little more gracefully so it 150 | // can terminate the application ones itself. 151 | if (class_exists(Application::class, false)) { 152 | $middlewares = array_merge($middlewares, $this->middleware); 153 | } 154 | 155 | foreach ($middlewares as $middleware) { 156 | if ($middleware instanceof Closure) { 157 | continue; 158 | } 159 | 160 | [$name, $parameters] = $this->parseMiddleware($middleware); 161 | 162 | $instance = $this->app->make($name); 163 | 164 | if (method_exists($instance, 'terminate')) { 165 | $instance->terminate($request, $response); 166 | } 167 | } 168 | } 169 | 170 | /** 171 | * Parse a middleware string to get the name and parameters. 172 | * 173 | * @author Taylor Otwell 174 | * 175 | * @param string $middleware 176 | * 177 | * @return array 178 | */ 179 | protected function parseMiddleware($middleware) 180 | { 181 | [$name, $parameters] = array_pad(explode(':', $middleware, 2), 2, []); 182 | 183 | if (is_string($parameters)) { 184 | $parameters = explode(',', $parameters); 185 | } 186 | 187 | return [$name, $parameters]; 188 | } 189 | 190 | /** 191 | * Gather the middlewares for the route. 192 | * 193 | * @param \Dingo\Api\Http\Request $request 194 | * 195 | * @return array 196 | */ 197 | protected function gatherRouteMiddlewares($request) 198 | { 199 | if ($route = $request->route()) { 200 | return $this->router->gatherRouteMiddlewares($route); 201 | } 202 | 203 | return []; 204 | } 205 | 206 | /** 207 | * Set the middlewares. 208 | * 209 | * @param array $middleware 210 | * 211 | * @return void 212 | */ 213 | public function setMiddlewares(array $middleware) 214 | { 215 | $this->middleware = $middleware; 216 | } 217 | 218 | /** 219 | * Merge new middlewares onto the existing middlewares. 220 | * 221 | * @param array $middleware 222 | * 223 | * @return void 224 | */ 225 | public function mergeMiddlewares(array $middleware) 226 | { 227 | $this->middleware = array_merge($this->middleware, $middleware); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/Http/Parser/Accept.php: -------------------------------------------------------------------------------- 1 | standardsTree = $standardsTree; 52 | $this->subtype = $subtype; 53 | $this->version = $version; 54 | $this->format = $format; 55 | } 56 | 57 | /** 58 | * Parse the accept header on the incoming request. If strict is enabled 59 | * then the accept header must be available and must be a valid match. 60 | * 61 | * @param \Illuminate\Http\Request $request 62 | * @param bool $strict 63 | * 64 | * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException 65 | * 66 | * @return array 67 | */ 68 | public function parse(Request $request, $strict = false) 69 | { 70 | $pattern = '/application\/'.$this->standardsTree.'\.('.$this->subtype.')\.([\w\d\.\-]+)\+([\w]+)/'; 71 | 72 | if (! preg_match($pattern, $request->header('accept'), $matches)) { 73 | if ($strict) { 74 | throw new BadRequestHttpException('Accept header could not be properly parsed because of a strict matching process.'); 75 | } 76 | 77 | $default = 'application/'.$this->standardsTree.'.'.$this->subtype.'.'.$this->version.'+'.$this->format; 78 | 79 | preg_match($pattern, $default, $matches); 80 | } 81 | 82 | return array_combine(['subtype', 'version', 'format'], array_slice($matches, 1)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Http/RateLimit/Handler.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 77 | $this->container = $container; 78 | $this->throttles = new Collection($throttles); 79 | } 80 | 81 | /** 82 | * Execute the rate limiting for the given request. 83 | * 84 | * @param \Dingo\Api\Http\Request $request 85 | * @param int $limit 86 | * @param int $expires 87 | * 88 | * @return void 89 | */ 90 | public function rateLimitRequest(Request $request, $limit = 0, $expires = 0) 91 | { 92 | $this->request = $request; 93 | 94 | // If the throttle instance is already set then we'll just carry on as 95 | // per usual. 96 | if ($this->throttle instanceof Throttle) { 97 | 98 | // If the developer specified a certain amount of requests or expiration 99 | // time on a specific route then we'll always use the route specific 100 | // throttle with the given values. 101 | } elseif ($limit > 0 || $expires > 0) { 102 | $this->throttle = new Route(['limit' => $limit, 'expires' => $expires]); 103 | $this->keyPrefix = sha1($request->path()); 104 | 105 | // Otherwise we'll use the throttle that gives the consumer the largest 106 | // amount of requests. If no matching throttle is found then rate 107 | // limiting will not be imposed for the request. 108 | } else { 109 | $this->throttle = $this->getMatchingThrottles()->sort(function ($a, $b) { 110 | return $a->getLimit() < $b->getLimit(); 111 | })->first(); 112 | } 113 | 114 | if (is_null($this->throttle)) { 115 | return; 116 | } 117 | 118 | if ($this->throttle instanceof HasRateLimiter) { 119 | $this->setRateLimiter([$this->throttle, 'getRateLimiter']); 120 | } 121 | 122 | $this->prepareCacheStore(); 123 | 124 | $this->cache('requests', 0, $this->throttle->getExpires()); 125 | $this->cache('expires', $this->throttle->getExpires(), $this->throttle->getExpires()); 126 | $this->cache('reset', time() + ($this->throttle->getExpires() * 60), $this->throttle->getExpires()); 127 | $this->increment('requests'); 128 | } 129 | 130 | /** 131 | * Prepare the cache store. 132 | * 133 | * @return void 134 | */ 135 | protected function prepareCacheStore() 136 | { 137 | if ($this->retrieve('expires') != $this->throttle->getExpires()) { 138 | $this->forget('requests'); 139 | $this->forget('expires'); 140 | $this->forget('reset'); 141 | } 142 | } 143 | 144 | /** 145 | * Determine if the rate limit has been exceeded. 146 | * 147 | * @return bool 148 | */ 149 | public function exceededRateLimit() 150 | { 151 | return $this->requestWasRateLimited() ? $this->retrieve('requests') > $this->throttle->getLimit() : false; 152 | } 153 | 154 | /** 155 | * Get matching throttles after executing the condition of each throttle. 156 | * 157 | * @return \Illuminate\Support\Collection 158 | */ 159 | protected function getMatchingThrottles() 160 | { 161 | return $this->throttles->filter(function ($throttle) { 162 | return $throttle->match($this->container); 163 | }); 164 | } 165 | 166 | /** 167 | * Namespace a cache key. 168 | * 169 | * @param string $key 170 | * 171 | * @return string 172 | */ 173 | protected function key($key) 174 | { 175 | return sprintf('dingo.api.%s.%s', $key, $this->getRateLimiter()); 176 | } 177 | 178 | /** 179 | * Cache a value under a given key for a certain amount of minutes. 180 | * 181 | * @param string $key 182 | * @param mixed $value 183 | * @param int $minutes 184 | * 185 | * @return void 186 | */ 187 | protected function cache($key, $value, $minutes) 188 | { 189 | $this->cache->add($this->key($key), $value, Carbon::now()->addMinutes($minutes)); 190 | } 191 | 192 | /** 193 | * Retrieve a value from the cache store. 194 | * 195 | * @param string $key 196 | * 197 | * @return mixed 198 | */ 199 | protected function retrieve($key) 200 | { 201 | return $this->cache->get($this->key($key)); 202 | } 203 | 204 | /** 205 | * Increment a key in the cache. 206 | * 207 | * @param string $key 208 | * 209 | * @return void 210 | */ 211 | protected function increment($key) 212 | { 213 | $this->cache->increment($this->key($key)); 214 | } 215 | 216 | /** 217 | * Forget a key in the cache. 218 | * 219 | * @param string $key 220 | * 221 | * @return void 222 | */ 223 | protected function forget($key) 224 | { 225 | $this->cache->forget($this->key($key)); 226 | } 227 | 228 | /** 229 | * Determine if the request was rate limited. 230 | * 231 | * @return bool 232 | */ 233 | public function requestWasRateLimited() 234 | { 235 | return ! is_null($this->throttle); 236 | } 237 | 238 | /** 239 | * Get the rate limiter. 240 | * 241 | * @return string 242 | */ 243 | public function getRateLimiter() 244 | { 245 | return call_user_func($this->limiter ?: function ($container, $request) { 246 | return $request->getClientIp(); 247 | }, $this->container, $this->request); 248 | } 249 | 250 | /** 251 | * Set the rate limiter. 252 | * 253 | * @param callable $limiter 254 | * 255 | * @return void 256 | */ 257 | public function setRateLimiter(callable $limiter) 258 | { 259 | $this->limiter = $limiter; 260 | } 261 | 262 | /** 263 | * Set the throttle to use for rate limiting. 264 | * 265 | * @param string|\Dingo\Api\Contract\Http\RateLimit\Throttle $throttle 266 | * 267 | * @return void 268 | */ 269 | public function setThrottle($throttle) 270 | { 271 | if (is_string($throttle)) { 272 | $throttle = $this->container->make($throttle); 273 | } 274 | 275 | $this->throttle = $throttle; 276 | } 277 | 278 | /** 279 | * Get the throttle used to rate limit the request. 280 | * 281 | * @return \Dingo\Api\Contract\Http\RateLimit\Throttle 282 | */ 283 | public function getThrottle() 284 | { 285 | return $this->throttle; 286 | } 287 | 288 | /** 289 | * Get the limit of the throttle used. 290 | * 291 | * @return int 292 | */ 293 | public function getThrottleLimit() 294 | { 295 | return $this->throttle->getLimit(); 296 | } 297 | 298 | /** 299 | * Get the remaining limit before the consumer is rate limited. 300 | * 301 | * @return int 302 | */ 303 | public function getRemainingLimit() 304 | { 305 | $remaining = $this->throttle->getLimit() - $this->retrieve('requests'); 306 | 307 | return $remaining > 0 ? $remaining : 0; 308 | } 309 | 310 | /** 311 | * Get the timestamp for when the current rate limiting will expire. 312 | * 313 | * @return int 314 | */ 315 | public function getRateLimitReset() 316 | { 317 | return $this->retrieve('reset'); 318 | } 319 | 320 | /** 321 | * Extend the rate limiter by adding a new throttle. 322 | * 323 | * @param callable|\Dingo\Api\Http\RateLimit\Throttle $throttle 324 | * 325 | * @return void 326 | */ 327 | public function extend($throttle) 328 | { 329 | if (is_callable($throttle)) { 330 | $throttle = call_user_func($throttle, $this->container); 331 | } 332 | 333 | $this->throttles->push($throttle); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/Http/RateLimit/Throttle/Authenticated.php: -------------------------------------------------------------------------------- 1 | check(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Http/RateLimit/Throttle/Route.php: -------------------------------------------------------------------------------- 1 | 60, 'expires' => 60]; 15 | 16 | /** 17 | * Create a new throttle instance. 18 | * 19 | * @param array $options 20 | * 21 | * @return void 22 | */ 23 | public function __construct(array $options = []) 24 | { 25 | $this->options = array_merge($this->options, $options); 26 | } 27 | 28 | /** 29 | * Get the throttles request limit. 30 | * 31 | * @return int 32 | */ 33 | public function getLimit() 34 | { 35 | return $this->options['limit']; 36 | } 37 | 38 | /** 39 | * Get the time in minutes that the throttles request limit will expire. 40 | * 41 | * @return int 42 | */ 43 | public function getExpires() 44 | { 45 | return $this->options['expires']; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Http/RateLimit/Throttle/Unauthenticated.php: -------------------------------------------------------------------------------- 1 | check(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | query->all(), $old->request->all(), $old->attributes->all(), 36 | $old->cookies->all(), $old->files->all(), $old->server->all(), $old->content 37 | ); 38 | 39 | if ($session = $old->getSession()) { 40 | $new->setLaravelSession($old->getSession()); 41 | } 42 | 43 | $new->setRouteResolver($old->getRouteResolver()); 44 | $new->setUserResolver($old->getUserResolver()); 45 | 46 | return $new; 47 | } 48 | 49 | /** 50 | * Get the defined version. 51 | * 52 | * @return string 53 | */ 54 | public function version() 55 | { 56 | $this->parseAcceptHeader(); 57 | 58 | return $this->accept['version']; 59 | } 60 | 61 | /** 62 | * Get the defined subtype. 63 | * 64 | * @return string 65 | */ 66 | public function subtype() 67 | { 68 | $this->parseAcceptHeader(); 69 | 70 | return $this->accept['subtype']; 71 | } 72 | 73 | /** 74 | * Get the expected format type. 75 | * 76 | * @return string 77 | */ 78 | public function format($default = 'html') 79 | { 80 | $this->parseAcceptHeader(); 81 | 82 | return $this->accept['format'] ?: parent::format($default); 83 | } 84 | 85 | /** 86 | * Parse the accept header. 87 | * 88 | * @return void 89 | */ 90 | protected function parseAcceptHeader() 91 | { 92 | if ($this->accept) { 93 | return; 94 | } 95 | 96 | $this->accept = static::$acceptParser->parse($this); 97 | } 98 | 99 | /** 100 | * Set the accept parser instance. 101 | * 102 | * @param \Dingo\Api\Http\Parser\Accept $acceptParser 103 | * 104 | * @return void 105 | */ 106 | public static function setAcceptParser(Accept $acceptParser) 107 | { 108 | static::$acceptParser = $acceptParser; 109 | } 110 | 111 | /** 112 | * Get the accept parser instance. 113 | * 114 | * @return \Dingo\Api\Http\Parser\Accept 115 | */ 116 | public static function getAcceptParser() 117 | { 118 | return static::$acceptParser; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Http/RequestValidator.php: -------------------------------------------------------------------------------- 1 | container = $container; 41 | } 42 | 43 | /** 44 | * Replace the validators. 45 | * 46 | * @param array $validators 47 | * 48 | * @return void 49 | */ 50 | public function replace(array $validators) 51 | { 52 | $this->validators = $validators; 53 | } 54 | 55 | /** 56 | * Merge an array of validators. 57 | * 58 | * @param array $validators 59 | * 60 | * @return void 61 | */ 62 | public function merge(array $validators) 63 | { 64 | $this->validators = array_merge($this->validators, $validators); 65 | } 66 | 67 | /** 68 | * Extend the validators. 69 | * 70 | * @param string|\Dingo\Api\Http\Validator $validator 71 | * 72 | * @return void 73 | */ 74 | public function extend($validator) 75 | { 76 | $this->validators[] = $validator; 77 | } 78 | 79 | /** 80 | * Validate a request. 81 | * 82 | * @param \Illuminate\Http\Request $request 83 | * 84 | * @return bool 85 | */ 86 | public function validateRequest(IlluminateRequest $request) 87 | { 88 | $passed = false; 89 | 90 | foreach ($this->validators as $validator) { 91 | $validator = $this->container->make($validator); 92 | 93 | if ($validator instanceof Validator && $validator->validate($request)) { 94 | $passed = true; 95 | } 96 | } 97 | 98 | // The accept validator will always be run once any of the previous validators have 99 | // been run. This ensures that we only run the accept validator once we know we 100 | // have a request that is targeting the API. 101 | if ($passed) { 102 | $this->container->make(Accept::class)->validate($request); 103 | } 104 | 105 | return $passed; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Http/Response/Factory.php: -------------------------------------------------------------------------------- 1 | transformer = $transformer; 33 | } 34 | 35 | /** 36 | * Respond with a created response and associate a location if provided. 37 | * 38 | * @param null|string $location 39 | * 40 | * @return \Dingo\Api\Http\Response 41 | */ 42 | public function created($location = null, $content = null) 43 | { 44 | $response = new Response($content); 45 | $response->setStatusCode(201); 46 | 47 | if (! is_null($location)) { 48 | $response->header('Location', $location); 49 | } 50 | 51 | return $response; 52 | } 53 | 54 | /** 55 | * Respond with an accepted response and associate a location and/or content if provided. 56 | * 57 | * @param null|string $location 58 | * @param mixed $content 59 | * 60 | * @return \Dingo\Api\Http\Response 61 | */ 62 | public function accepted($location = null, $content = null) 63 | { 64 | $response = new Response($content); 65 | $response->setStatusCode(202); 66 | 67 | if (! is_null($location)) { 68 | $response->header('Location', $location); 69 | } 70 | 71 | return $response; 72 | } 73 | 74 | /** 75 | * Respond with a no content response. 76 | * 77 | * @return \Dingo\Api\Http\Response 78 | */ 79 | public function noContent() 80 | { 81 | $response = new Response(null); 82 | 83 | return $response->setStatusCode(204); 84 | } 85 | 86 | /** 87 | * Bind a collection to a transformer and start building a response. 88 | * 89 | * @param \Illuminate\Support\Collection $collection 90 | * @param string|callable|object $transformer 91 | * @param array|\Closure $parameters 92 | * @param \Closure|null $after 93 | * 94 | * @return \Dingo\Api\Http\Response 95 | */ 96 | public function collection(Collection $collection, $transformer = null, $parameters = [], Closure $after = null) 97 | { 98 | if ($collection->isEmpty()) { 99 | $class = get_class($collection); 100 | } else { 101 | $class = get_class($collection->first()); 102 | } 103 | 104 | if ($parameters instanceof \Closure) { 105 | $after = $parameters; 106 | $parameters = []; 107 | } 108 | 109 | if ($transformer !== null) { 110 | $binding = $this->transformer->register($class, $transformer, $parameters, $after); 111 | } else { 112 | $binding = $this->transformer->getBinding($collection); 113 | } 114 | 115 | return new Response($collection, 200, [], $binding); 116 | } 117 | 118 | /** 119 | * Bind an item to a transformer and start building a response. 120 | * 121 | * @param object $item 122 | * @param null|string|callable|object $transformer 123 | * @param array $parameters 124 | * @param \Closure $after 125 | * 126 | * @return \Dingo\Api\Http\Response 127 | */ 128 | public function item($item, $transformer = null, $parameters = [], Closure $after = null) 129 | { 130 | // Check for $item being null 131 | if (! is_null($item)) { 132 | $class = get_class($item); 133 | } else { 134 | $class = \StdClass::class; 135 | } 136 | 137 | if ($parameters instanceof \Closure) { 138 | $after = $parameters; 139 | $parameters = []; 140 | } 141 | 142 | if ($transformer !== null) { 143 | $binding = $this->transformer->register($class, $transformer, $parameters, $after); 144 | } else { 145 | $binding = $this->transformer->getBinding($item); 146 | } 147 | 148 | return new Response($item, 200, [], $binding); 149 | } 150 | 151 | /** 152 | * Bind an arbitrary array to a transformer and start building a response. 153 | * 154 | * @param array $array 155 | * @param $transformer 156 | * @param array $parameters 157 | * @param Closure|null $after 158 | * 159 | * @return Response 160 | */ 161 | public function array(array $array, $transformer = null, $parameters = [], Closure $after = null) 162 | { 163 | if ($parameters instanceof \Closure) { 164 | $after = $parameters; 165 | $parameters = []; 166 | } 167 | 168 | // For backwards compatibility, allow no transformer 169 | if ($transformer) { 170 | // Use the PHP stdClass for this purpose, as a work-around, since we need to register a class binding 171 | $class = 'stdClass'; 172 | // This will convert the array into an stdClass 173 | $array = (object) $array; 174 | 175 | $binding = $this->transformer->register($class, $transformer, $parameters, $after); 176 | } else { 177 | $binding = null; 178 | } 179 | 180 | return new Response($array, 200, [], $binding); 181 | } 182 | 183 | /** 184 | * Bind a paginator to a transformer and start building a response. 185 | * 186 | * @param \Illuminate\Contracts\Pagination\Paginator $paginator 187 | * @param null|string|callable|object $transformer 188 | * @param array $parameters 189 | * @param \Closure $after 190 | * 191 | * @return \Dingo\Api\Http\Response 192 | */ 193 | public function paginator(Paginator $paginator, $transformer = null, array $parameters = [], Closure $after = null) 194 | { 195 | if ($paginator->isEmpty()) { 196 | $class = get_class($paginator); 197 | } else { 198 | $class = get_class($paginator->first()); 199 | } 200 | 201 | if ($transformer !== null) { 202 | $binding = $this->transformer->register($class, $transformer, $parameters, $after); 203 | } else { 204 | $binding = $this->transformer->getBinding($paginator->first()); 205 | } 206 | 207 | return new Response($paginator, 200, [], $binding); 208 | } 209 | 210 | /** 211 | * Return an error response. 212 | * 213 | * @param string $message 214 | * @param int $statusCode 215 | * 216 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 217 | * 218 | * @return void 219 | */ 220 | public function error($message, $statusCode) 221 | { 222 | throw new HttpException($statusCode, $message); 223 | } 224 | 225 | /** 226 | * Return a 404 not found error. 227 | * 228 | * @param string $message 229 | * 230 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 231 | * 232 | * @return void 233 | */ 234 | public function errorNotFound($message = 'Not Found') 235 | { 236 | $this->error($message, 404); 237 | } 238 | 239 | /** 240 | * Return a 400 bad request error. 241 | * 242 | * @param string $message 243 | * 244 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 245 | * 246 | * @return void 247 | */ 248 | public function errorBadRequest($message = 'Bad Request') 249 | { 250 | $this->error($message, 400); 251 | } 252 | 253 | /** 254 | * Return a 403 forbidden error. 255 | * 256 | * @param string $message 257 | * 258 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 259 | * 260 | * @return void 261 | */ 262 | public function errorForbidden($message = 'Forbidden') 263 | { 264 | $this->error($message, 403); 265 | } 266 | 267 | /** 268 | * Return a 500 internal server error. 269 | * 270 | * @param string $message 271 | * 272 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 273 | * 274 | * @return void 275 | */ 276 | public function errorInternal($message = 'Internal Error') 277 | { 278 | $this->error($message, 500); 279 | } 280 | 281 | /** 282 | * Return a 401 unauthorized error. 283 | * 284 | * @param string $message 285 | * 286 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 287 | * 288 | * @return void 289 | */ 290 | public function errorUnauthorized($message = 'Unauthorized') 291 | { 292 | $this->error($message, 401); 293 | } 294 | 295 | /** 296 | * Return a 405 method not allowed error. 297 | * 298 | * @param string $message 299 | * 300 | * @throws \Symfony\Component\HttpKernel\Exception\HttpException 301 | * 302 | * @return void 303 | */ 304 | public function errorMethodNotAllowed($message = 'Method Not Allowed') 305 | { 306 | $this->error($message, 405); 307 | } 308 | 309 | /** 310 | * Call magic methods beginning with "with". 311 | * 312 | * @param string $method 313 | * @param array $parameters 314 | * 315 | * @throws \ErrorException 316 | * 317 | * @return mixed 318 | */ 319 | public function __call($method, $parameters) 320 | { 321 | if (Str::startsWith($method, 'with')) { 322 | return call_user_func_array([$this, Str::camel(substr($method, 4))], $parameters); 323 | 324 | // Because PHP won't let us name the method "array" we'll simply watch for it 325 | // in here and return the new binding. Gross. This is now DEPRECATED and 326 | // should not be used. Just return an array or a new response instance. 327 | } elseif ($method == 'array') { 328 | return new Response($parameters[0]); 329 | } 330 | 331 | throw new ErrorException('Undefined method '.get_class($this).'::'.$method); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/Http/Response/Format/Format.php: -------------------------------------------------------------------------------- 1 | request = $request; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Set the response instance. 44 | * 45 | * @param \Illuminate\Http\Response $response 46 | * 47 | * @return \Dingo\Api\Http\Response\Format\Format 48 | */ 49 | public function setResponse($response) 50 | { 51 | $this->response = $response; 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Set the formats' options. 58 | * 59 | * @param array $options 60 | * 61 | * @return \Dingo\Api\Http\Response\Format\Format 62 | */ 63 | public function setOptions(array $options) 64 | { 65 | $this->options = $options; 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * Format an Eloquent model. 72 | * 73 | * @param \Illuminate\Database\Eloquent\Model $model 74 | * 75 | * @return string 76 | */ 77 | abstract public function formatEloquentModel($model); 78 | 79 | /** 80 | * Format an Eloquent collection. 81 | * 82 | * @param \Illuminate\Database\Eloquent\Collection $collection 83 | * 84 | * @return string 85 | */ 86 | abstract public function formatEloquentCollection($collection); 87 | 88 | /** 89 | * Format an array or instance implementing Arrayable. 90 | * 91 | * @param array|\Illuminate\Contracts\Support\Arrayable $content 92 | * 93 | * @return string 94 | */ 95 | abstract public function formatArray($content); 96 | 97 | /** 98 | * Get the response content type. 99 | * 100 | * @return string 101 | */ 102 | abstract public function getContentType(); 103 | } 104 | -------------------------------------------------------------------------------- /src/Http/Response/Format/Json.php: -------------------------------------------------------------------------------- 1 | getTable()); 27 | 28 | if (! $model::$snakeAttributes) { 29 | $key = Str::camel($key); 30 | } 31 | 32 | return $this->encode([$key => $model->toArray()]); 33 | } 34 | 35 | /** 36 | * Format an Eloquent collection. 37 | * 38 | * @param \Illuminate\Database\Eloquent\Collection $collection 39 | * 40 | * @return string 41 | */ 42 | public function formatEloquentCollection($collection) 43 | { 44 | if ($collection->isEmpty()) { 45 | return $this->encode([]); 46 | } 47 | 48 | $model = $collection->first(); 49 | $key = Str::plural($model->getTable()); 50 | 51 | if (! $model::$snakeAttributes) { 52 | $key = Str::camel($key); 53 | } 54 | 55 | return $this->encode([$key => $collection->toArray()]); 56 | } 57 | 58 | /** 59 | * Format an array or instance implementing Arrayable. 60 | * 61 | * @param array|\Illuminate\Contracts\Support\Arrayable $content 62 | * 63 | * @return string 64 | */ 65 | public function formatArray($content) 66 | { 67 | $content = $this->morphToArray($content); 68 | 69 | array_walk_recursive($content, function (&$value) { 70 | $value = $this->morphToArray($value); 71 | }); 72 | 73 | return $this->encode($content); 74 | } 75 | 76 | /** 77 | * Get the response content type. 78 | * 79 | * @return string 80 | */ 81 | public function getContentType() 82 | { 83 | return 'application/json'; 84 | } 85 | 86 | /** 87 | * Morph a value to an array. 88 | * 89 | * @param array|\Illuminate\Contracts\Support\Arrayable $value 90 | * 91 | * @return array 92 | */ 93 | protected function morphToArray($value) 94 | { 95 | return $value instanceof Arrayable ? $value->toArray() : $value; 96 | } 97 | 98 | /** 99 | * Encode the content to its JSON representation. 100 | * 101 | * @param mixed $content 102 | * 103 | * @return string 104 | */ 105 | protected function encode($content) 106 | { 107 | $jsonEncodeOptions = []; 108 | 109 | // Here is a place, where any available JSON encoding options, that 110 | // deal with users' requirements to JSON response formatting and 111 | // structure, can be conveniently applied to tweak the output. 112 | 113 | if ($this->isJsonPrettyPrintEnabled()) { 114 | $jsonEncodeOptions[] = JSON_PRETTY_PRINT; 115 | $jsonEncodeOptions[] = JSON_UNESCAPED_UNICODE; 116 | } 117 | 118 | $encodedString = $this->performJsonEncoding($content, $jsonEncodeOptions); 119 | 120 | if ($this->isCustomIndentStyleRequired()) { 121 | $encodedString = $this->indentPrettyPrintedJson( 122 | $encodedString, 123 | $this->options['indent_style'] 124 | ); 125 | } 126 | 127 | return $encodedString; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Http/Response/Format/JsonOptionalFormatting.php: -------------------------------------------------------------------------------- 1 | "\t", 33 | 'space' => ' ', 34 | ]; 35 | 36 | /* 37 | * JSON constants, that are allowed to be used as options while encoding. 38 | * Whitelist can be extended by other options in the future. 39 | * 40 | * @see http://php.net/manual/ru/json.constants.php 41 | * 42 | * @var array 43 | */ 44 | protected $jsonEncodeOptionsWhitelist = [ 45 | JSON_PRETTY_PRINT, 46 | JSON_UNESCAPED_UNICODE, 47 | ]; 48 | 49 | /** 50 | * Determine if JSON pretty print option is set to true. 51 | * 52 | * @return bool 53 | */ 54 | protected function isJsonPrettyPrintEnabled() 55 | { 56 | return isset($this->options['pretty_print']) && $this->options['pretty_print'] === true; 57 | } 58 | 59 | /** 60 | * Determine if JSON custom indent style is set. 61 | * 62 | * @return bool 63 | */ 64 | protected function isCustomIndentStyleRequired() 65 | { 66 | return $this->isJsonPrettyPrintEnabled() && 67 | isset($this->options['indent_style']) && 68 | in_array($this->options['indent_style'], $this->indentStyles); 69 | } 70 | 71 | /** 72 | * Perform JSON encode. 73 | * 74 | * @param string $content 75 | * @param array $jsonEncodeOptions 76 | * 77 | * @return string 78 | */ 79 | protected function performJsonEncoding($content, array $jsonEncodeOptions = []) 80 | { 81 | $jsonEncodeOptions = $this->filterJsonEncodeOptions($jsonEncodeOptions); 82 | 83 | $optionsBitmask = $this->calucateJsonEncodeOptionsBitmask($jsonEncodeOptions); 84 | 85 | if (($encodedString = json_encode($content, $optionsBitmask)) === false) { 86 | throw new \ErrorException('Error encoding data in JSON format: '.json_last_error()); 87 | } 88 | 89 | return $encodedString; 90 | } 91 | 92 | /** 93 | * Filter JSON encode options array against the whitelist array. 94 | * 95 | * @param array $jsonEncodeOptions 96 | * 97 | * @return array 98 | */ 99 | protected function filterJsonEncodeOptions(array $jsonEncodeOptions) 100 | { 101 | return array_intersect($jsonEncodeOptions, $this->jsonEncodeOptionsWhitelist); 102 | } 103 | 104 | /** 105 | * Sweep JSON encode options together to get options' bitmask. 106 | * 107 | * @param array $jsonEncodeOptions 108 | * 109 | * @return int 110 | */ 111 | protected function calucateJsonEncodeOptionsBitmask(array $jsonEncodeOptions) 112 | { 113 | return array_sum($jsonEncodeOptions); 114 | } 115 | 116 | /** 117 | * Indent pretty printed JSON string, using given indent style. 118 | * 119 | * @param string $jsonString 120 | * @param string $indentStyle 121 | * @param int $defaultIndentSize 122 | * 123 | * @return string 124 | */ 125 | protected function indentPrettyPrintedJson($jsonString, $indentStyle, $defaultIndentSize = 2) 126 | { 127 | $indentChar = $this->getIndentCharForIndentStyle($indentStyle); 128 | $indentSize = $this->getPrettyPrintIndentSize() ?: $defaultIndentSize; 129 | 130 | // If the given indentation style is allowed to have various indent size 131 | // (number of chars, that are used to indent one level in each line), 132 | // indent the JSON string with given (or default) indent size. 133 | if ($this->hasVariousIndentSize($indentStyle)) { 134 | return $this->peformIndentation($jsonString, $indentChar, $indentSize); 135 | } 136 | 137 | // Otherwise following the convention, that indent styles, that does not 138 | // allowed to have various indent size (e.g. tab) are indented using 139 | // one tabulation character per one indent level in each line. 140 | return $this->peformIndentation($jsonString, $indentChar); 141 | } 142 | 143 | /** 144 | * Get indent char for given indent style. 145 | * 146 | * @param string $indentStyle 147 | * 148 | * @return string 149 | */ 150 | protected function getIndentCharForIndentStyle($indentStyle) 151 | { 152 | return $this->indentChars[$indentStyle]; 153 | } 154 | 155 | /** 156 | * Get indent size for pretty printed JSON string. 157 | * 158 | * @return int|null 159 | */ 160 | protected function getPrettyPrintIndentSize() 161 | { 162 | return isset($this->options['indent_size']) 163 | ? (int) $this->options['indent_size'] 164 | : null; 165 | } 166 | 167 | /** 168 | * Determine if indent style is allowed to have various indent size. 169 | * 170 | * @param string $indentStyle 171 | * 172 | * @return bool 173 | */ 174 | protected function hasVariousIndentSize($indentStyle) 175 | { 176 | return in_array($indentStyle, $this->hasVariousIndentSize); 177 | } 178 | 179 | /** 180 | * Perform indentation for pretty printed JSON string with a given 181 | * indent char, repeated N times, as determined by indent size. 182 | * 183 | * @param string $jsonString JSON string, which must be indented 184 | * @param string $indentChar Char, used for indent (default is tab) 185 | * @param int $indentSize Number of times to repeat indent char per one indent level 186 | * @param int $defaultSpaces Default number of indent spaces after json_encode() 187 | * 188 | * @return string 189 | */ 190 | protected function peformIndentation($jsonString, $indentChar = "\t", $indentSize = 1, $defaultSpaces = 4) 191 | { 192 | $pattern = '/(^|\G) {'.$defaultSpaces.'}/m'; 193 | $replacement = str_repeat($indentChar, $indentSize).'$1'; 194 | 195 | return preg_replace($pattern, $replacement, $jsonString); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/Http/Response/Format/Jsonp.php: -------------------------------------------------------------------------------- 1 | callbackName = $callbackName; 24 | } 25 | 26 | /** 27 | * Determine if a callback is valid. 28 | * 29 | * @return bool 30 | */ 31 | protected function hasValidCallback() 32 | { 33 | return $this->request->query->has($this->callbackName); 34 | } 35 | 36 | /** 37 | * Get the callback from the query string. 38 | * 39 | * @return string 40 | */ 41 | protected function getCallback() 42 | { 43 | return $this->request->query->get($this->callbackName); 44 | } 45 | 46 | /** 47 | * Get the response content type. 48 | * 49 | * @return string 50 | */ 51 | public function getContentType() 52 | { 53 | if ($this->hasValidCallback()) { 54 | return 'application/javascript'; 55 | } 56 | 57 | return parent::getContentType(); 58 | } 59 | 60 | /** 61 | * Encode the content to its JSONP representation. 62 | * 63 | * @param mixed $content 64 | * 65 | * @return string 66 | */ 67 | protected function encode($content) 68 | { 69 | $jsonString = parent::encode($content); 70 | 71 | if ($this->hasValidCallback()) { 72 | return sprintf('%s(%s);', $this->getCallback(), $jsonString); 73 | } 74 | 75 | return $jsonString; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Http/Validation/Accept.php: -------------------------------------------------------------------------------- 1 | accept = $accept; 37 | $this->strict = $strict; 38 | } 39 | 40 | /** 41 | * Validate the accept header on the request. If this fails it will throw 42 | * an HTTP exception that will be caught by the middleware. This 43 | * validator should always be run last and must not return 44 | * a success boolean. 45 | * 46 | * @param \Illuminate\Http\Request $request 47 | * 48 | * @throws \Exception|\Symfony\Component\HttpKernel\Exception\BadRequestHttpException 49 | * 50 | * @return bool 51 | */ 52 | public function validate(Request $request) 53 | { 54 | try { 55 | $this->accept->parse($request, $this->strict); 56 | } catch (BadRequestHttpException $exception) { 57 | if ($request->getMethod() === 'OPTIONS') { 58 | return true; 59 | } 60 | 61 | throw $exception; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Http/Validation/Domain.php: -------------------------------------------------------------------------------- 1 | domain = $domain; 30 | } 31 | 32 | /** 33 | * Validate that the request domain matches the configured domain. 34 | * 35 | * @param \Illuminate\Http\Request $request 36 | * 37 | * @return bool 38 | */ 39 | public function validate(Request $request) 40 | { 41 | return ! is_null($this->domain) && $request->getHost() === $this->getStrippedDomain(); 42 | } 43 | 44 | /** 45 | * Strip the protocol from a domain. 46 | * 47 | * @param string $domain 48 | * 49 | * @return string 50 | */ 51 | protected function stripProtocol($domain) 52 | { 53 | if (Str::contains($domain, '://')) { 54 | $domain = substr($domain, strpos($domain, '://') + 3); 55 | } 56 | 57 | return $domain; 58 | } 59 | 60 | /** 61 | * Strip the port from a domain. 62 | * 63 | * @param $domain 64 | * 65 | * @return mixed 66 | */ 67 | protected function stripPort($domain) 68 | { 69 | if ($domainStripped = preg_replace(self::PATTERN_STRIP_PROTOCOL, null, $domain)) { 70 | return $domainStripped; 71 | } 72 | 73 | return $domain; 74 | } 75 | 76 | /** 77 | * Get the domain stripped from protocol and port. 78 | * 79 | * @return mixed 80 | */ 81 | protected function getStrippedDomain() 82 | { 83 | return $this->stripPort($this->stripProtocol($this->domain)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Http/Validation/Prefix.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 27 | } 28 | 29 | /** 30 | * Validate the request has a prefix and if it matches the configured 31 | * API prefix. 32 | * 33 | * @param \Illuminate\Http\Request $request 34 | * 35 | * @return bool 36 | */ 37 | public function validate(Request $request) 38 | { 39 | $prefix = $this->filterAndExplode($this->prefix); 40 | 41 | $path = $this->filterAndExplode($request->getPathInfo()); 42 | 43 | return ! is_null($this->prefix) && $prefix == array_slice($path, 0, count($prefix)); 44 | } 45 | 46 | /** 47 | * Explode array on slash and remove empty values. 48 | * 49 | * @param array $array 50 | * 51 | * @return array 52 | */ 53 | protected function filterAndExplode($array) 54 | { 55 | return array_filter(explode('/', $array)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Provider/DingoServiceProvider.php: -------------------------------------------------------------------------------- 1 | setResponseStaticInstances(); 24 | 25 | Request::setAcceptParser($this->app[\Dingo\Api\Http\Parser\Accept::class]); 26 | 27 | $this->app->rebinding('api.routes', function ($app, $routes) { 28 | $app['api.url']->setRouteCollections($routes); 29 | }); 30 | } 31 | 32 | protected function setResponseStaticInstances() 33 | { 34 | Response::setFormatters($this->config('formats')); 35 | Response::setFormatsOptions($this->config('formatsOptions')); 36 | Response::setTransformer($this->app['api.transformer']); 37 | Response::setEventDispatcher($this->app['events']); 38 | } 39 | 40 | /** 41 | * Register the service provider. 42 | * 43 | * @return void 44 | */ 45 | public function register() 46 | { 47 | $this->registerConfig(); 48 | 49 | $this->registerClassAliases(); 50 | 51 | $this->app->register(RoutingServiceProvider::class); 52 | 53 | $this->app->register(HttpServiceProvider::class); 54 | 55 | $this->registerExceptionHandler(); 56 | 57 | $this->registerDispatcher(); 58 | 59 | $this->registerAuth(); 60 | 61 | $this->registerTransformer(); 62 | 63 | $this->registerDocsCommand(); 64 | 65 | if (class_exists('Illuminate\Foundation\Application', false)) { 66 | $this->commands([ 67 | \Dingo\Api\Console\Command\Cache::class, 68 | \Dingo\Api\Console\Command\Routes::class, 69 | ]); 70 | } 71 | } 72 | 73 | /** 74 | * Register the configuration. 75 | * 76 | * @return void 77 | */ 78 | protected function registerConfig() 79 | { 80 | $this->mergeConfigFrom(realpath(__DIR__.'/../../config/api.php'), 'api'); 81 | 82 | if (! $this->app->runningInConsole() && empty($this->config('prefix')) && empty($this->config('domain'))) { 83 | throw new RuntimeException('Unable to boot ApiServiceProvider, configure an API domain or prefix.'); 84 | } 85 | } 86 | 87 | /** 88 | * Register the class aliases. 89 | * 90 | * @return void 91 | */ 92 | protected function registerClassAliases() 93 | { 94 | $serviceAliases = [ 95 | \Dingo\Api\Http\Request::class => \Dingo\Api\Contract\Http\Request::class, 96 | 'api.dispatcher' => \Dingo\Api\Dispatcher::class, 97 | 'api.http.validator' => \Dingo\Api\Http\RequestValidator::class, 98 | 'api.http.response' => \Dingo\Api\Http\Response\Factory::class, 99 | 'api.router' => \Dingo\Api\Routing\Router::class, 100 | 'api.router.adapter' => \Dingo\Api\Contract\Routing\Adapter::class, 101 | 'api.auth' => \Dingo\Api\Auth\Auth::class, 102 | 'api.limiting' => \Dingo\Api\Http\RateLimit\Handler::class, 103 | 'api.transformer' => \Dingo\Api\Transformer\Factory::class, 104 | 'api.url' => \Dingo\Api\Routing\UrlGenerator::class, 105 | 'api.exception' => [\Dingo\Api\Exception\Handler::class, \Dingo\Api\Contract\Debug\ExceptionHandler::class], 106 | ]; 107 | 108 | foreach ($serviceAliases as $key => $aliases) { 109 | foreach ((array) $aliases as $alias) { 110 | $this->app->alias($key, $alias); 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Register the exception handler. 117 | * 118 | * @return void 119 | */ 120 | protected function registerExceptionHandler() 121 | { 122 | $this->app->singleton('api.exception', function ($app) { 123 | return new ExceptionHandler($app['Illuminate\Contracts\Debug\ExceptionHandler'], $this->config('errorFormat'), $this->config('debug')); 124 | }); 125 | } 126 | 127 | /** 128 | * Register the internal dispatcher. 129 | * 130 | * @return void 131 | */ 132 | public function registerDispatcher() 133 | { 134 | $this->app->singleton('api.dispatcher', function ($app) { 135 | $dispatcher = new Dispatcher($app, $app['files'], $app[\Dingo\Api\Routing\Router::class], $app[\Dingo\Api\Auth\Auth::class]); 136 | 137 | $dispatcher->setSubtype($this->config('subtype')); 138 | $dispatcher->setStandardsTree($this->config('standardsTree')); 139 | $dispatcher->setPrefix($this->config('prefix')); 140 | $dispatcher->setDefaultVersion($this->config('version')); 141 | $dispatcher->setDefaultDomain($this->config('domain')); 142 | $dispatcher->setDefaultFormat($this->config('defaultFormat')); 143 | 144 | return $dispatcher; 145 | }); 146 | } 147 | 148 | /** 149 | * Register the auth. 150 | * 151 | * @return void 152 | */ 153 | protected function registerAuth() 154 | { 155 | $this->app->singleton('api.auth', function ($app) { 156 | return new Auth($app[\Dingo\Api\Routing\Router::class], $app, $this->config('auth')); 157 | }); 158 | } 159 | 160 | /** 161 | * Register the transformer factory. 162 | * 163 | * @return void 164 | */ 165 | protected function registerTransformer() 166 | { 167 | $this->app->singleton('api.transformer', function ($app) { 168 | return new TransformerFactory($app, $this->config('transformer')); 169 | }); 170 | } 171 | 172 | /** 173 | * Register the documentation command. 174 | * 175 | * @return void 176 | */ 177 | protected function registerDocsCommand() 178 | { 179 | $this->app->singleton(\Dingo\Api\Console\Command\Docs::class, function ($app) { 180 | return new Command\Docs( 181 | $app[\Dingo\Api\Routing\Router::class], 182 | $app[\Dingo\Blueprint\Blueprint::class], 183 | $app[\Dingo\Blueprint\Writer::class], 184 | $this->config('name'), 185 | $this->config('version') 186 | ); 187 | }); 188 | 189 | $this->commands([\Dingo\Api\Console\Command\Docs::class]); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/Provider/HttpServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerRateLimiting(); 34 | 35 | $this->registerHttpValidation(); 36 | 37 | $this->registerHttpParsers(); 38 | 39 | $this->registerResponseFactory(); 40 | 41 | $this->registerMiddleware(); 42 | } 43 | 44 | /** 45 | * Register the rate limiting. 46 | * 47 | * @return void 48 | */ 49 | protected function registerRateLimiting() 50 | { 51 | $this->app->singleton('api.limiting', function ($app) { 52 | return new RateLimitHandler($app, $app['cache'], $this->config('throttling')); 53 | }); 54 | } 55 | 56 | /** 57 | * Register the HTTP validation. 58 | * 59 | * @return void 60 | */ 61 | protected function registerHttpValidation() 62 | { 63 | $this->app->singleton('api.http.validator', function ($app) { 64 | return new RequestValidator($app); 65 | }); 66 | 67 | $this->app->singleton(Domain::class, function ($app) { 68 | return new Validation\Domain($this->config('domain')); 69 | }); 70 | 71 | $this->app->singleton(Prefix::class, function ($app) { 72 | return new Validation\Prefix($this->config('prefix')); 73 | }); 74 | 75 | $this->app->singleton(Accept::class, function ($app) { 76 | return new Validation\Accept( 77 | $this->app[AcceptParser::class], 78 | $this->config('strict') 79 | ); 80 | }); 81 | } 82 | 83 | /** 84 | * Register the HTTP parsers. 85 | * 86 | * @return void 87 | */ 88 | protected function registerHttpParsers() 89 | { 90 | $this->app->singleton(AcceptParser::class, function ($app) { 91 | return new AcceptParser( 92 | $this->config('standardsTree'), 93 | $this->config('subtype'), 94 | $this->config('version'), 95 | $this->config('defaultFormat') 96 | ); 97 | }); 98 | } 99 | 100 | /** 101 | * Register the response factory. 102 | * 103 | * @return void 104 | */ 105 | protected function registerResponseFactory() 106 | { 107 | $this->app->singleton('api.http.response', function ($app) { 108 | return new ResponseFactory($app[Factory::class]); 109 | }); 110 | } 111 | 112 | /** 113 | * Register the middleware. 114 | * 115 | * @return void 116 | */ 117 | protected function registerMiddleware() 118 | { 119 | $this->app->singleton(Request::class, function ($app) { 120 | $middleware = new Middleware\Request( 121 | $app, 122 | $app[ExceptionHandler::class], 123 | $app[Router::class], 124 | $app[RequestValidator::class], 125 | $app['events'] 126 | ); 127 | 128 | $middleware->setMiddlewares($this->config('middleware', false)); 129 | 130 | return $middleware; 131 | }); 132 | 133 | $this->app->singleton(AuthMiddleware::class, function ($app) { 134 | return new Middleware\Auth($app[Router::class], $app[Auth::class]); 135 | }); 136 | 137 | $this->app->singleton(RateLimit::class, function ($app) { 138 | return new Middleware\RateLimit($app[Router::class], $app[Handler::class]); 139 | }); 140 | 141 | $this->app->singleton(PrepareController::class, function ($app) { 142 | return new Middleware\PrepareController($app[Router::class]); 143 | }); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Provider/LaravelServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([realpath(__DIR__.'/../../config/api.php') => config_path('api.php')]); 32 | 33 | $kernel = $this->app->make(Kernel::class); 34 | 35 | $this->app[Request::class]->mergeMiddlewares( 36 | $this->gatherAppMiddleware($kernel) 37 | ); 38 | 39 | $this->addRequestMiddlewareToBeginning($kernel); 40 | 41 | $this->app['events']->listen(RequestWasMatched::class, function (RequestWasMatched $event) { 42 | $this->replaceRouteDispatcher(); 43 | 44 | $this->updateRouterBindings(); 45 | }); 46 | 47 | // Originally Validate FormRequest after resolving 48 | /* This casues the prepareForValidation() function to be called twice, and seemingly has no other benefit, see discussion at 49 | https://github.com/dingo/api/issues/1668 50 | This is already done by laravel service provider, and works with Dingo router 51 | $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) { 52 | $resolved->validateResolved(); 53 | }); 54 | */ 55 | 56 | $this->app->resolving(FormRequest::class, function (FormRequest $request, Application $app) { 57 | $this->initializeRequest($request, $app['request']); 58 | 59 | $request->setContainer($app)->setRedirector($app->make(Redirector::class)); 60 | }); 61 | 62 | $this->addMiddlewareAlias('api.auth', Auth::class); 63 | $this->addMiddlewareAlias('api.throttle', RateLimit::class); 64 | $this->addMiddlewareAlias('api.controllers', PrepareController::class); 65 | } 66 | 67 | /** 68 | * Replace the route dispatcher. 69 | * 70 | * @return void 71 | */ 72 | protected function replaceRouteDispatcher() 73 | { 74 | $this->app->singleton('illuminate.route.dispatcher', function ($app) { 75 | return new ControllerDispatcher($app['api.router.adapter']->getRouter(), $app); 76 | }); 77 | } 78 | 79 | /** 80 | * Grab the bindings from the Laravel router and set them on the adapters 81 | * router. 82 | * 83 | * @return void 84 | */ 85 | protected function updateRouterBindings() 86 | { 87 | foreach ($this->getRouterBindings() as $key => $binding) { 88 | $this->app['api.router.adapter']->getRouter()->bind($key, $binding); 89 | } 90 | } 91 | 92 | /** 93 | * Get the Laravel routers bindings. 94 | * 95 | * @return array 96 | */ 97 | protected function getRouterBindings() 98 | { 99 | $property = (new ReflectionClass($this->app['router']))->getProperty('binders'); 100 | $property->setAccessible(true); 101 | 102 | return $property->getValue($this->app['router']); 103 | } 104 | 105 | /** 106 | * Register the service provider. 107 | * 108 | * @return void 109 | */ 110 | public function register() 111 | { 112 | parent::register(); 113 | 114 | $this->registerRouterAdapter(); 115 | } 116 | 117 | /** 118 | * Register the router adapter. 119 | * 120 | * @return void 121 | */ 122 | protected function registerRouterAdapter() 123 | { 124 | $this->app->singleton('api.router.adapter', function ($app) { 125 | return new LaravelAdapter($app['router']); 126 | }); 127 | } 128 | 129 | /** 130 | * Add the request middleware to the beginning of the kernel. 131 | * 132 | * @param \Illuminate\Contracts\Http\Kernel $kernel 133 | * 134 | * @return void 135 | */ 136 | protected function addRequestMiddlewareToBeginning(Kernel $kernel) 137 | { 138 | $kernel->prependMiddleware(Request::class); 139 | } 140 | 141 | /** 142 | * Register a short-hand name for a middleware. For compatibility 143 | * with Laravel < 5.4 check if aliasMiddleware exists since this 144 | * method has been renamed. 145 | * 146 | * @param string $name 147 | * @param string $class 148 | * 149 | * @return void 150 | */ 151 | protected function addMiddlewareAlias($name, $class) 152 | { 153 | $router = $this->app['router']; 154 | 155 | if (method_exists($router, 'aliasMiddleware')) { 156 | return $router->aliasMiddleware($name, $class); 157 | } 158 | 159 | return $router->middleware($name, $class); 160 | } 161 | 162 | /** 163 | * Gather the application middleware besides this one so that we can send 164 | * our request through them, exactly how the developer wanted. 165 | * 166 | * @param \Illuminate\Contracts\Http\Kernel $kernel 167 | * 168 | * @return array 169 | */ 170 | protected function gatherAppMiddleware(Kernel $kernel) 171 | { 172 | $property = (new ReflectionClass($kernel))->getProperty('middleware'); 173 | $property->setAccessible(true); 174 | 175 | return $property->getValue($kernel); 176 | } 177 | 178 | /** 179 | * Initialize the form request with data from the given request. 180 | * 181 | * @param FormRequest $form 182 | * @param IlluminateRequest $current 183 | * 184 | * @return void 185 | */ 186 | protected function initializeRequest(FormRequest $form, IlluminateRequest $current) 187 | { 188 | $files = $current->files->all(); 189 | 190 | $files = is_array($files) ? array_filter($files) : $files; 191 | 192 | $form->initialize( 193 | $current->query->all(), 194 | $current->request->all(), 195 | $current->attributes->all(), 196 | $current->cookies->all(), 197 | $files, 198 | $current->server->all(), 199 | $current->getContent() 200 | ); 201 | 202 | $form->setJson($current->json()); 203 | 204 | if ($session = $current->getSession()) { 205 | $form->setLaravelSession($session); 206 | } 207 | 208 | $form->setUserResolver($current->getUserResolver()); 209 | 210 | $form->setRouteResolver($current->getRouteResolver()); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/Provider/LumenServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->configure('api'); 34 | 35 | $reflection = new ReflectionClass($this->app); 36 | 37 | $this->app[Request::class]->mergeMiddlewares( 38 | $this->gatherAppMiddleware($reflection) 39 | ); 40 | 41 | $this->addRequestMiddlewareToBeginning($reflection); 42 | 43 | // Because Lumen sets the route resolver at a very weird point we're going to 44 | // have to use reflection whenever the request instance is rebound to 45 | // set the route resolver to get the current route. 46 | $this->app->rebinding(IlluminateRequest::class, function ($app, $request) { 47 | $request->setRouteResolver(function () use ($app) { 48 | $reflection = new ReflectionClass($app); 49 | 50 | $property = $reflection->getProperty('currentRoute'); 51 | $property->setAccessible(true); 52 | 53 | return $property->getValue($app); 54 | }); 55 | }); 56 | 57 | // Validate FormRequest after resolving 58 | $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) { 59 | $resolved->validateResolved(); 60 | }); 61 | 62 | $this->app->resolving(FormRequest::class, function (FormRequest $request, Application $app) { 63 | $this->initializeRequest($request, $app['request']); 64 | 65 | $request->setContainer($app)->setRedirector($app->make(Redirector::class)); 66 | }); 67 | 68 | $this->app->routeMiddleware([ 69 | 'api.auth' => Auth::class, 70 | 'api.throttle' => RateLimit::class, 71 | 'api.controllers' => PrepareController::class, 72 | ]); 73 | } 74 | 75 | /** 76 | * Setup the configuration. 77 | * 78 | * @return void 79 | */ 80 | protected function setupConfig() 81 | { 82 | $this->app->configure('api'); 83 | 84 | parent::setupConfig(); 85 | } 86 | 87 | /** 88 | * Register the service provider. 89 | * 90 | * @return void 91 | */ 92 | public function register() 93 | { 94 | parent::register(); 95 | 96 | $this->app->singleton('api.router.adapter', function ($app) { 97 | return new LumenAdapter($app, new StdRouteParser, new GcbDataGenerator, $this->getDispatcherResolver()); 98 | }); 99 | } 100 | 101 | /** 102 | * Get the dispatcher resolver callback. 103 | * 104 | * @return \Closure 105 | */ 106 | protected function getDispatcherResolver() 107 | { 108 | return function ($routeCollector) { 109 | return new GroupCountBased($routeCollector->getData()); 110 | }; 111 | } 112 | 113 | /** 114 | * Add the request middleware to the beginning of the middleware stack on the 115 | * Lumen application instance. 116 | * 117 | * @param \ReflectionClass $reflection 118 | * 119 | * @return void 120 | */ 121 | protected function addRequestMiddlewareToBeginning(ReflectionClass $reflection) 122 | { 123 | $property = $reflection->getProperty('middleware'); 124 | $property->setAccessible(true); 125 | 126 | $middleware = $property->getValue($this->app); 127 | 128 | array_unshift($middleware, Request::class); 129 | 130 | $property->setValue($this->app, $middleware); 131 | $property->setAccessible(false); 132 | } 133 | 134 | /** 135 | * Gather the application middleware besides this one so that we can send 136 | * our request through them, exactly how the developer wanted. 137 | * 138 | * @param \ReflectionClass $reflection 139 | * 140 | * @return array 141 | */ 142 | protected function gatherAppMiddleware(ReflectionClass $reflection) 143 | { 144 | $property = $reflection->getProperty('middleware'); 145 | $property->setAccessible(true); 146 | 147 | $middleware = $property->getValue($this->app); 148 | 149 | return $middleware; 150 | } 151 | 152 | /** 153 | * Initialize the form request with data from the given request. 154 | * 155 | * @param FormRequest $form 156 | * @param IlluminateRequest $current 157 | * 158 | * @return void 159 | */ 160 | protected function initializeRequest(FormRequest $form, IlluminateRequest $current) 161 | { 162 | $files = $current->files->all(); 163 | 164 | $files = is_array($files) ? array_filter($files) : $files; 165 | 166 | $form->initialize( 167 | $current->query->all(), 168 | $current->request->all(), 169 | $current->attributes->all(), 170 | $current->cookies->all(), 171 | $files, 172 | $current->server->all(), 173 | $current->getContent() 174 | ); 175 | 176 | $form->setJson($current->json()); 177 | 178 | if ($session = $current->getSession()) { 179 | $form->setLaravelSession($session); 180 | } 181 | 182 | $form->setUserResolver($current->getUserResolver()); 183 | 184 | $form->setRouteResolver($current->getRouteResolver()); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/Provider/RoutingServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerRouter(); 19 | 20 | $this->registerUrlGenerator(); 21 | } 22 | 23 | /** 24 | * Register the router. 25 | */ 26 | protected function registerRouter() 27 | { 28 | $this->app->singleton('api.router', function ($app) { 29 | $router = new Router( 30 | $app[Adapter::class], 31 | $app[ExceptionHandler::class], 32 | $app, 33 | $this->config('domain'), 34 | $this->config('prefix') 35 | ); 36 | 37 | $router->setConditionalRequest($this->config('conditionalRequest')); 38 | 39 | return $router; 40 | }); 41 | 42 | $this->app->singleton(ResourceRegistrar::class, function ($app) { 43 | return new ResourceRegistrar($app[Router::class]); 44 | }); 45 | } 46 | 47 | /** 48 | * Register the URL generator. 49 | */ 50 | protected function registerUrlGenerator() 51 | { 52 | $this->app->singleton('api.url', function ($app) { 53 | $url = new UrlGenerator($app['request']); 54 | 55 | $url->setRouteCollections($app[Router::class]->getRoutes()); 56 | 57 | $url->setKeyResolver(function () { 58 | return $this->app->make('config')->get('app.key'); 59 | }); 60 | 61 | return $url; 62 | }); 63 | } 64 | 65 | /** 66 | * Get the URL generator request rebinder. 67 | * 68 | * @return \Closure 69 | */ 70 | private function requestRebinder() 71 | { 72 | return function ($app, $request) { 73 | $app['api.url']->setRequest($request); 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Provider/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['config']->get('api.'.$item); 29 | 30 | if (is_array($value)) { 31 | return $instantiate ? $this->instantiateConfigValues($item, $value) : $value; 32 | } 33 | 34 | return $instantiate ? $this->instantiateConfigValue($item, $value) : $value; 35 | } 36 | 37 | /** 38 | * Instantiate an array of instantiable configuration values. 39 | * 40 | * @param string $item 41 | * @param array $values 42 | * 43 | * @return array 44 | */ 45 | protected function instantiateConfigValues($item, array $values) 46 | { 47 | foreach ($values as $key => $value) { 48 | $values[$key] = $this->instantiateConfigValue($item, $value); 49 | } 50 | 51 | return $values; 52 | } 53 | 54 | /** 55 | * Instantiate an instantiable configuration value. 56 | * 57 | * @param string $item 58 | * @param mixed $value 59 | * 60 | * @return mixed 61 | */ 62 | protected function instantiateConfigValue($item, $value) 63 | { 64 | if (is_string($value) && in_array($item, $this->instantiable)) { 65 | return $this->app->make($value); 66 | } 67 | 68 | return $value; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Routing/Adapter/Laravel.php: -------------------------------------------------------------------------------- 1 | router = $router; 66 | } 67 | 68 | /** 69 | * Dispatch a request. 70 | * 71 | * @param \Illuminate\Http\Request $request 72 | * @param string $version 73 | * 74 | * @return mixed 75 | */ 76 | public function dispatch(Request $request, $version) 77 | { 78 | if (! isset($this->routes[$version])) { 79 | throw new UnknownVersionException; 80 | } 81 | 82 | $routes = $this->mergeOldRoutes($version); 83 | 84 | $this->router->setRoutes($routes); 85 | 86 | $router = clone $this->router; 87 | 88 | $response = $router->dispatch($request); 89 | 90 | unset($router); 91 | 92 | return $response; 93 | } 94 | 95 | /** 96 | * Merge the old application routes with the API routes. 97 | * 98 | * @param string $version 99 | * 100 | * @return array 101 | */ 102 | protected function mergeOldRoutes($version) 103 | { 104 | if (! isset($this->oldRoutes)) { 105 | $this->oldRoutes = $this->router->getRoutes(); 106 | } 107 | 108 | if (! isset($this->mergedRoutes[$version])) { 109 | $this->mergedRoutes[$version] = $this->routes[$version]; 110 | 111 | foreach ($this->oldRoutes as $route) { 112 | $this->mergedRoutes[$version]->add($route); 113 | } 114 | 115 | $this->mergedRoutes[$version]->refreshNameLookups(); 116 | $this->mergedRoutes[$version]->refreshActionLookups(); 117 | } 118 | 119 | return $this->mergedRoutes[$version]; 120 | } 121 | 122 | /** 123 | * Get the URI, methods, and action from the route. 124 | * 125 | * @param mixed $route 126 | * @param \Illuminate\Http\Request $request 127 | * 128 | * @return array 129 | */ 130 | public function getRouteProperties($route, Request $request) 131 | { 132 | if (method_exists($route, 'uri') && method_exists($route, 'methods')) { 133 | return [$route->uri(), $route->methods(), $route->getAction()]; 134 | } 135 | 136 | return [$route->getUri(), $route->getMethods(), $route->getAction()]; 137 | } 138 | 139 | /** 140 | * Add a route to the appropriate route collection. 141 | * 142 | * @param array $methods 143 | * @param array $versions 144 | * @param string $uri 145 | * @param mixed $action 146 | * 147 | * @return \Illuminate\Routing\Route 148 | */ 149 | public function addRoute(array $methods, array $versions, $uri, $action) 150 | { 151 | $this->createRouteCollections($versions); 152 | 153 | // Add where-patterns from original laravel router 154 | $action['where'] = array_merge($this->router->getPatterns(), $action['where'] ?? []); 155 | 156 | $route = new Route($methods, $uri, $action); 157 | $route->where($action['where']); 158 | 159 | foreach ($versions as $version) { 160 | $this->routes[$version]->add($route); 161 | } 162 | 163 | return $route; 164 | } 165 | 166 | /** 167 | * Create the route collections for the versions. 168 | * 169 | * @param array $versions 170 | * 171 | * @return void 172 | */ 173 | protected function createRouteCollections(array $versions) 174 | { 175 | foreach ($versions as $version) { 176 | if (! isset($this->routes[$version])) { 177 | $this->routes[$version] = new RouteCollection; 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Get all routes or only for a specific version. 184 | * 185 | * @param string $version 186 | * 187 | * @return mixed 188 | */ 189 | public function getRoutes($version = null) 190 | { 191 | if (! is_null($version)) { 192 | return $this->routes[$version]; 193 | } 194 | 195 | return $this->routes; 196 | } 197 | 198 | /** 199 | * Get a normalized iterable set of routes. 200 | * 201 | * @param string $version 202 | * 203 | * @return mixed 204 | */ 205 | public function getIterableRoutes($version = null) 206 | { 207 | return $this->getRoutes($version); 208 | } 209 | 210 | /** 211 | * Set the routes on the adapter. 212 | * 213 | * @param array $routes 214 | * 215 | * @return void 216 | */ 217 | public function setRoutes(array $routes) 218 | { 219 | $this->routes = $routes; 220 | } 221 | 222 | /** 223 | * Prepare a route for serialization. 224 | * 225 | * @param mixed $route 226 | * 227 | * @return mixed 228 | */ 229 | public function prepareRouteForSerialization($route) 230 | { 231 | $route->prepareForSerialization(); 232 | 233 | return $route; 234 | } 235 | 236 | /** 237 | * Gather the route middlewares. 238 | * 239 | * @param \Illuminate\Routing\Route $route 240 | * 241 | * @return array 242 | */ 243 | public function gatherRouteMiddlewares($route) 244 | { 245 | if (method_exists($this->router, 'gatherRouteMiddleware')) { 246 | return $this->router->gatherRouteMiddleware($route); 247 | } 248 | 249 | return $this->router->gatherRouteMiddlewares($route); 250 | } 251 | 252 | /** 253 | * Get the Laravel router instance. 254 | * 255 | * @return \Illuminate\Routing\Router 256 | */ 257 | public function getRouter() 258 | { 259 | return $this->router; 260 | } 261 | 262 | /** 263 | * Get the global "where" patterns. 264 | * 265 | * @return array 266 | */ 267 | public function getPatterns() 268 | { 269 | return $this->patterns; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/Routing/Helpers.php: -------------------------------------------------------------------------------- 1 | throttles[] = compact('class', 'options'); 57 | } 58 | 59 | /** 60 | * Rate limit controller methods. 61 | * 62 | * @param int $limit 63 | * @param int $expires 64 | * @param array $options 65 | * 66 | * @return void 67 | */ 68 | protected function rateLimit($limit, $expires, array $options = []) 69 | { 70 | $this->rateLimit[] = compact('limit', 'expires', 'options'); 71 | } 72 | 73 | /** 74 | * Add scopes to controller methods. 75 | * 76 | * @param string|array $scopes 77 | * @param array $options 78 | * 79 | * @return void 80 | */ 81 | protected function scopes($scopes, array $options = []) 82 | { 83 | $scopes = $this->getPropertyValue($scopes); 84 | 85 | $this->scopes[] = compact('scopes', 'options'); 86 | } 87 | 88 | /** 89 | * Authenticate with certain providers on controller methods. 90 | * 91 | * @param string|array $providers 92 | * @param array $options 93 | * 94 | * @return void 95 | */ 96 | protected function authenticateWith($providers, array $options = []) 97 | { 98 | $providers = $this->getPropertyValue($providers); 99 | 100 | $this->authenticationProviders[] = compact('providers', 'options'); 101 | } 102 | 103 | /** 104 | * Prepare a property value. 105 | * 106 | * @param string|array $value 107 | * 108 | * @return array 109 | */ 110 | protected function getPropertyValue($value) 111 | { 112 | return is_string($value) ? explode('|', $value) : $value; 113 | } 114 | 115 | /** 116 | * Get the controllers rate limiting throttles. 117 | * 118 | * @return array 119 | */ 120 | public function getThrottles() 121 | { 122 | return $this->throttles; 123 | } 124 | 125 | /** 126 | * Get the controllers rate limit and expiration. 127 | * 128 | * @return array 129 | */ 130 | public function getRateLimit() 131 | { 132 | return $this->rateLimit; 133 | } 134 | 135 | /** 136 | * Get the controllers scopes. 137 | * 138 | * @return array 139 | */ 140 | public function getScopes() 141 | { 142 | return $this->scopes; 143 | } 144 | 145 | /** 146 | * Get the controllers authentication providers. 147 | * 148 | * @return array 149 | */ 150 | public function getAuthenticationProviders() 151 | { 152 | return $this->authenticationProviders; 153 | } 154 | 155 | /** 156 | * Get the internal dispatcher instance. 157 | * 158 | * @return \Dingo\Api\Dispatcher 159 | */ 160 | public function api() 161 | { 162 | return app(Dispatcher::class); 163 | } 164 | 165 | /** 166 | * Get the authenticated user. 167 | * 168 | * @return mixed 169 | */ 170 | protected function user() 171 | { 172 | return app(Auth::class)->user(); 173 | } 174 | 175 | /** 176 | * Get the auth instance. 177 | * 178 | * @return \Dingo\Api\Auth\Auth 179 | */ 180 | protected function auth() 181 | { 182 | return app(Auth::class); 183 | } 184 | 185 | /** 186 | * Get the response factory instance. 187 | * 188 | * @return \Dingo\Api\Http\Response\Factory 189 | */ 190 | protected function response() 191 | { 192 | return app(Factory::class); 193 | } 194 | 195 | /** 196 | * Magically handle calls to certain properties. 197 | * 198 | * @param string $key 199 | * 200 | * @throws \ErrorException 201 | * 202 | * @return mixed 203 | */ 204 | public function __get($key) 205 | { 206 | $callable = [ 207 | 'api', 'user', 'auth', 'response', 208 | ]; 209 | 210 | if (in_array($key, $callable) && method_exists($this, $key)) { 211 | return $this->$key(); 212 | } 213 | 214 | throw new ErrorException('Undefined property '.get_class($this).'::'.$key); 215 | } 216 | 217 | /** 218 | * Magically handle calls to certain methods on the response factory. 219 | * 220 | * @param string $method 221 | * @param array $parameters 222 | * 223 | * @throws \ErrorException 224 | * 225 | * @return \Dingo\Api\Http\Response 226 | */ 227 | public function __call($method, $parameters) 228 | { 229 | if (method_exists($this->response(), $method) || $method == 'array') { 230 | return call_user_func_array([$this->response(), $method], $parameters); 231 | } 232 | 233 | throw new ErrorException('Undefined method '.get_class($this).'::'.$method); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/Routing/ResourceRegistrar.php: -------------------------------------------------------------------------------- 1 | router = $router; 27 | } 28 | 29 | /** 30 | * Route a resource to a controller. 31 | * 32 | * @param string $name 33 | * @param string $controller 34 | * @param array $options 35 | * 36 | * @return void 37 | */ 38 | public function register($name, $controller, array $options = []) 39 | { 40 | if (isset($options['parameters']) && ! isset($this->parameters)) { 41 | $this->parameters = $options['parameters']; 42 | } 43 | 44 | // If the resource name contains a slash, we will assume the developer wishes to 45 | // register these resource routes with a prefix so we will set that up out of 46 | // the box so they don't have to mess with it. Otherwise, we will continue. 47 | if (Str::contains($name, '/')) { 48 | $this->prefixedResource($name, $controller, $options); 49 | 50 | return; 51 | } 52 | 53 | // We need to extract the base resource from the resource name. Nested resources 54 | // are supported in the framework, but we need to know what name to use for a 55 | // place-holder on the route parameters, which should be the base resources. 56 | $base = $this->getResourceWildcard(last(explode('.', $name))); 57 | 58 | $defaults = $this->resourceDefaults; 59 | 60 | foreach ($this->getResourceMethods($defaults, $options) as $m) { 61 | $this->{'addResource'.ucfirst($m)}($name, $base, $controller, $options); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Routing/RouteCollection.php: -------------------------------------------------------------------------------- 1 | routes[] = $route; 42 | 43 | $this->addLookups($route); 44 | 45 | return $route; 46 | } 47 | 48 | /** 49 | * Add route lookups. 50 | * 51 | * @param \Dingo\Api\Routing\Route $route 52 | * 53 | * @return void 54 | */ 55 | protected function addLookups(Route $route) 56 | { 57 | $action = $route->getAction(); 58 | 59 | if (isset($action['as'])) { 60 | $this->names[$action['as']] = $route; 61 | } 62 | 63 | if (isset($action['controller'])) { 64 | $this->actions[$action['controller']] = $route; 65 | } 66 | } 67 | 68 | /** 69 | * Get a route by name. 70 | * 71 | * @param string $name 72 | * 73 | * @return \Dingo\Api\Routing\Route|null 74 | */ 75 | public function getByName($name) 76 | { 77 | return isset($this->names[$name]) ? $this->names[$name] : null; 78 | } 79 | 80 | /** 81 | * Get a route by action. 82 | * 83 | * @param string $action 84 | * 85 | * @return \Dingo\Api\Routing\Route|null 86 | */ 87 | public function getByAction($action) 88 | { 89 | return isset($this->actions[$action]) ? $this->actions[$action] : null; 90 | } 91 | 92 | /** 93 | * Get all routes. 94 | * 95 | * @return array 96 | */ 97 | public function getRoutes() 98 | { 99 | return $this->routes; 100 | } 101 | 102 | /** 103 | * Get an iterator for the items. 104 | * 105 | * @return \ArrayIterator 106 | */ 107 | public function getIterator() 108 | { 109 | return new ArrayIterator($this->getRoutes()); 110 | } 111 | 112 | /** 113 | * Count the number of items in the collection. 114 | * 115 | * @return int 116 | */ 117 | public function count() 118 | { 119 | return count($this->getRoutes()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Routing/UrlGenerator.php: -------------------------------------------------------------------------------- 1 | setRequest($request); 27 | } 28 | 29 | /** 30 | * Set the routes to use from the version. 31 | * 32 | * @param string $version 33 | * 34 | * @return \Dingo\Api\Routing\UrlGenerator 35 | */ 36 | public function version($version) 37 | { 38 | $this->routes = $this->collections[$version]; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * Set the route collection instance. 45 | * 46 | * @param array $collections 47 | */ 48 | public function setRouteCollections(array $collections) 49 | { 50 | $this->collections = $collections; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Transformer/Adapter/Fractal.php: -------------------------------------------------------------------------------- 1 | fractal = $fractal; 60 | $this->includeKey = $includeKey; 61 | $this->includeSeparator = $includeSeparator; 62 | $this->eagerLoading = $eagerLoading; 63 | } 64 | 65 | /** 66 | * Transform a response with a transformer. 67 | * 68 | * @param mixed $response 69 | * @param League\Fractal\TransformerAbstract|object $transformer 70 | * @param \Dingo\Api\Transformer\Binding $binding 71 | * @param \Dingo\Api\Http\Request $request 72 | * 73 | * @return array 74 | */ 75 | public function transform($response, $transformer, Binding $binding, Request $request) 76 | { 77 | $this->parseFractalIncludes($request); 78 | 79 | $resource = $this->createResource($response, $transformer, $parameters = $binding->getParameters()); 80 | 81 | // If the response is a paginator then we'll create a new paginator 82 | // adapter for Laravel and set the paginator instance on our 83 | // collection resource. 84 | if ($response instanceof IlluminatePaginator) { 85 | $paginator = $this->createPaginatorAdapter($response); 86 | 87 | $resource->setPaginator($paginator); 88 | } 89 | 90 | if ($this->shouldEagerLoad($response)) { 91 | $eagerLoads = $this->mergeEagerLoads($transformer, $this->fractal->getRequestedIncludes()); 92 | 93 | if ($transformer instanceof TransformerAbstract) { 94 | // Only eager load the items in available includes 95 | $eagerLoads = array_intersect($eagerLoads, $transformer->getAvailableIncludes()); 96 | } 97 | 98 | $response->load($eagerLoads); 99 | } 100 | 101 | foreach ($binding->getMeta() as $key => $value) { 102 | $resource->setMetaValue($key, $value); 103 | } 104 | 105 | $binding->fireCallback($resource, $this->fractal); 106 | 107 | $identifier = isset($parameters['identifier']) ? $parameters['identifier'] : null; 108 | 109 | return $this->fractal->createData($resource, $identifier)->toArray(); 110 | } 111 | 112 | /** 113 | * Eager loading is only performed when the response is or contains an 114 | * Eloquent collection and eager loading is enabled. 115 | * 116 | * @param mixed $response 117 | * 118 | * @return bool 119 | */ 120 | protected function shouldEagerLoad($response) 121 | { 122 | if ($response instanceof IlluminatePaginator) { 123 | $response = $response->getCollection(); 124 | } 125 | 126 | return $response instanceof EloquentCollection && $this->eagerLoading; 127 | } 128 | 129 | /** 130 | * Create the Fractal paginator adapter. 131 | * 132 | * @param \Illuminate\Contracts\Pagination\Paginator $paginator 133 | * 134 | * @return \League\Fractal\Pagination\IlluminatePaginatorAdapter 135 | */ 136 | protected function createPaginatorAdapter(IlluminatePaginator $paginator) 137 | { 138 | return new IlluminatePaginatorAdapter($paginator); 139 | } 140 | 141 | /** 142 | * Create a Fractal resource instance. 143 | * 144 | * @param mixed $response 145 | * @param \League\Fractal\TransformerAbstract $transformer 146 | * @param array $parameters 147 | * 148 | * @return \League\Fractal\Resource\Item|\League\Fractal\Resource\Collection 149 | */ 150 | protected function createResource($response, $transformer, array $parameters) 151 | { 152 | $key = isset($parameters['key']) ? $parameters['key'] : null; 153 | 154 | if ($response instanceof IlluminatePaginator || $response instanceof IlluminateCollection) { 155 | return new FractalCollection($response, $transformer, $key); 156 | } 157 | 158 | return new FractalItem($response, $transformer, $key); 159 | } 160 | 161 | /** 162 | * Parse the includes. 163 | * 164 | * @param \Dingo\Api\Http\Request $request 165 | * 166 | * @return void 167 | */ 168 | public function parseFractalIncludes(Request $request) 169 | { 170 | $includes = $request->input($this->includeKey); 171 | 172 | if (! is_array($includes)) { 173 | $includes = array_map('trim', array_filter(explode($this->includeSeparator, $includes))); 174 | } 175 | 176 | $this->fractal->parseIncludes($includes); 177 | } 178 | 179 | /** 180 | * Get the underlying Fractal instance. 181 | * 182 | * @return \League\Fractal\Manager 183 | */ 184 | public function getFractal() 185 | { 186 | return $this->fractal; 187 | } 188 | 189 | /** 190 | * Get includes as their array keys for eager loading. 191 | * 192 | * @param \League\Fractal\TransformerAbstract $transformer 193 | * @param string|array $requestedIncludes 194 | * 195 | * @return array 196 | */ 197 | protected function mergeEagerLoads($transformer, $requestedIncludes) 198 | { 199 | $includes = array_merge($requestedIncludes, $transformer->getDefaultIncludes()); 200 | 201 | $eagerLoads = []; 202 | 203 | foreach ($includes as $key => $value) { 204 | $eagerLoads[] = is_string($key) ? $key : $value; 205 | } 206 | 207 | if (property_exists($transformer, 'lazyLoadedIncludes')) { 208 | $eagerLoads = array_diff($eagerLoads, $transformer->lazyLoadedIncludes); 209 | } 210 | 211 | return $eagerLoads; 212 | } 213 | 214 | /** 215 | * Disable eager loading. 216 | * 217 | * @return \Dingo\Api\Transformer\Adapter\Fractal 218 | */ 219 | public function disableEagerLoading() 220 | { 221 | $this->eagerLoading = false; 222 | 223 | return $this; 224 | } 225 | 226 | /** 227 | * Enable eager loading. 228 | * 229 | * @return \Dingo\Api\Transformer\Adapter\Fractal 230 | */ 231 | public function enableEagerLoading() 232 | { 233 | $this->eagerLoading = true; 234 | 235 | return $this; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/Transformer/Binding.php: -------------------------------------------------------------------------------- 1 | container = $container; 59 | $this->resolver = $resolver; 60 | $this->parameters = $parameters; 61 | $this->callback = $callback; 62 | } 63 | 64 | /** 65 | * Resolve a transformer binding instance. 66 | * 67 | * @throws \RuntimeException 68 | * 69 | * @return object 70 | */ 71 | public function resolveTransformer() 72 | { 73 | if (is_string($this->resolver)) { 74 | return $this->container->make($this->resolver); 75 | } elseif (is_callable($this->resolver)) { 76 | return call_user_func($this->resolver, $this->container); 77 | } elseif (is_object($this->resolver)) { 78 | return $this->resolver; 79 | } 80 | 81 | throw new RuntimeException('Unable to resolve transformer binding.'); 82 | } 83 | 84 | /** 85 | * Fire the binding callback. 86 | * 87 | * @param string|array $parameters 88 | * 89 | * @return void 90 | */ 91 | public function fireCallback($parameters = null) 92 | { 93 | if (is_callable($this->callback)) { 94 | call_user_func_array($this->callback, func_get_args()); 95 | } 96 | } 97 | 98 | /** 99 | * Get the binding parameters. 100 | * 101 | * @return array 102 | */ 103 | public function getParameters() 104 | { 105 | return $this->parameters; 106 | } 107 | 108 | /** 109 | * Set the meta data for the binding. 110 | * 111 | * @param array $meta 112 | * 113 | * @return void 114 | */ 115 | public function setMeta(array $meta) 116 | { 117 | $this->meta = $meta; 118 | } 119 | 120 | /** 121 | * Add a meta data key/value pair. 122 | * 123 | * @param string $key 124 | * @param mixed $value 125 | * 126 | * @return void 127 | */ 128 | public function addMeta($key, $value) 129 | { 130 | $this->meta[$key] = $value; 131 | } 132 | 133 | /** 134 | * Get the binding meta data. 135 | * 136 | * @return array 137 | */ 138 | public function getMeta() 139 | { 140 | return $this->meta; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Transformer/Factory.php: -------------------------------------------------------------------------------- 1 | container = $container; 48 | $this->adapter = $adapter; 49 | } 50 | 51 | /** 52 | * Register a transformer binding resolver for a class. 53 | * 54 | * @param $class 55 | * @param $resolver 56 | * @param array $parameters 57 | * @param \Closure|null $after 58 | * 59 | * @return \Dingo\Api\Transformer\Binding 60 | */ 61 | public function register($class, $resolver, array $parameters = [], Closure $after = null) 62 | { 63 | return $this->bindings[$class] = $this->createBinding($resolver, $parameters, $after); 64 | } 65 | 66 | /** 67 | * Transform a response. 68 | * 69 | * @param string|object $response 70 | * 71 | * @return mixed 72 | */ 73 | public function transform($response) 74 | { 75 | $binding = $this->getBinding($response); 76 | 77 | return $this->adapter->transform($response, $binding->resolveTransformer(), $binding, $this->getRequest()); 78 | } 79 | 80 | /** 81 | * Determine if a response is transformable. 82 | * 83 | * @param mixed $response 84 | * 85 | * @return bool 86 | */ 87 | public function transformableResponse($response) 88 | { 89 | return $this->transformableType($response) && $this->hasBinding($response); 90 | } 91 | 92 | /** 93 | * Determine if a value is of a transformable type. 94 | * 95 | * @param mixed $value 96 | * 97 | * @return bool 98 | */ 99 | public function transformableType($value) 100 | { 101 | return is_object($value) || is_string($value); 102 | } 103 | 104 | /** 105 | * Get a registered transformer binding. 106 | * 107 | * @param string|object $class 108 | * 109 | * @throws \RuntimeException 110 | * 111 | * @return \Dingo\Api\Transformer\Binding 112 | */ 113 | public function getBinding($class) 114 | { 115 | if ($this->isCollection($class) && ! $class->isEmpty()) { 116 | return $this->getBindingFromCollection($class); 117 | } 118 | 119 | $class = is_object($class) ? get_class($class) : $class; 120 | 121 | if (! $this->hasBinding($class)) { 122 | throw new RuntimeException('Unable to find bound transformer for "'.$class.'" class.'); 123 | } 124 | 125 | return $this->bindings[$class]; 126 | } 127 | 128 | /** 129 | * Create a new binding instance. 130 | * 131 | * @param string|callable|object $resolver 132 | * @param array $parameters 133 | * @param \Closure $callback 134 | * 135 | * @return \Dingo\Api\Transformer\Binding 136 | */ 137 | protected function createBinding($resolver, array $parameters = [], Closure $callback = null) 138 | { 139 | return new Binding($this->container, $resolver, $parameters, $callback); 140 | } 141 | 142 | /** 143 | * Get a registered transformer binding from a collection of items. 144 | * 145 | * @param \Illuminate\Support\Collection $collection 146 | * 147 | * @return null|string|callable 148 | */ 149 | protected function getBindingFromCollection($collection) 150 | { 151 | return $this->getBinding($collection->first()); 152 | } 153 | 154 | /** 155 | * Determine if a class has a transformer binding. 156 | * 157 | * @param string|object $class 158 | * 159 | * @return bool 160 | */ 161 | protected function hasBinding($class) 162 | { 163 | if ($this->isCollection($class) && ! $class->isEmpty()) { 164 | $class = $class->first(); 165 | } 166 | 167 | $class = is_object($class) ? get_class($class) : $class; 168 | 169 | return isset($this->bindings[$class]); 170 | } 171 | 172 | /** 173 | * Determine if the instance is a collection. 174 | * 175 | * @param object $instance 176 | * 177 | * @return bool 178 | */ 179 | protected function isCollection($instance) 180 | { 181 | return $instance instanceof Collection || $instance instanceof Paginator; 182 | } 183 | 184 | /** 185 | * Get the array of registered transformer bindings. 186 | * 187 | * @return array 188 | */ 189 | public function getTransformerBindings() 190 | { 191 | return $this->bindings; 192 | } 193 | 194 | /** 195 | * Set the transformation layer at runtime. 196 | * 197 | * @param \Closure|\Dingo\Api\Contract\Transformer\Adapter $adapter 198 | * 199 | * @return void 200 | */ 201 | public function setAdapter($adapter) 202 | { 203 | if (is_callable($adapter)) { 204 | $adapter = call_user_func($adapter, $this->container); 205 | } 206 | 207 | $this->adapter = $adapter; 208 | } 209 | 210 | /** 211 | * Get the transformation layer adapter. 212 | * 213 | * @return \Dingo\Api\Contract\Transformer\Adapter 214 | */ 215 | public function getAdapter() 216 | { 217 | return $this->adapter; 218 | } 219 | 220 | /** 221 | * Get the request from the container. 222 | * 223 | * @return \Dingo\Api\Http\Request 224 | */ 225 | public function getRequest() 226 | { 227 | $request = $this->container['request']; 228 | 229 | if ($request instanceof IlluminateRequest && ! $request instanceof Request) { 230 | $request = (new Request())->createFromIlluminate($request); 231 | } 232 | 233 | return $request; 234 | } 235 | 236 | /** 237 | * Pass unknown method calls through to the adapter. 238 | * 239 | * @param string $method 240 | * @param array $parameters 241 | * 242 | * @return mixed 243 | */ 244 | public function __call($method, $parameters) 245 | { 246 | return call_user_func_array([$this->adapter, $method], $parameters); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | version($version); 14 | } 15 | } 16 | --------------------------------------------------------------------------------