Debugger.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\analysis;
  9. use ReflectionClass;
  10. use lithium\util\String;
  11. use lithium\analysis\Inspector;
  12. /**
  13. * The `Debugger` class provides basic facilities for generating and rendering meta-data about the
  14. * state of an application in its current context.
  15. */
  16. class Debugger extends \lithium\core\StaticObject {
  17. /**
  18. * Used for temporary closure caching.
  19. *
  20. * @see lithium\analysis\Debugger::_closureDef()
  21. * @var array
  22. */
  23. protected static $_closureCache = array();
  24. /**
  25. * Outputs a stack trace based on the supplied options.
  26. *
  27. * @param array $options Format for outputting stack trace. Available options are:
  28. * - `'args'`: A boolean indicating if arguments should be included.
  29. * - `'depth'`: The maximum depth of the trace.
  30. * - `'format'`: Either `null`, `'points'` or `'array'`.
  31. * - `'includeScope'`: A boolean indicating if items within scope
  32. * should be included.
  33. * - `'scope'`: Scope for items to include.
  34. * - `'start'`: The depth to start with.
  35. * - `'trace'`: A trace to use instead of generating one.
  36. * @return string Stack trace formatted according to `'format'` option.
  37. */
  38. public static function trace(array $options = array()) {
  39. $defaults = array(
  40. 'depth' => 999,
  41. 'format' => null,
  42. 'args' => false,
  43. 'start' => 0,
  44. 'scope' => array(),
  45. 'trace' => array(),
  46. 'includeScope' => true,
  47. 'closures' => true
  48. );
  49. $options += $defaults;
  50. $backtrace = $options['trace'] ?: debug_backtrace();
  51. $scope = $options['scope'];
  52. $count = count($backtrace);
  53. $back = array();
  54. $traceDefault = array(
  55. 'line' => '??', 'file' => '[internal]', 'class' => null, 'function' => '[main]'
  56. );
  57. for ($i = $options['start']; $i < $count && $i < $options['depth']; $i++) {
  58. $trace = array_merge(array('file' => '[internal]', 'line' => '??'), $backtrace[$i]);
  59. $function = '[main]';
  60. if (isset($backtrace[$i + 1])) {
  61. $next = $backtrace[$i + 1] + $traceDefault;
  62. $function = $next['function'];
  63. if (!empty($next['class'])) {
  64. $function = $next['class'] . '::' . $function . '(';
  65. if ($options['args'] && isset($next['args'])) {
  66. $args = array_map(array('static', 'export'), $next['args']);
  67. $function .= join(', ', $args);
  68. }
  69. $function .= ')';
  70. }
  71. }
  72. if ($options['closures'] && strpos($function, '{closure}') !== false) {
  73. $function = static::_closureDef($backtrace[$i], $function);
  74. }
  75. if (in_array($function, array('call_user_func_array', 'trigger_error'))) {
  76. continue;
  77. }
  78. $trace['functionRef'] = $function;
  79. if ($options['format'] === 'points' && $trace['file'] !== '[internal]') {
  80. $back[] = array('file' => $trace['file'], 'line' => $trace['line']);
  81. } elseif (is_string($options['format']) && $options['format'] != 'array') {
  82. $back[] = String::insert($options['format'], array_map(
  83. function($data) { return is_object($data) ? get_class($data) : $data; },
  84. $trace
  85. ));
  86. } elseif (empty($options['format'])) {
  87. $back[] = $function . ' - ' . $trace['file'] . ', line ' . $trace['line'];
  88. } else {
  89. $back[] = $trace;
  90. }
  91. if (!empty($scope) && array_intersect_assoc($scope, $trace) == $scope) {
  92. if (!$options['includeScope']) {
  93. $back = array_slice($back, 0, count($back) - 1);
  94. }
  95. break;
  96. }
  97. }
  98. if ($options['format'] === 'array' || $options['format'] === 'points') {
  99. return $back;
  100. }
  101. return join("\n", $back);
  102. }
  103. /**
  104. * Returns a parseable string representation of a variable.
  105. *
  106. * @param mixed $var The variable to export.
  107. * @return string The exported contents.
  108. */
  109. public static function export($var) {
  110. $export = var_export($var, true);
  111. if (is_array($var)) {
  112. $replace = array(" (", " )", " ", " )", "=> \n\t");
  113. $with = array("(", ")", "\t", "\t)", "=> ");
  114. $export = str_replace($replace, $with, $export);
  115. }
  116. return $export;
  117. }
  118. /**
  119. * Locates original location of closures.
  120. *
  121. * @param mixed $reference File or class name to inspect.
  122. * @param integer $callLine Line number of class reference.
  123. */
  124. protected static function _definition($reference, $callLine) {
  125. if (file_exists($reference)) {
  126. foreach (array_reverse(token_get_all(file_get_contents($reference))) as $token) {
  127. if (!is_array($token) || $token[2] > $callLine) {
  128. continue;
  129. }
  130. if ($token[0] === T_FUNCTION) {
  131. return $token[2];
  132. }
  133. }
  134. return;
  135. }
  136. list($class, $method) = explode('::', $reference);
  137. if (!class_exists($class)) {
  138. return;
  139. }
  140. $classRef = new ReflectionClass($class);
  141. $methodInfo = Inspector::info($reference);
  142. $methodDef = join("\n", Inspector::lines($classRef->getFileName(), range(
  143. $methodInfo['start'] + 1, $methodInfo['end'] - 1
  144. )));
  145. foreach (array_reverse(token_get_all("<?php {$methodDef} ?>")) as $token) {
  146. if (!is_array($token) || $token[2] > $callLine) {
  147. continue;
  148. }
  149. if ($token[0] === T_FUNCTION) {
  150. return $token[2] + $methodInfo['start'];
  151. }
  152. }
  153. }
  154. protected static function _closureDef($frame, $function) {
  155. $reference = '::';
  156. $frame += array('file' => '??', 'line' => '??');
  157. $cacheKey = "{$frame['file']}@{$frame['line']}";
  158. if (isset(static::$_closureCache[$cacheKey])) {
  159. return static::$_closureCache[$cacheKey];
  160. }
  161. if ($class = Inspector::classes(array('file' => $frame['file']))) {
  162. foreach (Inspector::methods(key($class), 'extents') as $method => $extents) {
  163. $line = $frame['line'];
  164. if (!($extents[0] <= $line && $line <= $extents[1])) {
  165. continue;
  166. }
  167. $class = key($class);
  168. $reference = "{$class}::{$method}";
  169. $function = "{$reference}()::{closure}";
  170. break;
  171. }
  172. } else {
  173. $reference = $frame['file'];
  174. $function = "{$reference}::{closure}";
  175. }
  176. $line = static::_definition($reference, $frame['line']) ?: '?';
  177. $function .= " @ {$line}";
  178. return static::$_closureCache[$cacheKey] = $function;
  179. }
  180. }
  181. ?>