1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\PhpGenerator;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class Helpers
17: {
18: use Nette\StaticClass;
19:
20: const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
21:
22: const WRAP_LENGTH = 100;
23:
24: const INDENT_LENGTH = 4;
25:
26: const MAX_DEPTH = 50;
27:
28:
29: 30: 31: 32:
33: public static function dump($var)
34: {
35: return self::_dump($var);
36: }
37:
38:
39: private static function _dump(&$var, $level = 0)
40: {
41: if ($var instanceof PhpLiteral) {
42: return (string) $var;
43:
44: } elseif (is_float($var)) {
45: if (is_finite($var)) {
46: $var = var_export($var, true);
47: return strpos($var, '.') === false ? $var . '.0' : $var;
48: }
49: return str_replace('.0', '', var_export($var, true));
50:
51: } elseif ($var === null) {
52: return 'null';
53:
54: } elseif (is_string($var) && (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error())) {
55: static $table;
56: if ($table === null) {
57: foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
58: $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
59: }
60: $table['\\'] = '\\\\';
61: $table["\r"] = '\r';
62: $table["\n"] = '\n';
63: $table["\t"] = '\t';
64: $table['$'] = '\$';
65: $table['"'] = '\"';
66: }
67: return '"' . strtr($var, $table) . '"';
68:
69: } elseif (is_string($var)) {
70: return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|\z)#', '\\\\$0', $var) . "'";
71:
72: } elseif (is_array($var)) {
73: $space = str_repeat("\t", $level);
74:
75: static $marker;
76: if ($marker === null) {
77: $marker = uniqid("\x00", true);
78: }
79: if (empty($var)) {
80: $out = '';
81:
82: } elseif ($level > self::MAX_DEPTH || isset($var[$marker])) {
83: throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
84:
85: } else {
86: $out = '';
87: $outWrapped = "\n$space";
88: $var[$marker] = true;
89: $counter = 0;
90: foreach ($var as $k => &$v) {
91: if ($k !== $marker) {
92: $item = ($k === $counter ? '' : self::_dump($k, $level + 1) . ' => ') . self::_dump($v, $level + 1);
93: $counter = is_int($k) ? max($k + 1, $counter) : $counter;
94: $out .= ($out === '' ? '' : ', ') . $item;
95: $outWrapped .= "\t$item,\n$space";
96: }
97: }
98: unset($var[$marker]);
99: }
100: $wrap = strpos($out, "\n") !== false || strlen($out) > self::WRAP_LENGTH - $level * self::INDENT_LENGTH;
101: return '[' . ($wrap ? $outWrapped : $out) . ']';
102:
103: } elseif ($var instanceof \Serializable) {
104: $var = serialize($var);
105: return 'unserialize(' . self::_dump($var, $level) . ')';
106:
107: } elseif ($var instanceof \Closure) {
108: throw new Nette\InvalidArgumentException('Cannot dump closure.');
109:
110: } elseif (is_object($var)) {
111: $class = get_class($var);
112: if (PHP_VERSION_ID >= 70000 && (new \ReflectionObject($var))->isAnonymous()) {
113: throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
114:
115: } elseif (in_array($class, ['DateTime', 'DateTimeImmutable'], true)) {
116: return self::formatArgs("new $class(?, new DateTimeZone(?))", [$var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()]);
117: }
118:
119: $arr = (array) $var;
120: $space = str_repeat("\t", $level);
121:
122: static $list = [];
123: if ($level > self::MAX_DEPTH || in_array($var, $list, true)) {
124: throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
125:
126: } else {
127: $out = "\n";
128: $list[] = $var;
129: if (method_exists($var, '__sleep')) {
130: foreach ($var->__sleep() as $v) {
131: $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
132: }
133: }
134: foreach ($arr as $k => &$v) {
135: if (!isset($props) || isset($props[$k])) {
136: $out .= "$space\t" . self::_dump($k, $level + 1) . ' => ' . self::_dump($v, $level + 1) . ",\n";
137: }
138: }
139: array_pop($list);
140: $out .= $space;
141: }
142: return $class === 'stdClass'
143: ? "(object) [$out]"
144: : __CLASS__ . "::createObject('$class', [$out])";
145:
146: } elseif (is_resource($var)) {
147: throw new Nette\InvalidArgumentException('Cannot dump resource.');
148:
149: } else {
150: return var_export($var, true);
151: }
152: }
153:
154:
155: 156: 157: 158: 159:
160: public static function format($statement, ...$args)
161: {
162: return self::formatArgs($statement, $args);
163: }
164:
165:
166: 167: 168: 169: 170:
171: public static function formatArgs($statement, array $args)
172: {
173: $tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
174: $res = '';
175: foreach ($tokens as $n => $token) {
176: if ($n % 2 === 0) {
177: $res .= $token;
178: } elseif ($token === '\\?') {
179: $res .= '?';
180: } elseif (!$args) {
181: throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
182: } elseif ($token === '?') {
183: $res .= self::dump(array_shift($args));
184: } elseif ($token === '...?' || $token === '?*') {
185: $arg = array_shift($args);
186: if (!is_array($arg)) {
187: throw new Nette\InvalidArgumentException('Argument must be an array.');
188: }
189: $items = [];
190: foreach ($arg as $tmp) {
191: $items[] = self::dump($tmp);
192: }
193: $res .= strlen($tmp = implode(', ', $items)) > self::WRAP_LENGTH && count($items) > 1
194: ? "\n" . Nette\Utils\Strings::indent(implode(",\n", $items), 1) . "\n"
195: : $tmp;
196:
197: } else {
198: $res .= substr($token, 0, -1) . self::formatMember(array_shift($args));
199: }
200: }
201: return $res;
202: }
203:
204:
205: 206: 207: 208:
209: public static function formatMember($name)
210: {
211: return $name instanceof PhpLiteral || !self::isIdentifier($name)
212: ? '{' . self::_dump($name) . '}'
213: : $name;
214: }
215:
216:
217: 218: 219: 220:
221: public static function ($content)
222: {
223: if (($s = trim($content)) === '') {
224: return '';
225: } elseif (strpos($content, "\n") === false) {
226: return "/** $s */\n";
227: } else {
228: return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
229: }
230: }
231:
232:
233: 234: 235: 236:
237: public static function ($comment)
238: {
239: return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
240: }
241:
242:
243: 244: 245:
246: public static function isIdentifier($value)
247: {
248: return is_string($value) && preg_match('#^' . self::PHP_IDENT . '\z#', $value);
249: }
250:
251:
252: 253: 254:
255: public static function isNamespaceIdentifier($value, $allowLeadingSlash = false)
256: {
257: $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*\z#';
258: return is_string($value) && preg_match($re, $value);
259: }
260:
261:
262: 263: 264: 265: 266:
267: public static function createObject($class, array $props)
268: {
269: return unserialize('O' . substr(serialize((string) $class), 1, -1) . substr(serialize($props), 1));
270: }
271:
272:
273: 274: 275: 276:
277: public static function ($name)
278: {
279: return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : '';
280: }
281:
282:
283: 284: 285: 286:
287: public static function ($name)
288: {
289: return ($pos = strrpos($name, '\\')) === false ? $name : substr($name, $pos + 1);
290: }
291:
292:
293: 294: 295: 296: 297:
298: public static function tabsToSpaces($s, $count = self::INDENT_LENGTH)
299: {
300: return str_replace("\t", str_repeat(' ', $count), $s);
301: }
302: }
303: