SourceContextProvider.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\VarDumper\Dumper\ContextProvider;
  11. use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
  12. use Symfony\Component\HttpKernel\Debug\FileLinkFormatter as LegacyFileLinkFormatter;
  13. use Symfony\Component\VarDumper\Cloner\VarCloner;
  14. use Symfony\Component\VarDumper\Dumper\HtmlDumper;
  15. use Symfony\Component\VarDumper\VarDumper;
  16. use Twig\Template;
  17. /**
  18. * Tries to provide context from sources (class name, file, line, code excerpt, ...).
  19. *
  20. * @author Nicolas Grekas <p@tchwork.com>
  21. * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
  22. */
  23. final class SourceContextProvider implements ContextProviderInterface
  24. {
  25. private int $limit;
  26. private ?string $charset;
  27. private ?string $projectDir;
  28. private FileLinkFormatter|LegacyFileLinkFormatter|null $fileLinkFormatter;
  29. public function __construct(?string $charset = null, ?string $projectDir = null, FileLinkFormatter|LegacyFileLinkFormatter|null $fileLinkFormatter = null, int $limit = 9)
  30. {
  31. $this->charset = $charset;
  32. $this->projectDir = $projectDir;
  33. $this->fileLinkFormatter = $fileLinkFormatter;
  34. $this->limit = $limit;
  35. }
  36. public function getContext(): ?array
  37. {
  38. $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit);
  39. $file = $trace[1]['file'];
  40. $line = $trace[1]['line'];
  41. $name = '-' === $file || 'Standard input code' === $file ? 'Standard input code' : false;
  42. $fileExcerpt = false;
  43. for ($i = 2; $i < $this->limit; ++$i) {
  44. if (isset($trace[$i]['class'], $trace[$i]['function'])
  45. && 'dump' === $trace[$i]['function']
  46. && VarDumper::class === $trace[$i]['class']
  47. ) {
  48. $file = $trace[$i]['file'] ?? $file;
  49. $line = $trace[$i]['line'] ?? $line;
  50. while (++$i < $this->limit) {
  51. if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) {
  52. $file = $trace[$i]['file'];
  53. $line = $trace[$i]['line'];
  54. break;
  55. } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) {
  56. $template = $trace[$i]['object'];
  57. $name = $template->getTemplateName();
  58. $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
  59. $info = $template->getDebugInfo();
  60. if (isset($info[$trace[$i - 1]['line']])) {
  61. $line = $info[$trace[$i - 1]['line']];
  62. $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null;
  63. if ($src) {
  64. $src = explode("\n", $src);
  65. $fileExcerpt = [];
  66. for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) {
  67. $fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>';
  68. }
  69. $fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
  70. }
  71. }
  72. break;
  73. }
  74. }
  75. break;
  76. }
  77. }
  78. if (false === $name) {
  79. $name = str_replace('\\', '/', $file);
  80. $name = substr($name, strrpos($name, '/') + 1);
  81. }
  82. $context = ['name' => $name, 'file' => $file, 'line' => $line];
  83. $context['file_excerpt'] = $fileExcerpt;
  84. if (null !== $this->projectDir) {
  85. $context['project_dir'] = $this->projectDir;
  86. if (str_starts_with($file, $this->projectDir)) {
  87. $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
  88. }
  89. }
  90. if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) {
  91. $context['file_link'] = $fileLink;
  92. }
  93. return $context;
  94. }
  95. private function htmlEncode(string $s): string
  96. {
  97. $html = '';
  98. $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset);
  99. $dumper->setDumpHeader('');
  100. $dumper->setDumpBoundaries('', '');
  101. $cloner = new VarCloner();
  102. $dumper->dump($cloner->cloneVar($s));
  103. return substr(strip_tags($html), 1, -1);
  104. }
  105. }