dumpVar($var, [], 0, $column); } private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string { if ($var instanceof Literal) { return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t"); } elseif ($var === null) { return 'null'; } elseif (is_string($var)) { return $this->dumpString($var); } elseif (is_array($var)) { return $this->dumpArray($var, $parents, $level, $column); } 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 $var): string { if (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()) { static $table; if ($table === null) { foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) { $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT); } $table['\\'] = '\\\\'; $table["\r"] = '\r'; $table["\n"] = '\n'; $table["\t"] = '\t'; $table['$'] = '\$'; $table['"'] = '\"'; } return '"' . strtr($var, $table) . '"'; } return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $var) . "'"; } 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("\t", $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 .= "\t" . $keyPart . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) . ",\n$space"; } array_pop($parents); $wrap = strpos($outInline, "\n") !== false || $level * self::INDENT_LENGTH + $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 \Closure) { 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("\t", $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\t" . ($keyPart = $this->dumpVar($k) . ' => ') . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) . ",\n"; } } array_pop($parents); $out .= $space; return $class === \stdClass::class ? "(object) [$out]" : '\\' . __CLASS__ . "::createObject('$class', [$out])"; } /** * Generates PHP statement. */ public function format(string $statement, ...$args): string { $tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $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 === '?*') { $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")); } 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): string { $outInline = $outWrapped = ''; foreach ($var as &$v) { $outInline .= $outInline === '' ? '' : ', '; $outInline .= $this->dumpVar($v, [$var], 0, $column + strlen($outInline)); $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n\t" . $this->dumpVar($v, [$var], 1); } return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength) ? $outWrapped . "\n" : $outInline; } /** * @return object * @internal */ public static function createObject(string $class, array $props) { return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1)); } }