Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5f55bef

Browse files
committed
feature #24403 [FrameworkBundle][Routing] Show welcome message if no routes are configured (yceruto)
This PR was merged into the 3.4 branch. Discussion ---------- [FrameworkBundle][Routing] Show welcome message if no routes are configured | Q | A | ------------- | --- | Branch? | 3.4 (it would be good, else 4.1) | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony/flex#20 | License | MIT | Doc PR | - Another attempt to improve the first time experience with a different technical approach this time. Just after a fresh "SymfonyFlex" installation: ```bash $ composer create-project symfony/skeleton flex $ cd flex $ make serve ``` ![welcome](https://user-images.githubusercontent.com/2028198/31088339-4b84f95a-a76e-11e7-8b70-be53507f18e1.png) When the first route is added, this message is no longer displayed (same if debug mode is disabled). ping @javiereguiluz, @sstok Commits ------- e097ab3 Show welcome message if no routing configuration could be found
2 parents 5e2f869 + e097ab3 commit 5f55bef

File tree

14 files changed

+228
-2
lines changed

14 files changed

+228
-2
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109
<argument type="service" id="request_stack" />
110110
<argument type="service" id="router.request_context" on-invalid="ignore" />
111111
<argument type="service" id="logger" on-invalid="ignore" />
112+
<argument>%kernel.project_dir%</argument>
113+
<argument>%kernel.debug%</argument>
112114
</service>
113115
</services>
114116
</container>

src/Symfony/Component/HttpKernel/EventListener/RouterListener.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
namespace Symfony\Component\HttpKernel\EventListener;
1313

1414
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\HttpFoundation\Response;
1516
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
1617
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
18+
use Symfony\Component\HttpKernel\Kernel;
1719
use Symfony\Component\HttpKernel\KernelEvents;
1820
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
1921
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2022
use Symfony\Component\HttpFoundation\RequestStack;
2123
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
24+
use Symfony\Component\Routing\Exception\NoConfigurationException;
2225
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
2326
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
2427
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
@@ -31,23 +34,28 @@
3134
* Initializes the context from the request and sets request attributes based on a matching route.
3235
*
3336
* @author Fabien Potencier <[email protected]>
37+
* @author Yonel Ceruto <[email protected]>
3438
*/
3539
class RouterListener implements EventSubscriberInterface
3640
{
3741
private $matcher;
3842
private $context;
3943
private $logger;
4044
private $requestStack;
45+
private $projectDir;
46+
private $debug;
4147

4248
/**
4349
* @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher
4450
* @param RequestStack $requestStack A RequestStack instance
4551
* @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface)
4652
* @param LoggerInterface|null $logger The logger
53+
* @param string $projectDir
54+
* @param bool $debug
4755
*
4856
* @throws \InvalidArgumentException
4957
*/
50-
public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null)
58+
public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, $projectDir = null, $debug = true)
5159
{
5260
if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) {
5361
throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.');
@@ -61,6 +69,8 @@ public function __construct($matcher, RequestStack $requestStack, RequestContext
6169
$this->context = $context ?: $matcher->getContext();
6270
$this->requestStack = $requestStack;
6371
$this->logger = $logger;
72+
$this->projectDir = $projectDir;
73+
$this->debug = $debug;
6474
}
6575

6676
private function setCurrentRequest(Request $request = null)
@@ -114,6 +124,12 @@ public function onKernelRequest(GetResponseEvent $event)
114124
unset($parameters['_route'], $parameters['_controller']);
115125
$request->attributes->set('_route_params', $parameters);
116126
} catch (ResourceNotFoundException $e) {
127+
if ($this->debug && $e instanceof NoConfigurationException) {
128+
$event->setResponse($this->createWelcomeResponse());
129+
130+
return;
131+
}
132+
117133
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
118134

119135
if ($referer = $request->headers->get('referer')) {
@@ -135,4 +151,16 @@ public static function getSubscribedEvents()
135151
KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)),
136152
);
137153
}
154+
155+
private function createWelcomeResponse()
156+
{
157+
$version = Kernel::VERSION;
158+
$baseDir = realpath($this->projectDir).DIRECTORY_SEPARATOR;
159+
$docVersion = substr(Kernel::VERSION, 0, 3);
160+
161+
ob_start();
162+
include __DIR__.'/../Resources/welcome.html.php';
163+
164+
return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND);
165+
}
138166
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Welcome!</title>
6+
<style>
7+
body { background: #F5F5F5; font: 18px/1.5 sans-serif; }
8+
h1, h2 { line-height: 1.2; margin: 0 0 .5em; }
9+
h1 { font-size: 36px; }
10+
h2 { font-size: 21px; margin-bottom: 1em; }
11+
p { margin: 0 0 1em 0; }
12+
a { color: #0000F0; }
13+
a:hover { text-decoration: none; }
14+
code { background: #F5F5F5; max-width: 100px; padding: 2px 6px; word-wrap: break-word; }
15+
#wrapper { background: #FFF; margin: 1em auto; max-width: 800px; width: 95%; }
16+
#container { padding: 2em; }
17+
#welcome, #status { margin-bottom: 2em; }
18+
#welcome h1 span { display: block; font-size: 75%; }
19+
#comment { font-size: 14px; text-align: center; color: #777777; background: #FEFFEA; padding: 10px; }
20+
#comment p { margin-bottom: 0; }
21+
#icon-status, #icon-book { float: left; height: 64px; margin-right: 1em; margin-top: -4px; width: 64px; }
22+
#icon-book { display: none; }
23+
24+
@media (min-width: 768px) {
25+
#wrapper { width: 80%; margin: 2em auto; }
26+
#icon-book { display: inline-block; }
27+
#status a, #next a { display: block; }
28+
29+
@-webkit-keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
30+
@keyframes fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
31+
.sf-toolbar { opacity: 0; -webkit-animation: fade-in 1s .2s forwards; animation: fade-in 1s .2s forwards;}
32+
}
33+
</style>
34+
</head>
35+
<body>
36+
<div id="wrapper">
37+
<div id="container">
38+
<div id="welcome">
39+
<h1><span>Welcome to</span> Symfony <?php echo $version; ?></h1>
40+
</div>
41+
42+
<div id="status">
43+
<p>
44+
<svg id="icon-status" width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1671 566q0 40-28 68l-724 724-136 136q-28 28-68 28t-68-28l-136-136-362-362q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 295 656-657q28-28 68-28t68 28l136 136q28 28 28 68z" fill="#759E1A"/></svg>
45+
46+
Your application is now ready. You can start working on it at:<br>
47+
<code><?php echo $baseDir; ?></code>
48+
</p>
49+
</div>
50+
51+
<div id="next">
52+
<h2>What's next?</h2>
53+
<p>
54+
<svg id="icon-book" version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="-12.5 9 64 64" enable-background="new -12.5 9 64 64" xml:space="preserve">
55+
<path fill="#AAA" d="M6.8,40.8c2.4,0.8,4.5-0.7,4.9-2.5c0.2-1.2-0.3-2.1-1.3-3.2l-0.8-0.8c-0.4-0.5-0.6-1.3-0.2-1.9
56+
c0.4-0.5,0.9-0.8,1.8-0.5c1.3,0.4,1.9,1.3,2.9,2.2c-0.4,1.4-0.7,2.9-0.9,4.2l-0.2,1c-0.7,4-1.3,6.2-2.7,7.5
57+
c-0.3,0.3-0.7,0.5-1.3,0.6c-0.3,0-0.4-0.3-0.4-0.3c0-0.3,0.2-0.3,0.3-0.4c0.2-0.1,0.5-0.3,0.4-0.8c0-0.7-0.6-1.3-1.3-1.3
58+
c-0.6,0-1.4,0.6-1.4,1.7s1,1.9,2.4,1.8c0.8,0,2.5-0.3,4.2-2.5c2-2.5,2.5-5.4,2.9-7.4l0.5-2.8c0.3,0,0.5,0.1,0.8,0.1
59+
c2.4,0.1,3.7-1.3,3.7-2.3c0-0.6-0.3-1.2-0.9-1.2c-0.4,0-0.8,0.3-1,0.8c-0.1,0.6,0.8,1.1,0.1,1.5c-0.5,0.3-1.4,0.6-2.7,0.4l0.3-1.3
60+
c0.5-2.6,1-5.7,3.2-5.8c0.2,0,0.8,0,0.8,0.4c0,0.2,0,0.2-0.2,0.5c-0.2,0.3-0.3,0.4-0.2,0.7c0,0.7,0.5,1.1,1.2,1.1
61+
c0.9,0,1.2-1,1.2-1.4c0-1.2-1.2-1.8-2.6-1.8c-1.5,0.1-2.8,0.9-3.7,2.1c-1.1,1.3-1.8,2.9-2.3,4.5c-0.9-0.8-1.6-1.8-3.1-2.3
62+
c-1.1-0.7-2.3-0.5-3.4,0.3c-0.5,0.4-0.8,1-1,1.6c-0.4,1.5,0.4,2.9,0.8,3.4l0.9,1c0.2,0.2,0.6,0.8,0.4,1.5c-0.3,0.8-1.2,1.3-2.1,1
63+
c-0.4-0.2-1-0.5-0.9-0.9c0.1-0.2,0.2-0.3,0.3-0.5s0.1-0.3,0.1-0.3c0.2-0.6-0.1-1.4-0.7-1.6c-0.6-0.2-1.2,0-1.3,0.8
64+
C4.3,38.4,4.7,40,6.8,40.8z M46.1,20.9c0-4.2-3.2-7.5-7.1-7.5h-3.8C34.8,10.8,32.7,9,30.2,9L-2.3,9.1c-2.8,0.1-4.9,2.4-4.9,5.4
65+
L-7,58.6c0,4.8,8.1,13.9,11.6,14.1l34.7-0.1c3.9,0,7-3.4,7-7.6L46.1,20.9z M-0.3,36.4c0-8.6,6.5-15.6,14.5-15.6
66+
c8,0,14.5,7,14.5,15.6S22.1,52,14.2,52C6.1,52-0.3,45-0.3,36.4z M42.1,65.1c0,1.8-1.5,3.1-3.1,3.1H4.6c-0.7,0-3-1.8-4.5-4.4h30.4
67+
c2.8,0,5-2.4,5-5.4V17.9h3.7c1.6,0,2.9,1.4,2.9,3.1V65.1L42.1,65.1z"/>
68+
</svg>
69+
70+
Read the documentation to learn
71+
<a href="https://symfony.com/doc/<?php echo $docVersion; ?>/page_creation.html">
72+
How to create your first page in Symfony
73+
</a>
74+
</p>
75+
</div>
76+
</div>
77+
<div id="comment">
78+
<p>
79+
You're seeing this message because you have debug mode enabled and you haven't configured any URLs.
80+
</p>
81+
</div>
82+
</div>
83+
</body>
84+
</html>

src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\HttpKernel\HttpKernelInterface;
2525
use Symfony\Component\HttpKernel\HttpKernel;
2626
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
27+
use Symfony\Component\Routing\Exception\NoConfigurationException;
2728
use Symfony\Component\Routing\RequestContext;
2829

2930
class RouterListenerTest extends TestCase
@@ -185,4 +186,26 @@ public function testWithBadRequest()
185186
$response = $kernel->handle($request);
186187
$this->assertSame(400, $response->getStatusCode());
187188
}
189+
190+
public function testNoRoutingConfigurationResponse()
191+
{
192+
$requestStack = new RequestStack();
193+
194+
$requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock();
195+
$requestMatcher
196+
->expects($this->once())
197+
->method('matchRequest')
198+
->willThrowException(new NoConfigurationException())
199+
;
200+
201+
$dispatcher = new EventDispatcher();
202+
$dispatcher->addSubscriber(new RouterListener($requestMatcher, $requestStack, new RequestContext()));
203+
204+
$kernel = new HttpKernel($dispatcher, new ControllerResolver(), $requestStack, new ArgumentResolver());
205+
206+
$request = Request::create('http://localhost/');
207+
$response = $kernel->handle($request);
208+
$this->assertSame(404, $response->getStatusCode());
209+
$this->assertContains('Welcome', $response->getContent());
210+
}
188211
}

src/Symfony/Component/HttpKernel/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"symfony/expression-language": "~2.8|~3.0|~4.0",
3434
"symfony/finder": "~2.8|~3.0|~4.0",
3535
"symfony/process": "~2.8|~3.0|~4.0",
36-
"symfony/routing": "~2.8|~3.0|~4.0",
36+
"symfony/routing": "~3.4|~4.0",
3737
"symfony/stopwatch": "~2.8|~3.0|~4.0",
3838
"symfony/templating": "~2.8|~3.0|~4.0",
3939
"symfony/translation": "~2.8|~3.0|~4.0",

src/Symfony/Component/Routing/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* Added `NoConfigurationException`.
78
* Added the possibility to define a prefix for all routes of a controller via @Route(name="prefix_")
89
* Added support for prioritized routing loaders.
910
* Add matched and default parameters to redirect responses
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Component\Routing\Exception;
13+
14+
/**
15+
* Exception thrown when no routes are configured.
16+
*
17+
* @author Yonel Ceruto <[email protected]>
18+
*/
19+
class NoConfigurationException extends ResourceNotFoundException
20+
{
21+
}

src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
155155
}
156156
}
157157

158+
if ('' === $code) {
159+
$code .= " if ('/' === \$pathinfo) {\n";
160+
$code .= " throw new Symfony\Component\Routing\Exception\NoConfigurationException();\n";
161+
$code .= " }\n";
162+
}
163+
158164
return $code;
159165
}
160166

src/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Routing\Matcher;
1313

1414
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Routing\Exception\NoConfigurationException;
1516
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
1617
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
1718

@@ -32,6 +33,7 @@ interface RequestMatcherInterface
3233
*
3334
* @return array An array of parameters
3435
*
36+
* @throws NoConfigurationException If no routing configuration could be found
3537
* @throws ResourceNotFoundException If no matching resource could be found
3638
* @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed
3739
*/

src/Symfony/Component/Routing/Matcher/UrlMatcher.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Routing\Matcher;
1313

1414
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
15+
use Symfony\Component\Routing\Exception\NoConfigurationException;
1516
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
1617
use Symfony\Component\Routing\RouteCollection;
1718
use Symfony\Component\Routing\RequestContext;
@@ -91,6 +92,10 @@ public function match($pathinfo)
9192
return $ret;
9293
}
9394

95+
if (0 === count($this->routes) && '/' === $pathinfo) {
96+
throw new NoConfigurationException();
97+
}
98+
9499
throw 0 < count($this->allow)
95100
? new MethodNotAllowedException(array_unique($this->allow))
96101
: new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));
@@ -123,6 +128,7 @@ public function addExpressionLanguageProvider(ExpressionFunctionProviderInterfac
123128
*
124129
* @return array An array of parameters
125130
*
131+
* @throws NoConfigurationException If no routing configuration could be found
126132
* @throws ResourceNotFoundException If the resource could not be found
127133
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed
128134
*/

src/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Routing\Matcher;
1313

14+
use Symfony\Component\Routing\Exception\NoConfigurationException;
1415
use Symfony\Component\Routing\RequestContextAwareInterface;
1516
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
1617
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
@@ -32,6 +33,7 @@ interface UrlMatcherInterface extends RequestContextAwareInterface
3233
*
3334
* @return array An array of parameters
3435
*
36+
* @throws NoConfigurationException If no routing configuration could be found
3537
* @throws ResourceNotFoundException If the resource could not be found
3638
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed
3739
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
4+
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
5+
use Symfony\Component\Routing\RequestContext;
6+
7+
/**
8+
* This class has been auto-generated
9+
* by the Symfony Routing Component.
10+
*/
11+
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
12+
{
13+
public function __construct(RequestContext $context)
14+
{
15+
$this->context = $context;
16+
}
17+
18+
public function match($pathinfo)
19+
{
20+
$allow = array();
21+
$pathinfo = rawurldecode($pathinfo);
22+
$trimmedPathinfo = rtrim($pathinfo, '/');
23+
$context = $this->context;
24+
$request = $this->request;
25+
$requestMethod = $canonicalMethod = $context->getMethod();
26+
$scheme = $context->getScheme();
27+
28+
if ('HEAD' === $requestMethod) {
29+
$canonicalMethod = 'GET';
30+
}
31+
32+
33+
if ('/' === $pathinfo) {
34+
throw new Symfony\Component\Routing\Exception\NoConfigurationException();
35+
}
36+
37+
throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
38+
}
39+
}

src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ public function getRouteCollections()
374374
$trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', array(), array(), array(), '', array(), array('POST')));
375375

376376
return array(
377+
array(new RouteCollection(), 'url_matcher0.php', array()),
377378
array($collection, 'url_matcher1.php', array()),
378379
array($redirectCollection, 'url_matcher2.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')),
379380
array($rootprefixCollection, 'url_matcher3.php', array()),

src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,4 +427,15 @@ public function testHostIsCaseInsensitive()
427427
$matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
428428
$this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/'));
429429
}
430+
431+
/**
432+
* @expectedException \Symfony\Component\Routing\Exception\NoConfigurationException
433+
*/
434+
public function testNoConfiguration()
435+
{
436+
$coll = new RouteCollection();
437+
438+
$matcher = new UrlMatcher($coll, new RequestContext());
439+
$matcher->match('/');
440+
}
430441
}

0 commit comments

Comments
 (0)