SourceContextProvider.php 4.7 KB

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