From 39e8c83af778d8086b0b5e8f4f2e21331b015b39 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Dec 2016 19:56:24 -0600 Subject: [PATCH] working on cleaning generator --- src/Illuminate/Routing/RouteUrlGenerator.php | 318 +++++++++++++ src/Illuminate/Routing/UrlGenerator.php | 453 +++++-------------- tests/Routing/RoutingUrlGeneratorTest.php | 2 +- 3 files changed, 428 insertions(+), 345 deletions(-) create mode 100644 src/Illuminate/Routing/RouteUrlGenerator.php diff --git a/src/Illuminate/Routing/RouteUrlGenerator.php b/src/Illuminate/Routing/RouteUrlGenerator.php new file mode 100644 index 000000000000..833726a723f1 --- /dev/null +++ b/src/Illuminate/Routing/RouteUrlGenerator.php @@ -0,0 +1,318 @@ + '/', + '%40' => '@', + '%3A' => ':', + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + '%3F' => '?', + '%26' => '&', + '%23' => '#', + '%25' => '%', + ]; + + /** + * Create a new Route URL generator. + * + * @param \Illuminate\Routing\UrlGenerator $url + * @param \Illuminate\Http\Request $request + * @return void + */ + public function __construct($url, $request) + { + $this->url = $url; + $this->request = $request; + } + + /** + * Generate a URL for the given route. + * + * @param \Illuminate\Routing\UrlGeneartor $url + * @param \Illuminate\Routing\Route $route + * @param array $parameters + * @param bool $absolute + * @return string + */ + public function to($route, $parameters = [], $absolute = false) + { + $domain = $this->getRouteDomain($route, $parameters); + + $uri = $this->addQueryString($this->url->format( + $root = $this->replaceRoot($route, $domain, $parameters), + $this->replaceRouteParameters($route->uri(), $parameters) + ), $parameters); + + if (preg_match('/\{.*?\}/', $uri)) { + throw UrlGenerationException::forMissingParameters($route); + } + + $uri = strtr(rawurlencode($uri), $this->dontEncode); + + return $absolute ? $uri : '/'.ltrim(str_replace($root, '', $uri), '/'); + } + + /** + * Get the formatted domain for a given route. + * + * @param \Illuminate\Routing\Route $route + * @param array $parameters + * @return string + */ + protected function getRouteDomain($route, &$parameters) + { + return $route->domain() ? $this->formatDomain($route, $parameters) : null; + } + + /** + * Format the domain and port for the route and request. + * + * @param \Illuminate\Routing\Route $route + * @param array $parameters + * @return string + */ + protected function formatDomain($route, &$parameters) + { + return $this->addPortToDomain($this->getDomainAndScheme($route)); + } + + /** + * Get the domain and scheme for the route. + * + * @param \Illuminate\Routing\Route $route + * @return string + */ + protected function getDomainAndScheme($route) + { + return $this->getRouteScheme($route).$route->domain(); + } + + /** + * Get the scheme for the given route. + * + * @param \Illuminate\Routing\Route $route + * @return string + */ + protected function getRouteScheme($route) + { + if ($route->httpOnly()) { + return 'http://'; + } elseif ($route->httpsOnly()) { + return 'https://'; + } + + return $this->url->formatScheme(null); + } + + /** + * Add the port to the domain if necessary. + * + * @param string $domain + * @return string + */ + protected function addPortToDomain($domain) + { + $secure = $this->request->isSecure(); + + $port = (int) $this->request->getPort(); + + if (($secure && $port === 443) || (! $secure && $port === 80)) { + return $domain; + } + + return $domain.':'.$port; + } + + /** + * Add a query string to the URI. + * + * @param string $uri + * @param array $parameters + * @return mixed|string + */ + protected function addQueryString($uri, array $parameters) + { + // If the URI has a fragment we will move it to the end of this URI since it will + // need to come after any query string that may be added to the URL else it is + // not going to be available. We will remove it then append it back on here. + if (! is_null($fragment = parse_url($uri, PHP_URL_FRAGMENT))) { + $uri = preg_replace('/#.*/', '', $uri); + } + + $uri .= $this->getRouteQueryString($parameters); + + return is_null($fragment) ? $uri : $uri."#{$fragment}"; + } + + /** + * Get the query string for a given route. + * + * @param array $parameters + * @return string + */ + protected function getRouteQueryString(array $parameters) + { + // First we will get all of the string parameters that are remaining after we + // have replaced the route wildcards. We'll then build a query string from + // these string parameters then use it as a starting point for the rest. + if (count($parameters) == 0) { + return ''; + } + + $query = http_build_query( + $keyed = $this->getStringParameters($parameters) + ); + + // Lastly, if there are still parameters remaining, we will fetch the numeric + // parameters that are in the array and add them to the query string or we + // will make the initial query string if it wasn't started with strings. + if (count($keyed) < count($parameters)) { + $query .= '&'.implode( + '&', $this->getNumericParameters($parameters) + ); + } + + return '?'.trim($query, '&'); + } + + /** + * Get the string parameters from a given list. + * + * @param array $parameters + * @return array + */ + protected function getStringParameters(array $parameters) + { + return array_filter($parameters, 'is_string', ARRAY_FILTER_USE_KEY); + } + + /** + * Get the numeric parameters from a given list. + * + * @param array $parameters + * @return array + */ + protected function getNumericParameters(array $parameters) + { + return array_filter($parameters, 'is_numeric', ARRAY_FILTER_USE_KEY); + } + + /** + * Replace the parameters on the root path. + * + * @param \Illuminate\Routing\Route $route + * @param string $domain + * @param array $parameters + * @return string + */ + protected function replaceRoot($route, $domain, &$parameters) + { + return $this->replaceRouteParameters( + $this->formatRouteRoot($route, $domain), $parameters + ); + } + + /** + * Get the root of the route URL. + * + * @param \Illuminate\Routing\Route $route + * @param string $domain + * @return string + */ + protected function formatRouteRoot($route, $domain) + { + return $this->url->formatRoot($this->getRouteScheme($route), $domain); + } + + /** + * Replace all of the wildcard parameters for a route path. + * + * @param string $path + * @param array $parameters + * @return string + */ + protected function replaceRouteParameters($path, array &$parameters) + { + $path = $this->replaceNamedParameters($path, $parameters); + + $path = preg_replace_callback('/\{.*?\}/', function ($match) use (&$parameters) { + return (empty($parameters) && ! Str::endsWith($match[0], '?}')) + ? $match[0] + : array_shift($parameters); + }, $path); + + return trim(preg_replace('/\{.*?\?\}/', '', $path), '/'); + } + + /** + * Replace all of the named parameters in the path. + * + * @param string $path + * @param array $parameters + * @return string + */ + protected function replaceNamedParameters($path, &$parameters) + { + return preg_replace_callback('/\{(.*?)\??\}/', function ($m) use (&$parameters) { + if (isset($parameters[$m[1]])) { + return Arr::pull($parameters, $m[1]); + } elseif (isset($this->defaultParameters[$m[1]])) { + return $this->defaultParameters[$m[1]]; + } else { + return $m[0]; + } + }, $path); + } + + /** + * Set the default named parameters used by the URL generator. + * + * @param array $defaults + * @return void + */ + public function defaults(array $defaults) + { + $this->defaultParameters = array_merge( + $this->defaultParameters, $defaults + ); + } +} diff --git a/src/Illuminate/Routing/UrlGenerator.php b/src/Illuminate/Routing/UrlGenerator.php index 1e24117a8937..6a0a003d339b 100755 --- a/src/Illuminate/Routing/UrlGenerator.php +++ b/src/Illuminate/Routing/UrlGenerator.php @@ -30,13 +30,6 @@ class UrlGenerator implements UrlGeneratorContract */ protected $request; - /** - * The named parameter defaults. - * - * @var array - */ - protected $defaultParameters = []; - /** * The forced URL root. * @@ -49,7 +42,7 @@ class UrlGenerator implements UrlGeneratorContract * * @var string */ - protected $forceSchema; + protected $forceScheme; /** * A cached copy of the URL root for the current request. @@ -94,26 +87,11 @@ class UrlGenerator implements UrlGeneratorContract protected $formatPathUsing; /** - * Characters that should not be URL encoded. + * The route URL generator instance. * - * @var array + * @var \Illuminate\Routing\RouteUrlGenerator */ - protected $dontEncode = [ - '%2F' => '/', - '%40' => '@', - '%3A' => ':', - '%3B' => ';', - '%2C' => ',', - '%3D' => '=', - '%2B' => '+', - '%21' => '!', - '%2A' => '*', - '%7C' => '|', - '%3F' => '?', - '%26' => '&', - '%23' => '#', - '%25' => '%', - ]; + protected $routeGenerator; /** * Create a new URL Generator instance. @@ -170,6 +148,18 @@ public function previous($fallback = false) } } + /** + * Get the previous URL from the session if possible. + * + * @return string|null + */ + protected function getPreviousUrlFromSession() + { + $session = $this->getSession(); + + return $session ? $session->previousUrl() : null; + } + /** * Generate an absolute URL to the given path. * @@ -187,27 +177,20 @@ public function to($path, $extra = [], $secure = null) return $path; } - $scheme = $this->getScheme($secure); - - $extra = $this->formatParameters($extra); - $tail = implode('/', array_map( - 'rawurlencode', (array) $extra) + 'rawurlencode', (array) $this->formatParameters($extra)) ); // Once we have the scheme we will compile the "tail" by collapsing the values // into a single string delimited by slashes. This just makes it convenient // for passing the array of parameters to this URL as a list of segments. - $root = $this->getRootUrl($scheme); + $root = $this->formatRoot($this->formatScheme($secure)); - if (($queryPosition = strpos($path, '?')) !== false) { - $query = substr($path, $queryPosition); - $path = substr($path, 0, $queryPosition); - } else { - $query = ''; - } + list($path, $query) = $this->extractQueryString($path); - return $this->buildCompleteUrl($root, $path, $tail).$query; + return $this->format( + $root, '/'.trim($path.'/'.$tail, '/') + ).$query; } /** @@ -238,11 +221,22 @@ public function asset($path, $secure = null) // Once we get the root URL, we will check to see if it contains an index.php // file in the paths. If it does, we will remove it since it is not needed // for asset paths, but only for routes to endpoints in the application. - $root = $this->getRootUrl($this->getScheme($secure)); + $root = $this->formatRoot($this->formatScheme($secure)); return $this->removeIndex($root).'/'.trim($path, '/'); } + /** + * Generate the URL to a secure asset. + * + * @param string $path + * @return string + */ + public function secureAsset($path) + { + return $this->asset($path, true); + } + /** * Generate the URL to an asset from a custom root domain such as CDN, etc. * @@ -256,7 +250,7 @@ public function assetFrom($root, $path, $secure = null) // Once we get the root URL, we will check to see if it contains an index.php // file in the paths. If it does, we will remove it since it is not needed // for asset paths, but only for routes to endpoints in the application. - $root = $this->getRootUrl($this->getScheme($secure), $root); + $root = $this->formatRoot($this->formatScheme($secure), $root); return $this->removeIndex($root).'/'.trim($path, '/'); } @@ -275,46 +269,22 @@ protected function removeIndex($root) } /** - * Generate the URL to a secure asset. - * - * @param string $path - * @return string - */ - public function secureAsset($path) - { - return $this->asset($path, true); - } - - /** - * Get the scheme for a raw URL. + * Get the default scheme for a raw URL. * * @param bool|null $secure * @return string */ - protected function getScheme($secure) + public function formatScheme($secure) { - if (is_null($secure)) { - if (is_null($this->cachedSchema)) { - $this->cachedSchema = $this->forceSchema ?: $this->request->getScheme().'://'; - } - - return $this->cachedSchema; + if (! is_null($secure)) { + return $secure ? 'https://' : 'http://'; } - return $secure ? 'https://' : 'http://'; - } - - /** - * Force the schema for URLs. - * - * @param string $schema - * @return void - */ - public function forceSchema($schema) - { - $this->cachedSchema = null; + if (is_null($this->cachedSchema)) { + $this->cachedSchema = $this->forceScheme ?: $this->request->getScheme().'://'; + } - $this->forceSchema = $schema.'://'; + return $this->cachedSchema; } /** @@ -348,118 +318,18 @@ public function route($name, $parameters = [], $absolute = true) */ protected function toRoute($route, $parameters, $absolute) { - $parameters = $this->formatParameters($parameters); - - $domain = $this->getRouteDomain($route, $parameters); - - $uri = $this->addQueryString($this->buildCompleteUrl( - $root = $this->replaceRoot($route, $domain, $parameters), - $this->replaceRouteParameters($route->uri(), $parameters) - ), $parameters); - - if (preg_match('/\{.*?\}/', $uri)) { - throw UrlGenerationException::forMissingParameters($route); - } - - $uri = strtr(rawurlencode($uri), $this->dontEncode); - - return $absolute ? $uri : '/'.ltrim(str_replace($root, '', $uri), '/'); - } - - /** - * Replace the parameters on the root path. - * - * @param \Illuminate\Routing\Route $route - * @param string $domain - * @param array $parameters - * @return string - */ - protected function replaceRoot($route, $domain, &$parameters) - { - return $this->replaceRouteParameters( - $this->getRouteRoot($route, $domain), $parameters + return $this->routeUrl()->to( + $route, $this->formatParameters($parameters), $absolute ); } - /** - * Replace all of the wildcard parameters for a route path. - * - * @param string $path - * @param array $parameters - * @return string - */ - protected function replaceRouteParameters($path, array &$parameters) - { - $path = $this->replaceNamedParameters($path, $parameters); - - $path = preg_replace_callback('/\{.*?\}/', function ($match) use (&$parameters) { - return (empty($parameters) && ! Str::endsWith($match[0], '?}')) - ? $match[0] - : array_shift($parameters); - }, $path); - - return trim(preg_replace('/\{.*?\?\}/', '', $path), '/'); - } - - /** - * Replace all of the named parameters in the path. - * - * @param string $path - * @param array $parameters - * @return string - */ - protected function replaceNamedParameters($path, &$parameters) - { - return preg_replace_callback('/\{(.*?)\??\}/', function ($m) use (&$parameters) { - if (isset($parameters[$m[1]])) { - return Arr::pull($parameters, $m[1]); - } elseif (isset($this->defaultParameters[$m[1]])) { - return $this->defaultParameters[$m[1]]; - } else { - return $m[0]; - } - }, $path); - } - - /** - * Add a query string to the URI. - * - * @param string $uri - * @param array $parameters - * @return mixed|string - */ - protected function addQueryString($uri, array $parameters) - { - // If the URI has a fragment we will move it to the end of this URI since it will - // need to come after any query string that may be added to the URL else it is - // not going to be available. We will remove it then append it back on here. - if (! is_null($fragment = parse_url($uri, PHP_URL_FRAGMENT))) { - $uri = preg_replace('/#.*/', '', $uri); - } - - $uri .= $this->getRouteQueryString($parameters); - - return is_null($fragment) ? $uri : $uri."#{$fragment}"; - } - /** * Format the array of URL parameters. * * @param mixed|array $parameters * @return array */ - protected function formatParameters($parameters) - { - return $this->replaceRoutableParameters($parameters); - } - - /** - * Replace UrlRoutable parameters with their route parameter. - * - * @param array $parameters - * @return array - */ - protected function replaceRoutableParameters($parameters = []) + public function formatParameters($parameters) { $parameters = is_array($parameters) ? $parameters : [$parameters]; @@ -472,142 +342,6 @@ protected function replaceRoutableParameters($parameters = []) return $parameters; } - /** - * Get the query string for a given route. - * - * @param array $parameters - * @return string - */ - protected function getRouteQueryString(array $parameters) - { - // First we will get all of the string parameters that are remaining after we - // have replaced the route wildcards. We'll then build a query string from - // these string parameters then use it as a starting point for the rest. - if (count($parameters) == 0) { - return ''; - } - - $query = http_build_query( - $keyed = $this->getStringParameters($parameters) - ); - - // Lastly, if there are still parameters remaining, we will fetch the numeric - // parameters that are in the array and add them to the query string or we - // will make the initial query string if it wasn't started with strings. - if (count($keyed) < count($parameters)) { - $query .= '&'.implode( - '&', $this->getNumericParameters($parameters) - ); - } - - return '?'.trim($query, '&'); - } - - /** - * Get the string parameters from a given list. - * - * @param array $parameters - * @return array - */ - protected function getStringParameters(array $parameters) - { - return array_filter($parameters, 'is_string', ARRAY_FILTER_USE_KEY); - } - - /** - * Get the numeric parameters from a given list. - * - * @param array $parameters - * @return array - */ - protected function getNumericParameters(array $parameters) - { - return array_filter($parameters, 'is_numeric', ARRAY_FILTER_USE_KEY); - } - - /** - * Get the formatted domain for a given route. - * - * @param \Illuminate\Routing\Route $route - * @param array $parameters - * @return string - */ - protected function getRouteDomain($route, &$parameters) - { - return $route->domain() ? $this->formatDomain($route, $parameters) : null; - } - - /** - * Format the domain and port for the route and request. - * - * @param \Illuminate\Routing\Route $route - * @param array $parameters - * @return string - */ - protected function formatDomain($route, &$parameters) - { - return $this->addPortToDomain($this->getDomainAndScheme($route)); - } - - /** - * Get the domain and scheme for the route. - * - * @param \Illuminate\Routing\Route $route - * @return string - */ - protected function getDomainAndScheme($route) - { - return $this->getRouteScheme($route).$route->domain(); - } - - /** - * Add the port to the domain if necessary. - * - * @param string $domain - * @return string - */ - protected function addPortToDomain($domain) - { - $secure = $this->request->isSecure(); - - $port = (int) $this->request->getPort(); - - if (($secure && $port === 443) || (! $secure && $port === 80)) { - return $domain; - } - - return $domain.':'.$port; - } - - /** - * Get the root of the route URL. - * - * @param \Illuminate\Routing\Route $route - * @param string $domain - * @return string - */ - protected function getRouteRoot($route, $domain) - { - return $this->getRootUrl($this->getRouteScheme($route), $domain); - } - - /** - * Get the scheme for the given route. - * - * @param \Illuminate\Routing\Route $route - * @return string - */ - protected function getRouteScheme($route) - { - if ($route->httpOnly()) { - return $this->getScheme(false); - } elseif ($route->httpsOnly()) { - return $this->getScheme(true); - } - - return $this->getScheme(null); - } - /** * Get the URL to a controller action. * @@ -640,7 +374,7 @@ public function action($action, $parameters = [], $absolute = true) * @param string $root * @return string */ - protected function getRootUrl($scheme, $root = null) + public function formatRoot($scheme, $root = null) { if (is_null($root)) { if (is_null($this->cachedRoot)) { @@ -656,31 +390,47 @@ protected function getRootUrl($scheme, $root = null) } /** - * Set the forced root URL. + * Extract the query string from the given path. * - * @param string $root + * @param string $path + * @return array + */ + protected function extractQueryString($path) + { + if (($queryPosition = strpos($path, '?')) !== false) { + return [ + substr($path, 0, $queryPosition), + substr($path, $queryPosition) + ]; + } + + return [$path, '']; + } + + /** + * Force the scheme for URLs. + * + * @param string $schema * @return void */ - public function forceRootUrl($root) + public function forceScheme($schema) { - $this->forcedRoot = rtrim($root, '/'); + $this->cachedSchema = null; - $this->cachedRoot = null; + $this->forceScheme = $schema.'://'; } /** - * Determine if the given path is a valid URL. + * Set the forced root URL. * - * @param string $path - * @return bool + * @param string $root + * @return void */ - public function isValidUrl($path) + public function forceRootUrl($root) { - if (Str::startsWith($path, ['#', '//', 'mailto:', 'tel:', 'http://', 'https://'])) { - return true; - } + $this->forcedRoot = rtrim($root, '/'); - return filter_var($path, FILTER_VALIDATE_URL) !== false; + $this->cachedRoot = null; } /** @@ -688,12 +438,11 @@ public function isValidUrl($path) * * @param string $root * @param string $path - * @param string $tail * @return string */ - protected function buildCompleteUrl($root, $path, $tail = '') + public function format($root, $path) { - $path = '/'.trim($path.'/'.$tail, '/'); + $path = '/'.trim($path, '/'); if ($this->formatHostUsing) { $root = call_user_func($this->formatHostUsing, $root); @@ -706,6 +455,35 @@ protected function buildCompleteUrl($root, $path, $tail = '') return trim($root.$path, '/'); } + /** + * Determine if the given path is a valid URL. + * + * @param string $path + * @return bool + */ + public function isValidUrl($path) + { + if (! Str::startsWith($path, ['#', '//', 'mailto:', 'tel:', 'http://', 'https://'])) { + return filter_var($path, FILTER_VALIDATE_URL) !== false; + } + + return true; + } + + /** + * Get the Route URL generator instance. + * + * @return \Illuminate\Routing\RouteUrlGenerator + */ + protected function routeUrl() + { + if (! $this->routeGenerator) { + $this->routeGenerator = new RouteUrlGenerator($this, $this->request); + } + + return $this->routeGenerator; + } + /** * Set the default named parameters used by the URL generator. * @@ -714,9 +492,7 @@ protected function buildCompleteUrl($root, $path, $tail = '') */ public function defaults(array $defaults) { - $this->defaultParameters = array_merge( - $this->defaultParameters, $defaults - ); + $this->routeUrl()->defaults($defaults); } /** @@ -779,6 +555,7 @@ public function setRequest(Request $request) $this->cachedRoot = null; $this->cachedSchema = null; + $this->routeGenerator = null; } /** @@ -794,18 +571,6 @@ public function setRoutes(RouteCollection $routes) return $this; } - /** - * Get the previous URL from the session if possible. - * - * @return string|null - */ - protected function getPreviousUrlFromSession() - { - $session = $this->getSession(); - - return $session ? $session->previousUrl() : null; - } - /** * Get the session implementation from the resolver. * diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index fb6ff5d73070..e43e8bf4d7b0 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -421,7 +421,7 @@ public function testForceRootUrl() $request = Request::create('http://www.foo.com/') ); - $url->forceSchema('https'); + $url->forceScheme('https'); $route = new Route(['GET'], '/foo', ['as' => 'plain']); $routes->add($route);