Browse Source

Merge branch 'master' into v4

# Conflicts:
#	CHANGELOG.md
#	src/Tools/Globals.php
#	tests/GenerateDocumentation/OutputTest.php
shalvah 2 years ago
parent
commit
dfc89f3330

+ 7 - 0
CHANGELOG.md

@@ -19,6 +19,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 - Support for subgroups ([7cf07738](https://github.com/knuckleswtf/scribe/commit/7cf0773864fbdd1772fea9a5ff9e7ffd3360d7d2)))
 - Nested response fields are now collapsed ([00b09bb](https://github.com/knuckleswtf/scribe/commit/00b09bbea8ec64006db864bf807004d48926c6d3)))
 
+
+## 3.33.1 (8 July 2022)
+### Fixed
+- Don't send empty query parameter field if it's optional ([#493](https://github.com/knuckleswtf/scribe/commit/493)))
+- Infer URL parameter name correctly when `getRouteKeyName()` is set ([#492](https://github.com/knuckleswtf/scribe/commit/492)))
+
+
 ## 3.33.0 (27 June 2022)
 ### Added
 - Include description in Postman collection for formdata body parameters ([10faa500](https://github.com/knuckleswtf/scribe/commit/10faa500e36e02d4efcecf8ad5e1d91ba1c7728d)))

+ 55 - 8
camel/Extraction/ExtractedEndpointData.php

@@ -2,6 +2,7 @@
 
 namespace Knuckles\Camel\Extraction;
 
+use Illuminate\Database\Eloquent\Model;
 use Illuminate\Routing\Route;
 use Illuminate\Support\Str;
 use Knuckles\Camel\BaseDTO;
@@ -81,11 +82,12 @@ class ExtractedEndpointData extends BaseDTO
 
     public function __construct(array $parameters = [])
     {
-        $parameters['uri'] = $this->normalizeResourceParamName($parameters['uri'], $parameters['route']);
         $parameters['metadata'] = $parameters['metadata'] ?? new Metadata([]);
         $parameters['responses'] = $parameters['responses'] ?? new ResponseCollection([]);
 
         parent::__construct($parameters);
+
+        $this->uri = $this->normalizeResourceParamName($this->uri, $this->route, $this->getTypeHintedArguments());
     }
 
     public static function fromRoute(Route $route, array $extras = []): self
@@ -131,7 +133,7 @@ class ExtractedEndpointData extends BaseDTO
         return $this->httpMethods[0] . str_replace(['/', '?', '{', '}', ':', '\\', '+', '|'], '-', $this->uri);
     }
 
-    public function normalizeResourceParamName(string $uri, Route $route): string
+    public function normalizeResourceParamName(string $uri, Route $route, array $typeHintedArguments): string
     {
         $params = [];
         preg_match_all('#\{(\w+?)}#', $uri, $params);
@@ -157,9 +159,11 @@ class ExtractedEndpointData extends BaseDTO
                     "{$pluralResource}/{{$singularResource}?}"
                 ];
 
-                // We'll replace with {id} by default, but if the user is using a different key,
-                // like /users/{user:uuid}, use that instead
-                $binding = static::getFieldBindingForUrlParam($route, $singularResource, 'id');
+                // If there is an inline binding in the route, like /users/{user:uuid}, use that key,
+                // Else, search for a type-hinted variable in the action, whose name matches the route segment name,
+                // If there is such variable (like User $user), call getRouteKeyName() on the model,
+                // Otherwise, use the id
+                $binding = static::getFieldBindingForUrlParam($route, $singularResource, $typeHintedArguments, 'id');
 
                 if (!$foundResourceParam) {
                     // Only the last resource param should be {id}
@@ -179,8 +183,8 @@ class ExtractedEndpointData extends BaseDTO
         }
 
         foreach ($params[1] as $param) {
-            // For non-resource parameters, if there's a field binding, replace that too:
-            if ($binding = static::getFieldBindingForUrlParam($route, $param)) {
+            // For non-resource parameters, if there's a field binding/type-hinted variable, replace that too:
+            if ($binding = static::getFieldBindingForUrlParam($route, $param, $typeHintedArguments)) {
                 $search = ["{{$param}}", "{{$param}?}"];
                 $replace = ["{{$param}_{$binding}}", "{{$param}_{$binding}?}"];
                 $uri = str_replace($search, $replace, $uri);
@@ -207,7 +211,8 @@ class ExtractedEndpointData extends BaseDTO
         return $copy;
     }
 
-    public static function getFieldBindingForUrlParam(Route $route, string $paramName, string $default = null): ?string
+    public static function getFieldBindingForUrlParam(Route $route, string $paramName, array $typeHintedArguments = [],
+                                                      string $default = null): ?string
     {
         $binding = null;
         // Was added in Laravel 7.x
@@ -215,6 +220,48 @@ class ExtractedEndpointData extends BaseDTO
             $binding = $route->bindingFieldFor($paramName);
         }
 
+        // Search for a type-hinted variable whose name matches the route segment name
+        if (is_null($binding) && array_key_exists($paramName, $typeHintedArguments)) {
+            $argumentType = $typeHintedArguments[$paramName]->getType();
+            $argumentClassName = $argumentType->getName();
+            $argumentInstance = new $argumentClassName;
+            $binding = $argumentInstance->getRouteKeyName();
+        }
+
         return $binding ?: $default;
     }
+
+    /**
+     * Return the type-hinted method arguments in the action that have a Model type,
+     * The arguments will be returned as an array of the form: $arguments[<variable_name>] = $argument
+     */
+    protected function getTypeHintedArguments(): array
+    {
+        $arguments = [];
+        if ($this->method) {
+            foreach ($this->method->getParameters() as $argument) {
+                if ($this->argumentHasModelType($argument)) {
+                    $arguments[$argument->getName()] = $argument;
+                }
+            }
+        }
+
+        return $arguments;
+    }
+
+    /**
+     * Determine whether the argument has a Model type
+     */
+    protected function argumentHasModelType(\ReflectionParameter $argument): bool
+    {
+        $argumentType = $argument->getType();
+        if (!$argumentType) {
+            // The argument does not have a type-hint
+            return false;
+        } else {
+            $argumentClassName = $argumentType->getName();
+            $argumentInstance = new $argumentClassName;
+            return ($argumentInstance instanceof Model);
+        }
+    }
 }

+ 13 - 0
tests/Fixtures/TestPost.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Knuckles\Scribe\Tests\Fixtures;
+
+use Illuminate\Database\Eloquent\Model;
+
+class TestPost extends Model
+{
+    public function getRouteKeyName()
+    {
+        return 'slug';
+    }
+}

+ 10 - 0
tests/Fixtures/TestPostController.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace Knuckles\Scribe\Tests\Fixtures;
+
+class TestPostController
+{
+    public function update(TestPost $post)
+    {
+    }
+}

+ 10 - 0
tests/Fixtures/TestPostUserController.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace Knuckles\Scribe\Tests\Fixtures;
+
+class TestPostUserController
+{
+    public function update(TestPost $post, TestUser $user)
+    {
+    }
+}