Sfoglia il codice sorgente

Update docs, refactor API

shalvah 5 anni fa
parent
commit
ffb3563bfd

+ 2 - 2
config/apidoc.php

@@ -184,10 +184,10 @@ return [
             \Mpociot\ApiDoc\Strategies\Metadata\GetFromDocBlocks::class,
         ],
         'bodyParameters' => [
-            \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromDocBlocks::class,
+            \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromBodyParamTag::class,
         ],
         'queryParameters' => [
-            \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromDocBlocks::class,
+            \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromQueryParamTag::class,
         ],
         'responses' => [
             \Mpociot\ApiDoc\Strategies\Responses\UseResponseTag::class,

+ 40 - 5
docs/plugins.md

@@ -3,7 +3,7 @@ You can use plugins to alter how the Generator fetches data about your routes. F
 
 ## The stages of route processing
 Route processing is performed in four stages:
-- metadata (this covers route title, route description, route group name, route group description, and authentication status)
+- metadata (this covers route `title`, route `description`, route `groupName`, route `groupDescription`, and authentication status (`authenticated`))
 - bodyParameters
 - queryParameters
 - responses
@@ -18,7 +18,7 @@ The `__invoke` method of the strategy is where you perform your actions and retu
 - the controller class handling the route (`\ReflectionClass`)
 - the controller method (`\ReflectionMethod $method`)
  - the rules specified in the apidoc.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.
+ - 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:
  
@@ -53,10 +53,10 @@ The last thing to do is to register the strategy. Strategies are registered in a
             \Mpociot\ApiDoc\Strategies\Metadata\GetFromDocBlocks::class,
         ],
         'bodyParameters' => [
-            \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromDocBlocks::class,
+            \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromBodyParamTag::class,
         ],
         'queryParameters' => [
-            \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromDocBlocks::class,
+            \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromQueryParamTag::class,
         ],
         'responses' => [
             \Mpociot\ApiDoc\Strategies\Responses\UseResponseTag::class,
@@ -73,7 +73,7 @@ You can add, replace or remove strategies from here. In our case, we're adding o
 ```php
 
         'bodyParameters' => [
-            \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromDocBlocks::class,
+            \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromBodyParamTag::class,
             AddOrganizationIdBodyParameter::class,
         ],
 ```
@@ -106,3 +106,38 @@ public function __invoke(Route $route, \ReflectionClass $controller, \Reflection
 ```
 
 The strategy class also has access to the current apidoc configuration via its config property. For instance, you can retrieve the deafult group with `$this->config->get('default_group')`.
+
+
+## Utilities
+You have access to a number of tools when developing strategies. They include:
+
+- The `RouteDocBlocker` class (in the `\Mpociot\ApiDoc\Tools` 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 `ParamsHelper` trait (in the `\Mpociot\ApiDoc\Tools` 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.
+
+## 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):
+
+```
+'groupName'
+'groupDescription'
+'title'
+'description'
+'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:
+
+```
+'type', // Only used in bodyParameters
+'description', 
+'required', // boolean
+'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 key in the array should be a HTTP status code, and each value should be an array with two keys:
+
+```
+status // The same status code
+content // The response content as a string
+```

+ 6 - 4
src/Strategies/BodyParameters/GetFromDocBlocks.php → src/Strategies/BodyParameters/GetFromBodyParamTag.php

@@ -9,13 +9,13 @@ use Mpociot\Reflection\DocBlock;
 use Mpociot\Reflection\DocBlock\Tag;
 use Mpociot\ApiDoc\Strategies\Strategy;
 use Mpociot\ApiDoc\Tools\RouteDocBlocker;
-use Mpociot\ApiDoc\Tools\Traits\ParamHelpers;
 use Dingo\Api\Http\FormRequest as DingoFormRequest;
+use Mpociot\ApiDoc\Tools\Traits\DocBlockParamHelpers;
 use Illuminate\Foundation\Http\FormRequest as LaravelFormRequest;
 
-class GetFromDocBlocks extends Strategy
+class GetFromBodyParamTag extends Strategy
 {
-    use ParamHelpers;
+    use DocBlockParamHelpers;
 
     public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
     {
@@ -78,7 +78,9 @@ class GetFromDocBlocks extends Strategy
 
                 $type = $this->normalizeParameterType($type);
                 list($description, $example) = $this->parseParamDescription($description, $type);
-                $value = is_null($example) && ! $this->shouldExcludeExample($tag) ? $this->generateDummyValue($type) : $example;
+                $value = is_null($example) && ! $this->shouldExcludeExample($tag)
+                    ? $this->generateDummyValue($type)
+                    : $example;
 
                 return [$name => compact('type', 'description', 'required', 'value')];
             })->toArray();

+ 3 - 3
src/Strategies/QueryParameters/GetFromDocBlocks.php → src/Strategies/QueryParameters/GetFromQueryParamTag.php

@@ -10,13 +10,13 @@ use Mpociot\Reflection\DocBlock;
 use Mpociot\Reflection\DocBlock\Tag;
 use Mpociot\ApiDoc\Strategies\Strategy;
 use Mpociot\ApiDoc\Tools\RouteDocBlocker;
-use Mpociot\ApiDoc\Tools\Traits\ParamHelpers;
 use Dingo\Api\Http\FormRequest as DingoFormRequest;
+use Mpociot\ApiDoc\Tools\Traits\DocBlockParamHelpers;
 use Illuminate\Foundation\Http\FormRequest as LaravelFormRequest;
 
-class GetFromDocBlocks extends Strategy
+class GetFromQueryParamTag extends Strategy
 {
-    use ParamHelpers;
+    use DocBlockParamHelpers;
 
     public function __invoke(Route $route, ReflectionClass $controller, ReflectionMethod $method, array $routeRules, array $context = [])
     {

+ 47 - 4
src/Tools/Generator.php

@@ -4,15 +4,12 @@ namespace Mpociot\ApiDoc\Tools;
 
 use ReflectionClass;
 use ReflectionMethod;
+use Illuminate\Support\Arr;
 use Illuminate\Support\Str;
 use Illuminate\Routing\Route;
-use Mpociot\ApiDoc\Tools\Traits\ParamHelpers;
-use Symfony\Component\HttpFoundation\Response;
 
 class Generator
 {
-    use ParamHelpers;
-
     /**
      * @var DocumentationConfig
      */
@@ -147,4 +144,50 @@ class Generator
         }
         return $context[$key];
     }
+
+    /**
+     * Create samples at index 0 for array parameters.
+     * Also filter out parameters which were excluded from having examples.
+     *
+     * @param array $params
+     *
+     * @return array
+     */
+    protected function cleanParams(array $params)
+    {
+        $values = [];
+
+        // Remove params which have no examples.
+        $params = array_filter($params, function ($details) {
+            return ! is_null($details['value']);
+        });
+
+        foreach ($params as $paramName => $details) {
+            $this->generateConcreteSampleForArrayKeys(
+                $paramName, $details['value'], $values
+            );
+        }
+
+        return $values;
+    }
+
+    /**
+     * For each array notation parameter (eg user.*, item.*.name, object.*.*, user[])
+     * generate concrete sample (user.0, item.0.name, object.0.0, user.0) with example as value
+     *
+     * @param string $paramName
+     * @param mixed $paramExample
+     * @param array $values The array that holds the result
+     *
+     * @return void
+     */
+    protected function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$values = [])
+    {
+        if (Str::contains($paramName, '[')) {
+            // Replace usages of [] with dot notation
+            $paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
+        }
+        // Then generate a sample item for the dot notation
+        Arr::set($values, str_replace('.*', '.0', $paramName), $paramExample);
+    }
 }

+ 12 - 6
src/Tools/RouteDocBlocker.php

@@ -8,9 +8,15 @@ use Mpociot\Reflection\DocBlock;
 
 class RouteDocBlocker
 {
-    public static $docBlocks = [];
-
-    public static function getDocBlocksFromRoute(Route $route)
+    protected static $docBlocks = [];
+
+    /**
+     * @param Route $route
+     *
+     * @return array<string, DocBlock> Method and class docblocks
+     * @throws \ReflectionException
+     */
+    public static function getDocBlocksFromRoute(Route $route): array
     {
         list($className, $methodName) = Utils::getRouteClassAndMethodNames($route);
         $docBlocks = self::getCachedDocBlock($route, $className, $methodName);
@@ -35,18 +41,18 @@ class RouteDocBlocker
 
     protected static function getCachedDocBlock(Route $route, string $className, string $methodName)
     {
-        $routeId = self::getRouteId($route, $className, $methodName);
+        $routeId = self::getRouteCacheId($route, $className, $methodName);
 
         return self::$docBlocks[$routeId] ?? null;
     }
 
     protected static function cacheDocBlocks(Route $route, string $className, string $methodName, array $docBlocks)
     {
-        $routeId = self::getRouteId($route, $className, $methodName);
+        $routeId = self::getRouteCacheId($route, $className, $methodName);
         self::$docBlocks[$routeId] = $docBlocks;
     }
 
-    private static function getRouteId(Route $route, string $className, string $methodName)
+    private static function getRouteCacheId(Route $route, string $className, string $methodName): string
     {
         return $route->uri()
             .':'

+ 48 - 0
src/Tools/Traits/DocBlockParamHelpers.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tools\Traits;
+
+use Faker\Factory;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+use Mpociot\Reflection\DocBlock\Tag;
+
+trait DocBlockParamHelpers
+{
+    use ParamHelpers;
+
+    /**
+     * Allows users to specify that we shouldn't generate an example for the parameter
+     * by writing 'No-example'.
+     *
+     * @param Tag $tag
+     *
+     * @return bool Whether no example should be generated
+     */
+    protected  function shouldExcludeExample(Tag $tag)
+    {
+        return strpos($tag->getContent(), ' No-example') !== false;
+    }
+
+    /**
+     * Allows users to specify an example for the parameter by writing 'Example: the-example',
+     * to be used in example requests and response calls.
+     *
+     * @param string $description
+     * @param string $type The type of the parameter. Used to cast the example provided, if any.
+     *
+     * @return array The description and included example.
+     */
+    protected  function parseParamDescription(string $description, string $type)
+    {
+        $example = null;
+        if (preg_match('/(.*)\s+Example:\s*(.*)\s*/', $description, $content)) {
+            $description = $content[1];
+
+            // examples are parsed as strings by default, we need to cast them properly
+            $example = $this->castToType($content[2], $type);
+        }
+
+        return [$description, $example];
+    }
+}

+ 11 - 79
src/Tools/Traits/ParamHelpers.php

@@ -3,64 +3,10 @@
 namespace Mpociot\ApiDoc\Tools\Traits;
 
 use Faker\Factory;
-use Illuminate\Support\Arr;
-use Illuminate\Support\Str;
-use Mpociot\Reflection\DocBlock\Tag;
 
 trait ParamHelpers
 {
-    /**
-     * Create proper arrays from dot-noted parameter names. Also filter out parameters which were excluded from having examples.
-     *
-     * @param array $params
-     *
-     * @return array
-     */
-    protected function cleanParams(array $params)
-    {
-        $values = [];
-        $params = array_filter($params, function ($details) {
-            return ! is_null($details['value']);
-        });
-
-        foreach ($params as $name => $details) {
-            $this->cleanValueFrom($name, $details['value'], $values);
-        }
-
-        return $values;
-    }
-
-    /**
-     * Converts dot notation names to arrays and sets the value at the right depth.
-     *
-     * @param string $name
-     * @param mixed $value
-     * @param array $values The array that holds the result
-     *
-     * @return void
-     */
-    protected function cleanValueFrom($name, $value, array &$values = [])
-    {
-        if (Str::contains($name, '[')) {
-            $name = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $name);
-        }
-        Arr::set($values, str_replace('.*', '.0', $name), $value);
-    }
-
-    /**
-     * Allows users to specify that we shouldn't generate an example for the parameter
-     * by writing 'No-example'.
-     *
-     * @param Tag $tag
-     *
-     * @return bool Whether no example should be generated
-     */
-    private function shouldExcludeExample(Tag $tag)
-    {
-        return strpos($tag->getContent(), ' No-example') !== false;
-    }
-
-    private function generateDummyValue(string $type)
+    protected  function generateDummyValue(string $type)
     {
         $faker = Factory::create();
         if ($this->config->get('faker_seed')) {
@@ -95,28 +41,6 @@ trait ParamHelpers
         return $fakeFactory();
     }
 
-    /**
-     * Allows users to specify an example for the parameter by writing 'Example: the-example',
-     * to be used in example requests and response calls.
-     *
-     * @param string $description
-     * @param string $type The type of the parameter. Used to cast the example provided, if any.
-     *
-     * @return array The description and included example.
-     */
-    private function parseParamDescription(string $description, string $type)
-    {
-        $example = null;
-        if (preg_match('/(.*)\s+Example:\s*(.*)\s*/', $description, $content)) {
-            $description = $content[1];
-
-            // examples are parsed as strings by default, we need to cast them properly
-            $example = $this->castToType($content[2], $type);
-        }
-
-        return [$description, $example];
-    }
-
     /**
      * Cast a value from a string to a specified type.
      *
@@ -125,7 +49,7 @@ trait ParamHelpers
      *
      * @return mixed
      */
-    private function castToType(string $value, string $type)
+    protected  function castToType(string $value, string $type)
     {
         $casts = [
             'integer' => 'intval',
@@ -147,7 +71,15 @@ trait ParamHelpers
         return $value;
     }
 
-    private function normalizeParameterType(string $type)
+    /**
+     * Normalizes the stated "type" of a parameter (eg "int", "integer", "double")
+     * to a number of standard types (integer, boolean, float).
+     *
+     * @param string $type
+     *
+     * @return mixed|string
+     */
+    protected function normalizeParameterType(string $type)
     {
         $typeMap = [
             'int' => 'integer',

+ 2 - 2
tests/Unit/GeneratorTestCase.php

@@ -22,10 +22,10 @@ abstract class GeneratorTestCase extends TestCase
                 \Mpociot\ApiDoc\Strategies\Metadata\GetFromDocBlocks::class,
             ],
             'bodyParameters' => [
-                \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromDocBlocks::class,
+                \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromBodyParamTag::class,
             ],
             'queryParameters' => [
-                \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromDocBlocks::class,
+                \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromQueryParamTag::class,
             ],
             'responses' => [
                 \Mpociot\ApiDoc\Strategies\Responses\UseResponseTag::class,