[Routing] add support for path-relative and scheme-relative URL generation

This commit is contained in:
Tobias Schultze 2012-04-16 10:25:33 +02:00
parent 18c520a5e8
commit 75f59ebe01
13 changed files with 306 additions and 68 deletions

View File

@ -40,14 +40,14 @@ class RoutingExtension extends \Twig_Extension
); );
} }
public function getPath($name, $parameters = array()) public function getPath($name, $parameters = array(), $relative = false)
{ {
return $this->generator->generate($name, $parameters, false); return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
} }
public function getUrl($name, $parameters = array()) public function getUrl($name, $parameters = array(), $schemeRelative = false)
{ {
return $this->generator->generate($name, $parameters, true); return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL);
} }
/** /**

View File

@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Controller; namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
@ -19,8 +20,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Form\FormTypeInterface; use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Doctrine\Bundle\DoctrineBundle\Registry; use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Component\HttpFoundation\Request;
/** /**
* Controller is a simple implementation of a Controller. * Controller is a simple implementation of a Controller.
@ -34,15 +35,15 @@ class Controller extends ContainerAware
/** /**
* Generates a URL from the given parameters. * Generates a URL from the given parameters.
* *
* @param string $route The name of the route * @param string $route The name of the route
* @param mixed $parameters An array of parameters * @param mixed $parameters An array of parameters
* @param Boolean $absolute Whether to generate an absolute URL * @param string $referenceType The type of reference (see UrlGeneratorInterface)
* *
* @return string The generated URL * @return string The generated URL
*/ */
public function generateUrl($route, $parameters = array(), $absolute = false) public function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{ {
return $this->container->get('router')->generate($route, $parameters, $absolute); return $this->container->get('router')->generate($route, $parameters, $referenceType);
} }
/** /**

View File

@ -12,8 +12,9 @@
namespace Symfony\Bundle\FrameworkBundle\Controller; namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/** /**
* Redirects a request to another URL. * Redirects a request to another URL.
@ -45,7 +46,7 @@ class RedirectController extends ContainerAware
$attributes = $this->container->get('request')->attributes->get('_route_params'); $attributes = $this->container->get('request')->attributes->get('_route_params');
unset($attributes['route'], $attributes['permanent']); unset($attributes['route'], $attributes['permanent']);
return new RedirectResponse($this->container->get('router')->generate($route, $attributes, true), $permanent ? 301 : 302); return new RedirectResponse($this->container->get('router')->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $permanent ? 301 : 302);
} }
/** /**

View File

@ -36,15 +36,15 @@ class RouterHelper extends Helper
/** /**
* Generates a URL from the given parameters. * Generates a URL from the given parameters.
* *
* @param string $name The name of the route * @param string $name The name of the route
* @param mixed $parameters An array of parameters * @param mixed $parameters An array of parameters
* @param Boolean $absolute Whether to generate an absolute URL * @param string $referenceType The type of reference (see UrlGeneratorInterface)
* *
* @return string The generated URL * @return string The generated URL
*/ */
public function generate($name, $parameters = array(), $absolute = false) public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{ {
return $this->generator->generate($name, $parameters, $absolute); return $this->generator->generate($name, $parameters, $referenceType);
} }
/** /**

View File

@ -55,36 +55,40 @@ class LogoutUrlHelper extends Helper
} }
/** /**
* Generate the relative logout URL for the firewall. * Generates the absolute logout path for the firewall.
* *
* @param string $key The firewall key * @param string $key The firewall key
* @return string The relative logout URL *
* @return string The logout path
*/ */
public function getLogoutPath($key) public function getLogoutPath($key)
{ {
return $this->generateLogoutUrl($key, false); return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_PATH);
} }
/** /**
* Generate the absolute logout URL for the firewall. * Generates the absolute logout URL for the firewall.
* *
* @param string $key The firewall key * @param string $key The firewall key
* @return string The absolute logout URL *
* @return string The logout URL
*/ */
public function getLogoutUrl($key) public function getLogoutUrl($key)
{ {
return $this->generateLogoutUrl($key, true); return $this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL);
} }
/** /**
* Generate the logout URL for the firewall. * Generates the logout URL for the firewall.
*
* @param string $key The firewall key
* @param string $referenceType The type of reference (see UrlGeneratorInterface)
* *
* @param string $key The firewall key
* @param Boolean $absolute Whether to generate an absolute URL
* @return string The logout URL * @return string The logout URL
*
* @throws \InvalidArgumentException if no LogoutListener is registered for the key * @throws \InvalidArgumentException if no LogoutListener is registered for the key
*/ */
private function generateLogoutUrl($key, $absolute) private function generateLogoutUrl($key, $referenceType)
{ {
if (!array_key_exists($key, $this->listeners)) { if (!array_key_exists($key, $this->listeners)) {
throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key)); throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key));
@ -97,13 +101,13 @@ class LogoutUrlHelper extends Helper
if ('/' === $logoutPath[0]) { if ('/' === $logoutPath[0]) {
$request = $this->container->get('request'); $request = $this->container->get('request');
$url = ($absolute ? $request->getUriForPath($logoutPath) : $request->getBasePath() . $logoutPath); $url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBasePath() . $logoutPath;
if (!empty($parameters)) { if (!empty($parameters)) {
$url .= '?' . http_build_query($parameters); $url .= '?' . http_build_query($parameters);
} }
} else { } else {
$url = $this->router->generate($logoutPath, $parameters, $absolute); $url = $this->router->generate($logoutPath, $parameters, $referenceType);
} }
return $url; return $url;

View File

@ -12,9 +12,10 @@
namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Security; namespace Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\FormLoginBundle\Security;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
class LocalizedFormFailureHandler implements AuthenticationFailureHandlerInterface class LocalizedFormFailureHandler implements AuthenticationFailureHandlerInterface
@ -28,6 +29,6 @@ class LocalizedFormFailureHandler implements AuthenticationFailureHandlerInterfa
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{ {
return new RedirectResponse($this->router->generate('localized_login_path', array(), true)); return new RedirectResponse($this->router->generate('localized_login_path', array(), UrlGeneratorInterface::ABSOLUTE_URL));
} }
} }

View File

@ -108,7 +108,7 @@ EOF;
private function generateGenerateMethod() private function generateGenerateMethod()
{ {
return <<<EOF return <<<EOF
public function generate(\$name, \$parameters = array(), \$absolute = false) public function generate(\$name, \$parameters = array(), \$referenceType = self::ABSOLUTE_PATH)
{ {
if (!isset(self::\$declaredRoutes[\$name])) { if (!isset(self::\$declaredRoutes[\$name])) {
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', \$name)); throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', \$name));
@ -116,7 +116,7 @@ EOF;
list(\$variables, \$defaults, \$requirements, \$tokens, \$hostnameTokens) = self::\$declaredRoutes[\$name]; list(\$variables, \$defaults, \$requirements, \$tokens, \$hostnameTokens) = self::\$declaredRoutes[\$name];
return \$this->doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$absolute, \$hostnameTokens); return \$this->doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$referenceType, \$hostnameTokens);
} }
EOF; EOF;
} }

View File

@ -127,7 +127,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function generate($name, $parameters = array(), $absolute = false) public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
{ {
if (null === $route = $this->routes->get($name)) { if (null === $route = $this->routes->get($name)) {
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name)); throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
@ -136,15 +136,40 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
// the Route has a cache of its own and is not recompiled as long as it does not get modified // the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile(); $compiledRoute = $route->compile();
return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $absolute, $compiledRoute->getHostnameTokens()); return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostnameTokens());
}
/**
* This method converts the reference type to the new value introduced in Symfony 2.2. It can be used by
* other UrlGenerator implementations to be BC with Symfony 2.1. Reference type was a Boolean called
* $absolute in Symfony 2.1 and only supported two reference types.
*
* @param Boolean $absolute Whether to generate an absolute URL
*
* @return string The new reference type
*
* @deprecated Deprecated since version 2.2, to be removed in 2.3.
*/
public static function convertReferenceType($absolute)
{
if (false === $absolute) {
return self::ABSOLUTE_PATH;
}
if (true === $absolute) {
return self::ABSOLUTE_URL;
}
return $absolute;
} }
/** /**
* @throws MissingMandatoryParametersException When route has some missing mandatory parameters * @throws MissingMandatoryParametersException When route has some missing mandatory parameters
* @throws InvalidParameterException When a parameter value is not correct * @throws InvalidParameterException When a parameter value is not correct
*/ */
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute, $hostnameTokens) protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostnameTokens)
{ {
$referenceType = self::convertReferenceType($referenceType);
$variables = array_flip($variables); $variables = array_flip($variables);
$mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters);
@ -186,8 +211,8 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$url = '/'; $url = '/';
} }
// do not encode the contexts base url as it is already encoded (see Symfony\Component\HttpFoundation\Request) // the contexts base url is already encoded (see Symfony\Component\HttpFoundation\Request)
$url = $this->context->getBaseUrl().strtr(rawurlencode($url), $this->decodedChars); $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 // 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 // so we need to encode them as they are not used for this purpose here
@ -199,16 +224,11 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$url = substr($url, 0, -1) . '%2E'; $url = substr($url, 0, -1) . '%2E';
} }
// add a query string if needed $schemeAuthority = '';
$extra = array_diff_key($parameters, $variables);
if ($extra && $query = http_build_query($extra, '', '&')) {
$url .= '?'.$query;
}
if ($host = $this->context->getHost()) { if ($host = $this->context->getHost()) {
$scheme = $this->context->getScheme(); $scheme = $this->context->getScheme();
if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme != $req) { if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) {
$absolute = true; $referenceType = self::ABSOLUTE_URL;
$scheme = $req; $scheme = $req;
} }
@ -231,18 +251,20 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
} }
$routeHost = $token[1].$mergedParams[$token[3]].$routeHost; $routeHost = $token[1].$mergedParams[$token[3]].$routeHost;
} elseif ('text' === $token[0]) { } else {
$routeHost = $token[1].$routeHost; $routeHost = $token[1].$routeHost;
} }
} }
if ($routeHost != $host) { if ($routeHost !== $host) {
$host = $routeHost; $host = $routeHost;
$absolute = true; if (self::ABSOLUTE_URL !== $referenceType) {
$referenceType = self::NETWORK_PATH;
}
} }
} }
if ($absolute) { if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) {
$port = ''; $port = '';
if ('http' === $scheme && 80 != $this->context->getHttpPort()) { if ('http' === $scheme && 80 != $this->context->getHttpPort()) {
$port = ':'.$this->context->getHttpPort(); $port = ':'.$this->context->getHttpPort();
@ -250,10 +272,74 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
$port = ':'.$this->context->getHttpsPort(); $port = ':'.$this->context->getHttpsPort();
} }
$url = $scheme.'://'.$host.$port.$url; $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);
if ($extra && $query = http_build_query($extra, '', '&')) {
$url .= '?'.$query;
}
return $url; return $url;
} }
/**
* Returns the target path as relative reference from the base path.
*
* Only the URIs path component (no schema, hostname 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;
}
} }

View File

@ -18,20 +18,34 @@ use Symfony\Component\Routing\Exception\RouteNotFoundException;
* UrlGeneratorInterface is the interface that all URL generator classes must implement. * UrlGeneratorInterface is the interface that all URL generator classes must implement.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
* *
* @api * @api
*/ */
interface UrlGeneratorInterface extends RequestContextAwareInterface interface UrlGeneratorInterface extends RequestContextAwareInterface
{ {
/**
* These constants define the different types of resource references that are declared
* in RFC 3986: http://tools.ietf.org/html/rfc3986
* We are using the term "URL" instead of "URI" as this is more common in web applications
* and we do not need to distinguish them as the difference is mostly semantical and
* less technical. Generating URIs, i.e. representation-independent resource identifiers,
* is still possible.
*/
const ABSOLUTE_URL = 'url';
const ABSOLUTE_PATH = 'path';
const RELATIVE_PATH = 'relative';
const NETWORK_PATH = 'network';
/** /**
* Generates a URL from the given parameters. * Generates a URL from the given parameters.
* *
* If the generator is not able to generate the url, it must throw the RouteNotFoundException * If the generator is not able to generate the url, it must throw the RouteNotFoundException
* as documented below. * as documented below.
* *
* @param string $name The name of the route * @param string $name The name of the route
* @param mixed $parameters An array of parameters * @param mixed $parameters An array of parameters
* @param Boolean $absolute Whether to generate an absolute URL * @param string $referenceType The type of reference to be generated (see defined constants)
* *
* @return string The generated URL * @return string The generated URL
* *
@ -39,5 +53,5 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface
* *
* @api * @api
*/ */
public function generate($name, $parameters = array(), $absolute = false); public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH);
} }

View File

@ -23,6 +23,7 @@ use Symfony\Component\HttpFoundation\Request;
class RequestContext class RequestContext
{ {
private $baseUrl; private $baseUrl;
private $pathInfo;
private $method; private $method;
private $host; private $host;
private $scheme; private $scheme;
@ -46,7 +47,7 @@ class RequestContext
* *
* @api * @api
*/ */
public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443) public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/')
{ {
$this->baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
$this->method = strtoupper($method); $this->method = strtoupper($method);
@ -54,11 +55,13 @@ class RequestContext
$this->scheme = strtolower($scheme); $this->scheme = strtolower($scheme);
$this->httpPort = $httpPort; $this->httpPort = $httpPort;
$this->httpsPort = $httpsPort; $this->httpsPort = $httpsPort;
$this->pathInfo = $path;
} }
public function fromRequest(Request $request) public function fromRequest(Request $request)
{ {
$this->setBaseUrl($request->getBaseUrl()); $this->setBaseUrl($request->getBaseUrl());
$this->setPathInfo($request->getPathInfo());
$this->setMethod($request->getMethod()); $this->setMethod($request->getMethod());
$this->setHost($request->getHost()); $this->setHost($request->getHost());
$this->setScheme($request->getScheme()); $this->setScheme($request->getScheme());
@ -88,6 +91,26 @@ class RequestContext
$this->baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
} }
/**
* Gets the path info.
*
* @return string The path info
*/
public function getPathInfo()
{
return $this->pathInfo;
}
/**
* Sets the path info.
*
* @param string $pathInfo The path info
*/
public function setPathInfo($pathInfo)
{
$this->pathInfo = $pathInfo;
}
/** /**
* Gets the HTTP method. * Gets the HTTP method.
* *

View File

@ -202,9 +202,9 @@ class Router implements RouterInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function generate($name, $parameters = array(), $absolute = false) public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
{ {
return $this->getGenerator()->generate($name, $parameters, $absolute); return $this->getGenerator()->generate($name, $parameters, $referenceType);
} }
/** /**

View File

@ -379,7 +379,6 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
*/ */
public function testDefaultRequirementOfVariableDisallowsNextSeparator() public function testDefaultRequirementOfVariableDisallowsNextSeparator()
{ {
$routes = $this->getRoutes('test', new Route('/{page}.{_format}')); $routes = $this->getRoutes('test', new Route('/{page}.{_format}'));
$this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html')); $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html'));
} }
@ -388,7 +387,7 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
{ {
$routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com'));
$this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' =>'Fabien', 'locale' => 'fr'))); $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' =>'Fabien', 'locale' => 'fr')));
} }
public function testWithHostnameSameAsContext() public function testWithHostnameSameAsContext()
@ -440,6 +439,120 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
$this->assertNull($generator->generate('test', array('foo' => 'baz'), false)); $this->assertNull($generator->generate('test', array('foo' => 'baz'), false));
} }
/**
* @dataProvider provideRelativePaths
*/
public function testGetRelativePath($sourcePath, $targetPath, $expectedPath)
{
$this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath));
}
public function provideRelativePaths()
{
return array(
array(
'/same/dir/',
'/same/dir/',
''
),
array(
'/same/file',
'/same/file',
''
),
array(
'/',
'/file',
'file'
),
array(
'/',
'/dir/file',
'dir/file'
),
array(
'/dir/file.html',
'/dir/different-file.html',
'different-file.html'
),
array(
'/same/dir/extra-file',
'/same/dir/',
'./'
),
array(
'/parent/dir/',
'/parent/',
'../'
),
array(
'/parent/dir/extra-file',
'/parent/',
'../'
),
array(
'/a/b/',
'/x/y/z/',
'../../x/y/z/'
),
array(
'/a/b/c/d/e',
'/a/c/d',
'../../../c/d'
),
array(
'/a/b/c//',
'/a/b/c/',
'../'
),
array(
'/a/b/c/',
'/a/b/c//',
'.//'
),
array(
'/root/a/b/c/',
'/root/x/b/c/',
'../../../x/b/c/'
),
array(
'/a/b/c/d/',
'/a',
'../../../../a'
),
array(
'/special-chars/sp%20ce/1€/mäh/e=mc²',
'/special-chars/sp%20ce/1€/<µ>/e=mc²',
'../<µ>/e=mc²'
),
array(
'not-rooted',
'dir/file',
'dir/file'
),
array(
'//dir/',
'',
'../../'
),
array(
'/dir/',
'/dir/file:with-colon',
'./file:with-colon'
),
array(
'/dir/',
'/dir/subdir/file:with-colon',
'subdir/file:with-colon'
),
array(
'/dir/',
'/dir/:subdir/',
'./:subdir/'
),
);
}
protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null)
{ {
$context = new RequestContext('/app.php'); $context = new RequestContext('/app.php');

View File

@ -67,8 +67,8 @@ class HttpUtils
public function createRequest(Request $request, $path) public function createRequest(Request $request, $path)
{ {
$newRequest = Request::create($this->generateUri($request, $path), 'get', array(), $request->cookies->all(), array(), $request->server->all()); $newRequest = Request::create($this->generateUri($request, $path), 'get', array(), $request->cookies->all(), array(), $request->server->all());
if ($session = $request->getSession()) { if ($request->hasSession()) {
$newRequest->setSession($session); $newRequest->setSession($request->getSession());
} }
if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) { if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
@ -127,15 +127,10 @@ class HttpUtils
return $request->getUriForPath($path); return $request->getUriForPath($path);
} }
return $this->generateUrl($path, true);
}
private function generateUrl($route, $absolute = false)
{
if (null === $this->urlGenerator) { if (null === $this->urlGenerator) {
throw new \LogicException('You must provide a UrlGeneratorInterface instance to be able to use routes.'); throw new \LogicException('You must provide a UrlGeneratorInterface instance to be able to use routes.');
} }
return $this->urlGenerator->generate($route, array(), $absolute); return $this->urlGenerator->generate($path, array(), UrlGeneratorInterface::ABSOLUTE_URL);
} }
} }