![]() Server : Apache/2.4.62 (Unix) OpenSSL/1.0.2k-fips System : Linux server1.richineiserver.com 3.10.0-1160.119.1.el7.x86_64 #1 SMP Tue Jun 4 14:43:51 UTC 2024 x86_64 User : fdshops ( 1003) PHP Version : 8.2.21 Disable Function : NONE Directory : /home/fdshops/public_html/vendor/knuckleswtf/scribe/src/Extracting/Shared/ |
<?php namespace Knuckles\Scribe\Extracting\Shared; use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\Route; use Illuminate\Support\Str; use ReflectionEnum; use ReflectionException; use ReflectionFunctionAbstract; /* * See https://laravel.com/docs/9.x/routing#route-model-binding */ class UrlParamsNormalizer { /** * Normalize a URL from Laravel-style to something that's clearer for a non-Laravel user. * For instance, `/posts/{post}` would be clearer as `/posts/{id}`, * and `/users/{user}/posts/{post}` would be clearer as `/users/{user_id}/posts/{id}` * * @param \Illuminate\Routing\Route $route * @param \ReflectionFunctionAbstract $method * * @return string */ public static function normalizeParameterNamesInRouteUri(Route $route, ReflectionFunctionAbstract $method): string { $params = []; $uri = $route->uri; preg_match_all('#\{(\w+?)}#', $uri, $params); $resourceRouteNames = [".index", ".show", ".update", ".destroy", ".store"]; $typeHintedEloquentModels = self::getTypeHintedEloquentModels($method); $routeName = $route->action['as'] ?? ''; if (Str::endsWith($routeName, $resourceRouteNames)) { // Note that resource routes can be nested eg users.posts.show $pluralResources = explode('.', $routeName); array_pop($pluralResources); // Remove the name of the action (eg `show`) $alreadyFoundResourceParam = false; foreach (array_reverse($pluralResources) as $pluralResource) { $singularResource = Str::singular($pluralResource); // Laravel turns hyphens in parameters to underscores // (`cool-things/{cool-thing}` to `cool-things/{cool_thing_id}`) // so we do the same $singularResourceParam = str_replace('-', '_', $singularResource); $urlPatternsToSearchFor = [ "{$pluralResource}/{{$singularResourceParam}}", "{$pluralResource}/{{$singularResource}}", "{$pluralResource}/{{$singularResourceParam}?}", "{$pluralResource}/{{$singularResource}?}", ]; $binding = self::getRouteKeyForUrlParam( $route, $singularResource, $typeHintedEloquentModels, 'id' ); if (!$alreadyFoundResourceParam) { // This is the first resource param (from the end). // We set it to `params/{id}` (or whatever field it's bound to) $replaceWith = ["$pluralResource/{{$binding}}", "$pluralResource/{{$binding}?}"]; $alreadyFoundResourceParam = true; } else { // Other resource parameters will be `params/{<param>_id}` $replaceWith = [ "{$pluralResource}/{{$singularResourceParam}_{$binding}}", "{$pluralResource}/{{$singularResource}_{$binding}}", "{$pluralResource}/{{$singularResourceParam}_{$binding}?}", "{$pluralResource}/{{$singularResource}_{$binding}?}", ]; } $uri = str_replace($urlPatternsToSearchFor, $replaceWith, $uri); } } foreach ($params[1] as $param) { // For non-resource parameters, if there's a field binding/type-hinted variable, replace that too: if ($binding = self::getRouteKeyForUrlParam($route, $param, $typeHintedEloquentModels)) { $urlPatternsToSearchFor = ["{{$param}}", "{{$param}?}"]; $replaceWith = ["{{$param}_{$binding}}", "{{$param}_{$binding}?}"]; $uri = str_replace($urlPatternsToSearchFor, $replaceWith, $uri); } } return $uri; } /** * Return the type-hinted method arguments in the action that are Eloquent models, * The arguments will be returned as an array of the form: [<variable_name> => $instance] */ public static function getTypeHintedEloquentModels(ReflectionFunctionAbstract $method): array { $arguments = []; foreach ($method->getParameters() as $argument) { if (($instance = self::instantiateMethodArgument($argument)) && $instance instanceof Model) { $arguments[$argument->getName()] = $instance; } } return $arguments; } /** * Return the type-hinted method arguments in the action that are enums, * The arguments will be returned as an array of the form: [<variable_name> => $instance] */ public static function getTypeHintedEnums(ReflectionFunctionAbstract $method): array { $arguments = []; foreach ($method->getParameters() as $argument) { $argumentType = $argument->getType(); if (!($argumentType instanceof \ReflectionNamedType)) continue; try { $reflectionEnum = new ReflectionEnum($argumentType->getName()); $arguments[$argument->getName()] = $reflectionEnum; } catch (ReflectionException) { continue; } } return $arguments; } /** * Given a URL that uses Eloquent model binding (for instance `/posts/{post}` -> `public function show(Post * $post)`), we need to figure out the field that Eloquent uses to retrieve the Post object. By default, this would * be `id`, but can be configured in a couple of ways: * * - Inline: `/posts/{post:slug}` * - `class Post { public function getRouteKeyName() { return 'slug'; } }` * * There are other ways, but they're dynamic and beyond our scope. * * @param \Illuminate\Routing\Route $route * @param string $paramName The name of the URL parameter * @param array<string, Model> $typeHintedEloquentModels * @param string|null $default Default field to use * * @return string|null */ protected static function getRouteKeyForUrlParam( Route $route, string $paramName, array $typeHintedEloquentModels = [], string $default = null ): ?string { if ($binding = self::getInlineRouteKey($route, $paramName)) { return $binding; } return self::getRouteKeyFromModel($paramName, $typeHintedEloquentModels) ?: $default; } /** * Return the `slug` in /posts/{post:slug} * * @param \Illuminate\Routing\Route $route * @param string $paramName * * @return string|null */ protected static function getInlineRouteKey(Route $route, string $paramName): ?string { // Was added in Laravel 7.x if (method_exists($route, 'bindingFieldFor')) { return $route->bindingFieldFor($paramName); } return null; } /** * Check if there's a type-hinted argument on the controller method matching the URL param name: * eg /posts/{post} -> public function show(Post $post) * If there is, check if it's an Eloquent model. * If it is, return it's `getRouteKeyName()`. * * @param string $paramName * @param Model[] $typeHintedEloquentModels * * @return string|null */ protected static function getRouteKeyFromModel(string $paramName, array $typeHintedEloquentModels): ?string { // Ensure param name is in camelCase so it matches the argument name (e.g. The '$userAddress' in `function show(BigThing $userAddress`) $paramName = Str::camel($paramName); if (array_key_exists($paramName, $typeHintedEloquentModels)) { $argumentInstance = $typeHintedEloquentModels[$paramName]; return $argumentInstance->getRouteKeyName(); } return null; } /** * Instantiate an argument on a controller method via its typehint. For instance, $post in: * * public function show(Post $post) * * This method takes in a method argument and returns an instance, or null if it couldn't be instantiated safely. * Cases where instantiation may fail: * - the argument has no type (eg `public function show($postId)`) * - the argument has a primitive type (eg `public function show(string $postId)`) * - the argument is an injected dependency that itself needs other dependencies * (eg `public function show(PostsManager $manager)`) * * @param \ReflectionParameter $argument * * @return object|null */ protected static function instantiateMethodArgument(\ReflectionParameter $argument): ?object { $argumentType = $argument->getType(); // No type-hint, or primitive type if (!($argumentType instanceof \ReflectionNamedType)) return null; $argumentClassName = $argumentType->getName(); if (class_exists($argumentClassName)) { try { return new $argumentClassName; } catch (\Throwable $e) { return null; } } if (interface_exists($argumentClassName)) { try { return app($argumentClassName); } catch (\Throwable $e) { return null; } } return null; } }