如何使用 Symfony Routing 作为独立组件来调试路由?

Posted

技术标签:

【中文标题】如何使用 Symfony Routing 作为独立组件来调试路由?【英文标题】:How to debug routes using Symfony Routing as a standalone component? 【发布时间】:2016-12-06 19:33:03 【问题描述】:

我正在使用 Symfony 的 3.1 路由组件作为独立组件。

我想调试路由。

据此: http://symfony.com/doc/current/routing/debug.html

这是通过运行以下命令来完成的:

php bin/console debug:router

虽然这对于运行完整 Symfony 框架的项目来说是微不足道的,但是当使用路由器组件作为独立模块时,我该如何运行它?

【问题讨论】:

【参考方案1】:

我会发表评论但没有足够的声誉..

无论如何,您应该尝试在项目中要求调试组件才能使用它:

$ composer require symfony/debug

答案更新

好的,我做了一些研究和测试,终于让路由器调试命令工作了。但是,我仍然使用两个 symfony 组件,控制台和配置,但我确信通过进一步搜索您可以避免配置一个。

我创建了一个全新的项目:

$ composer init
$ composer require symfony/routing
$ composer require symfony/console
$ composer require symfony/config

别忘了在 composer.json 中自动加载你的源代码:


    "name": "lolmx/test",
    "require": 
        "php": "^5.6",
        "symfony/console": "^3.1",
        "symfony/routing": "^3.1",
        "symfony/config": "^3.1"
    ,
    "autoload": 
        "psr-0": 
            "": "src/"
        
    

然后$ composer install

在你的项目目录$ touch bin/console创建控制台文件,并写入:

<?php

// Include composer autoloader
require_once __DIR__."/../vendor/autoload.php";

// Use statements
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Application;
use Symfony\Component\Routing\Router;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Loader\PhpFileLoader;
use AppBundle\Command\MyRouterDebugCommand;

$context = new RequestContext();
$locator = new FileLocator(array(__DIR__)); // I needed symfony/config for this
$router = new Router(
    new PhpFileLoader($locator), // And this class depends upon too
    '../src/AppBundle/Routes.php',
    array(),
    $context
);

$app = new Application();
$app->add(new MyRouterDebugCommand(null, $router));
$app->run();
?>

我只是简单地实例化我的路由器,将它提供给我的命令,然后将命令添加到控制台应用程序。

我的 Routes.php 如下所示:

// src/AppBundle/Routes.php
<?php

use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

$collection = new RouteCollection();
$collection->add('name', new Route("/myRoute", array(), array(), array(), "myHost", array('http', 'https'), array('GET', 'PUT')));
// more routes added here

return $collection;

现在让我们编写命令类本身:

<?php

namespace AppBundle\Command;

use AppBundle\Descriptor\DescriptorHelper;
use AppBundle\Descriptor\TextDescriptor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Routing\Router;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Route;

class MyRouterDebugCommand extends Command

    private $router;

    public function __construct($name = null, Router $router)
    
        parent::__construct($name);
        $this->router = $router;
    

    /**
     * @inheritdoc
     */
    public function isEnabled()
    
        if (is_null($this->router)) 
            return false;
        
        if (!$this->router instanceof RouterInterface) 
            return false;
        
        return parent::isEnabled();
    

    /**
     * @inheritdoc
     */
    protected function configure()
    
        $this
            ->setName('debug:router')
            ->setDefinition(array(
                new InputArgument('name', InputArgument::OPTIONAL, 'A route name'),
                new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'),
            ))
            ->setDescription('Displays current routes for an application')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> displays the configured routes:
  <info>php %command.full_name%</info>
EOF
            )
        ;
    

    /**
     * @inheritdoc
     *
     * @throws \InvalidArgumentException When route does not exist
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    
        $io = new SymfonyStyle($input, $output);
        $name = $input->getArgument('name');
        $helper = new DescriptorHelper();
        if ($name) 
            $route = $this->router->getRouteCollection()->get($name);
            if (!$route) 
                throw new \InvalidArgumentException(sprintf('The route "%s" does not exist.', $name));
            
            $this->convertController($route);
            $helper->describe($io, $route, array(
                'format' => $input->getOption('format'),
                'raw_text' => $input->getOption('raw'),
                'name' => $name,
                'output' => $io,
            ));
         else 
            $routes = $this->router->getRouteCollection();
            foreach ($routes as $route) 
                $this->convertController($route);
            
            $helper->describe($io, $routes, array(
                'format' => $input->getOption('format'),
                'raw_text' => $input->getOption('raw'),
                'show_controllers' => $input->getOption('show-controllers'),
                'output' => $io,
            ));
        
    

    private function convertController(Route $route)
    
        $nameParser = new TextDescriptor();
        if ($route->hasDefault('_controller')) 
            try 
                $route->setDefault('_controller', $nameParser->build($route->getDefault('_controller')));
             catch (\InvalidArgumentException $e) 
            
        
    

假设您正在使用默认描述符帮助程序 use Symfony\Component\Console\Descriptor\DescriptorHelper

$ php bin/console debug:router

将以这个奇妙的错误结束:

[Symfony\Component\Console\Exception\InvalidArgumentException]               
Object of type "Symfony\Component\Routing\RouteCollection" is not describable.

好的,所以我们需要创建我们的自定义 DescriptorHelper。先实现接口

<?php

namespace AppBundle\Descriptor;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

abstract class Descriptor implements DescriptorInterface

    /**
     * @var OutputInterface
     */
    protected $output;
    /**
     * @inheritdoc
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    
        $this->output = $output;
        switch (true) 
            case $object instanceof RouteCollection:
                $this->describeRouteCollection($object, $options);
                break;
            case $object instanceof Route:
                $this->describeRoute($object, $options);
                break;
            case is_callable($object):
                $this->describeCallable($object, $options);
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
        
    
    /**
     * Returns the output.
     *
     * @return OutputInterface The output
     */
    protected function getOutput()
    
        return $this->output;
    
    /**
     * Writes content to output.
     *
     * @param string $content
     * @param bool   $decorated
     */
    protected function write($content, $decorated = false)
    
        $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    
    /**
     * Describes an InputArgument instance.
     *
     * @param RouteCollection $routes
     * @param array           $options
     */
    abstract protected function describeRouteCollection(RouteCollection $routes, array $options = array());
    /**
     * Describes an InputOption instance.
     *
     * @param Route $route
     * @param array $options
     */
    abstract protected function describeRoute(Route $route, array $options = array());
    /**
     * Describes a callable.
     *
     * @param callable $callable
     * @param array    $options
     */
    abstract protected function describeCallable($callable, array $options = array());
    /**
     * Formats a value as string.
     *
     * @param mixed $value
     *
     * @return string
     */
    protected function formatValue($value)
    
        if (is_object($value)) 
           return sprintf('object(%s)', get_class($value));
        
        if (is_string($value)) 
            return $value;
        
        return preg_replace("/\n\s*/s", '', var_export($value, true));
    
    /**
     * Formats a parameter.
     *
     * @param mixed $value
     *
     * @return string
     */
    protected function formatParameter($value)
    
        if (is_bool($value) || is_array($value) || (null === $value)) 
            $jsonString = json_encode($value);
            if (preg_match('/^(.60)./us', $jsonString, $matches)) 
                return $matches[1].'...';
            
            return $jsonString;
        
        return (string) $value;
    

然后覆盖默认的DescriptorHelper来注册我们的描述符

<?php

namespace AppBundle\Descriptor;

use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper;

class DescriptorHelper extends BaseDescriptorHelper

    /**
     * Constructor.
     */
    public function __construct()
    
        $this
            ->register('txt', new TextDescriptor())
        ;
    

最后,实现我们的描述符

<?php

namespace AppBundle\Descriptor;

use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class TextDescriptor extends Descriptor

    /**
     * @inheritdoc
     */
    protected function describeRouteCollection(RouteCollection $routes, array $options = array())
    
        $showControllers = isset($options['show_controllers']) && $options['show_controllers'];
        $tableHeaders = array('Name', 'Method', 'Scheme', 'Host', 'Path');
        if ($showControllers) 
            $tableHeaders[] = 'Controller';
        
        $tableRows = array();
        foreach ($routes->all() as $name => $route) 
            $row = array(
                $name,
                $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY',
                $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY',
                '' !== $route->getHost() ? $route->getHost() : 'ANY',
                $route->getPath(),
            );
            if ($showControllers) 
                $controller = $route->getDefault('_controller');
                if ($controller instanceof \Closure) 
                    $controller = 'Closure';
                 elseif (is_object($controller)) 
                    $controller = get_class($controller);
                
                $row[] = $controller;
            
            $tableRows[] = $row;
        
        if (isset($options['output'])) 
            $options['output']->table($tableHeaders, $tableRows);
         else 
            $table = new Table($this->getOutput());
            $table->setHeaders($tableHeaders)->setRows($tableRows);
            $table->render();
        
    

    /**
     * @inheritdoc
     */
    protected function describeRoute(Route $route, array $options = array())
    
        $tableHeaders = array('Property', 'Value');
        $tableRows = array(
            array('Route Name', isset($options['name']) ? $options['name'] : ''),
            array('Path', $route->getPath()),
            array('Path Regex', $route->compile()->getRegex()),
            array('Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')),
            array('Host Regex', ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')),
            array('Scheme', ($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')),
            array('Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')),
            array('Requirements', ($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM')),
            array('Class', get_class($route)),
            array('Defaults', $this->formatRouterConfig($route->getDefaults())),
            array('Options', $this->formatRouterConfig($route->getOptions())),
        );
        $table = new Table($this->getOutput());
        $table->setHeaders($tableHeaders)->setRows($tableRows);
        $table->render();
    

    /**
     * @inheritdoc
     */
    protected function describeCallable($callable, array $options = array())
    
        $this->writeText($this->formatCallable($callable), $options);
    

    /**
     * @param array $config
     *
     * @return string
     */
    private function formatRouterConfig(array $config)
    
        if (empty($config)) 
            return 'NONE';
        
        ksort($config);
        $configAsString = '';
        foreach ($config as $key => $value) 
            $configAsString .= sprintf("\n%s: %s", $key, $this->formatValue($value));
        
        return trim($configAsString);
    

    /**
     * @param callable $callable
     *
     * @return string
     */
    private function formatCallable($callable)
    
        if (is_array($callable)) 
            if (is_object($callable[0])) 
                return sprintf('%s::%s()', get_class($callable[0]), $callable[1]);
            
            return sprintf('%s::%s()', $callable[0], $callable[1]);
        
        if (is_string($callable)) 
            return sprintf('%s()', $callable);
        
        if ($callable instanceof \Closure) 
            return '\Closure()';
        
        if (method_exists($callable, '__invoke')) 
            return sprintf('%s::__invoke()', get_class($callable));
        
        throw new \InvalidArgumentException('Callable is not describable.');
    

    /**
     * @param string $content
     * @param array  $options
     */
    private function writeText($content, array $options = array())
    
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    

现在写$ php bin/console debug:router会输出

 ------ --------- ------------ -------- ---------- 
  Name   Method    Scheme       Host     Path      
 ------ --------- ------------ -------- ---------- 
  name   GET|PUT   http|https   myHost   /myRoute  
 ------ --------- ------------ -------- ---------- 

我潜入 symfony 源代码以找到所有这些,不同的文件是/可能是来自 Symfony、symfony routing、console 和 framework-bundle 的代码的 sn-ps。

【讨论】:

是的,我需要它。但问题是我什至没有bin/console,因为我没有使用完整的 Symfony 框架。只需选择组件。 @lolmx - 可能想研究一下单个 Symfony 组件(例如路由组件)和 Symfony 框架之间的区别。框架实现了路由调试命令。再多安装单个组件也不会使路由调试命令出现。 是的。我想您可以查看源代码并尝试使其正常工作,但您最好自己编写一些东西。 源码在这里:github.com/symfony/framework-bundle/blob/master/Command/… @lolmx - 很抱歉,但还没有结束。您基本上不仅需要框架依赖项,还需要所有框架布线等等。换句话说,实际的框架。简单地关闭控制台组件并不能为您提供工作控制台。否决投票作为您的答案,这并没有解决避免框架的问题。

以上是关于如何使用 Symfony Routing 作为独立组件来调试路由?的主要内容,如果未能解决你的问题,请参考以下文章

从 Symfony2 中的 routing.yml 将一条路由重定向(301)到另一条路由

Symfony2 / 路由 / 使用参数作为控制器或动作名称

json 将Symfony安全组件作为独立组件使用

在 Symfony2 路由中,如何设置可选的子域

Symfony\Component\Routing\Exception\RouteNotFoundException: Route [Lala.search] 未定义

如何在 Symfony 路由中使用锚点?