Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Bar
  • BlueScreen
  • Debugger
  • Dumper
  • FireLogger
  • Helpers
  • Logger
  • OutputDebugger

Interfaces

  • IBarPanel
  • ILogger
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Tracy (http://tracy.nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6:  */
  7: 
  8: namespace Tracy;
  9: 
 10: use Tracy;
 11: 
 12: 
 13: /**
 14:  * Dumps a variable.
 15:  *
 16:  * @author     David Grudl
 17:  */
 18: class Dumper
 19: {
 20:     const DEPTH = 'depth', // how many nested levels of array/object properties display (defaults to 4)
 21:         TRUNCATE = 'truncate', // how truncate long strings? (defaults to 150)
 22:         COLLAPSE = 'collapse', // collapse top array/object or how big are collapsed? (defaults to 14)
 23:         COLLAPSE_COUNT = 'collapsecount', // how big array/object are collapsed? (defaults to 7)
 24:         LOCATION = 'location', // show location string? (defaults to 0)
 25:         OBJECT_EXPORTERS = 'exporters', // custom exporters for objects (defaults to Dumper::$objectexporters)
 26:         LIVE = 'live'; // will be rendered using JavaScript
 27: 
 28:     const
 29:         LOCATION_SOURCE = 1, // shows where dump was called
 30:         LOCATION_LINK = 2, // appends clickable anchor
 31:         LOCATION_CLASS = 4; // shows where class is defined
 32: 
 33:     /** @var array */
 34:     public static $terminalColors = array(
 35:         'bool' => '1;33',
 36:         'null' => '1;33',
 37:         'number' => '1;32',
 38:         'string' => '1;36',
 39:         'array' => '1;31',
 40:         'key' => '1;37',
 41:         'object' => '1;31',
 42:         'visibility' => '1;30',
 43:         'resource' => '1;37',
 44:         'indent' => '1;30',
 45:     );
 46: 
 47:     /** @var array */
 48:     public static $resources = array(
 49:         'stream' => 'stream_get_meta_data',
 50:         'stream-context' => 'stream_context_get_options',
 51:         'curl' => 'curl_getinfo',
 52:     );
 53: 
 54:     /** @var array */
 55:     public static $objectExporters = array(
 56:         'Closure' => 'Tracy\Dumper::exportClosure',
 57:         'SplFileInfo' => 'Tracy\Dumper::exportSplFileInfo',
 58:         'SplObjectStorage' => 'Tracy\Dumper::exportSplObjectStorage',
 59:         '__PHP_Incomplete_Class' => 'Tracy\Dumper::exportPhpIncompleteClass',
 60:     );
 61: 
 62:     /** @var array  */
 63:     private static $liveStorage = array();
 64: 
 65: 
 66:     /**
 67:      * Dumps variable to the output.
 68:      * @return mixed  variable
 69:      */
 70:     public static function dump($var, array $options = NULL)
 71:     {
 72:         if (PHP_SAPI !== 'cli' && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()))) {
 73:             echo self::toHtml($var, $options);
 74:         } elseif (self::detectColors()) {
 75:             echo self::toTerminal($var, $options);
 76:         } else {
 77:             echo self::toText($var, $options);
 78:         }
 79:         return $var;
 80:     }
 81: 
 82: 
 83:     /**
 84:      * Dumps variable to HTML.
 85:      * @return string
 86:      */
 87:     public static function toHtml($var, array $options = NULL)
 88:     {
 89:         $options = (array) $options + array(
 90:             self::DEPTH => 4,
 91:             self::TRUNCATE => 150,
 92:             self::COLLAPSE => 14,
 93:             self::COLLAPSE_COUNT => 7,
 94:             self::OBJECT_EXPORTERS => NULL,
 95:         );
 96:         $loc = & $options[self::LOCATION];
 97:         $loc = $loc === TRUE ? ~0 : (int) $loc;
 98:         $options[self::OBJECT_EXPORTERS] = (array) $options[self::OBJECT_EXPORTERS] + self::$objectExporters;
 99:         $live = !empty($options[self::LIVE]) && $var && (is_array($var) || is_object($var) || is_resource($var));
100:         list($file, $line, $code) = $loc ? self::findLocation() : NULL;
101:         $locAttrs = $file && $loc & self::LOCATION_SOURCE ? Helpers::formatHtml(
102:             ' title="%in file % on line %" data-tracy-href="%"', "$code\n", $file, $line, Helpers::editorUri($file, $line)
103:         ) : NULL;
104: 
105:         return '<pre class="tracy-dump' . ($live && $options[self::COLLAPSE] === TRUE ? ' tracy-collapsed' : '') . '"'
106:             . $locAttrs
107:             . ($live ? " data-tracy-dump='" . str_replace("'", '&#039;', json_encode(self::toJson($var, $options))) . "'>" : '>')
108:             . ($live ? '' : self::dumpVar($var, $options))
109:             . ($file && $loc & self::LOCATION_LINK ? '<small>in ' . Helpers::editorLink($file, $line) . '</small>' : '')
110:             . "</pre>\n";
111:     }
112: 
113: 
114:     /**
115:      * Dumps variable to plain text.
116:      * @return string
117:      */
118:     public static function toText($var, array $options = NULL)
119:     {
120:         return htmlspecialchars_decode(strip_tags(self::toHtml($var, $options)), ENT_QUOTES);
121:     }
122: 
123: 
124:     /**
125:      * Dumps variable to x-terminal.
126:      * @return string
127:      */
128:     public static function toTerminal($var, array $options = NULL)
129:     {
130:         return htmlspecialchars_decode(strip_tags(preg_replace_callback('#<span class="tracy-dump-(\w+)">|</span>#', function($m) {
131:             return "\033[" . (isset($m[1], Dumper::$terminalColors[$m[1]]) ? Dumper::$terminalColors[$m[1]] : '0') . 'm';
132:         }, self::toHtml($var, $options))), ENT_QUOTES);
133:     }
134: 
135: 
136:     /**
137:      * Internal toHtml() dump implementation.
138:      * @param  mixed  variable to dump
139:      * @param  array  options
140:      * @param  int    current recursion level
141:      * @return string
142:      */
143:     private static function dumpVar(& $var, array $options, $level = 0)
144:     {
145:         if (method_exists(__CLASS__, $m = 'dump' . gettype($var))) {
146:             return self::$m($var, $options, $level);
147:         } else {
148:             return "<span>unknown type</span>\n";
149:         }
150:     }
151: 
152: 
153:     private static function dumpNull()
154:     {
155:         return "<span class=\"tracy-dump-null\">NULL</span>\n";
156:     }
157: 
158: 
159:     private static function dumpBoolean(& $var)
160:     {
161:         return '<span class="tracy-dump-bool">' . ($var ? 'TRUE' : 'FALSE') . "</span>\n";
162:     }
163: 
164: 
165:     private static function dumpInteger(& $var)
166:     {
167:         return "<span class=\"tracy-dump-number\">$var</span>\n";
168:     }
169: 
170: 
171:     private static function dumpDouble(& $var)
172:     {
173:         $var = is_finite($var)
174:             ? ($tmp = json_encode($var)) . (strpos($tmp, '.') === FALSE ? '.0' : '')
175:             : var_export($var, TRUE);
176:         return "<span class=\"tracy-dump-number\">$var</span>\n";
177:     }
178: 
179: 
180:     private static function dumpString(& $var, $options)
181:     {
182:         return '<span class="tracy-dump-string">"'
183:             . htmlspecialchars(self::encodeString($var, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8')
184:             . '"</span>' . (strlen($var) > 1 ? ' (' . strlen($var) . ')' : '') . "\n";
185:     }
186: 
187: 
188:     private static function dumpArray(& $var, $options, $level)
189:     {
190:         static $marker;
191:         if ($marker === NULL) {
192:             $marker = uniqid("\x00", TRUE);
193:         }
194: 
195:         $out = '<span class="tracy-dump-array">array</span> (';
196: 
197:         if (empty($var)) {
198:             return $out . ")\n";
199: 
200:         } elseif (isset($var[$marker])) {
201:             return $out . (count($var) - 1) . ") [ <i>RECURSION</i> ]\n";
202: 
203:         } elseif (!$options[self::DEPTH] || $level < $options[self::DEPTH]) {
204:             $collapsed = $level ? count($var) >= $options[self::COLLAPSE_COUNT]
205:                 : (is_int($options[self::COLLAPSE]) ? count($var) >= $options[self::COLLAPSE] : $options[self::COLLAPSE]);
206:             $out = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '">'
207:                 . $out . count($var) . ")</span>\n<div" . ($collapsed ? ' class="tracy-collapsed"' : '') . '>';
208:             $var[$marker] = TRUE;
209:             foreach ($var as $k => & $v) {
210:                 if ($k !== $marker) {
211:                     $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . htmlspecialchars(self::encodeString($k, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8') . '"';
212:                     $out .= '<span class="tracy-dump-indent">   ' . str_repeat('|  ', $level) . '</span>'
213:                         . '<span class="tracy-dump-key">' . $k . '</span> => '
214:                         . self::dumpVar($v, $options, $level + 1);
215:                 }
216:             }
217:             unset($var[$marker]);
218:             return $out . '</div>';
219: 
220:         } else {
221:             return $out . count($var) . ") [ ... ]\n";
222:         }
223:     }
224: 
225: 
226:     private static function dumpObject(& $var, $options, $level)
227:     {
228:         $fields = self::exportObject($var, $options[self::OBJECT_EXPORTERS]);
229:         $editor = NULL;
230:         if ($options[self::LOCATION] & self::LOCATION_CLASS) {
231:             $rc = $var instanceof \Closure ? new \ReflectionFunction($var) : new \ReflectionClass($var);
232:             $editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine());
233:         }
234:         $out = '<span class="tracy-dump-object"'
235:             . ($editor ? Helpers::formatHtml(
236:                 ' title="Declared in file % on line %" data-tracy-href="%"', $rc->getFileName(), $rc->getStartLine(), $editor
237:             ) : '')
238:             . '>' . get_class($var) . '</span> <span class="tracy-dump-hash">#' . substr(md5(spl_object_hash($var)), 0, 4) . '</span>';
239: 
240:         static $list = array();
241: 
242:         if (empty($fields)) {
243:             return $out . "\n";
244: 
245:         } elseif (in_array($var, $list, TRUE)) {
246:             return $out . " { <i>RECURSION</i> }\n";
247: 
248:         } elseif (!$options[self::DEPTH] || $level < $options[self::DEPTH] || $var instanceof \Closure) {
249:             $collapsed = $level ? count($fields) >= $options[self::COLLAPSE_COUNT]
250:                 : (is_int($options[self::COLLAPSE]) ? count($fields) >= $options[self::COLLAPSE] : $options[self::COLLAPSE]);
251:             $out = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '">'
252:                 . $out . "</span>\n<div" . ($collapsed ? ' class="tracy-collapsed"' : '') . '>';
253:             $list[] = $var;
254:             foreach ($fields as $k => & $v) {
255:                 $vis = '';
256:                 if ($k[0] === "\x00") {
257:                     $vis = ' <span class="tracy-dump-visibility">' . ($k[1] === '*' ? 'protected' : 'private') . '</span>';
258:                     $k = substr($k, strrpos($k, "\x00") + 1);
259:                 }
260:                 $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . htmlspecialchars(self::encodeString($k, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8') . '"';
261:                 $out .= '<span class="tracy-dump-indent">   ' . str_repeat('|  ', $level) . '</span>'
262:                     . '<span class="tracy-dump-key">' . $k . "</span>$vis => "
263:                     . self::dumpVar($v, $options, $level + 1);
264:             }
265:             array_pop($list);
266:             return $out . '</div>';
267: 
268:         } else {
269:             return $out . " { ... }\n";
270:         }
271:     }
272: 
273: 
274:     private static function dumpResource(& $var, $options, $level)
275:     {
276:         $type = get_resource_type($var);
277:         $out = '<span class="tracy-dump-resource">' . htmlSpecialChars($type, ENT_IGNORE, 'UTF-8') . ' resource</span> '
278:             . '<span class="tracy-dump-hash">#' . intval($var) . '</span>';
279:         if (isset(self::$resources[$type])) {
280:             $out = "<span class=\"tracy-toggle tracy-collapsed\">$out</span>\n<div class=\"tracy-collapsed\">";
281:             foreach (call_user_func(self::$resources[$type], $var) as $k => $v) {
282:                 $out .= '<span class="tracy-dump-indent">   ' . str_repeat('|  ', $level) . '</span>'
283:                     . '<span class="tracy-dump-key">' . htmlSpecialChars($k, ENT_IGNORE, 'UTF-8') . "</span> => " . self::dumpVar($v, $options, $level + 1);
284:             }
285:             return $out . '</div>';
286:         }
287:         return "$out\n";
288:     }
289: 
290: 
291:     /**
292:      * @return mixed
293:      */
294:     private static function toJson(& $var, $options, $level = 0)
295:     {
296:         if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) {
297:             return is_finite($var) ? $var : array('type' => (string) $var);
298: 
299:         } elseif (is_string($var)) {
300:             return self::encodeString($var, $options[self::TRUNCATE]);
301: 
302:         } elseif (is_array($var)) {
303:             static $marker;
304:             if ($marker === NULL) {
305:                 $marker = uniqid("\x00", TRUE);
306:             }
307:             if (isset($var[$marker]) || $level >= $options[self::DEPTH]) {
308:                 return array(NULL);
309:             }
310:             $res = array();
311:             $var[$marker] = TRUE;
312:             foreach ($var as $k => & $v) {
313:                 if ($k !== $marker) {
314:                     $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"';
315:                     $res[] = array($k, self::toJson($v, $options, $level + 1));
316:                 }
317:             }
318:             unset($var[$marker]);
319:             return $res;
320: 
321:         } elseif (is_object($var)) {
322:             $obj = & self::$liveStorage[spl_object_hash($var)];
323:             if ($obj && $obj['level'] <= $level) {
324:                 return array('object' => $obj['id']);
325:             }
326: 
327:             if ($options[self::LOCATION] & self::LOCATION_CLASS) {
328:                 $rc = $var instanceof \Closure ? new \ReflectionFunction($var) : new \ReflectionClass($var);
329:                 $editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine());
330:             }
331:             static $counter = 1;
332:             $obj = $obj ?: array(
333:                 'id' => '0' . $counter++, // differentiate from resources
334:                 'name' => get_class($var),
335:                 'editor' => empty($editor) ? NULL : array('file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor),
336:                 'level' => $level,
337:                 'object' => $var,
338:             );
339: 
340:             if ($level < $options[self::DEPTH] || !$options[self::DEPTH]) {
341:                 $obj['level'] = $level;
342:                 $obj['items'] = array();
343: 
344:                 foreach (self::exportObject($var, $options[self::OBJECT_EXPORTERS]) as $k => $v) {
345:                     $vis = 0;
346:                     if ($k[0] === "\x00") {
347:                         $vis = $k[1] === '*' ? 1 : 2;
348:                         $k = substr($k, strrpos($k, "\x00") + 1);
349:                     }
350:                     $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"';
351:                     $obj['items'][] = array($k, self::toJson($v, $options, $level + 1), $vis);
352:                 }
353:             }
354:             return array('object' => $obj['id']);
355: 
356:         } elseif (is_resource($var)) {
357:             $obj = & self::$liveStorage[(string) $var];
358:             if (!$obj) {
359:                 $type = get_resource_type($var);
360:                 $obj = array('id' => (int) $var, 'name' => $type . ' resource');
361:                 if (isset(self::$resources[$type])) {
362:                     foreach (call_user_func(self::$resources[$type], $var) as $k => $v) {
363:                         $obj['items'][] = array($k, self::toJson($v, $options, $level + 1));
364:                     }
365:                 }
366:             }
367:             return array('resource' => $obj['id']);
368: 
369:         } else {
370:             return 'unknown type';
371:         }
372:     }
373: 
374: 
375:     /** @return array  */
376:     public static function fetchLiveData()
377:     {
378:         $res = array();
379:         foreach (self::$liveStorage as $obj) {
380:             $id = $obj['id'];
381:             unset($obj['level'], $obj['object'], $obj['id']);
382:             $res[$id] = $obj;
383:         }
384:         self::$liveStorage = array();
385:         return $res;
386:     }
387: 
388: 
389:     /**
390:      * @internal
391:      * @return string UTF-8
392:      */
393:     public static function encodeString($s, $maxLength = NULL)
394:     {
395:         static $table;
396:         if ($table === NULL) {
397:             foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
398:                 $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
399:             }
400:             $table["\\"] = '\\\\';
401:             $table["\r"] = '\r';
402:             $table["\n"] = '\n';
403:             $table["\t"] = '\t';
404:         }
405: 
406:         if (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $s) || preg_last_error()) {
407:             if ($maxLength && strlen($s) > $maxLength) {
408:                 $s = substr($s, 0, $maxLength) . ' ... ';
409:             }
410:             $s = strtr($s, $table);
411:         } elseif ($maxLength && strlen(utf8_decode($s)) > $maxLength) {
412:             $s = iconv_substr($s, 0, $maxLength, 'UTF-8') . ' ... ';
413:         }
414: 
415:         return $s;
416:     }
417: 
418: 
419:     /**
420:      * @return array
421:      */
422:     private static function exportObject($obj, array $exporters)
423:     {
424:         foreach ($exporters as $type => $dumper) {
425:             if ($obj instanceof $type) {
426:                 return call_user_func($dumper, $obj);
427:             }
428:         }
429:         return (array) $obj;
430:     }
431: 
432: 
433:     /**
434:      * @return array
435:      */
436:     private static function exportClosure(\Closure $obj)
437:     {
438:         $rc = new \ReflectionFunction($obj);
439:         $res = array();
440:         foreach ($rc->getParameters() as $param) {
441:             $res[] = '$' . $param->getName();
442:         }
443:         return array(
444:             'file' => $rc->getFileName(),
445:             'line' => $rc->getStartLine(),
446:             'variables' => $rc->getStaticVariables(),
447:             'parameters' => implode(', ', $res),
448:         );
449:     }
450: 
451: 
452:     /**
453:      * @return array
454:      */
455:     private static function exportSplFileInfo(\SplFileInfo $obj)
456:     {
457:         return array('path' => $obj->getPathname());
458:     }
459: 
460: 
461:     /**
462:      * @return array
463:      */
464:     private static function exportSplObjectStorage(\SplObjectStorage $obj)
465:     {
466:         $res = array();
467:         foreach (clone $obj as $item) {
468:             $res[] = array('object' => $item, 'data' => $obj[$item]);
469:         }
470:         return $res;
471:     }
472: 
473: 
474:     /**
475:      * @return array
476:      */
477:     private static function exportPhpIncompleteClass(\__PHP_Incomplete_Class $obj)
478:     {
479:         $info = array('className' => NULL, 'private' => array(), 'protected' => array(), 'public' => array());
480:         foreach ((array) $obj as $name => $value) {
481:             if ($name === '__PHP_Incomplete_Class_Name') {
482:                 $info['className'] = $value;
483:             } elseif (preg_match('#^\x0\*\x0(.+)\z#', $name, $m)) {
484:                 $info['protected'][$m[1]] = $value;
485:             } elseif (preg_match('#^\x0(.+)\x0(.+)\z#', $name, $m)) {
486:                 $info['private'][$m[1] . '::$' . $m[2]] = $value;
487:             } else {
488:                 $info['public'][$name] = $value;
489:             }
490:         }
491:         return $info;
492:     }
493: 
494: 
495:     /**
496:      * Finds the location where dump was called.
497:      * @return array [file, line, code]
498:      */
499:     private static function findLocation()
500:     {
501:         foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) {
502:             if (isset($item['class']) && $item['class'] === __CLASS__) {
503:                 $location = $item;
504:                 continue;
505:             } elseif (isset($item['function'])) {
506:                 try {
507:                     $reflection = isset($item['class'])
508:                         ? new \ReflectionMethod($item['class'], $item['function'])
509:                         : new \ReflectionFunction($item['function']);
510:                     if ($reflection->isInternal() || preg_match('#\s@tracySkipLocation\s#', $reflection->getDocComment())) {
511:                         $location = $item;
512:                         continue;
513:                     }
514:                 } catch (\ReflectionException $e) {}
515:             }
516:             break;
517:         }
518: 
519:         if (isset($location['file'], $location['line']) && is_file($location['file'])) {
520:             $lines = file($location['file']);
521:             $line = $lines[$location['line'] - 1];
522:             return array(
523:                 $location['file'],
524:                 $location['line'],
525:                 trim(preg_match('#\w*dump(er::\w+)?\(.*\)#i', $line, $m) ? $m[0] : $line)
526:             );
527:         }
528:     }
529: 
530: 
531:     /**
532:      * @return bool
533:      */
534:     private static function detectColors()
535:     {
536:         return self::$terminalColors &&
537:             (getenv('ConEmuANSI') === 'ON'
538:             || getenv('ANSICON') !== FALSE
539:             || (defined('STDOUT') && function_exists('posix_isatty') && posix_isatty(STDOUT)));
540:     }
541: 
542: }
543: 
Nette 2.3.1 API API documentation generated by ApiGen 2.8.0