Skip to content

[12.x] Various URL generation bugfixes #54811

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 8 commits into from
Mar 22, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions src/Illuminate/Routing/RouteUrlGenerator.php
Original file line number Diff line number Diff line change
@@ -2,8 +2,11 @@

namespace Illuminate\Routing;

use BackedEnum;
use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Routing\Exceptions\UrlGenerationException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

class RouteUrlGenerator
{
@@ -74,6 +77,8 @@ public function __construct($url, $request)
*/
public function to($route, $parameters = [], $absolute = false)
{
$parameters = $this->formatParameters($route, $parameters);

$domain = $this->getRouteDomain($route, $parameters);

// First we will construct the entire URI including the root and query string. Once it
@@ -167,6 +172,95 @@ protected function addPortToDomain($domain)
: $domain.':'.$port;
}

/**
* Format the array of route parameters.
*
* @param \Illuminate\Routing\Route $route
* @param mixed $parameters
* @return array
*/
protected function formatParameters(Route $route, $parameters)
{
$parameters = Arr::wrap($parameters);

$passedParameterCount = count($parameters);

$namedParameters = [];
$namedQueryParameters = [];
$routeParametersWithoutDefaultsOrNamedParameters = [];

$routeParameters = $route->parameterNames();

foreach ($routeParameters as $name) {
if (isset($parameters[$name])) {
// Named parameters don't need any special handling...
$namedParameters[$name] = $parameters[$name];
unset($parameters[$name]);

continue;
} elseif (! isset($this->defaultParameters[$name])) {
// No named parameter or default value, try to match to positional parameter below...
array_push($routeParametersWithoutDefaultsOrNamedParameters, $name);
}

$namedParameters[$name] = '';
}

// Named parameters that don't have route parameters will be used for query string...
foreach ($parameters as $key => $value) {
if (is_string($key)) {
$namedQueryParameters[$key] = $value;

unset($parameters[$key]);
}
}

// Match positional parameters to the route parameters that didn't have a value in order...
if (count($parameters) == count($routeParametersWithoutDefaultsOrNamedParameters)) {
foreach (array_reverse($routeParametersWithoutDefaultsOrNamedParameters) as $name) {
if (count($parameters) === 0) {
break;
}

$namedParameters[$name] = array_pop($parameters);
}
}

// If there are extra parameters, just fill left to right... if not, fill right to left and try to use defaults...
$extraParameters = $passedParameterCount > count($routeParameters);

foreach ($extraParameters ? $namedParameters : array_reverse($namedParameters) as $key => $value) {
$bindingField = $route->bindingFieldFor($key);

$defaultParameterKey = $bindingField ? "$key:$bindingField" : $key;

if ($value !== '') {
continue;
} elseif (! empty($parameters)) {
$namedParameters[$key] = $extraParameters ? array_shift($parameters) : array_pop($parameters);
} elseif (isset($this->defaultParameters[$defaultParameterKey])) {
$namedParameters[$key] = $this->defaultParameters[$defaultParameterKey];
}
}

// Any remaining values in $parameters are unnamed query string parameters...
$parameters = array_merge($namedParameters, $namedQueryParameters, $parameters);

$parameters = Collection::wrap($parameters)->map(function ($value, $key) use ($route) {
return $value instanceof UrlRoutable && $route->bindingFieldFor($key)
? $value->{$route->bindingFieldFor($key)}
: $value;
})->all();

array_walk_recursive($parameters, function (&$item) {
if ($item instanceof BackedEnum) {
$item = $item->value;
}
});

return $this->url->formatParameters($parameters);
}

/**
* Replace the parameters on the root path.
*
14 changes: 1 addition & 13 deletions src/Illuminate/Routing/UrlGenerator.php
Original file line number Diff line number Diff line change
@@ -538,20 +538,8 @@ public function route($name, $parameters = [], $absolute = true)
*/
public function toRoute($route, $parameters, $absolute)
{
$parameters = Collection::wrap($parameters)->map(function ($value, $key) use ($route) {
return $value instanceof UrlRoutable && $route->bindingFieldFor($key)
? $value->{$route->bindingFieldFor($key)}
: $value;
})->all();

array_walk_recursive($parameters, function (&$item) {
if ($item instanceof BackedEnum) {
$item = $item->value;
}
});

return $this->routeUrl()->to(
$route, $this->formatParameters($parameters), $absolute
$route, $parameters, $absolute
);
}

Loading
Loading