Skip to content

Commit 3266887

Browse files
committed
Allow to configure a redirection from the route configuration
1 parent 332ad0a commit 3266887

File tree

12 files changed

+266
-1
lines changed

12 files changed

+266
-1
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\EventListener;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;
15+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
16+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
17+
use Symfony\Component\HttpKernel\KernelEvents;
18+
19+
/**
20+
* Resolve the controller for requests containing the `_redirect_to` attributes.
21+
*
22+
* @author Samuel Roze <samuel.roze@gmail.com>
23+
*/
24+
class ResolveRedirectControllerSubscriber implements EventSubscriberInterface
25+
{
26+
public static function getSubscribedEvents()
27+
{
28+
return array(
29+
KernelEvents::REQUEST => array('onKernelRequest', 20),
30+
);
31+
}
32+
33+
public function onKernelRequest(GetResponseEvent $event)
34+
{
35+
$requestAttributes = $event->getRequest()->attributes;
36+
37+
if (!$requestAttributes->has('_controller') && $redirectTo = $requestAttributes->get('_redirect_to')) {
38+
if ($this->looksLikeUrl($redirectTo)) {
39+
$requestAttributes->set('_controller', array(RedirectController::class, 'urlRedirectAction'));
40+
$requestAttributes->set('path', $redirectTo);
41+
} else {
42+
$requestAttributes->set('_controller', array(RedirectController::class, 'redirectAction'));
43+
$requestAttributes->set('route', $redirectTo);
44+
}
45+
46+
if (!$requestAttributes->has('permanent')) {
47+
$requestAttributes->set('permanent', $requestAttributes->get('_redirect_permanent', false));
48+
}
49+
}
50+
}
51+
52+
private function looksLikeUrl(string $urlOrRouteName): bool
53+
{
54+
foreach (array('/', 'http://', 'https://') as $pattern) {
55+
if (0 === strpos($urlOrRouteName, $pattern)) {
56+
return true;
57+
}
58+
}
59+
60+
return false;
61+
}
62+
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,9 @@
7575
<argument type="service" id="controller_name_converter" />
7676
<tag name="kernel.event_subscriber" />
7777
</service>
78+
79+
<service id="resolve_redirect_controller_name_subscriber" class="Symfony\Bundle\FrameworkBundle\EventListener\ResolveRedirectControllerSubscriber">
80+
<tag name="kernel.event_subscriber" />
81+
</service>
7882
</services>
7983
</container>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\EventListener;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;
15+
use Symfony\Bundle\FrameworkBundle\EventListener\ResolveRedirectControllerSubscriber;
16+
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
19+
use Symfony\Component\HttpKernel\HttpKernelInterface;
20+
21+
class ResolveRedirectControllerSubscriberTest extends TestCase
22+
{
23+
/**
24+
* @dataProvider provideRedirectionExamples
25+
*/
26+
public function testSetControllerForRedirectToRoute(Request $request, array $expectedAttributes)
27+
{
28+
$httpKernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock();
29+
$subscriber = new ResolveRedirectControllerSubscriber();
30+
$subscriber->onKernelRequest(new GetResponseEvent($httpKernel, $request, HttpKernelInterface::MASTER_REQUEST));
31+
32+
foreach ($expectedAttributes as $name => $value) {
33+
$this->assertEquals($value, $request->attributes->get($name));
34+
}
35+
}
36+
37+
public function provideRedirectionExamples()
38+
{
39+
// No redirection
40+
yield array($this->requestWithAttributes(array(
41+
'_controller' => 'AppBundle:Starting:format',
42+
)), array(
43+
'_controller' => 'AppBundle:Starting:format',
44+
));
45+
46+
// Controller win over redirection
47+
yield array($this->requestWithAttributes(array(
48+
'_controller' => 'AppBundle:Starting:format',
49+
'_redirect_to' => 'https://google.com',
50+
)), array(
51+
'_controller' => 'AppBundle:Starting:format',
52+
));
53+
54+
// Redirection to URL
55+
yield array($this->requestWithAttributes(array(
56+
'_redirect_to' => 'https://google.com',
57+
)), array(
58+
'_controller' => array(RedirectController::class, 'urlRedirectAction'),
59+
'path' => 'https://google.com',
60+
));
61+
62+
// Redirection to route
63+
yield array($this->requestWithAttributes(array(
64+
'_redirect_to' => 'route',
65+
)), array(
66+
'_controller' => array(RedirectController::class, 'redirectAction'),
67+
'route' => 'route',
68+
));
69+
70+
// Permanent redirection to route
71+
yield array($this->requestWithAttributes(array(
72+
'_redirect_to' => 'route',
73+
'_redirect_permanent' => true,
74+
)), array(
75+
'_controller' => array(RedirectController::class, 'redirectAction'),
76+
'route' => 'route',
77+
'permanent' => true,
78+
));
79+
}
80+
81+
private function requestWithAttributes(array $attributes): Request
82+
{
83+
$request = new Request();
84+
85+
foreach ($attributes as $name => $value) {
86+
$request->attributes->set($name, $value);
87+
}
88+
89+
return $request;
90+
}
91+
}

src/Symfony/Component/Routing/Loader/XmlFileLoader.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,15 @@ private function parseConfigs(\DOMElement $node, $path)
243243
$defaults['_controller'] = $controller;
244244
}
245245

246+
if ($redirectTo = $node->getAttribute('redirect-to')) {
247+
if (isset($defaults['_controller'])) {
248+
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both a controller and a redirection.', $path));
249+
}
250+
251+
$defaults['_redirect_to'] = $redirectTo;
252+
$defaults['_redirect_permanent'] = (bool) $node->getAttribute('redirect-permanent');
253+
}
254+
246255
return array($defaults, $requirements, $options, $condition);
247256
}
248257

src/Symfony/Component/Routing/Loader/YamlFileLoader.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
class YamlFileLoader extends FileLoader
2929
{
3030
private static $availableKeys = array(
31-
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix',
31+
'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'redirect_to', 'redirect_permanent',
3232
);
3333
private $yamlParser;
3434

@@ -120,6 +120,15 @@ protected function parseRoute(RouteCollection $collection, $name, array $config,
120120
$defaults['_controller'] = $config['controller'];
121121
}
122122

123+
if (isset($config['redirect_to'])) {
124+
if (isset($defaults['_controller'])) {
125+
throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both a controller and a redirection.', $path));
126+
}
127+
128+
$defaults['_redirect_to'] = $config['redirect_to'];
129+
$defaults['_redirect_permanent'] = $config['redirect_permanent'] ?? false;
130+
}
131+
123132
$route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
124133

125134
$collection->add($name, $route);

src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
<xsd:attribute name="schemes" type="xsd:string" />
4343
<xsd:attribute name="methods" type="xsd:string" />
4444
<xsd:attribute name="controller" type="xsd:string" />
45+
<xsd:attribute name="redirect-to" type="xsd:string" />
46+
<xsd:attribute name="redirect-permanent" type="xsd:boolean" />
4547
</xsd:complexType>
4648

4749
<xsd:complexType name="import">
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<routes xmlns="http://symfony.com/schema/routing"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/routing
5+
http://symfony.com/schema/routing/routing-1.0.xsd">
6+
7+
<route id="app_symfony" path="/symfony" redirect-to="https://symfony.com" controller="App\Controller\Symfony" />
8+
</routes>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
app_symfony:
2+
path: /symfony
3+
controller: App\Controller\Symfony
4+
redirect_to: https://symfony.com
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<routes xmlns="http://symfony.com/schema/routing"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/routing
5+
http://symfony.com/schema/routing/routing-1.0.xsd">
6+
7+
<route id="app_homepage" path="/" controller="AppBundle:Homepage:show" />
8+
<route id="app_temporary_redirect" path="/home" redirect-to="app_homepage" />
9+
<route id="app_permanent_redirect" path="/permanent-home" redirect-to="/" redirect-permanent="true" />
10+
</routes>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
app_homepage:
2+
path: /
3+
controller: AppBundle:Homepage:show
4+
5+
app_temporary_redirect:
6+
path: /home
7+
redirect_to: app_homepage
8+
9+
app_permanent_redirect:
10+
path: /permanent-home
11+
redirect_to: /
12+
redirect_permanent: true

src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,4 +372,31 @@ public function testImportRouteWithNamePrefix()
372372
$this->assertNotNull($routeCollection->get('api_app_blog'));
373373
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
374374
}
375+
376+
public function testRedirections()
377+
{
378+
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/redirect_to')));
379+
$routeCollection = $loader->load('routing.xml');
380+
381+
$route = $routeCollection->get('app_homepage');
382+
$this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller'));
383+
384+
$route = $routeCollection->get('app_temporary_redirect');
385+
$this->assertSame('app_homepage', $route->getDefault('_redirect_to'));
386+
$this->assertFalse($route->getDefault('_redirect_permanent'));
387+
388+
$route = $routeCollection->get('app_permanent_redirect');
389+
$this->assertSame('/', $route->getDefault('_redirect_to'));
390+
$this->assertTrue($route->getDefault('_redirect_permanent'));
391+
}
392+
393+
/**
394+
* @expectedException \InvalidArgumentException
395+
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both a controller and a redirection\./
396+
*/
397+
public function testRedirectionCannotBeUsedWithController()
398+
{
399+
$loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/redirect_to')));
400+
$loader->load('redirect_with_controller.xml');
401+
}
375402
}

src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,31 @@ public function testImportRouteWithNamePrefix()
193193
$this->assertNotNull($routeCollection->get('api_app_blog'));
194194
$this->assertEquals('/api/blog', $routeCollection->get('api_app_blog')->getPath());
195195
}
196+
197+
public function testRedirections()
198+
{
199+
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/redirect_to')));
200+
$routeCollection = $loader->load('routing.yml');
201+
202+
$route = $routeCollection->get('app_homepage');
203+
$this->assertSame('AppBundle:Homepage:show', $route->getDefault('_controller'));
204+
205+
$route = $routeCollection->get('app_temporary_redirect');
206+
$this->assertSame('app_homepage', $route->getDefault('_redirect_to'));
207+
$this->assertFalse($route->getDefault('_redirect_permanent'));
208+
209+
$route = $routeCollection->get('app_permanent_redirect');
210+
$this->assertSame('/', $route->getDefault('_redirect_to'));
211+
$this->assertTrue($route->getDefault('_redirect_permanent'));
212+
}
213+
214+
/**
215+
* @expectedException \InvalidArgumentException
216+
* @expectedExceptionMessageRegExp /The routing file "[^"]*" must not specify both a controller and a redirection\./
217+
*/
218+
public function testRedirectionCannotBeUsedWithController()
219+
{
220+
$loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures/redirect_to')));
221+
$loader->load('redirect_with_controller.yml');
222+
}
196223
}

0 commit comments

Comments
 (0)