Ver Fonte

Adddocblockhandling (#192)

* Add the handling for a @transformer @transformercollection or @result tag in the method docblock

* Update the readme to document the @transform, @transformercollection and @response tag

* Fix the styleci issues

* Apply patch form styleci

* Add a transformermodel tag and support PHP 5

* Fix the version without a modeltag

* Always use the transformermodel tag and a codestyle issue
Tobias van Beek há 8 anos atrás
pai
commit
6b1c502758

+ 52 - 0
README.md

@@ -130,6 +130,58 @@ public function rules()
 
 **Result:** ![Form Request](http://marcelpociot.de/documentarian/form_request.png)
 
+#### Controller method doc block
+It is possible to override the results for the response. This will also show the responses for other request methods then GET.
+
+#### @transformer
+With the transformer you can define the transformer that is used for the result of the method. It will try the next parts to get a result if it can find the transformer. The first successfull will be used.
+
+1. Check if there is a transformermodel tag to define the model
+2. Get a model from the modelfactory
+2. If the parameter is a Eloquent model it will load the first from the database.
+3. A new instance from the class
+
+```php
+/**
+ * @transformer \Mpociot\ApiDoc\Tests\Fixtures\TestTransformer
+ */
+public function transformerTag()
+{
+    return '';
+}
+```
+
+#### @transformercollection
+This is the same idea as the @tranformer tag with one different, instead of the return of an item, it will generate the return of a set with two items
+
+```php
+/**
+ * @transformercollection \Mpociot\ApiDoc\Tests\Fixtures\TestTransformer
+ */
+public function transformerCollectionTag()
+{
+    return '';
+}
+```
+
+#### @transformermodel
+The @transformermodel tag is needed for PHP 5.* to get the model. For PHP 7 is it optional to specify the model that is used for the transformer.
+
+#### @response
+If you expliciet want to specify the result of a function you can set it in the docblock
+
+```php
+/**
+ * @response {
+ *  data: [],
+ *}
+ */
+public function responseTag()
+{
+    return '';
+}
+```
+
 #### API responses
 
 If your API route accepts a `GET` method, this package tries to call the API route with all middleware disabled to fetch an example API response. 

+ 26 - 0
src/Mpociot/ApiDoc/Generators/AbstractGenerator.php

@@ -7,6 +7,7 @@ use ReflectionClass;
 use Illuminate\Support\Arr;
 use Illuminate\Support\Str;
 use Mpociot\Reflection\DocBlock;
+use Mpociot\Reflection\DocBlock\Tag;
 use Illuminate\Support\Facades\Validator;
 use Illuminate\Foundation\Http\FormRequest;
 use Mpociot\ApiDoc\Parsers\RuleDescriptionParser as Description;
@@ -45,6 +46,30 @@ abstract class AbstractGenerator
      */
     abstract public function prepareMiddleware($disable = false);
 
+    /**
+     * Get the response from the docblock if available.
+     *
+     * @param array $tags
+     *
+     * @return mixed
+     */
+    protected function getDocblockResponse($tags)
+    {
+        $responseTags = array_filter($tags, function ($tag) {
+            if (! ($tag instanceof Tag)) {
+                return false;
+            }
+
+            return \strtolower($tag->getName()) == 'response';
+        });
+        if (empty($responseTags)) {
+            return;
+        }
+        $responseTag = \array_first($responseTags);
+
+        return \response(\json_encode($responseTag->getContent()));
+    }
+
     /**
      * @param array $routeData
      * @param array $routeAction
@@ -131,6 +156,7 @@ abstract class AbstractGenerator
         return [
             'short' => $phpdoc->getShortDescription(),
             'long' => $phpdoc->getLongDescription()->getContents(),
+            'tags' => $phpdoc->getTags(),
         ];
     }
 

+ 117 - 1
src/Mpociot/ApiDoc/Generators/LaravelGenerator.php

@@ -3,9 +3,13 @@
 namespace Mpociot\ApiDoc\Generators;
 
 use ReflectionClass;
+use League\Fractal\Manager;
 use Illuminate\Routing\Route;
+use League\Fractal\Resource\Item;
 use Illuminate\Support\Facades\App;
+use Mpociot\Reflection\DocBlock\Tag;
 use Illuminate\Support\Facades\Request;
+use League\Fractal\Resource\Collection;
 use Illuminate\Foundation\Http\FormRequest;
 
 class LaravelGenerator extends AbstractGenerator
@@ -53,9 +57,27 @@ class LaravelGenerator extends AbstractGenerator
         $routeAction = $route->getAction();
         $routeGroup = $this->getRouteGroup($routeAction['uses']);
         $routeDescription = $this->getRouteDescription($routeAction['uses']);
+        $showresponse = null;
 
         if ($withResponse) {
-            $response = $this->getRouteResponse($route, $bindings, $headers);
+            $response = null;
+            $docblockResponse = $this->getDocblockResponse($routeDescription['tags']);
+            if ($docblockResponse) {
+                // we have a response from the docblock ( @response )
+                $response = $docblockResponse;
+                $showresponse = true;
+            }
+            if (! $response) {
+                $transformerResponse = $this->getTransformerResponse($routeDescription['tags']);
+                if ($transformerResponse) {
+                    // we have a transformer response from the docblock ( @transformer || @transformercollection )
+                    $response = $transformerResponse;
+                    $showresponse = true;
+                }
+            }
+            if (! $response) {
+                $response = $this->getRouteResponse($route, $bindings, $headers);
+            }
             if ($response->headers->get('Content-Type') === 'application/json') {
                 $content = json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT);
             } else {
@@ -72,6 +94,7 @@ class LaravelGenerator extends AbstractGenerator
             'uri' => $this->getUri($route),
             'parameters' => [],
             'response' => $content,
+            'showresponse' => $showresponse,
         ], $routeAction, $bindings);
     }
 
@@ -125,6 +148,99 @@ class LaravelGenerator extends AbstractGenerator
         return $response;
     }
 
+    /**
+     * 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 (version_compare(PHP_VERSION, '7.0.0') >= 0 && \is_null($type)) {
+                // we can only get the type with reflection for PHP 7
+                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;
+        }
+    }
+
     /**
      * @param  string $route
      * @param  array $bindings

+ 1 - 1
src/resources/views/partials/route.blade.php

@@ -38,7 +38,7 @@ $.ajax(settings).done(function (response) {
 });
 ```
 
-@if(in_array('GET',$parsedRoute['methods']))
+@if(in_array('GET',$parsedRoute['methods']) || isset($parsedRoute['showresponse']) && $parsedRoute['showresponse'])
 > Example response:
 
 ```json

+ 75 - 0
tests/ApiDocGeneratorTest.php

@@ -336,4 +336,79 @@ class ApiDocGeneratorTest extends TestCase
             }
         }
     }
+
+    public function testCanParseResponseTag()
+    {
+        RouteFacade::post('/responseTag', TestController::class.'@responseTag');
+        $route = new Route(['GET'], '/responseTag', ['uses' => TestController::class.'@responseTag']);
+        $parsed = $this->generator->processRoute($route);
+        $this->assertTrue(is_array($parsed));
+        $this->assertArrayHasKey('showresponse', $parsed);
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertSame($parsed['response'], '"{\n data: [],\n}"');
+    }
+
+    public function testCanParseTransformerTag()
+    {
+        if (version_compare(PHP_VERSION, '7.0.0', '<')) {
+            $this->markTestSkipped('The transformer tag without model need PHP 7');
+        }
+        RouteFacade::post('/transformerTag', TestController::class.'@transformerTag');
+        $route = new Route(['GET'], '/transformerTag', ['uses' => TestController::class.'@transformerTag']);
+        $parsed = $this->generator->processRoute($route);
+        $this->assertTrue(is_array($parsed));
+        $this->assertArrayHasKey('showresponse', $parsed);
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertSame(
+            $parsed['response'],
+            '{"data":{"id":1,"description":"Welcome on this test versions","name":"TestName"}}'
+        );
+    }
+
+    public function testCanParseTransformerTagWithModel()
+    {
+        RouteFacade::post('/transformerTagWithModel', TestController::class.'@transformerTagWithModel');
+        $route = new Route(['GET'], '/transformerTagWithModel', ['uses' => TestController::class.'@transformerTagWithModel']);
+        $parsed = $this->generator->processRoute($route);
+        $this->assertTrue(is_array($parsed));
+        $this->assertArrayHasKey('showresponse', $parsed);
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertSame(
+            $parsed['response'],
+            '{"data":{"id":1,"description":"Welcome on this test versions","name":"TestName"}}'
+        );
+    }
+
+    public function testCanParseTransformerCollectionTag()
+    {
+        if (version_compare(PHP_VERSION, '7.0.0', '<')) {
+            $this->markTestSkipped('The transformer tag without model need PHP 7');
+        }
+        RouteFacade::post('/transformerCollectionTag', TestController::class.'@transformerCollectionTag');
+        $route = new Route(['GET'], '/transformerCollectionTag', ['uses' => TestController::class.'@transformerCollectionTag']);
+        $parsed = $this->generator->processRoute($route);
+        $this->assertTrue(is_array($parsed));
+        $this->assertArrayHasKey('showresponse', $parsed);
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertSame(
+            $parsed['response'],
+            '{"data":[{"id":1,"description":"Welcome on this test versions","name":"TestName"},'.
+            '{"id":1,"description":"Welcome on this test versions","name":"TestName"}]}'
+        );
+    }
+
+    public function testCanParseTransformerCollectionTagWithModel()
+    {
+        RouteFacade::post('/transformerCollectionTagWithModel', TestController::class.'@transformerCollectionTagWithModel');
+        $route = new Route(['GET'], '/transformerCollectionTagWithModel', ['uses' => TestController::class.'@transformerCollectionTagWithModel']);
+        $parsed = $this->generator->processRoute($route);
+        $this->assertTrue(is_array($parsed));
+        $this->assertArrayHasKey('showresponse', $parsed);
+        $this->assertTrue($parsed['showresponse']);
+        $this->assertSame(
+            $parsed['response'],
+            '{"data":[{"id":1,"description":"Welcome on this test versions","name":"TestName"},'.
+            '{"id":1,"description":"Welcome on this test versions","name":"TestName"}]}'
+        );
+    }
 }

+ 44 - 0
tests/Fixtures/TestController.php

@@ -71,4 +71,48 @@ class TestController extends Controller
     public function skip()
     {
     }
+
+    /**
+     * @response {
+     *  data: [],
+     *}
+     */
+    public function responseTag()
+    {
+        return '';
+    }
+
+    /**
+     * @transformer \Mpociot\ApiDoc\Tests\Fixtures\TestTransformer
+     */
+    public function transformerTag()
+    {
+        return '';
+    }
+
+    /**
+     * @transformer \Mpociot\ApiDoc\Tests\Fixtures\TestTransformer
+     * @transformermodel \Mpociot\ApiDoc\Tests\Fixtures\TestModel
+     */
+    public function transformerTagWithModel()
+    {
+        return '';
+    }
+
+    /**
+     * @transformercollection \Mpociot\ApiDoc\Tests\Fixtures\TestTransformer
+     */
+    public function transformerCollectionTag()
+    {
+        return '';
+    }
+
+    /**
+     * @transformercollection \Mpociot\ApiDoc\Tests\Fixtures\TestTransformer
+     * @transformermodel \Mpociot\ApiDoc\Tests\Fixtures\TestModel
+     */
+    public function transformerCollectionTagWithModel()
+    {
+        return '';
+    }
 }

+ 17 - 0
tests/Fixtures/TestModel.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tests\Fixtures;
+
+/**
+ * A demo test model.
+ *
+ * @author Tobias van Beek <t.vanbeek@tjvb.nl>
+ */
+class TestModel
+{
+    public $id = 1;
+
+    public $name = 'TestName';
+
+    public $description = 'Welcome on this test versions';
+}

+ 27 - 0
tests/Fixtures/TestTransformer.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Mpociot\ApiDoc\Tests\Fixtures;
+
+use League\Fractal\TransformerAbstract;
+
+/**
+ * A test transformer to show the functions.
+ *
+ * @author Tobias van Beek <t.vanbeek@tjvb.nl>
+ */
+class TestTransformer extends TransformerAbstract
+{
+    /**
+     * A Fractal transformer.
+     *
+     * @return array
+     */
+    public function transform(TestModel $model)
+    {
+        return [
+            'id' => $model->id,
+            'description' => $model->description,
+            'name' => $model->name,
+        ];
+    }
+}