AbstractDumper.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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;
  11. use Symfony\Component\VarDumper\Cloner\Data;
  12. use Symfony\Component\VarDumper\Cloner\DumperInterface;
  13. /**
  14. * Abstract mechanism for dumping a Data object.
  15. *
  16. * @author Nicolas Grekas <p@tchwork.com>
  17. */
  18. abstract class AbstractDumper implements DataDumperInterface, DumperInterface
  19. {
  20. public const DUMP_LIGHT_ARRAY = 1;
  21. public const DUMP_STRING_LENGTH = 2;
  22. public const DUMP_COMMA_SEPARATOR = 4;
  23. public const DUMP_TRAILING_COMMA = 8;
  24. public static $defaultOutput = 'php://output';
  25. protected $line = '';
  26. protected $lineDumper;
  27. protected $outputStream;
  28. protected $decimalPoint; // This is locale dependent
  29. protected $indentPad = ' ';
  30. protected $flags;
  31. private $charset = '';
  32. /**
  33. * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput
  34. * @param string|null $charset The default character encoding to use for non-UTF8 strings
  35. * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation
  36. */
  37. public function __construct($output = null, string $charset = null, int $flags = 0)
  38. {
  39. $this->flags = $flags;
  40. $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8');
  41. $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point'];
  42. $this->setOutput($output ?: static::$defaultOutput);
  43. if (!$output && \is_string(static::$defaultOutput)) {
  44. static::$defaultOutput = $this->outputStream;
  45. }
  46. }
  47. /**
  48. * Sets the output destination of the dumps.
  49. *
  50. * @param callable|resource|string $output A line dumper callable, an opened stream or an output path
  51. *
  52. * @return callable|resource|string The previous output destination
  53. */
  54. public function setOutput($output)
  55. {
  56. $prev = $this->outputStream ?? $this->lineDumper;
  57. if (\is_callable($output)) {
  58. $this->outputStream = null;
  59. $this->lineDumper = $output;
  60. } else {
  61. if (\is_string($output)) {
  62. $output = fopen($output, 'w');
  63. }
  64. $this->outputStream = $output;
  65. $this->lineDumper = [$this, 'echoLine'];
  66. }
  67. return $prev;
  68. }
  69. /**
  70. * Sets the default character encoding to use for non-UTF8 strings.
  71. *
  72. * @param string $charset The default character encoding to use for non-UTF8 strings
  73. *
  74. * @return string The previous charset
  75. */
  76. public function setCharset($charset)
  77. {
  78. $prev = $this->charset;
  79. $charset = strtoupper($charset);
  80. $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset;
  81. $this->charset = $charset;
  82. return $prev;
  83. }
  84. /**
  85. * Sets the indentation pad string.
  86. *
  87. * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level
  88. *
  89. * @return string The previous indent pad
  90. */
  91. public function setIndentPad($pad)
  92. {
  93. $prev = $this->indentPad;
  94. $this->indentPad = $pad;
  95. return $prev;
  96. }
  97. /**
  98. * Dumps a Data object.
  99. *
  100. * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump
  101. *
  102. * @return string|null The dump as string when $output is true
  103. */
  104. public function dump(Data $data, $output = null)
  105. {
  106. $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point'];
  107. if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) {
  108. setlocale(\LC_NUMERIC, 'C');
  109. }
  110. if ($returnDump = true === $output) {
  111. $output = fopen('php://memory', 'r+');
  112. }
  113. if ($output) {
  114. $prevOutput = $this->setOutput($output);
  115. }
  116. try {
  117. $data->dump($this);
  118. $this->dumpLine(-1);
  119. if ($returnDump) {
  120. $result = stream_get_contents($output, -1, 0);
  121. fclose($output);
  122. return $result;
  123. }
  124. } finally {
  125. if ($output) {
  126. $this->setOutput($prevOutput);
  127. }
  128. if ($locale) {
  129. setlocale(\LC_NUMERIC, $locale);
  130. }
  131. }
  132. return null;
  133. }
  134. /**
  135. * Dumps the current line.
  136. *
  137. * @param int $depth The recursive depth in the dumped structure for the line being dumped,
  138. * or -1 to signal the end-of-dump to the line dumper callable
  139. */
  140. protected function dumpLine($depth)
  141. {
  142. ($this->lineDumper)($this->line, $depth, $this->indentPad);
  143. $this->line = '';
  144. }
  145. /**
  146. * Generic line dumper callback.
  147. *
  148. * @param string $line The line to write
  149. * @param int $depth The recursive depth in the dumped structure
  150. * @param string $indentPad The line indent pad
  151. */
  152. protected function echoLine($line, $depth, $indentPad)
  153. {
  154. if (-1 !== $depth) {
  155. fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n");
  156. }
  157. }
  158. /**
  159. * Converts a non-UTF-8 string to UTF-8.
  160. *
  161. * @param string|null $s The non-UTF-8 string to convert
  162. *
  163. * @return string|null The string converted to UTF-8
  164. */
  165. protected function utf8Encode($s)
  166. {
  167. if (null === $s || preg_match('//u', $s)) {
  168. return $s;
  169. }
  170. if (!\function_exists('iconv')) {
  171. throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
  172. }
  173. if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) {
  174. return $c;
  175. }
  176. if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) {
  177. return $c;
  178. }
  179. return iconv('CP850', 'UTF-8', $s);
  180. }
  181. }