Browse Source

Fix #68: handle Lumen URL parameters properly

shalvah 4 years ago
parent
commit
53208d9385

+ 1 - 0
composer.json

@@ -39,6 +39,7 @@
     "laravel/legacy-factories": "^1.0.4",
     "laravel/lumen-framework": "^6.0|^7.0|^8.0",
     "league/fractal": "^0.19.0",
+    "nikic/fast-route": "^1.3",
     "orchestra/testbench": "^4.0|^5.0",
     "phpstan/phpstan": "^0.12.19",
     "phpunit/phpunit": "^8.0|^9.0"

+ 1 - 0
config/scribe.php

@@ -326,6 +326,7 @@ INTRO
         ],
         'urlParameters' => [
             \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromLaravelAPI::class,
+            \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromLumenAPI::class,
             \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromUrlParamTag::class,
         ],
         'queryParameters' => [

+ 0 - 2
phpstan.neon

@@ -15,5 +15,3 @@ parameters:
         - '#Instantiated class Whoops\\Exception\\Inspector not found\.#'
         - '#.+Dingo.+#'
         - '#Right side of && is always false.#'
-        - '#Inner named functions#'
-        - '#Function isPresentInThisPossibility#'

+ 2 - 1
src/Extracting/Generator.php

@@ -95,7 +95,7 @@ class Generator
         $urlParameters = $this->fetchUrlParameters($controller, $method, $route, $routeRules, $parsedRoute);
         $parsedRoute['urlParameters'] = $urlParameters;
         $parsedRoute['cleanUrlParameters'] = self::cleanParams($urlParameters);
-        $parsedRoute['boundUri'] = u::getFullUrl($route, $parsedRoute['cleanUrlParameters']);
+        $parsedRoute['boundUri'] = u::getUrlWithBoundParameters($route, $parsedRoute['cleanUrlParameters']);
 
         $parsedRoute = $this->addAuthField($parsedRoute);
 
@@ -198,6 +198,7 @@ class Generator
             ],
             'urlParameters' => [
                 \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromLaravelAPI::class,
+                \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromLumenAPI::class,
                 \Knuckles\Scribe\Extracting\Strategies\UrlParameters\GetFromUrlParamTag::class,
             ],
             'queryParameters' => [

+ 1 - 1
src/Extracting/Strategies/Responses/ResponseCalls.php

@@ -117,7 +117,7 @@ class ResponseCalls extends Strategy
      */
     protected function prepareRequest(Route $route, array $rulesToApply, array $urlParams, array $bodyParams, array $queryParams, array $fileParameters, array $headers)
     {
-        $uri = Utils::getFullUrl($route, $urlParams);
+        $uri = Utils::getUrlWithBoundParameters($route, $urlParams);
         $routeMethods = $this->getMethods($route);
         $method = array_shift($routeMethods);
         $cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : [];

+ 5 - 0
src/Extracting/Strategies/UrlParameters/GetFromLaravelAPI.php

@@ -6,6 +6,7 @@ use Illuminate\Routing\Route;
 use Illuminate\Support\Str;
 use Knuckles\Scribe\Extracting\ParamHelpers;
 use Knuckles\Scribe\Extracting\Strategies\Strategy;
+use Knuckles\Scribe\Tools\Utils;
 use ReflectionClass;
 use ReflectionFunctionAbstract;
 
@@ -17,6 +18,10 @@ class GetFromLaravelAPI extends Strategy
 
     public function __invoke(Route $route, ReflectionClass $controller, ReflectionFunctionAbstract $method, array $routeRules, array $alreadyExtractedData = [])
     {
+        if (Utils::isLumen()) {
+            return null;
+        }
+
         $parameters = [];
 
         $path = $alreadyExtractedData['uri'];

+ 65 - 0
src/Extracting/Strategies/UrlParameters/GetFromLumenAPI.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace Knuckles\Scribe\Extracting\Strategies\UrlParameters;
+
+use FastRoute\RouteParser\Std;
+use Illuminate\Routing\Route;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+use Knuckles\Scribe\Extracting\ParamHelpers;
+use Knuckles\Scribe\Extracting\Strategies\Strategy;
+use Knuckles\Scribe\Tools\Utils;
+use ReflectionClass;
+use ReflectionFunctionAbstract;
+
+class GetFromLumenAPI extends Strategy
+{
+    public $stage = 'urlParameters';
+
+    use ParamHelpers;
+
+    public function __invoke(Route $route, ReflectionClass $controller, ReflectionFunctionAbstract $method, array $routeRules, array $alreadyExtractedData = [])
+    {
+        if (!Utils::isLumen()) {
+            return null;
+        }
+
+        $path = $alreadyExtractedData['uri'];
+
+        $parameters = [];
+        $possibilities = (new Std)->parse($path);
+        // See https://github.com/nikic/FastRoute#overriding-the-route-parser-and-dispatcher
+        $possibilityWithAllSegmentsPresent = end($possibilities);
+
+        foreach ($possibilityWithAllSegmentsPresent as $part) {
+            if (!is_array($part)) {
+                // It's just a path segment, not a URL parameter'
+                continue;
+            }
+
+            $name = $part[0];
+            $isThisParameterOptional = Arr::first($possibilities, function ($possibility) use ($name) {
+                // This function checks if this parameter is present in the current possibility
+                return (function () use ($possibility, $name) {
+                        foreach ($possibility as $part) {
+                            if (is_array($part) && $part[0] === $name) {
+                                return true;
+                            }
+                        }
+                        return false;
+                    })() === false;
+            }, false);
+            $type = 'string';
+            $parameters[$name] = [
+                'name' => $name,
+                'description' => '',
+                'required' => !boolval($isThisParameterOptional),
+                'value' => $this->generateDummyValue($type),
+                'type' => $type,
+            ];
+        }
+
+        dd($parameters);
+        return $parameters;
+    }
+}

+ 59 - 29
src/Tools/Utils.php

@@ -4,6 +4,7 @@ namespace Knuckles\Scribe\Tools;
 
 use Closure;
 use Exception;
+use FastRoute\RouteParser\Std;
 use Illuminate\Routing\Route;
 use Illuminate\Support\Str;
 use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
@@ -16,38 +17,13 @@ use ReflectionFunctionAbstract;
 
 class Utils
 {
-    public static function getFullUrl(Route $route, array $urlParameters = []): string
+    public static function getUrlWithBoundParameters(Route $route, array $urlParameters = []): string
     {
         $uri = $route->uri();
 
         return self::replaceUrlParameterPlaceholdersWithValues($uri, $urlParameters);
     }
 
-    public static function getRouteClassAndMethodNames(Route $route): array
-    {
-        $action = $route->getAction();
-
-        $uses = $action['uses'];
-
-        if ($uses !== null) {
-            if (is_array($uses)) {
-                return $uses;
-            } elseif (is_string($uses)) {
-                return explode('@', $uses);
-            } elseif (static::isInvokableObject($uses)) {
-                return [$uses, '__invoke'];
-            }
-        }
-        if (array_key_exists(0, $action) && array_key_exists(1, $action)) {
-            return [
-                0 => $action[0],
-                1 => $action[1],
-            ];
-        }
-
-        throw new Exception("Couldn't get class and method names for route " . c::getRouteRepresentation($route) . '.');
-    }
-
     /**
      * Transform parameters in URLs into real values (/users/{user} -> /users/2).
      * Uses @urlParam values specified by caller, otherwise just uses '1'.
@@ -63,8 +39,23 @@ class Utils
             return $uri;
         }
 
-        foreach ($urlParameters as $parameterName => $example) {
-            $uri = preg_replace('#\{' . $parameterName . '\??}#', $example, $uri);
+        if (self::isLumen()) {
+            $boundUri = '';
+            $possibilities = (new Std)->parse($uri);
+            // See https://github.com/nikic/FastRoute#overriding-the-route-parser-and-dispatcher
+            $possibilityWithAllSegmentsPresent = end($possibilities);
+            foreach ($possibilityWithAllSegmentsPresent as $part) {
+                if (!is_array($part)) {
+                    // It's just a path segment, not a URL parameter'
+                    $boundUri .= $part;
+                    continue;
+                }
+
+                $name = $part[0];
+                $boundUri .= $urlParameters[$name];
+            }
+
+            return $boundUri;
         }
 
         // Remove unbound optional parameters with nothing
@@ -75,6 +66,31 @@ class Utils
         return $uri;
     }
 
+    public static function getRouteClassAndMethodNames(Route $route): array
+    {
+        $action = $route->getAction();
+
+        $uses = $action['uses'];
+
+        if ($uses !== null) {
+            if (is_array($uses)) {
+                return $uses;
+            } elseif (is_string($uses)) {
+                return explode('@', $uses);
+            } elseif (static::isInvokableObject($uses)) {
+                return [$uses, '__invoke'];
+            }
+        }
+        if (array_key_exists(0, $action) && array_key_exists(1, $action)) {
+            return [
+                0 => $action[0],
+                1 => $action[1],
+            ];
+        }
+
+        throw new Exception("Couldn't get class and method names for route " . c::getRouteRepresentation($route) . '.');
+    }
+
     public static function deleteDirectoryAndContents($dir, $base = null)
     {
         $dir = ltrim($dir, '/');
@@ -98,9 +114,9 @@ class Utils
      *
      * @param array $routeControllerAndMethod
      *
+     * @return ReflectionFunctionAbstract
      * @throws ReflectionException
      *
-     * @return ReflectionFunctionAbstract
      */
     public static function getReflectedRouteMethod(array $routeControllerAndMethod): ReflectionFunctionAbstract
     {
@@ -142,4 +158,18 @@ class Utils
         return $factory;
     }
 
+    public static function isLumen(): bool
+    {
+        // See https://github.com/laravel/lumen-framework/blob/99330e6ca2198e228f5894cf84d843c2a539a250/src/Application.php#L163
+        $app = app();
+        if ($app
+            && is_callable([$app, 'version'])
+            && Str::startsWith($app->version(), 'Lumen')
+        ) {
+            return true;
+        }
+
+        return false;
+    }
+
 }