class UrlGenerator

Same name in this branch

UrlGenerator can generate a URL or a path for any route in the RouteCollection based on the passed parameters.

@author Fabien Potencier <fabien@symfony.com> @author Tobias Schultze <http://tobion.de>

@api

Hierarchy

Expanded class hierarchy of UrlGenerator

2 files declare their use of UrlGenerator
ProviderBasedGenerator.php in drupal/core/vendor/symfony-cmf/routing/Symfony/Cmf/Component/Routing/ProviderBasedGenerator.php
UrlGeneratorTest.php in drupal/core/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php
1 string reference to 'UrlGenerator'
UrlGeneratorTest::getInfo in drupal/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
This method exists to support the simpletest UI runner.

File

drupal/core/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php, line 30

Namespace

Symfony\Component\Routing\Generator
View source
class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface {

  /**
   * @var RouteCollection
   */
  protected $routes;

  /**
   * @var RequestContext
   */
  protected $context;

  /**
   * @var Boolean|null
   */
  protected $strictRequirements = true;

  /**
   * @var LoggerInterface|null
   */
  protected $logger;

  /**
   * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL.
   *
   * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars
   * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g.
   * "?" and "#" (would be interpreted wrongly as query and fragment identifier),
   * "'" and """ (are used as delimiters in HTML).
   */
  protected $decodedChars = array(
    // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning
    // some webservers don't allow the slash in encoded form in the path for security reasons anyway
    // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss
    '%2F' => '/',
    // the following chars are general delimiters in the URI specification but have only special meaning in the authority component
    // so they can safely be used in the path in unencoded form
    '%40' => '@',
    '%3A' => ':',
    // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally
    // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability
    '%3B' => ';',
    '%2C' => ',',
    '%3D' => '=',
    '%2B' => '+',
    '%21' => '!',
    '%2A' => '*',
    '%7C' => '|',
  );

  /**
   * Constructor.
   *
   * @param RouteCollection      $routes  A RouteCollection instance
   * @param RequestContext       $context The context
   * @param LoggerInterface|null $logger  A logger instance
   *
   * @api
   */
  public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) {
    $this->routes = $routes;
    $this->context = $context;
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public function setContext(RequestContext $context) {
    $this->context = $context;
  }

  /**
   * {@inheritdoc}
   */
  public function getContext() {
    return $this->context;
  }

  /**
   * {@inheritdoc}
   */
  public function setStrictRequirements($enabled) {
    $this->strictRequirements = null === $enabled ? null : (bool) $enabled;
  }

  /**
   * {@inheritdoc}
   */
  public function isStrictRequirements() {
    return $this->strictRequirements;
  }

  /**
   * {@inheritDoc}
   */
  public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) {
    if (null === ($route = $this->routes
      ->get($name))) {
      throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route 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, $referenceType, $compiledRoute
      ->getHostTokens());
  }

  /**
   * @throws MissingMandatoryParametersException When some parameters are missing that mandatory for the route
   * @throws InvalidParameterException           When a parameter value for a placeholder is not correct because
   *                                             it does not match the requirement
   */
  protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens) {
    $variables = array_flip($variables);
    $mergedParams = array_replace($defaults, $this->context
      ->getParameters(), $parameters);

    // all params must be given
    if ($diff = array_diff_key($variables, $mergedParams)) {
      throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name));
    }
    $url = '';
    $optional = true;
    foreach ($tokens as $token) {
      if ('variable' === $token[0]) {
        if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) {

          // check requirement
          if (null !== $this->strictRequirements && !preg_match('#^' . $token[2] . '$#', $mergedParams[$token[3]])) {
            $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]);
            if ($this->strictRequirements) {
              throw new InvalidParameterException($message);
            }
            if ($this->logger) {
              $this->logger
                ->error($message);
            }
            return null;
          }
          $url = $token[1] . $mergedParams[$token[3]] . $url;
          $optional = false;
        }
      }
      else {

        // static text
        $url = $token[1] . $url;
        $optional = false;
      }
    }
    if ('' === $url) {
      $url = '/';
    }

    // the contexts base url is already encoded (see Symfony\Component\HttpFoundation\Request)
    $url = strtr(rawurlencode($url), $this->decodedChars);

    // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3
    // so we need to encode them as they are not used for this purpose here
    // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route
    $url = strtr($url, array(
      '/../' => '/%2E%2E/',
      '/./' => '/%2E/',
    ));
    if ('/..' === substr($url, -3)) {
      $url = substr($url, 0, -2) . '%2E%2E';
    }
    elseif ('/.' === substr($url, -2)) {
      $url = substr($url, 0, -1) . '%2E';
    }
    $schemeAuthority = '';
    if ($host = $this->context
      ->getHost()) {
      $scheme = $this->context
        ->getScheme();
      if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) {
        $referenceType = self::ABSOLUTE_URL;
        $scheme = $req;
      }
      if ($hostTokens) {
        $routeHost = '';
        foreach ($hostTokens as $token) {
          if ('variable' === $token[0]) {
            if (null !== $this->strictRequirements && !preg_match('#^' . $token[2] . '$#', $mergedParams[$token[3]])) {
              $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]);
              if ($this->strictRequirements) {
                throw new InvalidParameterException($message);
              }
              if ($this->logger) {
                $this->logger
                  ->error($message);
              }
              return null;
            }
            $routeHost = $token[1] . $mergedParams[$token[3]] . $routeHost;
          }
          else {
            $routeHost = $token[1] . $routeHost;
          }
        }
        if ($routeHost !== $host) {
          $host = $routeHost;
          if (self::ABSOLUTE_URL !== $referenceType) {
            $referenceType = self::NETWORK_PATH;
          }
        }
      }
      if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) {
        $port = '';
        if ('http' === $scheme && 80 != $this->context
          ->getHttpPort()) {
          $port = ':' . $this->context
            ->getHttpPort();
        }
        elseif ('https' === $scheme && 443 != $this->context
          ->getHttpsPort()) {
          $port = ':' . $this->context
            ->getHttpsPort();
        }
        $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "{$scheme}://";
        $schemeAuthority .= $host . $port;
      }
    }
    if (self::RELATIVE_PATH === $referenceType) {
      $url = self::getRelativePath($this->context
        ->getPathInfo(), $url);
    }
    else {
      $url = $schemeAuthority . $this->context
        ->getBaseUrl() . $url;
    }

    // add a query string if needed
    $extra = array_diff_key($parameters, $variables, $defaults);
    if ($extra && ($query = http_build_query($extra, '', '&'))) {
      $url .= '?' . $query;
    }
    return $url;
  }

  /**
   * Returns the target path as relative reference from the base path.
   *
   * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash.
   * Both paths must be absolute and not contain relative parts.
   * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
   * Furthermore, they can be used to reduce the link size in documents.
   *
   * Example target paths, given a base path of "/a/b/c/d":
   * - "/a/b/c/d"     -> ""
   * - "/a/b/c/"      -> "./"
   * - "/a/b/"        -> "../"
   * - "/a/b/c/other" -> "other"
   * - "/a/x/y"       -> "../../x/y"
   *
   * @param string $basePath   The base path
   * @param string $targetPath The target path
   *
   * @return string The relative target path
   */
  public static function getRelativePath($basePath, $targetPath) {
    if ($basePath === $targetPath) {
      return '';
    }
    $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
    $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath);
    array_pop($sourceDirs);
    $targetFile = array_pop($targetDirs);
    foreach ($sourceDirs as $i => $dir) {
      if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
        unset($sourceDirs[$i], $targetDirs[$i]);
      }
      else {
        break;
      }
    }
    $targetDirs[] = $targetFile;
    $path = str_repeat('../', count($sourceDirs)) . implode('/', $targetDirs);

    // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
    // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
    // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
    // (see http://tools.ietf.org/html/rfc3986#section-4.2).
    return '' === $path || '/' === $path[0] || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) ? "./{$path}" : $path;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
UrlGenerator::$context protected property
UrlGenerator::$decodedChars protected property This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL.
UrlGenerator::$logger protected property
UrlGenerator::$routes protected property
UrlGenerator::$strictRequirements protected property
UrlGenerator::doGenerate protected function 2
UrlGenerator::generate public function Generates a URL or path for a specific route based on the given parameters. Overrides UrlGeneratorInterface::generate 2
UrlGenerator::getContext public function Gets the request context. Overrides RequestContextAwareInterface::getContext 1
UrlGenerator::getRelativePath public static function Returns the target path as relative reference from the base path.
UrlGenerator::isStrictRequirements public function Returns whether to throw an exception on incorrect parameters. Null means the requirements check is deactivated completely. Overrides ConfigurableRequirementsInterface::isStrictRequirements
UrlGenerator::setContext public function Sets the request context. Overrides RequestContextAwareInterface::setContext 1
UrlGenerator::setStrictRequirements public function Enables or disables the exception on incorrect parameters. Passing null will deactivate the requirements check completely. Overrides ConfigurableRequirementsInterface::setStrictRequirements
UrlGenerator::__construct public function Constructor. 2
UrlGeneratorInterface::ABSOLUTE_PATH constant Generates an absolute path, e.g. "/dir/file".
UrlGeneratorInterface::ABSOLUTE_URL constant Generates an absolute URL, e.g. "http://example.com/dir/file".
UrlGeneratorInterface::NETWORK_PATH constant Generates a network path, e.g. "//example.com/dir/file". Such reference reuses the current scheme but specifies the host.
UrlGeneratorInterface::RELATIVE_PATH constant Generates a relative path based on the current request path, e.g. "../parent-file".