• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / php-generator / 8196034575

07 Mar 2024 11:06PM UTC coverage: 93.769%. Remained the same
8196034575

push

github

dg
PsrPrinter: opening bracket on the correct line [Closes #155]

6 of 6 new or added lines in 2 files covered. (100.0%)

1 existing line in 1 file now uncovered.

1535 of 1637 relevant lines covered (93.77%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.56
/src/PhpGenerator/Dumper.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\PhpGenerator;
11

12
use Nette;
13

14

15
/**
16
 * Generates a PHP representation of a variable.
17
 */
18
final class Dumper
19
{
20
        private const IndentLength = 4;
21

22
        public int $maxDepth = 50;
23
        public int $wrapLength = 120;
24
        public string $indentation = "\t";
25
        public bool $customObjects = true;
26

27

28
        /**
29
         * Returns a PHP representation of a variable.
30
         */
31
        public function dump(mixed $var, int $column = 0): string
1✔
32
        {
33
                return $this->dumpVar($var, [], 0, $column);
1✔
34
        }
35

36

37
        /** @param  array<mixed[]|object>  $parents */
38
        private function dumpVar(mixed $var, array $parents = [], int $level = 0, int $column = 0): string
1✔
39
        {
40
                if ($var === null) {
1✔
41
                        return 'null';
1✔
42

43
                } elseif (is_string($var)) {
1✔
44
                        return $this->dumpString($var);
1✔
45

46
                } elseif (is_array($var)) {
1✔
47
                        return $this->dumpArray($var, $parents, $level, $column);
1✔
48

49
                } elseif ($var instanceof Literal) {
1✔
50
                        return $this->dumpLiteral($var, $level);
1✔
51

52
                } elseif (is_object($var)) {
1✔
53
                        return $this->dumpObject($var, $parents, $level, $column);
1✔
54

55
                } elseif (is_resource($var)) {
1✔
56
                        throw new Nette\InvalidStateException('Cannot dump value of type resource.');
×
57

58
                } else {
59
                        return var_export($var, return: true);
1✔
60
                }
61
        }
62

63

64
        private function dumpString(string $s): string
1✔
65
        {
66
                $special = [
1✔
67
                        "\r" => '\r',
68
                        "\n" => '\n',
69
                        "\t" => '\t',
70
                        "\e" => '\e',
71
                        '\\' => '\\\\',
72
                ];
73

74
                $utf8 = preg_match('##u', $s);
1✔
75
                $escaped = preg_replace_callback(
1✔
76
                        $utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#',
1✔
77
                        fn($m) => $special[$m[0]] ?? (strlen($m[0]) === 1
1✔
78
                                        ? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . ''
1✔
79
                                        : '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}'),
1✔
80
                        $s,
81
                );
82
                return $s === str_replace('\\\\', '\\', $escaped)
1✔
83
                        ? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'"
1✔
84
                        : '"' . addcslashes($escaped, '"$') . '"';
1✔
85
        }
86

87

88
        private static function utf8Ord(string $c): int
1✔
89
        {
90
                $ord0 = ord($c[0]);
1✔
91
                return match (true) {
92
                        $ord0 < 0x80 => $ord0,
1✔
93
                        $ord0 < 0xE0 => ($ord0 << 6) + ord($c[1]) - 0x3080,
1✔
94
                        $ord0 < 0xF0 => ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080,
1✔
95
                        default => ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080,
1✔
96
                };
97
        }
98

99

100
        /**
101
         * @param  mixed[]  $var
102
         * @param  array<mixed[]|object>  $parents
103
         */
104
        private function dumpArray(array $var, array $parents, int $level, int $column): string
1✔
105
        {
106
                if (empty($var)) {
1✔
107
                        return '[]';
1✔
108

109
                } elseif ($level > $this->maxDepth || in_array($var, $parents, strict: true)) {
1✔
110
                        throw new Nette\InvalidStateException('Nesting level too deep or recursive dependency.');
1✔
111
                }
112

113
                $parents[] = $var;
1✔
114
                $hideKeys = is_int(($keys = array_keys($var))[0]) && $keys === range($keys[0], $keys[0] + count($var) - 1);
1✔
115
                $pairs = [];
1✔
116

117
                foreach ($var as $k => $v) {
1✔
118
                        $keyPart = $hideKeys && ($k !== $keys[0] || $k === 0)
1✔
119
                                ? ''
1✔
120
                                : $this->dumpVar($k) . ' => ';
1✔
121
                        $pairs[] = $keyPart . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart) + 1); // 1 = comma after item
1✔
122
                }
123

124
                $line = '[' . implode(', ', $pairs) . ']';
1✔
125
                $space = str_repeat($this->indentation, $level);
1✔
126
                return !str_contains($line, "\n") && $level * self::IndentLength + $column + strlen($line) <= $this->wrapLength
1✔
127
                        ? $line
1✔
128
                        : "[\n$space" . $this->indentation . implode(",\n$space" . $this->indentation, $pairs) . ",\n$space]";
1✔
129
        }
130

131

132
        /** @param  array<mixed[]|object>  $parents */
133
        private function dumpObject(object $var, array $parents, int $level, int $column): string
1✔
134
        {
135
                if ($level > $this->maxDepth || in_array($var, $parents, strict: true)) {
1✔
136
                        throw new Nette\InvalidStateException('Nesting level too deep or recursive dependency.');
1✔
137
                } elseif ((new \ReflectionObject($var))->isAnonymous()) {
1✔
138
                        throw new Nette\InvalidStateException('Cannot dump an instance of an anonymous class.');
1✔
139
                }
140

141
                $class = $var::class;
1✔
142
                $parents[] = $var;
1✔
143

144
                if ($class === \stdClass::class) {
1✔
145
                        $var = (array) $var;
1✔
146
                        return '(object) ' . $this->dumpArray($var, $parents, $level, $column + 10);
1✔
147

148
                } elseif ($class === \DateTime::class || $class === \DateTimeImmutable::class) {
1✔
149
                        return $this->format(
1✔
150
                                "new \\$class(?, new \\DateTimeZone(?))",
1✔
151
                                $var->format('Y-m-d H:i:s.u'),
1✔
152
                                $var->getTimeZone()->getName(),
1✔
153
                        );
154

155
                } elseif ($var instanceof \UnitEnum) {
1✔
UNCOV
156
                        return '\\' . $var::class . '::' . $var->name;
×
157

158
                } elseif ($var instanceof \Closure) {
1✔
159
                        $inner = Nette\Utils\Callback::unwrap($var);
1✔
160
                        if (Nette\Utils\Callback::isStatic($inner)) {
1✔
161
                                return PHP_VERSION_ID < 80100
1✔
162
                                        ? '\Closure::fromCallable(' . $this->dump($inner) . ')'
1✔
163
                                        : implode('::', (array) $inner) . '(...)';
1✔
164
                        }
165

166
                        throw new Nette\InvalidStateException('Cannot dump object of type Closure.');
1✔
167

168
                } elseif ($this->customObjects) {
1✔
169
                        return $this->dumpCustomObject($var, $parents, $level);
1✔
170

171
                } else {
172
                        throw new Nette\InvalidStateException("Cannot dump object of type $class.");
1✔
173
                }
174
        }
175

176

177
        /** @param  array<mixed[]|object>  $parents */
178
        private function dumpCustomObject(object $var, array $parents, int $level): string
1✔
179
        {
180
                $class = $var::class;
1✔
181
                $space = str_repeat($this->indentation, $level);
1✔
182
                $out = "\n";
1✔
183

184
                if (method_exists($var, '__serialize')) {
1✔
185
                        $arr = $var->__serialize();
1✔
186
                } else {
187
                        $arr = (array) $var;
1✔
188
                        if (method_exists($var, '__sleep')) {
1✔
189
                                foreach ($var->__sleep() as $v) {
1✔
190
                                        $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
1✔
191
                                }
192
                        }
193
                }
194

195
                foreach ($arr as $k => $v) {
1✔
196
                        if (!isset($props) || isset($props[$k])) {
1✔
197
                                $out .= $space . $this->indentation
1✔
198
                                        . ($keyPart = $this->dumpVar($k) . ' => ')
1✔
199
                                        . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
1✔
200
                                        . ",\n";
1✔
201
                        }
202
                }
203

204
                return '\\' . self::class . "::createObject(\\$class::class, [$out$space])";
1✔
205
        }
206

207

208
        private function dumpLiteral(Literal $var, int $level): string
1✔
209
        {
210
                $s = $var->formatWith($this);
1✔
211
                $s = Nette\Utils\Strings::normalizeNewlines($s);
1✔
212
                $s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation);
1✔
213
                return ltrim($s, $this->indentation);
1✔
214
        }
215

216

217
        /**
218
         * Generates PHP statement. Supports placeholders: ?  \?  $?  ->?  ::?  ...?  ...?:  ?*
219
         */
220
        public function format(string $statement, mixed ...$args): string
1✔
221
        {
222
                $tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?(?!\w))#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
1✔
223
                $res = '';
1✔
224
                foreach ($tokens as $n => $token) {
1✔
225
                        if ($n % 2 === 0) {
1✔
226
                                $res .= $token;
1✔
227
                        } elseif ($token === '\?') {
1✔
228
                                $res .= '?';
1✔
229
                        } elseif (!$args) {
1✔
230
                                throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
1✔
231
                        } elseif ($token === '?') {
1✔
232
                                $res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n"));
1✔
233
                        } elseif ($token === '...?' || $token === '...?:' || $token === '?*') {
1✔
234
                                $arg = array_shift($args);
1✔
235
                                if (!is_array($arg)) {
1✔
236
                                        throw new Nette\InvalidArgumentException('Argument must be an array.');
1✔
237
                                }
238

239
                                $res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n"), $token === '...?:');
1✔
240

241
                        } else { // $  ->  ::
242
                                $arg = array_shift($args);
1✔
243
                                if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) {
1✔
244
                                        $arg = '{' . $this->dumpVar($arg) . '}';
1✔
245
                                }
246

247
                                $res .= substr($token, 0, -1) . $arg;
1✔
248
                        }
249
                }
250

251
                if ($args) {
1✔
252
                        throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
1✔
253
                }
254

255
                return $res;
1✔
256
        }
257

258

259
        /** @param  mixed[]  $args */
260
        private function dumpArguments(array $args, int $column, bool $named): string
1✔
261
        {
262
                $pairs = [];
1✔
263
                foreach ($args as $k => $v) {
1✔
264
                        $name = $named && !is_int($k) ? $k . ': ' : '';
1✔
265
                        $pairs[] = $name . $this->dumpVar($v, [$args], 0, $column + strlen($name) + 1); // 1 = ) after args
1✔
266
                }
267

268
                $line = implode(', ', $pairs);
1✔
269
                return count($args) < 2 || (!str_contains($line, "\n") && $column + strlen($line) <= $this->wrapLength)
1✔
270
                        ? $line
1✔
271
                        : "\n" . $this->indentation . implode(",\n" . $this->indentation, $pairs) . ",\n";
1✔
272
        }
273

274

275
        /**
276
         * @param  mixed[]  $props
277
         * @internal
278
         */
279
        public static function createObject(string $class, array $props): object
1✔
280
        {
281
                if (method_exists($class, '__serialize')) {
1✔
282
                        $obj = (new \ReflectionClass($class))->newInstanceWithoutConstructor();
1✔
283
                        $obj->__unserialize($props);
1✔
284
                        return $obj;
1✔
285
                }
286
                return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
1✔
287
        }
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc