Procházet zdrojové kódy

Refactor response retrieval method to use strategies

shalvah před 6 roky
rodič
revize
060b825a03

+ 1 - 1
README.md

@@ -223,7 +223,7 @@ public function show($id)
 #### @transformer, @transformerCollection, and @transformerModel
 You can define the transformer that is used for the result of the route using the `@transformer` tag (or `@transformerCollection` if the route returns a list). The package will attempt to generate an instance of the model to be transformed using the following steps, stopping at the first successful one:
 
-1. Check if there is a `@transformerModel` tag to define the model being transformed. If there is none, use the class of the first parameter to the method.
+1. Check if there is a `@transformerModel` tag to define the model being transformed. If there is none, use the class of the first parameter to the transformer's `transform()` method.
 2. Get an instance of the model from the Eloquent model factory
 2. If the parameter is an Eloquent model, load the first from the database.
 3. Create an instance using `new`.

+ 1 - 1
src/Commands/GenerateDocumentation.php

@@ -187,7 +187,7 @@ class GenerateDocumentation extends Command
             $route = $routeItem['route'];
             /** @var Route $route */
             if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction()['uses'])) {
-                $parsedRoutes[] = $generator->processRoute($route) + $routeItem['apply'];
+                $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply']);
                 $this->info('Processed route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route));
             } else {
                 $this->warn('Skipping route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route));

+ 21 - 166
src/Generators/AbstractGenerator.php

@@ -3,6 +3,7 @@
 namespace Mpociot\ApiDoc\Generators;
 
 use Faker\Factory;
+use Mpociot\ApiDoc\Tools\ResponseResolver;
 use ReflectionClass;
 use Illuminate\Support\Str;
 use League\Fractal\Manager;
@@ -11,6 +12,7 @@ use Mpociot\Reflection\DocBlock;
 use League\Fractal\Resource\Item;
 use Mpociot\Reflection\DocBlock\Tag;
 use League\Fractal\Resource\Collection;
+use ReflectionMethod;
 
 abstract class AbstractGenerator
 {
@@ -50,14 +52,18 @@ abstract class AbstractGenerator
      *
      * @return array
      */
-    public function processRoute($route)
+    public function processRoute(Route $route, array $rulesToApply = [])
     {
         $routeAction = $route->getAction();
-        $routeGroup = $this->getRouteGroup($routeAction['uses']);
-        $docBlock = $this->parseDocBlock($routeAction['uses']);
-        $content = $this->getResponse($docBlock['tags']);
+        list($class, $method) = explode('@', $routeAction['uses']);
+        $controller = new ReflectionClass($class);
+        $method = $controller->getMethod($method);
 
-        return [
+        $routeGroup = $this->getRouteGroup($controller, $method);
+        $docBlock = $this->parseDocBlock($method);
+        $content = ResponseResolver::getResponse($route, $docBlock['tags'], $rulesToApply);
+
+        $parsedRoute = [
             'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))),
             'group' => $routeGroup,
             'title' => $docBlock['short'],
@@ -69,6 +75,9 @@ abstract class AbstractGenerator
             'response' => $content,
             'showresponse' => ! empty($content),
         ];
+        $parsedRoute['headers'] = $rulesToApply['headers'] ?? [];
+
+        return $parsedRoute;
     }
 
     /**
@@ -80,26 +89,6 @@ abstract class AbstractGenerator
      */
     abstract public function prepareMiddleware($enable = false);
 
-    /**
-     * Get the response from the docblock if available.
-     *
-     * @param array $tags
-     *
-     * @return mixed
-     */
-    protected function getDocblockResponse($tags)
-    {
-        $responseTags = array_filter($tags, function ($tag) {
-            return $tag instanceof Tag && \strtolower($tag->getName()) == 'response';
-        });
-        if (empty($responseTags)) {
-            return;
-        }
-        $responseTag = \array_first($responseTags);
-
-        return \response(json_encode($responseTag->getContent()), 200, ['Content-Type' => 'application/json']);
-    }
-
     /**
      * @param array $tags
      *
@@ -198,17 +187,13 @@ abstract class AbstractGenerator
     }
 
     /**
-     * @param  \Illuminate\Routing\Route  $routeAction
+     * @param ReflectionMethod $method
      *
      * @return array
      */
-    protected function parseDocBlock(string $routeAction)
+    protected function parseDocBlock(ReflectionMethod $method)
     {
-        list($class, $method) = explode('@', $routeAction);
-        $reflection = new ReflectionClass($class);
-        $reflectionMethod = $reflection->getMethod($method);
-
-        $comment = $reflectionMethod->getDocComment();
+        $comment = $method->getDocComment();
         $phpdoc = new DocBlock($comment);
 
         return [
@@ -219,17 +204,15 @@ abstract class AbstractGenerator
     }
 
     /**
-     * @param  string  $routeAction
+     * @param ReflectionClass $controller
+     * @param ReflectionMethod $method
      *
      * @return string
+     *
      */
-    protected function getRouteGroup(string $routeAction)
+    protected function getRouteGroup(ReflectionClass $controller, ReflectionMethod $method)
     {
-        list($class, $method) = explode('@', $routeAction);
-        $controller = new ReflectionClass($class);
-
         // @group tag on the method overrides that on the controller
-        $method = $controller->getMethod($method);
         $docBlockComment = $method->getDocComment();
         if ($docBlockComment) {
             $phpdoc = new DocBlock($docBlockComment);
@@ -293,134 +276,6 @@ abstract class AbstractGenerator
         return $server;
     }
 
-    /**
-     * @param $response
-     *
-     * @return mixed
-     */
-    private function getResponseContent($response)
-    {
-        if (empty($response)) {
-            return '';
-        }
-        if ($response->headers->get('Content-Type') === 'application/json') {
-            $content = json_decode($response->getContent(), JSON_PRETTY_PRINT);
-        } else {
-            $content = $response->getContent();
-        }
-
-        return $content;
-    }
-
-    /**
-     * Get a response from the transformer tags.
-     *
-     * @param array $tags
-     *
-     * @return mixed
-     */
-    protected function getTransformerResponse($tags)
-    {
-        try {
-            $transFormerTags = array_filter($tags, function ($tag) {
-                if (! ($tag instanceof Tag)) {
-                    return false;
-                }
-
-                return \in_array(\strtolower($tag->getName()), ['transformer', 'transformercollection']);
-            });
-            if (empty($transFormerTags)) {
-                // we didn't have any of the tags so goodbye
-                return false;
-            }
-
-            $modelTag = array_first(array_filter($tags, function ($tag) {
-                if (! ($tag instanceof Tag)) {
-                    return false;
-                }
-
-                return \in_array(\strtolower($tag->getName()), ['transformermodel']);
-            }));
-            $tag = \array_first($transFormerTags);
-            $transformer = $tag->getContent();
-            if (! \class_exists($transformer)) {
-                // if we can't find the transformer we can't generate a response
-                return;
-            }
-            $demoData = [];
-
-            $reflection = new ReflectionClass($transformer);
-            $method = $reflection->getMethod('transform');
-            $parameter = \array_first($method->getParameters());
-            $type = null;
-            if ($modelTag) {
-                $type = $modelTag->getContent();
-            }
-            if (\is_null($type)) {
-                if ($parameter->hasType() &&
-                    ! $parameter->getType()->isBuiltin() &&
-                    \class_exists((string) $parameter->getType())) {
-                    //we have a type
-                    $type = (string) $parameter->getType();
-                }
-            }
-            if ($type) {
-                // we have a class so we try to create an instance
-                $demoData = new $type;
-                try {
-                    // try a factory
-                    $demoData = \factory($type)->make();
-                } catch (\Exception $e) {
-                    if ($demoData instanceof \Illuminate\Database\Eloquent\Model) {
-                        // we can't use a factory but can try to get one from the database
-                        try {
-                            // check if we can find one
-                            $newDemoData = $type::first();
-                            if ($newDemoData) {
-                                $demoData = $newDemoData;
-                            }
-                        } catch (\Exception $e) {
-                            // do nothing
-                        }
-                    }
-                }
-            }
-
-            $fractal = new Manager();
-            $resource = [];
-            if ($tag->getName() == 'transformer') {
-                // just one
-                $resource = new Item($demoData, new $transformer);
-            }
-            if ($tag->getName() == 'transformercollection') {
-                // a collection
-                $resource = new Collection([$demoData, $demoData], new $transformer);
-            }
-
-            return \response($fractal->createData($resource)->toJson());
-        } catch (\Exception $e) {
-            // it isn't possible to parse the transformer
-            return;
-        }
-    }
-
-    private function getResponse(array $annotationTags)
-    {
-        $response = null;
-        if ($docblockResponse = $this->getDocblockResponse($annotationTags)) {
-            // we have a response from the docblock ( @response )
-            $response = $docblockResponse;
-        }
-        if (! $response && ($transformerResponse = $this->getTransformerResponse($annotationTags))) {
-            // we have a transformer response from the docblock ( @transformer || @transformercollection )
-            $response = $transformerResponse;
-        }
-
-        $content = $response ? $this->getResponseContent($response) : null;
-
-        return $content;
-    }
-
     private function normalizeParameterType($type)
     {
         $typeMap = [

+ 60 - 0
src/Tools/ResponseResolver.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tools;
+
+use Illuminate\Routing\Route;
+
+class ResponseResolver
+{
+    public static $strategies = [
+        ResponseTagStrategy::class,
+        TransformerTagsStrategy::class,
+   //     ResponseCallStrategy::class,
+    ];
+
+    /**
+     * @var Route
+     */
+    private $route;
+
+    public function __construct(Route $route)
+    {
+        $this->route = $route;
+    }
+
+    private function resolve(array $tags, array $rulesToApply)
+    {
+        $response = null;
+        foreach (static::$strategies as $strategy) {
+            $strategy = new $strategy();
+            $response = $strategy($this->route, $tags, $rulesToApply);
+            if (!is_null($response)) {
+                return $this->getResponseContent($response);
+            }
+        }
+    }
+
+    public static function getResponse($route, $tags, $rulesToApply)
+    {
+        return (new static($route))->resolve($tags, $rulesToApply);
+    }
+
+    /**
+     * @param $response
+     *
+     * @return mixed
+     */
+    private function getResponseContent($response)
+    {
+        if (empty($response)) {
+            return '';
+        }
+        if ($response->headers->get('Content-Type') === 'application/json') {
+            $content = json_decode($response->getContent(), JSON_PRETTY_PRINT);
+        } else {
+            $content = $response->getContent();
+        }
+
+        return $content;
+    }
+}

+ 44 - 0
src/Tools/ResponseTagStrategy.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Created by shalvah
+ * Date: 15-Oct-18
+ * Time: 15:07
+ */
+
+namespace Mpociot\ApiDoc\Tools;
+
+
+use Illuminate\Routing\Route;
+use Mpociot\Reflection\DocBlock\Tag;
+
+/**
+ * Get a response from the docblock ( @response ).
+ */
+class ResponseTagStrategy
+{
+    public function __invoke(Route $route, array $tags, array $rulesToApply)
+    {
+        return $this->getDocBlockResponse($tags);
+    }
+
+    /**
+     * Get the response from the docblock if available.
+     *
+     * @param array $tags
+     *
+     * @return mixed
+     */
+    protected function getDocBlockResponse(array $tags)
+    {
+        $responseTags = array_filter($tags, function ($tag) {
+            return $tag instanceof Tag && \strtolower($tag->getName()) == 'response';
+        });
+        if (empty($responseTags)) {
+            return;
+        }
+        $responseTag = \array_first($responseTags);
+
+        return \response()->json($responseTag->getContent());
+    }
+
+}

+ 136 - 0
src/Tools/TransformerTagsStrategy.php

@@ -0,0 +1,136 @@
+<?php
+/**
+ * Created by shalvah
+ * Date: 15-Oct-18
+ * Time: 15:07
+ */
+
+namespace Mpociot\ApiDoc\Tools;
+
+
+use ReflectionClass;
+use ReflectionMethod;
+use League\Fractal\Manager;
+use Illuminate\Routing\Route;
+use League\Fractal\Resource\Item;
+use Mpociot\Reflection\DocBlock\Tag;
+use League\Fractal\Resource\Collection;
+
+/**
+ * Parse a transformer response from the docblock ( @transformer || @transformercollection ).
+ */
+class TransformerTagsStrategy
+{
+    public function __invoke(Route $route, array $tags, array $rulesToApply)
+    {
+        return $this->getTransformerResponse($tags);
+    }
+
+    /**
+     * Get a response from the transformer tags.
+     *
+     * @param array $tags
+     *
+     * @return mixed
+     */
+    protected function getTransformerResponse(array $tags)
+    {
+        try {
+            if(empty($transformerTag = $this->getTransformerTag($tags))) {
+                return;
+            }
+
+            $transformer = $this->getTransformerClass($transformerTag);
+            $model = $this->getClassToBeTransformed($tags, (new ReflectionClass($transformer))->getMethod('transform'));
+            $modelInstance = $this->instantiateTransformerModel($model);
+
+            $fractal = new Manager();
+            $resource = (strtolower($transformerTag->getName()) == 'transformercollection')
+                ? new Collection([$modelInstance, $modelInstance], new $transformer)
+                : new Item($modelInstance, new $transformer);
+
+            return response($fractal->createData($resource)->toJson());
+        } catch (\Exception $e) {
+
+            return;
+        }
+    }
+
+    /**
+     * @param Tag $tag
+     *
+     * @return string|null
+     */
+    private function getTransformerClass($tag)
+    {
+        return $tag->getContent();
+    }
+
+    /**
+     * @param array $tags
+     * @param ReflectionMethod $transformerMethod
+     *
+     * @return null|string
+     */
+    private function getClassToBeTransformed(array $tags, ReflectionMethod $transformerMethod)
+    {
+        $modelTag = array_first(array_filter($tags, function ($tag) {
+            return ($tag instanceof Tag) && strtolower($tag->getName()) == 'transformermodel';
+        }));
+
+        $type = null;
+        if ($modelTag) {
+            $type = $modelTag->getContent();
+        } else {
+            $parameter = array_first($transformerMethod->getParameters());
+            if ($parameter->hasType() && ! $parameter->getType()->isBuiltin() && class_exists((string) $parameter->getType())) {
+                // ladies and gentlemen, we have a type!
+                $type = (string) $parameter->getType();
+            }
+        }
+        return $type;
+
+    }
+
+    /**
+     * @param string $type
+     *
+     * @return mixed
+     */
+    protected function instantiateTransformerModel(string $type)
+    {
+        // our fallback
+        $modelInstance = new $type;
+
+        try {
+            // try Eloquent model factory
+            $modelInstance = factory($type)->make();
+        } catch (\Exception $e) {
+            if ($modelInstance instanceof \Illuminate\Database\Eloquent\Model) {
+                try {
+                    // we can't use a factory but can try to get one from the database
+                    $newDemoData = $type::first();
+                    if ($newDemoData) {
+                        $modelInstance = $newDemoData;
+                    }
+                } catch (\Exception $e) {
+                    // okay, we'll stick with `new`
+                }
+            }
+        }
+        return $modelInstance;
+    }
+
+    /**
+     * @param array $tags
+     *
+     * @return Tag|null
+     */
+    private function getTransformerTag(array $tags)
+    {
+        $transFormerTags = array_filter($tags, function ($tag) {
+            return ($tag instanceof Tag) && in_array(strtolower($tag->getName()), ['transformer', 'transformercollection']);
+        });
+        return array_first($transFormerTags);
+    }
+}