|
@@ -1,61 +1,26 @@
|
|
# Extending functionality with plugins
|
|
# Extending functionality with plugins
|
|
-
|
|
|
|
-You can use plugins to alter how the Generator fetches data about your routes. For instance, suppose all your routes have a body parameter `organizationId`, and you don't want to annotate this with `@queryParam` on each method. You can create a plugin that adds this to all your body parameters. Let's see how to do this.
|
|
|
|
|
|
+You can use plugins to alter Scribe's behaviour when extracting info about your endpoints. For instance, suppose all your listing endpoints support pagination query parameters `ageSize` and `page`, and yopu don't want toannotate with `@queryParam` on each method. You can create a plugin that adds this to all your query parameters. Let's see how to do this.
|
|
|
|
|
|
## The stages of route processing
|
|
## The stages of route processing
|
|
-Route processing is performed in six stages:
|
|
|
|
-- metadata (this covers route `title`, route `description`, route `groupName`, route `groupDescription`, and authentication status (`authenticated`))
|
|
|
|
|
|
+Route processing is performed in six stages, in this order:
|
|
|
|
+- metadata (this includes `title`, `description`, `groupName`, `groupDescription`, and authentication status (`authenticated`))
|
|
- urlParameters
|
|
- urlParameters
|
|
- queryParameters
|
|
- queryParameters
|
|
- headers (headers to be added to example request and response calls)
|
|
- headers (headers to be added to example request and response calls)
|
|
- bodyParameters
|
|
- bodyParameters
|
|
- responses
|
|
- responses
|
|
-- responseFields
|
|
|
|
-
|
|
|
|
-For each stage, the Generator attempts the specified strategies to fetch data. The Generator will call of the strategies configured, progressively combining their results together before to produce the final output of that stage.
|
|
|
|
-
|
|
|
|
-There are a number of strategies included with the package, so you don't have to set up anything to get it working.
|
|
|
|
-
|
|
|
|
-> Note: The included ResponseCalls strategy is designed to stop if a response with a 2xx status code has already been gotten via any other strategy.
|
|
|
|
-
|
|
|
|
-## Strategies
|
|
|
|
-To create a strategy, create a class that extends `\Knuckles\Scribe\Extracting\Strategies\Strategy`.
|
|
|
|
-
|
|
|
|
-The `__invoke` method of the strategy is where you perform your actions and return data. It receives the following arguments:
|
|
|
|
-- the route (instance of `\Illuminate\Routing\Route`)
|
|
|
|
-- the controller class handling the route (`\ReflectionClass`)
|
|
|
|
-- the controller method (`\ReflectionMethod $method`)
|
|
|
|
- - the rules specified in the scribe.php config file for the group this route belongs to, under the `apply` section (array)
|
|
|
|
- - the context. This contains all data for the route that has been parsed thus far in the previous stages. This means, by the `responses` stage, the context will contain the following keys: `metadata`, `bodyParameters` and `queryParameters`.
|
|
|
|
-
|
|
|
|
- Here's what your strategy in our example would look like:
|
|
|
|
-
|
|
|
|
- ```php
|
|
|
|
-<?php
|
|
|
|
|
|
+- responseFields (descriptions of fields in the response)
|
|
|
|
|
|
-use Illuminate\Routing\Route;
|
|
|
|
-use Knuckles\Scribe\Extracting\Strategies\Strategy;
|
|
|
|
|
|
+For each stage, the Generator attempts to use various "strategies" to fetch data. The Generator will call all of the strategies configured in `scribe.php`, progressively combining their results together to produce the final output of that stage.
|
|
|
|
|
|
-class AddOrganizationIdBodyParameter extends Strategy
|
|
|
|
-{
|
|
|
|
- public function __invoke(Route $route, \ReflectionClass $controller, \ReflectionMethod $method, array $routeRules, array $context = [])
|
|
|
|
- {
|
|
|
|
- return [
|
|
|
|
- 'organizationId' => [
|
|
|
|
- 'type' => 'integer',
|
|
|
|
- 'description' => 'The ID of the organization',
|
|
|
|
- 'required' => true,
|
|
|
|
- 'value' => 2,
|
|
|
|
- ]
|
|
|
|
- ];
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+```eval_rst
|
|
|
|
+.. Note:: Unlike other stages, the `responses` stage is additive. This means that all responses from all strategies in that stage will be saved. Responses cannot overwrite each other, even if they have the same status code. By comparison, if you return a value for a body parameter from one strategy, it will overwrite any other values for that parameter gotten from previous strategies.
|
|
```
|
|
```
|
|
|
|
|
|
-The last thing to do is to register the strategy. Strategies are registered in a `strategies` key in the `scribe.php` file. Here's what the file looks like by default:
|
|
|
|
|
|
+There are a number of strategies included with the package, so you don't have to set up anything to get it working. Here's what's included in `scribe.php` by default:
|
|
|
|
|
|
```php
|
|
```php
|
|
-...
|
|
|
|
|
|
+
|
|
'strategies' => [
|
|
'strategies' => [
|
|
'metadata' => [
|
|
'metadata' => [
|
|
\Knuckles\Scribe\Extracting\Strategies\Metadata\GetFromDocBlocks::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\Metadata\GetFromDocBlocks::class,
|
|
@@ -70,6 +35,7 @@ The last thing to do is to register the strategy. Strategies are registered in a
|
|
\Knuckles\Scribe\Extracting\Strategies\Headers\GetFromRouteRules::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\Headers\GetFromRouteRules::class,
|
|
],
|
|
],
|
|
'bodyParameters' => [
|
|
'bodyParameters' => [
|
|
|
|
+ \Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromFormRequest::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromBodyParamTag::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromBodyParamTag::class,
|
|
],
|
|
],
|
|
'responses' => [
|
|
'responses' => [
|
|
@@ -79,64 +45,160 @@ The last thing to do is to register the strategy. Strategies are registered in a
|
|
\Knuckles\Scribe\Extracting\Strategies\Responses\UseApiResourceTags::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\Responses\UseApiResourceTags::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\Responses\ResponseCalls::class,
|
|
\Knuckles\Scribe\Extracting\Strategies\Responses\ResponseCalls::class,
|
|
],
|
|
],
|
|
|
|
+ 'responseFields' => [
|
|
|
|
+ \Knuckles\Scribe\Extracting\Strategies\ResponseFields\GetFromResponseFieldTag::class,
|
|
|
|
+ ],
|
|
],
|
|
],
|
|
-...
|
|
|
|
```
|
|
```
|
|
|
|
|
|
-You can add, replace or remove strategies from here. In our case, we're adding our bodyParameter strategy:
|
|
|
|
-
|
|
|
|
-```php
|
|
|
|
|
|
+```eval_rst
|
|
|
|
+.. Note:: The included ResponseCalls strategy is designed to stop if a response with a 2xx status code has already been gotten via any other strategy.
|
|
|
|
+```
|
|
|
|
|
|
- 'bodyParameters' => [
|
|
|
|
- \Knuckles\Scribe\Extracting\Strategies\BodyParameters\GetFromBodyParamTag::class,
|
|
|
|
- AddOrganizationIdBodyParameter::class,
|
|
|
|
- ],
|
|
|
|
|
|
+```eval_rst
|
|
|
|
+.. Tip:: Check out our `community wiki <https://github.com/knuckleswtf/scribe/wiki>`_ for a list of strategies contributed by the community.
|
|
```
|
|
```
|
|
|
|
|
|
-And we're done. Now, when we run `php artisan docs:generate`, all our routes will have this bodyParameter added.
|
|
|
|
|
|
+## Creating a strategy
|
|
|
|
+To create a strategy, create a class that extends `\Knuckles\Scribe\Extracting\Strategies\Strategy`. You can do this by running the `scribe:strategy` command. By default, this will place the strategy in your `App\Docs\Strategies` namespace. The first argument to `scribe:strategy` is the name of the strategy, the second is the stage it belongs to.
|
|
|
|
|
|
|
|
+```sh
|
|
|
|
+php artisan scribe:strategy AddPaginationParameters queryParameters
|
|
|
|
+```
|
|
|
|
|
|
-We could go further and modify our strategy so it doesn't add this parameter if the route is a GET route or is authenticated:
|
|
|
|
|
|
+This creates a class like this:
|
|
|
|
|
|
```php
|
|
```php
|
|
-public function __invoke(Route $route, \ReflectionClass $controller, \ReflectionMethod $method, array $routeRules, array $context = [])
|
|
|
|
|
|
+<?php
|
|
|
|
+
|
|
|
|
+namespace App\Docs\Strategies;
|
|
|
|
+
|
|
|
|
+use Illuminate\Routing\Route;
|
|
|
|
+use Knuckles\Scribe\Extracting\ParamHelpers;
|
|
|
|
+use Knuckles\Scribe\Extracting\RouteDocBlocker;
|
|
|
|
+use Knuckles\Scribe\Extracting\Strategies\Strategy;
|
|
|
|
+use ReflectionClass;
|
|
|
|
+use ReflectionFunctionAbstract;
|
|
|
|
+
|
|
|
|
+class AddPaginationParameters extends Strategy
|
|
{
|
|
{
|
|
- if (in_array('GET', $route->methods()) {
|
|
|
|
|
|
+ public $stage = 'queryParameters';
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Trait containing some helper methods for dealing with "parameters".
|
|
|
|
+ * Useful if your strategy extracts information about parameters.
|
|
|
|
+ */
|
|
|
|
+ use ParamHelpers;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @link https://scribe.readthedocs.io/en/latest/plugins.html
|
|
|
|
+ * @param Route $route The route which we are currently extracting queryParameters for.
|
|
|
|
+ * @param ReflectionClass $controller The class handling the current route.
|
|
|
|
+ * @param ReflectionFunctionAbstract $method The method/closure handling the current route.
|
|
|
|
+ * @param array $routeRules Array of rules for the ruleset which this route belongs to.
|
|
|
|
+ * @param array $alreadyExtractedData Data already extracted from previous stages and earlier strategies in this stage
|
|
|
|
+ *
|
|
|
|
+ * See the documentation linked above for more details about writing custom strategies.
|
|
|
|
+ *
|
|
|
|
+ * @return array|null
|
|
|
|
+ */
|
|
|
|
+ public function __invoke(
|
|
|
|
+ Route $route,
|
|
|
|
+ ReflectionClass $controller,
|
|
|
|
+ ReflectionFunctionAbstract $method,
|
|
|
|
+ array $routeRules,
|
|
|
|
+ array $alreadyExtractedData = []
|
|
|
|
+ )
|
|
|
|
+ {
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
- if ($context['metadata']['authenticated']) {
|
|
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+Alternatively, if you're creating a strategy that you'd like people to download and install via Composer, you can generate one from [this GitHub template]().
|
|
|
|
+
|
|
|
|
+## Writing strategies
|
|
|
|
+Let's take a look at the contents of our Strategy class.
|
|
|
|
+
|
|
|
|
+First, there's a `$stage` property that states the stage the strategy belongs too. Used internally by Scribe.
|
|
|
|
+
|
|
|
|
+The `__invoke` method of the strategy is where you extract and return data. It receives the following arguments:
|
|
|
|
+- the `route` being processed (an instance of `\Illuminate\Routing\Route`)
|
|
|
|
+- the `controller` handling the route (`\ReflectionClass`)
|
|
|
|
+- the `method` handling the route (`\ReflectionFunctionAbstract`)
|
|
|
|
+- the `rules` specified in the `scribe.php` config file for the group this route belongs to, under the `apply` section (array)
|
|
|
|
+- `alreadyExtractedData`. This contains all data for the route that has been parsed thus far in the previous stages, as well as earlier strategies in this stage.
|
|
|
|
+
|
|
|
|
+The strategy class also has access to the current Scribe configuration via its `config` property. For instance, you can retrieve the router in use with `$this->config->get('router')`. You can also specify a default value to be returned if the config key is not set:
|
|
|
|
+
|
|
|
|
+```php
|
|
|
|
+$router = $this->config->get('router', 'laravel');
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+Let's add some code to make our strategy work:
|
|
|
|
+
|
|
|
|
+ ```php
|
|
|
|
+ public function __invoke(
|
|
|
|
+ Route $route,
|
|
|
|
+ ReflectionClass $controller,
|
|
|
|
+ ReflectionFunctionAbstract $method,
|
|
|
|
+ array $routeRules,
|
|
|
|
+ array $alreadyExtractedData = []
|
|
|
|
+ )
|
|
|
|
+ {
|
|
|
|
+ $isGetRoute = in_array('GET', $route->methods());
|
|
|
|
+ $isIndexRoute = strpos($route->getName(), '.index') !== false;
|
|
|
|
+ if ($isGetRoute && $isIndexRoute) {
|
|
|
|
+ return [
|
|
|
|
+ 'page' => [
|
|
|
|
+ 'description' => 'Page number to return.',
|
|
|
|
+ 'required' => false,
|
|
|
|
+ 'value' => 1,
|
|
|
|
+ ],
|
|
|
|
+ 'pageSize' => [
|
|
|
|
+ 'description' => 'Number of items to return in a page. Defaults to 10.',
|
|
|
|
+ 'required' => false,
|
|
|
|
+ 'value' => null, // So it doesn't get included in the examples
|
|
|
|
+ ],
|
|
|
|
+ ];
|
|
|
|
+ }
|
|
|
|
+
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
-
|
|
|
|
- return [
|
|
|
|
- 'organizationId' => [
|
|
|
|
- 'type' => 'integer',
|
|
|
|
- 'description' => 'The ID of the organization',
|
|
|
|
- 'required' => true,
|
|
|
|
- 'value' => 2,
|
|
|
|
- ]
|
|
|
|
- ];
|
|
|
|
-}
|
|
|
|
```
|
|
```
|
|
|
|
|
|
-> Note: If you would like a parameter (body or query) to be included in the documentation but excluded from examples, set its `value` property to `null`.
|
|
|
|
|
|
+## Using your strategy
|
|
|
|
+To use your strategy, you need to register it in the `strategies` key in the `scribe.php` file.
|
|
|
|
|
|
-The strategy class also has access to the current scribe configuration via its `config` property. For instance, you can retrieve the default group with `$this->config->get('default_group')`.
|
|
|
|
|
|
+```php
|
|
|
|
|
|
-You are also provided with the instance pproperty `stage`, which is set to the name of the currently executing stage.
|
|
|
|
|
|
+ 'queryParameters' => [
|
|
|
|
+ \Knuckles\Scribe\Extracting\Strategies\QueryParameters\GetFromQueryParamTag::class,
|
|
|
|
+ \App\Docs\Strategies\AddPaginationParameters::class,
|
|
|
|
+ ],
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+```eval_rst
|
|
|
|
+.. Tip:: You can also install strategies from Packagist via :code:`composer require` and then register them here.
|
|
|
|
+```
|
|
|
|
|
|
|
|
+And we're done! Now, when we run `php artisan scribe:generate`, all our GET routes that end with `.index` will have the pagination parameters added.
|
|
|
|
+
|
|
|
|
+
|
|
|
|
|
|
## Utilities
|
|
## Utilities
|
|
You have access to a number of tools when developing strategies. They include:
|
|
You have access to a number of tools when developing strategies. They include:
|
|
|
|
|
|
-- The `RouteDocBlocker` class (in the `\Knuckles\Scribe\Extracting` namespace) has a single public static method, `getDocBlocksFromRoute(Route $route)`. It allows you to retrieve the docblocks for a given route. It returns an array of with two keys: `method` and `class` containing the docblocks for the method and controller handling the route respectively. Both are instances of `\Mpociot\Reflection\DocBlock`.
|
|
|
|
|
|
+- The `RouteDocBlocker` class (in the `\Knuckles\Scribe\Extracting` namespace) has a single public static method, `getDocBlocksFromRoute(Route $route)`. It allows you to retrieve the docblocks for a given route. It returns an array with two keys: `method` and `class` containing the docblocks for the method and controller handling the route respectively. Both are instances of `\Mpociot\Reflection\DocBlock`.
|
|
|
|
+
|
|
|
|
+- The `ParamHelpers` trait (in the `\Knuckles\Scribe\Extracting` namespace) can be included in your strategies. It contains a number of useful methods for working with parameters, including type casting and generating dummy values for different types.
|
|
|
|
|
|
-- The `ParamsHelper` trait (in the `\Knuckles\Scribe\Extracting` namespace) can be included in your strategies. It contains a number of useful methods for working with parameters, including type casting and generating dummy values.
|
|
|
|
|
|
+- The `DatabaseTransactionHelpers` trait (in the `\Knuckles\Scribe\Extracting` namespace), which is helpful to prevent data being persisted if your strategy needs to interact with the database. This trait contains methods to start and end database transactions.
|
|
|
|
|
|
## API
|
|
## API
|
|
-Each strategy class must implement the __invoke method with the parameters as described above. This method must return the needed data for the intended stage, or `null` to indicate failure.
|
|
|
|
-- In the `metadata` stage, strategies should return an array. These are the expected keys (you may omit some, or all):
|
|
|
|
|
|
+Each strategy class must implement the `__invoke` method with the parameters as described above. This method must return the needed data for the intended stage, or `null` to indicate failure.
|
|
|
|
+- In the `metadata` stage, strategies should return an array with some or all of the following keys:
|
|
|
|
|
|
```
|
|
```
|
|
'groupName'
|
|
'groupName'
|
|
@@ -146,7 +208,7 @@ Each strategy class must implement the __invoke method with the parameters as de
|
|
'authenticated' // boolean
|
|
'authenticated' // boolean
|
|
```
|
|
```
|
|
|
|
|
|
-- In the `bodyParameters` and `queryParameters` stages, you can return an array with arbitrary keys. These keys will serve as the names of your parameters. Array keys can be indicated with Laravel's dot notation. The value of each key should be an array with the following keys:
|
|
|
|
|
|
+- In the `urlParameters`, `queryParameters`, and `bodyParameters` stages, you can return an array with arbitrary keys. These keys will be the names of your parameters. Array keys can be indicated with Laravel's dot notation. The value of each key should be an array with the following keys:
|
|
|
|
|
|
```
|
|
```
|
|
'type', // Only valid in bodyParameters
|
|
'type', // Only valid in bodyParameters
|
|
@@ -154,11 +216,18 @@ Each strategy class must implement the __invoke method with the parameters as de
|
|
'required', // boolean
|
|
'required', // boolean
|
|
'value', // An example value for the parameter
|
|
'value', // An example value for the parameter
|
|
```
|
|
```
|
|
-- In the `responses` stage, your strategy should return an array containing the responses for different status codes. Each item in the array should be an array representing the response with a `status` key containing the HTTP status code, and a `content` key a string containing the response. For example:
|
|
|
|
|
|
+
|
|
|
|
+```eval_rst
|
|
|
|
+.. Tip:: If you would like a parameter (body or query) to be included in the documentation but excluded from examples, set `required` to false and `value` property to `null`, like we did in our example above.
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+- In the `headers` stage, you can return a key-value array of headers. You may also get rid of already set headers by setting `false` as the header value.
|
|
|
|
+
|
|
|
|
+- In the `responses` stage, your strategy should return an array containing the responses it was able to extract. Each item in the array should be an array representing the response, with a `status` key containing the HTTP status code, and a `content` key, which is a string containing the response. For example:
|
|
|
|
|
|
```php
|
|
```php
|
|
|
|
|
|
- public function __invoke(Route $route, \ReflectionClass $controller, \ReflectionMethod $method, array $routeRules, array $context = [])
|
|
|
|
|
|
+ public function __invoke(Route $route, \ReflectionClass $controller, \ReflectionFunctionAbstract $method, array $routeRules, array $alreadyExtractedData = [])
|
|
{
|
|
{
|
|
return [
|
|
return [
|
|
[
|
|
[
|
|
@@ -166,21 +235,17 @@ Each strategy class must implement the __invoke method with the parameters as de
|
|
'status' => 201
|
|
'status' => 201
|
|
],
|
|
],
|
|
[
|
|
[
|
|
- 'content' => "Nope",
|
|
|
|
|
|
+ 'content' => '{"error": "Nope"}',
|
|
'status' => 404
|
|
'status' => 404
|
|
],
|
|
],
|
|
]
|
|
]
|
|
}
|
|
}
|
|
```
|
|
```
|
|
|
|
|
|
-Responses are _additive_. This means all the responses returned from each stage are added to the `responses` array. But note that the `ResponseCalls` strategy will only attempt to fetch a response if there are no responses with a status code of 2xx already.
|
|
|
|
-
|
|
|
|
-- In the `headers` stage, you can return an array of headers. You may also negate existing headers by providing `false` as the header value.
|
|
|
|
-
|
|
|
|
|
|
|
|
-Check out our [community wiki](https://github.com/knuckleswtf/scribe/wiki/Helpful-strategies-(snippets)) for a list of helpful extraction strategies.
|
|
|
|
|
|
+- In the `responseFields` stage, you can return an array with arbitrary keys. These keys will be the names of fields in your response. The value of each key should be an array with the following keys:
|
|
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-## Reworked Strategy API
|
|
|
|
-- `stage` property.
|
|
|
|
|
|
+```
|
|
|
|
+'type',
|
|
|
|
+'description',
|
|
|
|
+```
|