SourceContextProvider.php 4.9 KB

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