Skip to content

Commit

Permalink
[Routing] UrlHelper to get absolute URL for a path
Browse files Browse the repository at this point in the history
  • Loading branch information
vudaltsov authored and fabpot committed Apr 7, 2019
1 parent b4ffe39 commit 29a57c7
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CHANGELOG
* deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`.
* deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`.
* deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`.
* added `UrlHelper` that allows to get an absolute URL and a relative path for a given path

4.2.0
-----
Expand Down
143 changes: 143 additions & 0 deletions Tests/UrlHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpFoundation\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\UrlHelper;
use Symfony\Component\Routing\RequestContext;

class UrlHelperTest extends TestCase
{
/**
* @dataProvider getGenerateAbsoluteUrlData()
*/
public function testGenerateAbsoluteUrl($expected, $path, $pathinfo)
{
$stack = new RequestStack();
$stack->push(Request::create($pathinfo));
$helper = new UrlHelper($stack);

$this->assertEquals($expected, $helper->getAbsoluteUrl($path));
}

public function getGenerateAbsoluteUrlData()
{
return [
['http://localhost/foo.png', '/foo.png', '/foo/bar.html'],
['http://localhost/foo/foo.png', 'foo.png', '/foo/bar.html'],
['http://localhost/foo/foo.png', 'foo.png', '/foo/bar'],
['http://localhost/foo/bar/foo.png', 'foo.png', '/foo/bar/'],

['http://example.com/baz', 'http://example.com/baz', '/'],
['https://example.com/baz', 'https://example.com/baz', '/'],
['//example.com/baz', '//example.com/baz', '/'],

['http://localhost/foo/bar?baz', '?baz', '/foo/bar'],
['http://localhost/foo/bar?baz=1', '?baz=1', '/foo/bar?foo=1'],
['http://localhost/foo/baz?baz=1', 'baz?baz=1', '/foo/bar?foo=1'],

['http://localhost/foo/bar#baz', '#baz', '/foo/bar'],
['http://localhost/foo/bar?0#baz', '#baz', '/foo/bar?0'],
['http://localhost/foo/bar?baz=1#baz', '?baz=1#baz', '/foo/bar?foo=1'],
['http://localhost/foo/baz?baz=1#baz', 'baz?baz=1#baz', '/foo/bar?foo=1'],
];
}

/**
* @dataProvider getGenerateAbsoluteUrlRequestContextData
*/
public function testGenerateAbsoluteUrlWithRequestContext($path, $baseUrl, $host, $scheme, $httpPort, $httpsPort, $expected)
{
if (!class_exists('Symfony\Component\Routing\RequestContext')) {
$this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.');
}

$requestContext = new RequestContext($baseUrl, 'GET', $host, $scheme, $httpPort, $httpsPort, $path);
$helper = new UrlHelper(new RequestStack(), $requestContext);

$this->assertEquals($expected, $helper->getAbsoluteUrl($path));
}

/**
* @dataProvider getGenerateAbsoluteUrlRequestContextData
*/
public function testGenerateAbsoluteUrlWithoutRequestAndRequestContext($path)
{
if (!class_exists('Symfony\Component\Routing\RequestContext')) {
$this->markTestSkipped('The Routing component is needed to run tests that depend on its request context.');
}

$helper = new UrlHelper(new RequestStack());

$this->assertEquals($path, $helper->getAbsoluteUrl($path));
}

public function getGenerateAbsoluteUrlRequestContextData()
{
return [
['/foo.png', '/foo', 'localhost', 'http', 80, 443, 'http://localhost/foo.png'],
['foo.png', '/foo', 'localhost', 'http', 80, 443, 'http://localhost/foo/foo.png'],
['foo.png', '/foo/bar/', 'localhost', 'http', 80, 443, 'http://localhost/foo/bar/foo.png'],
['/foo.png', '/foo', 'localhost', 'https', 80, 443, 'https://localhost/foo.png'],
['foo.png', '/foo', 'localhost', 'https', 80, 443, 'https://localhost/foo/foo.png'],
['foo.png', '/foo/bar/', 'localhost', 'https', 80, 443, 'https://localhost/foo/bar/foo.png'],
['/foo.png', '/foo', 'localhost', 'http', 443, 80, 'http://localhost:443/foo.png'],
['/foo.png', '/foo', 'localhost', 'https', 443, 80, 'https://localhost:80/foo.png'],
];
}

public function testGenerateAbsoluteUrlWithScriptFileName()
{
$request = Request::create('http://localhost/app/web/app_dev.php');
$request->server->set('SCRIPT_FILENAME', '/var/www/app/web/app_dev.php');

$stack = new RequestStack();
$stack->push($request);
$helper = new UrlHelper($stack);

$this->assertEquals(
'http://localhost/app/web/bundles/framework/css/structure.css',
$helper->getAbsoluteUrl('/app/web/bundles/framework/css/structure.css')
);
}

/**
* @dataProvider getGenerateRelativePathData()
*/
public function testGenerateRelativePath($expected, $path, $pathinfo)
{
if (!method_exists('Symfony\Component\HttpFoundation\Request', 'getRelativeUriForPath')) {
$this->markTestSkipped('Your version of Symfony HttpFoundation is too old.');
}

$stack = new RequestStack();
$stack->push(Request::create($pathinfo));
$urlHelper = new UrlHelper($stack);

$this->assertEquals($expected, $urlHelper->getRelativePath($path));
}

public function getGenerateRelativePathData()
{
return [
['../foo.png', '/foo.png', '/foo/bar.html'],
['../baz/foo.png', '/baz/foo.png', '/foo/bar.html'],
['baz/foo.png', 'baz/foo.png', '/foo/bar.html'],

['http://example.com/baz', 'http://example.com/baz', '/'],
['https://example.com/baz', 'https://example.com/baz', '/'],
['//example.com/baz', '//example.com/baz', '/'],
];
}
}
102 changes: 102 additions & 0 deletions UrlHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpFoundation;

use Symfony\Component\Routing\RequestContext;

/**
* A helper service for manipulating URLs within and outside the request scope.
*
* @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
*/
final class UrlHelper
{
private $requestStack;
private $requestContext;

public function __construct(RequestStack $requestStack, ?RequestContext $requestContext = null)
{
$this->requestStack = $requestStack;
$this->requestContext = $requestContext;
}

public function getAbsoluteUrl(string $path): string
{
if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
return $path;
}

if (null === $request = $this->requestStack->getMasterRequest()) {
return $this->getAbsoluteUrlFromContext($path);
}

if ('#' === $path[0]) {
$path = $request->getRequestUri().$path;
} elseif ('?' === $path[0]) {
$path = $request->getPathInfo().$path;
}

if (!$path || '/' !== $path[0]) {
$prefix = $request->getPathInfo();
$last = \strlen($prefix) - 1;
if ($last !== $pos = strrpos($prefix, '/')) {
$prefix = substr($prefix, 0, $pos).'/';
}

return $request->getUriForPath($prefix.$path);
}

return $request->getSchemeAndHttpHost().$path;
}

public function getRelativePath(string $path): string
{
if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
return $path;
}

if (null === $request = $this->requestStack->getMasterRequest()) {
return $path;
}

return $request->getRelativeUriForPath($path);
}

private function getAbsoluteUrlFromContext(string $path): string
{
if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) {
return $path;
}

$scheme = $this->requestContext->getScheme();
$port = '';

if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) {
$port = ':'.$this->requestContext->getHttpPort();
} elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) {
$port = ':'.$this->requestContext->getHttpsPort();
}

if ('#' === $path[0]) {
$queryString = $this->requestContext->getQueryString();
$path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path;
} elseif ('?' === $path[0]) {
$path = $this->requestContext->getPathInfo().$path;
}

if ('/' !== $path[0]) {
$path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path;
}

return $scheme.'://'.$host.$port.$path;
}
}

0 comments on commit 29a57c7

Please sign in to comment.