Browse Source

NEW RouteMatcher can now be extended in user code

- Created a `RouteMatcherInterface` to describe the required API of a route matcher
- Created `Match` to provide better type saftey for the return value of the route matcher
- The `RouteMatcher` is now resolved through the application container
Guy Marriott 5 years ago
parent
commit
408d735421

+ 7 - 0
config/apidoc.php

@@ -242,4 +242,11 @@ return [
      *
      */
     'faker_seed' => null,
+
+    /**
+     * If you would like to customize how routes are matched beyond the route configuration you may
+     * declare your own implementation of RouteMatcherInterface
+     *
+     */
+    'routeMatcher' => \Mpociot\ApiDoc\Matching\RouteMatcher::class,
 ];

+ 5 - 1
src/ApiDocGeneratorServiceProvider.php

@@ -5,6 +5,8 @@ namespace Mpociot\ApiDoc;
 use Illuminate\Support\ServiceProvider;
 use Mpociot\ApiDoc\Commands\GenerateDocumentation;
 use Mpociot\ApiDoc\Commands\RebuildDocumentation;
+use Mpociot\ApiDoc\Matching\RouteMatcher;
+use Mpociot\ApiDoc\Matching\RouteMatcherInterface;
 
 class ApiDocGeneratorServiceProvider extends ServiceProvider
 {
@@ -33,6 +35,9 @@ class ApiDocGeneratorServiceProvider extends ServiceProvider
                 RebuildDocumentation::class,
             ]);
         }
+
+        // Bind the route matcher implementation
+        $this->app->bind(RouteMatcherInterface::class, $this->app['config']['apidoc']['routeMatcher']);
     }
 
     /**
@@ -42,6 +47,5 @@ class ApiDocGeneratorServiceProvider extends ServiceProvider
      */
     public function register()
     {
-        //
     }
 }

+ 14 - 7
src/Commands/GenerateDocumentation.php

@@ -7,7 +7,8 @@ use Illuminate\Routing\Route;
 use Illuminate\Support\Collection;
 use Illuminate\Support\Facades\URL;
 use Mpociot\ApiDoc\Extracting\Generator;
-use Mpociot\ApiDoc\Matching\RouteMatcher;
+use Mpociot\ApiDoc\Matching\RouteMatcher\Match;
+use Mpociot\ApiDoc\Matching\RouteMatcherInterface;
 use Mpociot\ApiDoc\Tools\DocumentationConfig;
 use Mpociot\ApiDoc\Tools\Flags;
 use Mpociot\ApiDoc\Tools\Utils;
@@ -44,8 +45,15 @@ class GenerateDocumentation extends Command
      */
     private $baseUrl;
 
-    public function __construct()
+    /**
+     * @var RouteMatcherInterface
+     */
+    private $routeMatcher;
+
+    public function __construct(RouteMatcherInterface $routeMatcher)
     {
+        $this->routeMatcher = $routeMatcher;
+
         parent::__construct();
     }
 
@@ -65,8 +73,7 @@ class GenerateDocumentation extends Command
 
         URL::forceRootUrl($this->baseUrl);
 
-        $routeMatcher = new RouteMatcher($this->docConfig->get('routes'), $this->docConfig->get('router'));
-        $routes = $routeMatcher->getRoutes();
+        $routes = $this->routeMatcher->getRoutes($this->docConfig->get('routes'), $this->docConfig->get('router'));
 
         $generator = new Generator($this->docConfig);
         $parsedRoutes = $this->processRoutes($generator, $routes);
@@ -87,7 +94,7 @@ class GenerateDocumentation extends Command
 
     /**
      * @param \Mpociot\ApiDoc\Extracting\Generator $generator
-     * @param array $routes
+     * @param Match[] $routes
      *
      * @return array
      */
@@ -95,7 +102,7 @@ class GenerateDocumentation extends Command
     {
         $parsedRoutes = [];
         foreach ($routes as $routeItem) {
-            $route = $routeItem['route'];
+            $route = $routeItem->getRoute();
             /** @var Route $route */
             $messageFormat = '%s route: [%s] %s';
             $routeMethods = implode(',', $generator->getMethods($route));
@@ -107,7 +114,7 @@ class GenerateDocumentation extends Command
             }
 
             try {
-                $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply'] ?? []);
+                $parsedRoutes[] = $generator->processRoute($route, $routeItem->getRules());
                 $this->info(sprintf($messageFormat, 'Processed', $routeMethods, $routePath));
             } catch (\Exception $exception) {
                 $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath).' - '.$exception->getMessage());

+ 8 - 26
src/Matching/RouteMatcher.php

@@ -6,35 +6,21 @@ use Dingo\Api\Routing\RouteCollection;
 use Illuminate\Routing\Route;
 use Illuminate\Support\Facades\Route as RouteFacade;
 use Illuminate\Support\Str;
+use Mpociot\ApiDoc\Matching\RouteMatcher\Match;
 
-class RouteMatcher
+class RouteMatcher implements RouteMatcherInterface
 {
-    /**
-     * @var string
-     */
-    protected $router;
-
-    /**
-     * @var array
-     */
-    protected $routeRules;
-
-    public function __construct(array $routeRules = [], string $router = 'laravel')
+    public function getRoutes(array $routeRules = [], string $router = 'laravel')
     {
-        $this->router = $router;
-        $this->routeRules = $routeRules;
-    }
+        $usingDingoRouter = strtolower($router) == 'dingo';
 
-    public function getRoutes()
-    {
-        $usingDingoRouter = strtolower($this->router) == 'dingo';
-
-        return $this->getRoutesToBeDocumented($this->routeRules, $usingDingoRouter);
+        return $this->getRoutesToBeDocumented($routeRules, $usingDingoRouter);
     }
 
-    protected function getRoutesToBeDocumented(array $routeRules, bool $usingDingoRouter = false)
+    private function getRoutesToBeDocumented(array $routeRules, bool $usingDingoRouter = false)
     {
         $allRoutes = $this->getAllRoutes($usingDingoRouter);
+
         $matchedRoutes = [];
 
         foreach ($routeRules as $routeRule) {
@@ -50,11 +36,7 @@ class RouteMatcher
                 }
 
                 if ($this->shouldIncludeRoute($route, $routeRule, $includes, $usingDingoRouter)) {
-                    $matchedRoutes[] = [
-                        'route' => $route,
-                        'apply' => $routeRule['apply'] ?? [],
-                    ];
-                    continue;
+                    $matchedRoutes[] = new Match($route, $routeRule['apply'] ?? []);
                 }
             }
         }

+ 76 - 0
src/Matching/RouteMatcher/Match.php

@@ -0,0 +1,76 @@
+<?php
+namespace Mpociot\ApiDoc\Matching\RouteMatcher;
+
+use Illuminate\Routing\Route;
+
+class Match implements \ArrayAccess
+{
+    /**
+     * @var Route
+     */
+    protected $route;
+
+    /**
+     * @var array
+     */
+    protected $rules;
+
+    /**
+     * Match constructor.
+     * @param Route $route
+     * @param array $applyRules
+     */
+    public function __construct(Route $route, array $applyRules)
+    {
+        $this->route = $route;
+        $this->rules = $applyRules;
+    }
+
+    /**
+     * @return Route
+     */
+    public function getRoute()
+    {
+        return $this->route;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRules()
+    {
+        return $this->rules;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function offsetExists($offset)
+    {
+        return is_callable([$this, 'get' . ucfirst($offset)]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function offsetGet($offset)
+    {
+        return call_user_func([$this, 'get' . ucfirst($offset)]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function offsetSet($offset, $value)
+    {
+        return $this->$offset = $value;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function offsetUnset($offset)
+    {
+        $this->$offset = null;
+    }
+}

+ 17 - 0
src/Matching/RouteMatcherInterface.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Mpociot\ApiDoc\Matching;
+
+use Mpociot\ApiDoc\Matching\RouteMatcher\Match;
+
+interface RouteMatcherInterface
+{
+    /**
+     * Resolve matched routes that should be documented
+     * 
+     * @param array $routeRules Route rules defined under the "routes" section in config
+     * @param string $router
+     * @return Match[]
+     */
+    public function getRoutes(array $routeRules = [], string $router = 'laravel');
+}

+ 56 - 56
tests/Unit/RouteMatcherTest.php

@@ -23,26 +23,26 @@ class RouteMatcherTest extends TestCase
         $routeRules[0]['match']['prefixes'] = ['*'];
 
         $routeRules[0]['match']['domains'] = ['*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(12, $routes);
 
         $routeRules[0]['match']['domains'] = ['domain1.*', 'domain2.*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(12, $routes);
 
         $routeRules[0]['match']['domains'] = ['domain1.*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(6, $routes);
         foreach ($routes as $route) {
             $this->assertContains('domain1', $route['route']->getDomain());
         }
 
         $routeRules[0]['match']['domains'] = ['domain2.*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(6, $routes);
         foreach ($routes as $route) {
             $this->assertContains('domain2', $route['route']->getDomain());
@@ -56,26 +56,26 @@ class RouteMatcherTest extends TestCase
         $routeRules[0]['match']['prefixes'] = ['*'];
 
         $routeRules[0]['match']['domains'] = ['*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(12, $routes);
 
         $routeRules[0]['match']['domains'] = ['domain1.*', 'domain2.*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(12, $routes);
 
         $routeRules[0]['match']['domains'] = ['domain1.*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(6, $routes);
         foreach ($routes as $route) {
             $this->assertContains('domain1', $route['route']->getDomain());
         }
 
         $routeRules[0]['match']['domains'] = ['domain2.*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(6, $routes);
         foreach ($routes as $route) {
             $this->assertContains('domain2', $route['route']->getDomain());
@@ -88,26 +88,26 @@ class RouteMatcherTest extends TestCase
         $routeRules[0]['match']['domains'] = ['*'];
 
         $routeRules[0]['match']['prefixes'] = ['*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(12, $routes);
 
         $routeRules[0]['match']['prefixes'] = ['prefix1/*', 'prefix2/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(8, $routes);
 
         $routeRules[0]['match']['prefixes'] = ['prefix1/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(4, $routes);
         foreach ($routes as $route) {
             $this->assertTrue(Str::is('prefix1/*', $route['route']->uri()));
         }
 
         $routeRules[0]['match']['prefixes'] = ['prefix2/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(4, $routes);
         foreach ($routes as $route) {
             $this->assertTrue(Str::is('prefix2/*', $route['route']->uri()));
@@ -121,26 +121,26 @@ class RouteMatcherTest extends TestCase
         $routeRules[0]['match']['domains'] = ['*'];
 
         $routeRules[0]['match']['prefixes'] = ['*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(12, $routes);
 
         $routeRules[0]['match']['prefixes'] = ['prefix1/*', 'prefix2/*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(8, $routes);
 
         $routeRules[0]['match']['prefixes'] = ['prefix1/*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(4, $routes);
         foreach ($routes as $route) {
             $this->assertTrue(Str::is('prefix1/*', $route['route']->uri()));
         }
 
         $routeRules[0]['match']['prefixes'] = ['prefix2/*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(4, $routes);
         foreach ($routes as $route) {
             $this->assertTrue(Str::is('prefix2/*', $route['route']->uri()));
@@ -154,8 +154,8 @@ class RouteMatcherTest extends TestCase
         $routeRules[0]['match']['versions'] = ['v2'];
         $routeRules[0]['match']['domains'] = ['*'];
         $routeRules[0]['match']['prefixes'] = ['*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(6, $routes);
         foreach ($routes as $route) {
             $this->assertNotEmpty(array_intersect($route['route']->versions(), ['v2']));
@@ -164,8 +164,8 @@ class RouteMatcherTest extends TestCase
         $routeRules[0]['match']['versions'] = ['v1', 'v2'];
         $routeRules[0]['match']['domains'] = ['*'];
         $routeRules[0]['match']['prefixes'] = ['*'];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(18, $routes);
     }
 
@@ -177,8 +177,8 @@ class RouteMatcherTest extends TestCase
 
         $routeRules[0]['match']['domains'] = ['domain1.*'];
         $routeRules[0]['match']['prefixes'] = ['prefix1/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustInclude) {
             return $route['route']->getName() === $mustInclude;
         });
@@ -200,8 +200,8 @@ class RouteMatcherTest extends TestCase
                 'include' => [$mustInclude],
             ],
         ];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustInclude) {
             return $route['route']->getName() === $mustInclude;
         });
@@ -217,8 +217,8 @@ class RouteMatcherTest extends TestCase
 
         $routeRules[0]['match']['domains'] = ['domain1.*'];
         $routeRules[0]['match']['prefixes'] = ['prefix1/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustInclude) {
             return in_array($route['route']->getName(), $mustInclude);
         });
@@ -241,8 +241,8 @@ class RouteMatcherTest extends TestCase
                 'include' => [$includePattern],
             ],
         ];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustInclude) {
             return in_array($route['route']->getName(), $mustInclude);
         });
@@ -257,8 +257,8 @@ class RouteMatcherTest extends TestCase
 
         $routeRules[0]['match']['domains'] = ['domain1.*'];
         $routeRules[0]['match']['prefixes'] = ['prefix1/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustNotInclude) {
             return $route['route']->getName() === $mustNotInclude;
         });
@@ -280,8 +280,8 @@ class RouteMatcherTest extends TestCase
                 'exclude' => [$mustNotInclude],
             ],
         ];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustNotInclude) {
             return $route['route']->getName() === $mustNotInclude;
         });
@@ -297,8 +297,8 @@ class RouteMatcherTest extends TestCase
 
         $routeRules[0]['match']['domains'] = ['domain1.*'];
         $routeRules[0]['match']['prefixes'] = ['prefix1/*'];
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustNotInclude) {
             return in_array($route['route']->getName(), $mustNotInclude);
         });
@@ -321,8 +321,8 @@ class RouteMatcherTest extends TestCase
                 'exclude' => [$excludePattern],
             ],
         ];
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $oddRuleOut = collect($routes)->filter(function ($route) use ($mustNotInclude) {
             return in_array($route['route']->getName(), $mustNotInclude);
         });
@@ -348,8 +348,8 @@ class RouteMatcherTest extends TestCase
             ],
         ];
 
-        $matcher = new RouteMatcher($routeRules);
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules);
         $this->assertCount(4, $routes);
 
         $routes = collect($routes);
@@ -386,8 +386,8 @@ class RouteMatcherTest extends TestCase
             ],
         ];
 
-        $matcher = new RouteMatcher($routeRules, 'dingo');
-        $routes = $matcher->getRoutes();
+        $matcher = new RouteMatcher();
+        $routes = $matcher->getRoutes($routeRules, 'dingo');
         $this->assertCount(18, $routes);
 
         $routes = collect($routes);