Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FrameworkBundle] added a command to help debugging route matching pr…
…oblems
  • Loading branch information
fabpot committed Oct 24, 2011
1 parent 24b9392 commit 8cc3158
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
88 changes: 88 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php
@@ -0,0 +1,88 @@
<?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\Bundle\FrameworkBundle\Command;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;

/**
* A console command to test route matching.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterMatchCommand extends ContainerAwareCommand
{
/**
* {@inheritDoc}
*/
public function isEnabled()
{
if (!$this->getContainer()->has('router')) {
return false;
}
$router = $this->getContainer()->get('router');
if (!$router instanceof RouterInterface) {
return false;
}
return parent::isEnabled();
}

/**
* @see Command
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'),
))
->setName('router:match')
->setDescription('Helps debug routes by simulating a path info match')
->setHelp(<<<EOF
The <info>router:match</info> simulates a path info match:
<info>router:match /foo</info>
EOF
)
;
}

/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$router = $this->getContainer()->get('router');
$matcher = new TraceableUrlMatcher($router->getRouteCollection(), $router->getContext());

This comment has been minimized.

Copy link
@stof

stof Oct 24, 2011

Member

I see an issue with such a setup: getRouteCollection calls the loader (it is intended to do so) so running the command in the prod (non-debug) environment may produce a different result than the routing of a request because the request will use the cache. A common issue according to what I see on IRC is forgetting to clear the prod cache after changing the routing. It could be a good idea to have a command doing the matching with the cached router.

This comment has been minimized.

Copy link
@fabpot

fabpot Oct 24, 2011

Author Member

Doing the matching with the cached router is not possible. But in the dev env, the cache should never be out of date, and in the production env, there is no way to check if it is up to date or not as we don't have the meta file.

This comment has been minimized.

Copy link
@stof

stof Oct 24, 2011

Member

yeah, but using php app/console --env=prod --no-debug router:match or php app/console --env=prod --no-debug router:debug will not produce the same result than doing the prod request. So we should at least give a warning to the user to make sure he clears the cache to update the router when he makes a change because the CLI will show him the new routes even when the cache is outdated.


$traces = $matcher->getTraces($input->getArgument('path_info'));

$matches = false;
foreach ($traces as $i => $trace) {
if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) {
$output->writeln(sprintf('<fg=yellow>Route "%s" almost matches but %s</>', $trace['name'], lcfirst($trace['log'])));
} elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) {
$output->writeln(sprintf('<fg=green>Route "%s" matches</>', $trace['name']));
$matches = true;
} elseif ($input->getOption('verbose')) {
$output->writeln(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log']));
}
}

if (!$matches) {
$output->writeln('<fg=red>None of the routes matches</>');
}
}
}
@@ -0,0 +1,67 @@
<?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\Bundle\FrameworkBundle\DataCollector;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;
use Symfony\Component\Routing\RouterInterface;

/**
* RouterDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RouterDataCollector extends DataCollector
{
private $router;

public function __construct(RouterInterface $router = null)
{
$this->router = $router;
}

/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data['path_info'] = $request->getPathInfo();

if (!$this->router) {
$this->data['traces'] = array();
} else {
$matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $this->router->getContext());

$this->data['traces'] = $matcher->getTraces($request->getPathInfo());
}
}

public function getPathInfo()
{
return $this->data['path_info'];
}

public function getTraces()
{
return $this->data['traces'];
}

/**
* {@inheritdoc}
*/
public function getName()
{
return 'router';
}
}
Expand Up @@ -12,6 +12,7 @@
<parameter key="data_collector.logger.class">Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector</parameter>
<parameter key="data_collector.time.class">Symfony\Component\HttpKernel\DataCollector\TimeDataCollector</parameter>
<parameter key="data_collector.memory.class">Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector</parameter>
<parameter key="data_collector.router.class">Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector</parameter>
</parameters>

<services>
Expand Down Expand Up @@ -50,5 +51,10 @@
<service id="data_collector.memory" class="%data_collector.memory.class%" public="false">
<tag name="data_collector" template="WebProfilerBundle:Collector:memory" id="memory" priority="255" />
</service>

<service id="data_collector.router" class="%data_collector.router.class%" public="false">
<tag name="data_collector" template="WebProfilerBundle:Collector:router" id="router" priority="255" />
<argument type="service" id="router" on-invalid="ignore" />
</service>
</services>
</container>

0 comments on commit 8cc3158

Please sign in to comment.