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

时间:2016-08-01 16:20:08

标签: routing symfony symfony-3.1

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

我希望调试路线。

根据这个: http://symfony.com/doc/current/routing/debug.html

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

php bin/console debug:router

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

1 个答案:

答案 0 :(得分:2)

我发表评论但声誉不够......

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

$ 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 routingconsole和{{3}的代码片段}。