vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php line 46

  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\HttpKernel\DataCollector;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\RequestStack;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
  15. use Symfony\Component\Stopwatch\Stopwatch;
  16. use Symfony\Component\VarDumper\Cloner\Data;
  17. use Symfony\Component\VarDumper\Cloner\VarCloner;
  18. use Symfony\Component\VarDumper\Dumper\CliDumper;
  19. use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
  20. use Symfony\Component\VarDumper\Dumper\DataDumperInterface;
  21. use Symfony\Component\VarDumper\Dumper\HtmlDumper;
  22. use Symfony\Component\VarDumper\Server\Connection;
  23. /**
  24.  * @author Nicolas Grekas <p@tchwork.com>
  25.  *
  26.  * @final
  27.  */
  28. class DumpDataCollector extends DataCollector implements DataDumperInterface
  29. {
  30.     private ?Stopwatch $stopwatch = null;
  31.     private string|FileLinkFormatter|false $fileLinkFormat;
  32.     private int $dataCount = 0;
  33.     private bool $isCollected = true;
  34.     private int $clonesCount = 0;
  35.     private int $clonesIndex = 0;
  36.     private array $rootRefs;
  37.     private string $charset;
  38.     private ?RequestStack $requestStack;
  39.     private DataDumperInterface|Connection|null $dumper;
  40.     private mixed $sourceContextProvider;
  41.     public function __construct(Stopwatch $stopwatch = null, string|FileLinkFormatter $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, DataDumperInterface|Connection $dumper = null)
  42.     {
  43.         $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
  44.         $this->stopwatch = $stopwatch;
  45.         $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter && false === $fileLinkFormat->format('', 0) ? false : $fileLinkFormat;
  46.         $this->charset = $charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8';
  47.         $this->requestStack = $requestStack;
  48.         $this->dumper = $dumper;
  49.         // All clones share these properties by reference:
  50.         $this->rootRefs = [
  51.             &$this->data,
  52.             &$this->dataCount,
  53.             &$this->isCollected,
  54.             &$this->clonesCount,
  55.         ];
  56.         $this->sourceContextProvider = $dumper instanceof Connection && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset);
  57.     }
  58.     public function __clone()
  59.     {
  60.         $this->clonesIndex = ++$this->clonesCount;
  61.     }
  62.     public function dump(Data $data)
  63.     {
  64.         $this->stopwatch?->start('dump');
  65.         ['name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt] = $this->sourceContextProvider->getContext();
  66.         if ($this->dumper instanceof Connection) {
  67.             if (!$this->dumper->write($data)) {
  68.                 $this->isCollected = false;
  69.             }
  70.         } elseif ($this->dumper) {
  71.             $this->doDump($this->dumper, $data, $name, $file, $line);
  72.         } else {
  73.             $this->isCollected = false;
  74.         }
  75.         if (!$this->dataCount) {
  76.             $this->data = [];
  77.         }
  78.         $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
  79.         ++$this->dataCount;
  80.         $this->stopwatch?->stop('dump');
  81.     }
  82.     public function collect(Request $request, Response $response, \Throwable $exception = null)
  83.     {
  84.         if (!$this->dataCount) {
  85.             $this->data = [];
  86.         }
  87.         // Sub-requests and programmatic calls stay in the collected profile.
  88.         if ($this->dumper || ($this->requestStack && $this->requestStack->getMainRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) {
  89.             return;
  90.         }
  91.         // In all other conditions that remove the web debug toolbar, dumps are written on the output.
  92.         if (!$this->requestStack
  93.             || !$response->headers->has('X-Debug-Token')
  94.             || $response->isRedirection()
  95.             || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
  96.             || 'html' !== $request->getRequestFormat()
  97.             || false === strripos($response->getContent(), '</body>')
  98.         ) {
  99.             if ($response->headers->has('Content-Type') && str_contains($response->headers->get('Content-Type'), 'html')) {
  100.                 $dumper = new HtmlDumper('php://output', $this->charset);
  101.                 $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
  102.             } else {
  103.                 $dumper = new CliDumper('php://output', $this->charset);
  104.                 if (method_exists($dumper, 'setDisplayOptions')) {
  105.                     $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
  106.                 }
  107.             }
  108.             foreach ($this->data as $dump) {
  109.                 $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']);
  110.             }
  111.         }
  112.     }
  113.     public function reset()
  114.     {
  115.         $this->stopwatch?->reset();
  116.         $this->data = [];
  117.         $this->dataCount = 0;
  118.         $this->isCollected = true;
  119.         $this->clonesCount = 0;
  120.         $this->clonesIndex = 0;
  121.     }
  122.     /**
  123.      * @internal
  124.      */
  125.     public function __sleep(): array
  126.     {
  127.         if (!$this->dataCount) {
  128.             $this->data = [];
  129.         }
  130.         if ($this->clonesCount !== $this->clonesIndex) {
  131.             return [];
  132.         }
  133.         $this->data[] = $this->fileLinkFormat;
  134.         $this->data[] = $this->charset;
  135.         $this->dataCount = 0;
  136.         $this->isCollected = true;
  137.         return parent::__sleep();
  138.     }
  139.     /**
  140.      * @internal
  141.      */
  142.     public function __wakeup()
  143.     {
  144.         parent::__wakeup();
  145.         $charset = array_pop($this->data);
  146.         $fileLinkFormat = array_pop($this->data);
  147.         $this->dataCount = \count($this->data);
  148.         foreach ($this->data as $dump) {
  149.             if (!\is_string($dump['name']) || !\is_string($dump['file']) || !\is_int($dump['line'])) {
  150.                 throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  151.             }
  152.         }
  153.         self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null);
  154.     }
  155.     public function getDumpsCount(): int
  156.     {
  157.         return $this->dataCount;
  158.     }
  159.     public function getDumps(string $format, int $maxDepthLimit = -1, int $maxItemsPerDepth = -1): array
  160.     {
  161.         $data = fopen('php://memory', 'r+');
  162.         if ('html' === $format) {
  163.             $dumper = new HtmlDumper($data, $this->charset);
  164.             $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
  165.         } else {
  166.             throw new \InvalidArgumentException(sprintf('Invalid dump format: "%s".', $format));
  167.         }
  168.         $dumps = [];
  169.         if (!$this->dataCount) {
  170.             return $this->data = [];
  171.         }
  172.         foreach ($this->data as $dump) {
  173.             $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
  174.             $dump['data'] = stream_get_contents($data, -1, 0);
  175.             ftruncate($data, 0);
  176.             rewind($data);
  177.             $dumps[] = $dump;
  178.         }
  179.         return $dumps;
  180.     }
  181.     public function getName(): string
  182.     {
  183.         return 'dump';
  184.     }
  185.     public function __destruct()
  186.     {
  187.         if (0 === $this->clonesCount-- && !$this->isCollected && $this->dataCount) {
  188.             $this->clonesCount = 0;
  189.             $this->isCollected = true;
  190.             $h = headers_list();
  191.             $i = \count($h);
  192.             array_unshift($h, 'Content-Type: '.\ini_get('default_mimetype'));
  193.             while (0 !== stripos($h[$i], 'Content-Type:')) {
  194.                 --$i;
  195.             }
  196.             if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && stripos($h[$i], 'html')) {
  197.                 $dumper = new HtmlDumper('php://output', $this->charset);
  198.                 $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
  199.             } else {
  200.                 $dumper = new CliDumper('php://output', $this->charset);
  201.                 if (method_exists($dumper, 'setDisplayOptions')) {
  202.                     $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]);
  203.                 }
  204.             }
  205.             foreach ($this->data as $i => $dump) {
  206.                 $this->data[$i] = null;
  207.                 $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']);
  208.             }
  209.             $this->data = [];
  210.             $this->dataCount = 0;
  211.         }
  212.     }
  213.     private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line)
  214.     {
  215.         if ($dumper instanceof CliDumper) {
  216.             $contextDumper = function ($name, $file, $line, $fmt) {
  217.                 if ($this instanceof HtmlDumper) {
  218.                     if ($file) {
  219.                         $s = $this->style('meta', '%s');
  220.                         $f = strip_tags($this->style('', $file));
  221.                         $name = strip_tags($this->style('', $name));
  222.                         if ($fmt && $link = \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line)) {
  223.                             $name = sprintf('<a href="%s" title="%s">'.$s.'</a>', strip_tags($this->style('', $link)), $f, $name);
  224.                         } else {
  225.                             $name = sprintf('<abbr title="%s">'.$s.'</abbr>', $f, $name);
  226.                         }
  227.                     } else {
  228.                         $name = $this->style('meta', $name);
  229.                     }
  230.                     $this->line = $name.' on line '.$this->style('meta', $line).':';
  231.                 } else {
  232.                     $this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':';
  233.                 }
  234.                 $this->dumpLine(0);
  235.             };
  236.             $contextDumper = $contextDumper->bindTo($dumper, $dumper);
  237.             $contextDumper($name, $file, $line, $this->fileLinkFormat);
  238.         } else {
  239.             $cloner = new VarCloner();
  240.             $dumper->dump($cloner->cloneVar($name.' on line '.$line.':'));
  241.         }
  242.         $dumper->dump($data);
  243.     }
  244. }