| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- <?php
- /**
- * This file is part of the Nette Framework (https://nette.org)
- * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
- */
- declare(strict_types=1);
- namespace Nette\PhpGenerator;
- use Nette;
- /**
- * PHP code generator utils.
- */
- final class Dumper
- {
- private const IndentLength = 4;
- /** @var int */
- public $maxDepth = 50;
- /** @var int */
- public $wrapLength = 120;
- /** @var string */
- public $indentation = "\t";
- /**
- * Returns a PHP representation of a variable.
- */
- public function dump($var, int $column = 0): string
- {
- return $this->dumpVar($var, [], 0, $column);
- }
- private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string
- {
- if ($var === null) {
- return 'null';
- } elseif (is_string($var)) {
- return $this->dumpString($var);
- } elseif (is_array($var)) {
- return $this->dumpArray($var, $parents, $level, $column);
- } elseif ($var instanceof Literal) {
- return $this->dumpLiteral($var, $level);
- } elseif (is_object($var)) {
- return $this->dumpObject($var, $parents, $level);
- } elseif (is_resource($var)) {
- throw new Nette\InvalidArgumentException('Cannot dump resource.');
- } else {
- return var_export($var, true);
- }
- }
- private function dumpString(string $s): string
- {
- static $special = [
- "\r" => '\r',
- "\n" => '\n',
- "\t" => '\t',
- "\e" => '\e',
- '\\' => '\\\\',
- ];
- $utf8 = preg_match('##u', $s);
- $escaped = preg_replace_callback(
- $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#',
- function ($m) use ($special) {
- return $special[$m[0]] ?? (strlen($m[0]) === 1
- ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . ''
- : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}');
- },
- $s
- );
- return $s === str_replace('\\\\', '\\', $escaped)
- ? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'"
- : '"' . addcslashes($escaped, '"$') . '"';
- }
- private static function utf8Ord(string $c): int
- {
- $ord0 = ord($c[0]);
- if ($ord0 < 0x80) {
- return $ord0;
- } elseif ($ord0 < 0xE0) {
- return ($ord0 << 6) + ord($c[1]) - 0x3080;
- } elseif ($ord0 < 0xF0) {
- return ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080;
- } else {
- return ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080;
- }
- }
- private function dumpArray(array &$var, array $parents, int $level, int $column): string
- {
- if (empty($var)) {
- return '[]';
- } elseif ($level > $this->maxDepth || in_array($var, $parents, true)) {
- throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
- }
- $space = str_repeat($this->indentation, $level);
- $outInline = '';
- $outWrapped = "\n$space";
- $parents[] = $var;
- $counter = 0;
- $hideKeys = is_int(($tmp = array_keys($var))[0]) && $tmp === range($tmp[0], $tmp[0] + count($var) - 1);
- foreach ($var as $k => &$v) {
- $keyPart = $hideKeys && $k === $counter
- ? ''
- : $this->dumpVar($k) . ' => ';
- $counter = is_int($k) ? max($k + 1, $counter) : $counter;
- $outInline .= ($outInline === '' ? '' : ', ') . $keyPart;
- $outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline));
- $outWrapped .= $this->indentation
- . $keyPart
- . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
- . ",\n$space";
- }
- array_pop($parents);
- $wrap = strpos($outInline, "\n") !== false || $level * self::IndentLength + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [],
- return '[' . ($wrap ? $outWrapped : $outInline) . ']';
- }
- private function dumpObject($var, array $parents, int $level): string
- {
- if ($var instanceof \Serializable) {
- return 'unserialize(' . $this->dumpString(serialize($var)) . ')';
- } elseif ($var instanceof \UnitEnum) {
- return '\\' . get_class($var) . '::' . $var->name;
- } elseif ($var instanceof \Closure) {
- $inner = Nette\Utils\Callback::unwrap($var);
- if (Nette\Utils\Callback::isStatic($inner)) {
- return PHP_VERSION_ID < 80100
- ? '\Closure::fromCallable(' . $this->dump($inner) . ')'
- : implode('::', (array) $inner) . '(...)';
- }
- throw new Nette\InvalidArgumentException('Cannot dump closure.');
- }
- $class = get_class($var);
- if ((new \ReflectionObject($var))->isAnonymous()) {
- throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
- } elseif (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) {
- return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName());
- }
- $arr = (array) $var;
- $space = str_repeat($this->indentation, $level);
- if ($level > $this->maxDepth || in_array($var, $parents, true)) {
- throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
- }
- $out = "\n";
- $parents[] = $var;
- if (method_exists($var, '__sleep')) {
- foreach ($var->__sleep() as $v) {
- $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
- }
- }
- foreach ($arr as $k => &$v) {
- if (!isset($props) || isset($props[$k])) {
- $out .= $space . $this->indentation
- . ($keyPart = $this->dumpVar($k) . ' => ')
- . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
- . ",\n";
- }
- }
- array_pop($parents);
- $out .= $space;
- return $class === \stdClass::class
- ? "(object) [$out]"
- : '\\' . self::class . "::createObject('$class', [$out])";
- }
- private function dumpLiteral(Literal $var, int $level): string
- {
- $s = $var->formatWith($this);
- $s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation);
- return ltrim($s, $this->indentation);
- }
- /**
- * Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?*
- */
- public function format(string $statement, ...$args): string
- {
- $tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?(?!\w))#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
- $res = '';
- foreach ($tokens as $n => $token) {
- if ($n % 2 === 0) {
- $res .= $token;
- } elseif ($token === '\?') {
- $res .= '?';
- } elseif (!$args) {
- throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
- } elseif ($token === '?') {
- $res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n"));
- } elseif ($token === '...?' || $token === '...?:' || $token === '?*') {
- $arg = array_shift($args);
- if (!is_array($arg)) {
- throw new Nette\InvalidArgumentException('Argument must be an array.');
- }
- $res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n"), $token === '...?:');
- } else { // $ -> ::
- $arg = array_shift($args);
- if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) {
- $arg = '{' . $this->dumpVar($arg) . '}';
- }
- $res .= substr($token, 0, -1) . $arg;
- }
- }
- if ($args) {
- throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
- }
- return $res;
- }
- private function dumpArguments(array &$var, int $column, bool $named): string
- {
- $outInline = $outWrapped = '';
- foreach ($var as $k => &$v) {
- $k = !$named || is_int($k) ? '' : $k . ': ';
- $outInline .= $outInline === '' ? '' : ', ';
- $outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline));
- $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n"
- . $this->indentation . $k . $this->dumpVar($v, [$var], 1);
- }
- return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength)
- ? $outWrapped . "\n"
- : $outInline;
- }
- /**
- * @internal
- */
- public static function createObject(string $class, array $props): object
- {
- return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
- }
- }
|