From 56fe8d1ffd87d656c294260c5e5af320b1e4dad8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 12 Nov 2012 15:06:38 +0100 Subject: [PATCH] duplicated the code helper code to the Twig bundle The code has been duplicated and not moved for BC reasons. This code has been duplicated in the Twig bundle to be able to decouple the web profiler and the exception templates. --- .../TwigBundle/Extension/CodeExtension.php | 167 ++++++++++++++++-- .../TwigBundle/Resources/config/twig.xml | 4 +- .../Tests/Extension/CodeExtensionTest.php} | 26 +-- 3 files changed, 167 insertions(+), 30 deletions(-) rename src/Symfony/Bundle/{FrameworkBundle/Tests/Templating/Helper/CodeHelperTest.php => TwigBundle/Tests/Extension/CodeExtensionTest.php} (68%) diff --git a/src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php index d3f16aef18..63a2be151b 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/CodeExtension.php @@ -11,26 +11,33 @@ namespace Symfony\Bundle\TwigBundle\Extension; -use Symfony\Component\DependencyInjection\ContainerInterface; +if (!defined('ENT_SUBSTITUTE')) { + define('ENT_SUBSTITUTE', 8); +} /** - * Twig extension for Symfony code helper - * + * Twig extension relate to PHP code and used by the profiler and the default exception templates. * * @author Fabien Potencier */ class CodeExtension extends \Twig_Extension { - private $container; + private $fileLinkFormat; + private $rootDir; + private $charset; /** - * Constructor of Twig Extension to provide functions for code formatting + * Constructor. * - * @param ContainerInterface $container A ContainerInterface instance + * @param string $fileLinkFormat The format for links to source files + * @param string $rootDir The project root directory + * @param string $charset The charset */ - public function __construct(ContainerInterface $container) + public function __construct($fileLinkFormat, $rootDir, $charset) { - $this->container = $container; + $this->fileLinkFormat = empty($fileLinkFormat) ? ini_get('xdebug.file_link_format') : $fileLinkFormat; + $this->rootDir = str_replace('\\', '/', $rootDir).'/'; + $this->charset = $charset; } /** @@ -52,46 +59,174 @@ class CodeExtension extends \Twig_Extension public function abbrClass($class) { - return $this->container->get('templating.helper.code')->abbrClass($class); + $parts = explode('\\', $class); + $short = array_pop($parts); + + return sprintf("%s", $class, $short); } public function abbrMethod($method) { - return $this->container->get('templating.helper.code')->abbrMethod($method); + if (false !== strpos($method, '::')) { + list($class, $method) = explode('::', $method, 2); + $result = sprintf("%s::%s()", $this->abbrClass($class), $method); + } elseif ('Closure' === $method) { + $result = sprintf("%s", $method, $method); + } else { + $result = sprintf("%s()", $method, $method); + } + + return $result; } + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ public function formatArgs($args) { - return $this->container->get('templating.helper.code')->formatArgs($args); + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $parts = explode('\\', $item[1]); + $short = array_pop($parts); + $formattedValue = sprintf("object(%s)", $item[1], $short); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf("array(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->getCharset())); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->getCharset()), true)); + } + + $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + } + + return implode(', ', $result); } + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ public function formatArgsAsText($args) { - return $this->container->get('templating.helper.code')->formatArgsAsText($args); + return strip_tags($this->formatArgs($args)); } + /** + * Returns an excerpt of a code file around the given line number. + * + * @param string $file A file path + * @param int $line The selected line number + * + * @return string An HTML string + */ public function fileExcerpt($file, $line) { - return $this->container->get('templating.helper.code')->fileExcerpt($file, $line); + if (is_readable($file)) { + $code = highlight_file($file, true); + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + $content = preg_split('#
#', $code); + + $lines = array(); + for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; $i++) { + $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; + } + + return '
    '.implode("\n", $lines).'
'; + } } + /** + * Formats a file path. + * + * @param string $file An absolute file path + * @param integer $line The line number + * @param string $text Use this text for the link rather than the file path + * + * @return string + */ public function formatFile($file, $line, $text = null) { - return $this->container->get('templating.helper.code')->formatFile($file, $line, $text); + if (null === $text) { + $file = trim($file); + $fileStr = $file; + if (0 === strpos($fileStr, $this->rootDir)) { + $fileStr = str_replace($this->rootDir, '', str_replace('\\', '/', $fileStr)); + $fileStr = sprintf('kernel.root_dir/%s', $this->rootDir, $fileStr); + } + + $text = "$fileStr at line $line"; + } + + if (false !== $link = $this->getFileLink($file, $line)) { + return sprintf('%s', htmlspecialchars($link, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), $text); + } + + return $text; } + /** + * Returns the link for a given file/line pair. + * + * @param string $file An absolute file path + * @param integer $line The line number + * + * @return string A link of false + */ public function getFileLink($file, $line) { - return $this->container->get('templating.helper.code')->getFileLink($file, $line); + if ($this->fileLinkFormat && is_file($file)) { + return strtr($this->fileLinkFormat, array('%f' => $file, '%l' => $line)); + } + + return false; } public function formatFileFromText($text) { - return $this->container->get('templating.helper.code')->formatFileFromText($text); + $that = $this; + + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) use ($that) { + return 'in '.$that->formatFile($match[2], $match[3]); + }, $text); } public function getName() { return 'code'; } + + protected static function fixCodeMarkup($line) + { + // ending tag from previous line + $opening = strpos($line, ''); + if (false !== $closing && (false === $opening || $closing < $opening)) { + $line = substr_replace($line, '', $closing, 7); + } + + // missing tag at the end of line + $opening = strpos($line, ''); + if (false !== $opening && (false === $closing || $closing > $opening)) { + $line .= ''; + } + + return $line; + } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 4d9dbe3428..8f1210aeac 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -66,7 +66,9 @@ - + %templating.helper.code.file_link_format% + %kernel.root_dir% + %kernel.charset% diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/CodeHelperTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Extension/CodeExtensionTest.php similarity index 68% rename from src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/CodeHelperTest.php rename to src/Symfony/Bundle/TwigBundle/Tests/Extension/CodeExtensionTest.php index 0d53544df3..15f7b5e5bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/CodeHelperTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Extension/CodeExtensionTest.php @@ -9,23 +9,18 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper; +namespace Symfony\Bundle\TwigBundle\Tests\Extension; -use Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper; +use Symfony\Bundle\TwigBundle\Extension\CodeExtension; -class CodeHelperTest extends \PHPUnit_Framework_TestCase +class CodeExtensionTest extends \PHPUnit_Framework_TestCase { - protected static $helper; - - public static function setUpBeforeClass() - { - self::$helper = new CodeHelper('txmt://open?url=file://%f&line=%l', '/root', 'UTF-8'); - } + protected $helper; public function testFormatFile() { $expected = sprintf('%s at line 25', __FILE__, __FILE__); - $this->assertEquals($expected, self::$helper->formatFile(__FILE__, 25)); + $this->assertEquals($expected, $this->getExtension()->formatFile(__FILE__, 25)); } /** @@ -33,7 +28,7 @@ class CodeHelperTest extends \PHPUnit_Framework_TestCase */ public function testGettingClassAbbreviation($class, $abbr) { - $this->assertEquals(self::$helper->abbrClass($class), $abbr); + $this->assertEquals($this->getExtension()->abbrClass($class), $abbr); } /** @@ -41,7 +36,7 @@ class CodeHelperTest extends \PHPUnit_Framework_TestCase */ public function testGettingMethodAbbreviation($method, $abbr) { - $this->assertEquals(self::$helper->abbrMethod($method), $abbr); + $this->assertEquals($this->getExtension()->abbrMethod($method), $abbr); } public function getClassNameProvider() @@ -64,6 +59,11 @@ class CodeHelperTest extends \PHPUnit_Framework_TestCase public function testGetName() { - $this->assertEquals('code', self::$helper->getName()); + $this->assertEquals('code', $this->getExtension()->getName()); + } + + protected function getExtension() + { + return new CodeExtension('txmt://open?url=file://%f&line=%l', '/root', 'UTF-8'); } }