* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Routing\Generator; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; /** * UrlGenerator generates a URL based on a set of routes. * * @author Fabien Potencier * * @api */ class UrlGenerator implements UrlGeneratorInterface { protected $context; protected $decodedChars = array( // %2F is not valid in a URL, so we don't encode it (which is fine as the requirements explicitely allowed it) '%2F' => '/', ); protected $routes; /** * Constructor. * * @param RouteCollection $routes A RouteCollection instance * @param RequestContext $context The context * * @api */ public function __construct(RouteCollection $routes, RequestContext $context) { $this->routes = $routes; $this->context = $context; } /** * Sets the request context. * * @param RequestContext $context The context * * @api */ public function setContext(RequestContext $context) { $this->context = $context; } /** * Gets the request context. * * @return RequestContext The context */ public function getContext() { return $this->context; } /** * {@inheritDoc} * * @api */ public function generate($name, $parameters = array(), $absolute = false) { if (null === $route = $this->routes->get($name)) { throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name)); } // the Route has a cache of its own and is not recompiled as long as it does not get modified $compiledRoute = $route->compile(); return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $absolute); } /** * @throws MissingMandatoryParametersException When route has some missing mandatory parameters * @throws InvalidParameterException When a parameter value is not correct */ protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute) { $variables = array_flip($variables); $originParameters = $parameters; $parameters = array_replace($this->context->getParameters(), $parameters); $tparams = array_replace($defaults, $parameters); // all params must be given if ($diff = array_diff_key($variables, $tparams)) { throw new MissingMandatoryParametersException(sprintf('The "%s" route has some missing mandatory parameters ("%s").', $name, implode('", "', array_keys($diff)))); } $url = ''; $optional = true; foreach ($tokens as $token) { if ('variable' === $token[0]) { if (false === $optional || !array_key_exists($token[3], $defaults) || (isset($parameters[$token[3]]) && (string) $parameters[$token[3]] != (string) $defaults[$token[3]])) { if (!$isEmpty = in_array($tparams[$token[3]], array(null, '', false), true)) { // check requirement if ($tparams[$token[3]] && !preg_match('#^'.$token[2].'$#', $tparams[$token[3]])) { throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]])); } } if (!$isEmpty || !$optional) { $url = $token[1].strtr(rawurlencode($tparams[$token[3]]), $this->decodedChars).$url; } $optional = false; } } elseif ('text' === $token[0]) { $url = $token[1].$url; $optional = false; } } if (!$url) { $url = '/'; } // add a query string if needed $extra = array_diff_key($originParameters, $variables, $defaults); if ($extra && $query = http_build_query($extra)) { $url .= '?'.$query; } $url = $this->context->getBaseUrl().$url; if ($this->context->getHost()) { $scheme = $this->context->getScheme(); if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme != $req) { $absolute = true; $scheme = $req; } if ($absolute) { $port = ''; if ('http' === $scheme && 80 != $this->context->getHttpPort()) { $port = ':'.$this->context->getHttpPort(); } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { $port = ':'.$this->context->getHttpsPort(); } $url = $scheme.'://'.$this->context->getHost().$port.$url; } } return $url; } }