Browse Source

Added Dingo support
Added support for route model bindings
Added grouping of controllers

Marcel Pociot 9 năm trước cách đây
mục cha
commit
e819439ba9

+ 109 - 27
src/Mpociot/ApiDoc/Commands/GenerateDocumentation.php

@@ -3,8 +3,11 @@
 namespace Mpociot\ApiDoc\Commands;
 
 use Illuminate\Console\Command;
-use Mpociot\ApiDoc\ApiDocGenerator;
+use Illuminate\Support\Collection;
 use Illuminate\Support\Facades\Route;
+use Mpociot\ApiDoc\Generators\AbstractGenerator;
+use Mpociot\ApiDoc\Generators\DingoGenerator;
+use Mpociot\ApiDoc\Generators\LaravelGenerator;
 use Mpociot\Documentarian\Documentarian;
 
 class GenerateDocumentation extends Command
@@ -19,6 +22,8 @@ class GenerateDocumentation extends Command
                             {--routePrefix= : The route prefix to use for generation}
                             {--routes=* : The route names to use for generation}
                             {--actAsUserId= : The user ID to use for API response calls}
+                            {--router=laravel : The router to be used (Laravel or Dingo)}
+                            {--bindings= : Route Model Bindings}
     ';
 
     /**
@@ -45,9 +50,16 @@ class GenerateDocumentation extends Command
      */
     public function handle()
     {
+        if ($this->option('router') === 'laravel') {
+            $generator = new LaravelGenerator();
+        } else {
+            $generator = new DingoGenerator();
+        }
+
         $allowedRoutes = $this->option('routes');
         $routePrefix = $this->option('routePrefix');
-        $actAs = $this->option('actAsUserId');
+
+        $this->setUserToBeImpersonated($this->option('actAsUserId'));
 
         if ($routePrefix === null && ! count($allowedRoutes)) {
             $this->error('You must provide either a route prefix or a route to generate the documentation.');
@@ -55,36 +67,18 @@ class GenerateDocumentation extends Command
             return false;
         }
 
-        if ($actAs !== null) {
-            if (version_compare($this->laravel->version(), '5.2.0', '<')) {
-                $userModel = config('auth.model');
-                $user = $userModel::find($actAs);
-                $this->laravel['auth']->setUser($user);
-            } else {
-                $userModel = config('auth.providers.users.model');
-                $user = $userModel::find($actAs);
-                $this->laravel['auth']->guard()->setUser($user);
-            }
-        }
-
-        $routes = Route::getRoutes();
-
-        $generator = new ApiDocGenerator();
-
-        /* @var \Illuminate\Routing\Route $route */
-        $parsedRoutes = [];
-        foreach ($routes as $route) {
-            if (in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $route->getUri())) {
-                $parsedRoutes[] = $generator->processRoute($route);
-                $this->info('Processed route: '.$route->getUri());
-            }
+        if ($this->option('router') === 'laravel') {
+            $parsedRoutes = $this->processLaravelRoutes($generator, $allowedRoutes, $routePrefix);
+        } else {
+            $parsedRoutes = $this->processDingoRoutes($generator, $allowedRoutes, $routePrefix);
         }
+        $parsedRoutes = collect($parsedRoutes)->sortBy('resource')->groupBy('resource');
 
         $this->writeMarkdown($parsedRoutes);
     }
 
     /**
-     * @param  array  $parsedRoutes
+     * @param  Collection $parsedRoutes
      *
      * @return void
      */
@@ -94,7 +88,7 @@ class GenerateDocumentation extends Command
 
         $documentarian = new Documentarian();
 
-        $markdown = view('apidoc::documentarian')->with('parsedRoutes', $parsedRoutes);
+        $markdown = view('apidoc::documentarian')->with('parsedRoutes', $parsedRoutes->all());
 
         if (! is_dir($outputPath)) {
             $documentarian->create($outputPath);
@@ -110,4 +104,92 @@ class GenerateDocumentation extends Command
 
         $this->info('Wrote HTML documentation to: '.$outputPath.'/public/index.html');
     }
+
+    /**
+     * @return array
+     */
+    private function getBindings()
+    {
+        $bindings = $this->option('bindings');
+        if (empty($bindings)) {
+            return [];
+        }
+        $bindings = explode('|', $bindings);
+        $resultBindings = [];
+        foreach ($bindings as $binding) {
+            list($name, $id) = explode(',', $binding);
+            $resultBindings[$name] = $id;
+        }
+        return $resultBindings;
+    }
+
+    /**
+     * @param $actAs
+     */
+    private function setUserToBeImpersonated($actAs)
+    {
+        if (!empty($actAs)) {
+            if (version_compare($this->laravel->version(), '5.2.0', '<')) {
+                $userModel = config('auth.model');
+                $user = $userModel::find($actAs);
+                $this->laravel['auth']->setUser($user);
+            } else {
+                $userModel = config('auth.providers.users.model');
+                $user = $userModel::find($actAs);
+                $this->laravel['auth']->guard()->setUser($user);
+            }
+        }
+    }
+
+    /**
+     * @return mixed
+     */
+    private function getRoutes()
+    {
+        if ($this->option('router') === 'laravel') {
+            return Route::getRoutes();
+        } else {
+            return app('Dingo\Api\Routing\Router')->getRoutes()[$this->option('routePrefix')];
+        }
+    }
+
+    /**
+     * @param AbstractGenerator  $generator
+     * @param $allowedRoutes
+     * @param $routePrefix
+     * @return array
+     */
+    private function processLaravelRoutes(AbstractGenerator $generator, $allowedRoutes, $routePrefix)
+    {
+        $routes = $this->getRoutes();
+        $bindings = $this->getBindings();
+        $parsedRoutes = [];
+        foreach ($routes as $route) {
+            if (in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $route->getUri())) {
+                $parsedRoutes[] = $generator->processRoute($route, $bindings);
+                $this->info('Processed route: '.$route->getUri());
+            }
+        }
+        return $parsedRoutes;
+    }
+
+    /**
+     * @param AbstractGenerator $generator
+     * @param $allowedRoutes
+     * @param $routePrefix
+     * @return array
+     */
+    private function processDingoRoutes(AbstractGenerator $generator, $allowedRoutes, $routePrefix)
+    {
+        $routes = $this->getRoutes();
+        $bindings = $this->getBindings();
+        $parsedRoutes = [];
+        foreach ($routes as $route) {
+            if (empty($allowedRoutes) || in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $route->uri())) {
+                $parsedRoutes[] = $generator->processRoute($route, $bindings);
+                $this->info('Processed route: '.$route->uri());
+            }
+        }
+        return $parsedRoutes;
+    }
 }

+ 61 - 49
src/Mpociot/ApiDoc/ApiDocGenerator.php → src/Mpociot/ApiDoc/Generators/AbstractGenerator.php

@@ -1,43 +1,38 @@
 <?php
 
-namespace Mpociot\ApiDoc;
+namespace Mpociot\ApiDoc\Generators;
 
 use Faker\Factory;
 use ReflectionClass;
 use Illuminate\Support\Str;
 use Illuminate\Routing\Route;
-use Illuminate\Support\Facades\App;
 use phpDocumentor\Reflection\DocBlock;
-use Illuminate\Support\Facades\Request;
 use Illuminate\Support\Facades\Validator;
 use Illuminate\Foundation\Http\FormRequest;
 
-class ApiDocGenerator
+abstract class AbstractGenerator
 {
+
     /**
-     * @param  \Illuminate\Routing\Route  $route
+     * @param Route $route
+     * @return mixed
+     */
+    abstract protected function getUri(Route $route);
+
+    /**
+     * @param  \Illuminate\Routing\Route $route
+     * @param array $bindings
      *
      * @return array
      */
-    public function processRoute(Route $route)
-    {
-        $routeAction = $route->getAction();
-        $response = $this->getRouteResponse($route);
-        $routeDescription = $this->getRouteDescription($routeAction['uses']);
-        if ($response->headers->get('Content-Type') === 'application/json') {
-            $content = json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT);
-        } else {
-            $content = $response->getContent();
-        }
-        $routeData = [
-            'title' => $routeDescription['short'],
-            'description' => $routeDescription['long'],
-            'methods' => $route->getMethods(),
-            'uri' => $route->getUri(),
-            'parameters' => [],
-            'response' => $content,
-        ];
+    abstract public function processRoute(Route $route, $bindings = []);
 
+    /**
+     * @param array $routeData
+     * @param array $routeAction
+     * @return mixed
+     */
+    protected function getParameters($routeData, $routeAction) {
         $validator = Validator::make([], $this->getRouteRules($routeAction['uses']));
         foreach ($validator->getRules() as $attribute => $rules) {
             $attributeData = [
@@ -61,11 +56,28 @@ class ApiDocGenerator
      *
      * @return \Illuminate\Http\Response
      */
-    private function getRouteResponse(Route $route)
+    protected function getRouteResponse(Route $route, $bindings)
     {
+        $uri = $this->addRouteModelBindings($route, $bindings);
+
         $methods = $route->getMethods();
 
-        return $this->callRoute(array_shift($methods), $route->getUri());
+        return $this->callRoute(array_shift($methods), $uri);
+    }
+
+
+    /**
+     * @param Route $route
+     * @param array $bindings
+     * @return mixed
+     */
+    protected function addRouteModelBindings(Route $route, $bindings)
+    {
+        $uri = $this->getUri($route);
+        foreach ($bindings as $model => $id) {
+            $uri = str_replace('{'.$model.'}', $id, $uri);
+        }
+        return $uri;
     }
 
     /**
@@ -73,7 +85,7 @@ class ApiDocGenerator
      *
      * @return string
      */
-    private function getRouteDescription($route)
+    protected function getRouteDescription($route)
     {
         list($class, $method) = explode('@', $route);
         $reflection = new ReflectionClass($class);
@@ -88,12 +100,32 @@ class ApiDocGenerator
         ];
     }
 
+
     /**
-     * @param  \Illuminate\Routing\Route  $route
+     * @param  string  $route
+     *
+     * @return string
+     */
+    protected function getRouteGroup($route)
+    {
+        list($class, $method) = explode('@', $route);
+        $reflection = new ReflectionClass($class);
+        $comment = $reflection->getDocComment();
+        $phpdoc = new DocBlock($comment);
+        foreach ($phpdoc->getTags() as $tag) {
+            if ($tag->getName() === 'resource') {
+                return $tag->getContent();
+            }
+        }
+        return 'general';
+    }
+
+    /**
+     * @param  $route
      *
      * @return array
      */
-    private function getRouteRules($route)
+    protected function getRouteRules($route)
     {
         list($class, $method) = explode('@', $route);
         $reflection = new ReflectionClass($class);
@@ -320,27 +352,7 @@ class ApiDocGenerator
      *
      * @return \Illuminate\Http\Response
      */
-    public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
-    {
-        $kernel = App::make('Illuminate\Contracts\Http\Kernel');
-        App::instance('middleware.disable', true);
-
-        $server = [
-            'CONTENT_TYPE' => 'application/json',
-            'Accept' => 'application/json',
-        ];
-
-        $request = Request::create(
-            $uri, $method, $parameters,
-            $cookies, $files, $this->transformHeadersToServerVars($server), $content
-        );
-
-        $response = $kernel->handle($request);
-
-        $kernel->terminate($request, $response);
-
-        return $response;
-    }
+    abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null);
 
     /**
      * Transform headers array to array of $_SERVER vars with HTTP_* format.

+ 60 - 0
src/Mpociot/ApiDoc/Generators/DingoGenerator.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Mpociot\ApiDoc\Generators;
+
+use Illuminate\Routing\Route;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\Request;
+
+class DingoGenerator extends AbstractGenerator
+{
+
+    /**
+     * @param  \Illuminate\Routing\Route $route
+     * @param array $bindings
+     *
+     * @return array
+     */
+    public function processRoute(Route $route, $bindings = [])
+    {
+        $response = $this->getRouteResponse($route, $bindings);
+
+        $routeAction = $route->getAction();
+        $routeGroup = $this->getRouteGroup($routeAction['uses']);
+        $routeDescription = $this->getRouteDescription($routeAction['uses']);
+        
+        if ($response->headers->get('Content-Type') === 'application/json') {
+            $content = json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT);
+        } else {
+            $content = $response->getContent();
+        }
+
+        return $this->getParameters([
+            'resource'    => $routeGroup,
+            'title' => $routeDescription['short'],
+            'description' => $routeDescription['long'],
+            'methods' => $route->getMethods(),
+            'uri' => $route->getUri(),
+            'parameters' => [],
+            'response' => $content,
+        ], $routeAction);
+    }
+
+
+    /**
+     * {@inheritdoc}
+     */
+    public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
+    {
+        return call_user_func_array([app('Dingo\Api\Dispatcher'), strtolower($method)], [$uri]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getUri(Route $route)
+    {
+        return $route->uri();
+    }
+    
+}

+ 87 - 0
src/Mpociot/ApiDoc/Generators/LaravelGenerator.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace Mpociot\ApiDoc\Generators;
+
+use Illuminate\Routing\Route;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\Request;
+
+class LaravelGenerator extends AbstractGenerator
+{
+
+    /**
+     * @param Route $route
+     * @return mixed
+     */
+    protected function getUri(Route $route)
+    {
+        return $route->getUri();
+    }
+
+    /**
+     * @param  \Illuminate\Routing\Route $route
+     * @param array $bindings
+     *
+     * @return array
+     */
+    public function processRoute(Route $route, $bindings = [])
+    {
+        $response = $this->getRouteResponse($route, $bindings);
+        
+        $routeAction = $route->getAction();
+        $routeGroup = $this->getRouteGroup($routeAction['uses']);
+        $routeDescription = $this->getRouteDescription($routeAction['uses']);
+        
+        if ($response->headers->get('Content-Type') === 'application/json') {
+            $content = json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT);
+        } else {
+            $content = $response->getContent();
+        }
+
+        return $this->getParameters([
+            'resource'    => $routeGroup,
+            'title' => $routeDescription['short'],
+            'description' => $routeDescription['long'],
+            'methods' => $route->getMethods(),
+            'uri' => $route->getUri(),
+            'parameters' => [],
+            'response' => $content,
+        ], $routeAction);
+    }
+
+    /**
+     * Call the given URI and return the Response.
+     *
+     * @param  string  $method
+     * @param  string  $uri
+     * @param  array  $parameters
+     * @param  array  $cookies
+     * @param  array  $files
+     * @param  array  $server
+     * @param  string  $content
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
+    {
+        $kernel = App::make('Illuminate\Contracts\Http\Kernel');
+        App::instance('middleware.disable', true);
+
+        $server = [
+            'CONTENT_TYPE' => 'application/json',
+            'Accept' => 'application/json',
+        ];
+
+        $request = Request::create(
+            $uri, $method, $parameters,
+            $cookies, $files, $this->transformHeadersToServerVars($server), $content
+        );
+
+        $response = $kernel->handle($request);
+
+        $kernel->terminate($request, $response);
+
+        return $response;
+    }
+    
+}

+ 10 - 5
src/resources/views/documentarian.blade.php

@@ -18,7 +18,11 @@ toc_footers:
 Welcome to the generated API reference.
 
 # Available routes
-@foreach($parsedRoutes as $parsedRoute)
+@foreach($parsedRoutes as $group => $routes)
+@if($group)
+#{{$group}}
+@endif
+@foreach($routes as $parsedRoute)
 @if($parsedRoute['title'] != '')## {{ $parsedRoute['title']}}
 @else## {{$parsedRoute['uri']}}
 @endif
@@ -59,11 +63,11 @@ console.log(response);
 ```
 
 @if(in_array('GET',$parsedRoute['methods']))
-    > Example response:
+> Example response:
 
-    ```json
-    {!! $parsedRoute['response'] !!}
-    ```
+```json
+{!! str_limit(json_encode(json_decode($parsedRoute['response']), JSON_PRETTY_PRINT)) !!}
+```
 @endif
 
 ### HTTP Request
@@ -82,3 +86,4 @@ Parameter | Type | Status | Description
 @endif
 
 @endforeach
+@endforeach

+ 4 - 3
tests/ApiDocGeneratorTest.php

@@ -3,8 +3,9 @@
 namespace Mpociot\ApiDoc\Tests;
 
 use Illuminate\Routing\Route;
+use Mpociot\ApiDoc\Generators\LaravelGenerator;
 use Orchestra\Testbench\TestCase;
-use Mpociot\ApiDoc\ApiDocGenerator;
+use Mpociot\ApiDoc\AbstractGenerator;
 use Mpociot\ApiDoc\Tests\Fixtures\TestRequest;
 use Mpociot\ApiDoc\Tests\Fixtures\TestController;
 use Illuminate\Support\Facades\Route as RouteFacade;
@@ -12,7 +13,7 @@ use Illuminate\Support\Facades\Route as RouteFacade;
 class ApiDocGeneratorTest extends TestCase
 {
     /**
-     * @var \Mpociot\ApiDoc\ApiDocGenerator
+     * @var \Mpociot\ApiDoc\AbstractGenerator
      */
     protected $generator;
 
@@ -23,7 +24,7 @@ class ApiDocGeneratorTest extends TestCase
     {
         parent::setUp();
 
-        $this->generator = new ApiDocGenerator();
+        $this->generator = new LaravelGenerator();
     }
 
     public function testCanParseMethodDescription()