Skip to content

Commit

Permalink
Refactor URL params normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
shalvah committed Jul 16, 2022
1 parent 8dcfcd3 commit 97065d9
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 290 deletions.
132 changes: 2 additions & 130 deletions camel/Extraction/ExtractedEndpointData.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Routing\Route;
use Illuminate\Support\Str;
use Knuckles\Camel\BaseDTO;
use Knuckles\Scribe\Extracting\UrlParamsNormalizer;
use Knuckles\Scribe\Tools\Utils as u;
use ReflectionClass;

Expand Down Expand Up @@ -87,7 +88,7 @@ public function __construct(array $parameters = [])

parent::__construct($parameters);

$this->uri = $this->normalizeResourceParamName($this->uri, $this->route, $this->getTypeHintedArguments());
$this->uri = UrlParamsNormalizer::normalizeParameterNamesInRouteUri($this->route, $this->method);
}

public static function fromRoute(Route $route, array $extras = []): self
Expand Down Expand Up @@ -133,67 +134,6 @@ public function endpointId()
return $this->httpMethods[0] . str_replace(['/', '?', '{', '}', ':', '\\', '+', '|'], '-', $this->uri);
}

public function normalizeResourceParamName(string $uri, Route $route, array $typeHintedArguments): string
{
$params = [];
preg_match_all('#\{(\w+?)}#', $uri, $params);

$resourceRouteNames = [
".index", ".show", ".update", ".destroy",
];

if (Str::endsWith($route->action['as'] ?? '', $resourceRouteNames)) {
// Note that resource routes can be nested eg users.posts.show
$pluralResources = explode('.', $route->action['as']);
array_pop($pluralResources);

$foundResourceParam = false;
foreach (array_reverse($pluralResources) as $pluralResource) {
$singularResource = Str::singular($pluralResource);
$singularResourceParam = str_replace('-', '_', $singularResource);

$search = [
"{$pluralResource}/{{$singularResourceParam}}",
"{$pluralResource}/{{$singularResource}}",
"{$pluralResource}/{{$singularResourceParam}?}",
"{$pluralResource}/{{$singularResource}?}"
];

// 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}
$replace = ["$pluralResource/{{$binding}}", "$pluralResource/{{$binding}?}"];
$foundResourceParam = true;
} else {
// Earlier ones should be {<param>_id}
$replace = [
"{$pluralResource}/{{$singularResource}_{$binding}}",
"{$pluralResource}/{{$singularResourceParam}_{$binding}}",
"{$pluralResource}/{{$singularResource}_{$binding}?}",
"{$pluralResource}/{{$singularResourceParam}_{$binding}?}"
];
}
$uri = str_replace($search, $replace, $uri);
}
}

foreach ($params[1] as $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);
}
}

return $uri;
}

/**
* Prepare the endpoint data for serialising.
*/
Expand All @@ -209,72 +149,4 @@ public function forSerialisation()

return $copy;
}

protected static function instantiateTypedArgument(\ReflectionNamedType $argumentType): ?object
{
$argumentClassName = $argumentType->getName();

if (class_exists($argumentClassName)) {
return new $argumentClassName;
}

if (interface_exists($argumentClassName)) {
return app($argumentClassName);
}

return null;
}

public static function getFieldBindingForUrlParam(
Route $route, string $paramName, array $typeHintedArguments = [], string $default = null
): ?string
{
$binding = null;
// Was added in Laravel 7.x
if (method_exists($route, 'bindingFieldFor')) {
$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();
$argumentInstance = self::instantiateTypedArgument($argumentType);
$binding = $argumentInstance instanceof Model ? $argumentInstance->getRouteKeyName() : null;
}

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 instanceof \ReflectionNamedType)) {
// The argument does not have a type-hint, or is a primitive type (`string`, ..)
return false;
}

$argumentInstance = self::instantiateTypedArgument($argumentType);
return ($argumentInstance instanceof Model);
}
}
Loading

0 comments on commit 97065d9

Please sign in to comment.