[FrameworkBundle] Add %debug.file_link_format% with remapping for IDE links

This commit is contained in:
Nicolas Grekas 2016-08-31 10:43:03 +02:00
parent 03a824a117
commit 1c4ca8c9a4
20 changed files with 143 additions and 76 deletions

View File

@ -25,13 +25,20 @@ class CodeExtension extends \Twig_Extension
/**
* Constructor.
*
* @param string $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
* @param string|array $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
*/
public function __construct($fileLinkFormat, $rootDir, $charset)
{
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
if ($fileLinkFormat && !is_array($fileLinkFormat)) {
$i = max(strpos($fileLinkFormat, '%f'), strpos($fileLinkFormat, '%l'));
$i = strpos($fileLinkFormat, '#', $i) ?: strlen($fileLinkFormat);
$fileLinkFormat = array(substr($fileLinkFormat, 0, $i), substr($fileLinkFormat, $i + 1));
parse_str($fileLinkFormat[1], $fileLinkFormat[1]);
}
$this->fileLinkFormat = $fileLinkFormat;
$this->rootDir = str_replace('/', DIRECTORY_SEPARATOR, dirname($rootDir)).DIRECTORY_SEPARATOR;
$this->charset = $charset;
}
@ -190,8 +197,15 @@ class CodeExtension extends \Twig_Extension
*/
public function getFileLink($file, $line)
{
if ($this->fileLinkFormat && is_file($file)) {
return strtr($this->fileLinkFormat, array('%f' => $file, '%l' => $line));
if ($this->fileLinkFormat && file_exists($file)) {
foreach ($this->fileLinkFormat[1] as $k => $v) {
if (0 === strpos($file, $k)) {
$file = substr_replace($file, $v, 0, strlen($k));
break;
}
}
return strtr($this->fileLinkFormat[0], array('%f' => $file, '%l' => $line));
}
return false;

View File

@ -19,7 +19,7 @@ class CodeExtensionTest extends \PHPUnit_Framework_TestCase
public function testFormatFile()
{
$expected = sprintf('<a href="txmt://open?url=file://%s&amp;line=25" title="Click to open this file" class="file_link">%s at line 25</a>', __FILE__, __FILE__);
$expected = sprintf('<a href="proto://foobar%s#&amp;line=25" title="Click to open this file" class="file_link">%s at line 25</a>', substr(__FILE__, 5), __FILE__);
$this->assertEquals($expected, $this->getExtension()->formatFile(__FILE__, 25));
}
@ -64,6 +64,6 @@ class CodeExtensionTest extends \PHPUnit_Framework_TestCase
protected function getExtension()
{
return new CodeExtension('txmt://open?url=file://%f&line=%l', '/root', 'UTF-8');
return new CodeExtension('proto://%f#&line=%l#'.substr(__FILE__, 0, 5).'=foobar', '/root', 'UTF-8');
}
}

View File

@ -33,10 +33,6 @@ class DumpDataCollectorPass implements CompilerPassInterface
$definition = $container->getDefinition('data_collector.dump');
if ($container->hasParameter('templating.helper.code.file_link_format')) {
$definition->replaceArgument(1, $container->getParameter('templating.helper.code.file_link_format'));
}
if (!$container->hasParameter('web_profiler.debug_toolbar.mode') || WebDebugToolbarListener::DISABLED === $container->getParameter('web_profiler.debug_toolbar.mode')) {
$definition->replaceArgument(3, null);
}

View File

@ -8,12 +8,13 @@
<service id="twig.extension.dump" class="Symfony\Bridge\Twig\Extension\DumpExtension" public="false">
<tag name="twig.extension" />
<argument type="service" id="var_dumper.cloner" />
<argument type="service" id="var_dumper.html_dumper" />
</service>
<service id="data_collector.dump" class="Symfony\Component\HttpKernel\DataCollector\DumpDataCollector">
<tag name="data_collector" id="dump" template="@Debug/Profiler/dump.html.twig" priority="240" />
<argument type="service" id="debug.stopwatch" on-invalid="ignore" />
<argument>null</argument><!-- %templating.helper.code.file_link_format% -->
<argument>%debug.file_link_format%</argument>
<argument>%kernel.charset%</argument>
<argument type="service" id="request_stack" />
<argument>null</argument><!-- var_dumper.cli_dumper when debug.dump_destination is set -->
@ -29,6 +30,17 @@
<service id="var_dumper.cli_dumper" class="Symfony\Component\VarDumper\Dumper\CliDumper">
<argument>null</argument><!-- debug.dump_destination -->
<argument>%kernel.charset%</argument>
<argument>0</argument> <!-- flags -->
</service>
<service id="var_dumper.html_dumper" class="Symfony\Component\VarDumper\Dumper\HtmlDumper" public="false">
<argument>null</argument>
<argument>%kernel.charset%</argument>
<argument>0</argument> <!-- flags -->
<call method="setDisplayOptions">
<argument type="collection">
<argument key="fileLinkFormat">%debug.file_link_format%</argument>
</argument>
</call>
</service>
</services>

View File

@ -19,20 +19,6 @@ use Symfony\Component\HttpFoundation\RequestStack;
class DumpDataCollectorPassTest extends \PHPUnit_Framework_TestCase
{
public function testProcessWithFileLinkFormatParameter()
{
$container = new ContainerBuilder();
$container->addCompilerPass(new DumpDataCollectorPass());
$container->setParameter('templating.helper.code.file_link_format', 'file-link-format');
$definition = new Definition('Symfony\Component\HttpKernel\DataCollector\DumpDataCollector', array(null, null, null, null));
$container->setDefinition('data_collector.dump', $definition);
$container->compile();
$this->assertSame('file-link-format', $definition->getArgument(1));
}
public function testProcessWithoutFileLinkFormatParameter()
{
$container = new ContainerBuilder();

View File

@ -90,6 +90,21 @@ class FrameworkExtension extends Extension
$container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']);
$container->setParameter('kernel.default_locale', $config['default_locale']);
if (!$container->hasParameter('debug.file_link_format')) {
if (!$container->hasParameter('templating.helper.code.file_link_format')) {
$links = array(
'textmate' => 'txmt://open?url=file://%%f&line=%%l',
'macvim' => 'mvim://open?url=file://%%f&line=%%l',
'emacs' => 'emacs://open?url=file://%%f&line=%%l',
'sublime' => 'subl://open?url=file://%%f&line=%%l',
);
$ide = $config['ide'];
$container->setParameter('templating.helper.code.file_link_format', str_replace('%', '%%', ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) ?: (isset($links[$ide]) ? $links[$ide] : $ide));
}
$container->setParameter('debug.file_link_format', '%templating.helper.code.file_link_format%');
}
if (!empty($config['test'])) {
$loader->load('test.xml');
}
@ -120,7 +135,7 @@ class FrameworkExtension extends Extension
}
if ($this->isConfigEnabled($container, $config['templating'])) {
$this->registerTemplatingConfiguration($config['templating'], $config['ide'], $container, $loader);
$this->registerTemplatingConfiguration($config['templating'], $container, $loader);
}
$this->registerValidationConfiguration($config['validation'], $container, $loader);
@ -431,11 +446,6 @@ class FrameworkExtension extends Extension
}
$definition->replaceArgument(4, $debug);
if ($container->hasParameter('templating.helper.code.file_link_format')) {
$definition->replaceArgument(5, '%templating.helper.code.file_link_format%');
}
$definition->replaceArgument(6, $debug);
}
@ -553,25 +563,13 @@ class FrameworkExtension extends Extension
* Loads the templating configuration.
*
* @param array $config A templating configuration array
* @param string $ide
* @param ContainerBuilder $container A ContainerBuilder instance
* @param XmlFileLoader $loader An XmlFileLoader instance
*/
private function registerTemplatingConfiguration(array $config, $ide, ContainerBuilder $container, XmlFileLoader $loader)
private function registerTemplatingConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
$loader->load('templating.xml');
if (!$container->hasParameter('templating.helper.code.file_link_format')) {
$links = array(
'textmate' => 'txmt://open?url=file://%%f&line=%%l',
'macvim' => 'mvim://open?url=file://%%f&line=%%l',
'emacs' => 'emacs://open?url=file://%%f&line=%%l',
'sublime' => 'subl://open?url=file://%%f&line=%%l',
);
$container->setParameter('templating.helper.code.file_link_format', str_replace('%', '%%', ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) ?: (isset($links[$ide]) ? $links[$ide] : $ide));
}
$container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']);
if ($container->getParameter('kernel.debug')) {

View File

@ -17,7 +17,7 @@
<argument>-1</argument><!-- Log levels map for enabled error levels -->
<argument>%debug.error_handler.throw_at%</argument>
<argument>true</argument>
<argument>null</argument><!-- %templating.helper.code.file_link_format% -->
<argument>%debug.file_link_format%</argument>
<argument>true</argument>
</service>

View File

@ -44,7 +44,7 @@
<service id="templating.helper.code" class="Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper">
<tag name="templating.helper" alias="code" />
<argument>%templating.helper.code.file_link_format%</argument>
<argument>%debug.file_link_format%</argument>
<argument>%kernel.root_dir%</argument>
<argument>%kernel.charset%</argument>
</service>

View File

@ -27,13 +27,20 @@ class CodeHelper extends Helper
/**
* Constructor.
*
* @param string $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
* @param string|array $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
*/
public function __construct($fileLinkFormat, $rootDir, $charset)
{
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
if ($fileLinkFormat && !is_array($fileLinkFormat)) {
$i = max(strpos($fileLinkFormat, '%f'), strpos($fileLinkFormat, '%l'));
$i = strpos($fileLinkFormat, '#', $i) ?: strlen($fileLinkFormat);
$fileLinkFormat = array(substr($fileLinkFormat, 0, $i), substr($fileLinkFormat, $i + 1));
parse_str($fileLinkFormat[1], $fileLinkFormat[1]);
}
$this->fileLinkFormat = $fileLinkFormat;
$this->rootDir = str_replace('\\', '/', $rootDir).'/';
$this->charset = $charset;
}
@ -186,7 +193,14 @@ class CodeHelper extends Helper
public function getFileLink($file, $line)
{
if ($this->fileLinkFormat && is_file($file)) {
return strtr($this->fileLinkFormat, array('%f' => $file, '%l' => $line));
foreach ($this->fileLinkFormat[1] as $k => $v) {
if (0 === strpos($file, $k)) {
$file = substr_replace($file, $v, 0, strlen($k));
break;
}
}
return strtr($this->fileLinkFormat[0], array('%f' => $file, '%l' => $line));
}
return false;

View File

@ -359,7 +359,7 @@ abstract class FrameworkExtensionTest extends TestCase
{
$container = $this->createContainerFromFile('full');
$this->assertEquals('file%link%format', $container->getParameter('templating.helper.code.file_link_format'));
$this->assertEquals('file%link%format', $container->getParameter('debug.file_link_format'));
}
public function testValidationAnnotations()

View File

@ -55,10 +55,6 @@ class ExtensionPass implements CompilerPassInterface
$container->getDefinition('twig.extension.httpfoundation')->addTag('twig.extension');
}
if ($container->hasParameter('templating.helper.code.file_link_format')) {
$container->getDefinition('twig.extension.code')->replaceArgument(0, $container->getParameter('templating.helper.code.file_link_format'));
}
if ($container->getParameter('kernel.debug')) {
$container->getDefinition('twig.extension.profiler')->addTag('twig.extension');
$container->getDefinition('twig.extension.debug')->addTag('twig.extension');

View File

@ -83,7 +83,7 @@
<service id="twig.extension.code" class="Symfony\Bridge\Twig\Extension\CodeExtension" public="false">
<tag name="twig.extension" />
<argument /> <!-- %templating.helper.code.file_link_format% -->
<argument>%debug.file_link_format%</argument>
<argument>%kernel.root_dir%</argument>
<argument>%kernel.charset%</argument>
</service>

View File

@ -31,7 +31,7 @@
"symfony/routing": "~2.8|~3.0",
"symfony/templating": "~2.8|~3.0",
"symfony/yaml": "~2.8|~3.0",
"symfony/framework-bundle": "~2.8|~3.0"
"symfony/framework-bundle": "~3.2"
},
"autoload": {
"psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" },

View File

@ -34,6 +34,18 @@
<service id="twig.extension.webprofiler" class="Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension" public="false">
<tag name="twig.extension" />
<argument type="service">
<service class="Symfony\Component\VarDumper\Dumper\HtmlDumper">
<argument>null</argument>
<argument>%kernel.charset%</argument>
<argument type="constant">Symfony\Component\VarDumper\Dumper\HtmlDumper::DUMP_LIGHT_ARRAY</argument>
<call method="setDisplayOptions">
<argument type="collection">
<argument key="fileLinkFormat">%debug.file_link_format%</argument>
</argument>
</call>
</service>
</argument>
</service>
</services>
</container>

View File

@ -56,6 +56,8 @@ class WebProfilerExtensionTest extends TestCase
$this->container->setParameter('kernel.cache_dir', __DIR__);
$this->container->setParameter('kernel.debug', false);
$this->container->setParameter('kernel.root_dir', __DIR__);
$this->container->setParameter('kernel.charset', 'UTF-8');
$this->container->setParameter('debug.file_link_format', null);
$this->container->setParameter('profiler.class', array('Symfony\\Component\\HttpKernel\\Profiler\\Profiler'));
$this->container->register('profiler', $this->getMockClass('Symfony\\Component\\HttpKernel\\Profiler\\Profiler'))
->addArgument(new Definition($this->getMockClass('Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface')));

View File

@ -20,7 +20,8 @@
"symfony/http-kernel": "~3.1",
"symfony/polyfill-php70": "~1.0",
"symfony/routing": "~2.8|~3.0",
"symfony/twig-bridge": "~2.8|~3.0"
"symfony/twig-bridge": "~2.8|~3.0",
"symfony/var-dumper": "~3.2"
},
"require-dev": {
"symfony/config": "~2.8|~3.0",

View File

@ -37,9 +37,16 @@ class ExceptionHandler
public function __construct($debug = true, $charset = null, $fileLinkFormat = null)
{
$fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
if ($fileLinkFormat && !is_array($fileLinkFormat)) {
$i = max(strpos($fileLinkFormat, '%f'), strpos($fileLinkFormat, '%l'));
$i = strpos($fileLinkFormat, '#', $i) ?: strlen($fileLinkFormat);
$fileLinkFormat = array(substr($fileLinkFormat, 0, $i), substr($fileLinkFormat, $i + 1));
parse_str($fileLinkFormat[1], $fileLinkFormat[1]);
}
$this->debug = $debug;
$this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8';
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->fileLinkFormat = $fileLinkFormat;
}
/**
@ -82,14 +89,22 @@ class ExceptionHandler
/**
* Sets the format for links to source files.
*
* @param string $format The format for links to source files
* @param string|array $format The format for links to source files
*
* @return string The previous file link format
*/
public function setFileLinkFormat($format)
public function setFileLinkFormat($fileLinkFormat)
{
$old = $this->fileLinkFormat;
$this->fileLinkFormat = $format;
if ($fileLinkFormat && !is_array($fileLinkFormat)) {
$i = strpos($fileLinkFormat, '#') ?: strlen($fileLinkFormat);
$fileLinkFormat = array(substr($fileLinkFormat, 0, $i), substr($fileLinkFormat, $i + 1));
parse_str($fileLinkFormat[1], $fileLinkFormat[1]);
}
$this->fileLinkFormat = $fileLinkFormat;
if ($old) {
$old = $old[0].($old[1] ? '#'.http_build_query($old[1], '', '&') : '');
}
return $old;
}
@ -350,16 +365,21 @@ EOF;
private function formatPath($path, $line)
{
$path = $this->escapeHtml($path);
$file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path;
$file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path);
if ($linkFormat = $this->fileLinkFormat) {
$link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line));
if ($fileLinkFormat = $this->fileLinkFormat) {
foreach ($fileLinkFormat[1] as $k => $v) {
if (0 === strpos($path, $k)) {
$path = substr_replace($path, $v, 0, strlen($k));
break;
}
}
$link = strtr($fileLinkFormat[0], array('%f' => $path, '%l' => (int) $line));
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $link, $file, $line);
return sprintf(' in <a href="%s" title="Go to source">%s line %d</a>', $this->escapeHtml($link), $file, $line);
}
return sprintf(' in <a title="%s line %3$d">%s line %d</a>', $path, $file, $line);
return sprintf(' in <a title="%s line %3$d">%s line %d</a>', $this->escapeHtml($path), $file, $line);
}
/**

View File

@ -40,8 +40,15 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null)
{
$fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
if ($fileLinkFormat && !is_array($fileLinkFormat)) {
$i = max(strpos($fileLinkFormat, '%f'), strpos($fileLinkFormat, '%l'));
$i = strpos($fileLinkFormat, '#', $i) ?: strlen($fileLinkFormat);
$fileLinkFormat = array(substr($fileLinkFormat, 0, $i), substr($fileLinkFormat, $i + 1));
parse_str($fileLinkFormat[1], $fileLinkFormat[1]);
}
$this->stopwatch = $stopwatch;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->fileLinkFormat = $fileLinkFormat;
$this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8';
$this->requestStack = $requestStack;
$this->dumper = $dumper;
@ -149,6 +156,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
) {
if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
$this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat));
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
}
@ -198,6 +206,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if ('html' === $format) {
$dumper = new HtmlDumper($data, $this->charset);
$dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat));
} else {
throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format));
}
@ -234,6 +243,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) {
$this->dumper = new HtmlDumper('php://output', $this->charset);
$dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat));
} else {
$this->dumper = new CliDumper('php://output', $this->charset);
}
@ -258,7 +268,13 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
$name = strip_tags($this->style('', $name));
$file = strip_tags($this->style('', $file));
if ($fileLinkFormat) {
$link = strtr(strip_tags($this->style('', $fileLinkFormat)), array('%f' => $file, '%l' => (int) $line));
foreach ($fileLinkFormat[1] as $k => $v) {
if (0 === strpos($file, $k)) {
$file = substr_replace($file, $v, 0, strlen($k));
break;
}
}
$link = strtr(strip_tags($this->style('', $fileLinkFormat[0])), array('%f' => $file, '%l' => (int) $line));
$name = sprintf('<a href="%s" title="%s">'.$s.'</a>', $link, $file, $name);
} else {
$name = sprintf('<abbr title="%s">'.$s.'</abbr>', $file, $name);

View File

@ -44,7 +44,7 @@ class DebugHandlersListener implements EventSubscriberInterface
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
* @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value
* @param bool $scream Enables/disables screaming mode, where even silenced errors are logged
* @param string $fileLinkFormat The format for links to source files
* @param string|array $fileLinkFormat The format for links to source files
* @param bool $scope Enables/disables scoping mode
*/
public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true)
@ -54,7 +54,7 @@ class DebugHandlersListener implements EventSubscriberInterface
$this->levels = null === $levels ? E_ALL : $levels;
$this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? E_ALL : null));
$this->scream = (bool) $scream;
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
$this->fileLinkFormat = $fileLinkFormat;
$this->scope = (bool) $scope;
}

View File

@ -37,7 +37,7 @@
"symfony/stopwatch": "~2.8|~3.0",
"symfony/templating": "~2.8|~3.0",
"symfony/translation": "~2.8|~3.0",
"symfony/var-dumper": "~2.8|~3.0"
"symfony/var-dumper": "~3.2"
},
"conflict": {
"symfony/config": "<2.8"