mirror of
https://github.com/processwire/processwire.git
synced 2025-08-15 19:24:28 +02:00
Add new static Debug::backtrace() method to the Debug class. This returns a backtrace array that is simpler and more PW-specific than PHP's version. By default it excludes likely irrelevant (for most) hook-related method calls that usually fill up the backtrace.
This commit is contained in:
@@ -154,4 +154,234 @@ class Debug {
|
|||||||
self::$timers = array();
|
self::$timers = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a backtrace array that is simpler and more PW-specific relative to PHP’s debug_backtrace
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
* @return array|string
|
||||||
|
* @since 3.0.136
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static public function backtrace(array $options = array()) {
|
||||||
|
|
||||||
|
$defaults = array(
|
||||||
|
'limit' => 0, // the limit argument for the debug_backtrace call
|
||||||
|
'flags' => DEBUG_BACKTRACE_PROVIDE_OBJECT, // flags for PHP debug_backtrace method
|
||||||
|
'showHooks' => false, // show internal methods for hook calls?
|
||||||
|
'getString' => false, // get newline separated string rather than array?
|
||||||
|
'maxCount' => 10, // max size for arrays
|
||||||
|
'maxStrlen' => 100, // max length for strings
|
||||||
|
'maxDepth' => 5, // max allowed recursion depth when converting variables to strings
|
||||||
|
'ellipsis' => ' …', // show this ellipsis when a long value is truncated
|
||||||
|
'skipCalls' => array(), // method/function calls to skip
|
||||||
|
);
|
||||||
|
|
||||||
|
$options = array_merge($defaults, $options);
|
||||||
|
if($options['limit']) $options['limit']++;
|
||||||
|
$traces = @debug_backtrace($options['flags'], $options['limit']);
|
||||||
|
$config = wire('config');
|
||||||
|
$rootPath = ProcessWire::getRootPath(true);
|
||||||
|
$rootPath2 = $config && $config->paths ? $config->paths->root : $rootPath;
|
||||||
|
array_shift($traces); // shift of the simpleBacktrace call, which is not needed
|
||||||
|
$apiVars = array();
|
||||||
|
$result = array();
|
||||||
|
$cnt = 0;
|
||||||
|
|
||||||
|
foreach(wire('all') as $name => $value) {
|
||||||
|
if(!is_object($value)) continue;
|
||||||
|
$apiVars[wireClassName($value)] = '$' . $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($traces as $n => $trace) {
|
||||||
|
|
||||||
|
if(!is_array($trace) || !isset($trace['function']) || !isset($trace['file'])) {
|
||||||
|
continue;
|
||||||
|
} else if(count($options['skipCalls']) && in_array($trace['function'], $options['skipCalls'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$obj = null;
|
||||||
|
$class = '';
|
||||||
|
$type = '';
|
||||||
|
$args = $trace['args'];
|
||||||
|
$argStr = '';
|
||||||
|
$file = $trace['file'];
|
||||||
|
$basename = basename($file);
|
||||||
|
$function = $trace['function'];
|
||||||
|
$isHookableCall = false;
|
||||||
|
|
||||||
|
if(isset($trace['object'])) {
|
||||||
|
$obj = $trace['object'];
|
||||||
|
$class = wireClassName($obj);
|
||||||
|
} else if(isset($trace['class'])) {
|
||||||
|
$class = wireClassName($trace['class']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($class) {
|
||||||
|
$type = isset($trace['type']) ? $trace['type'] : '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$options['showHooks']) {
|
||||||
|
if($basename === 'Wire.php' && !wireMethodExists('Wire', $function)) continue;
|
||||||
|
if($class === 'WireHooks' || $basename === 'WireHooks.php') continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strpos($function, '___') === 0) {
|
||||||
|
$isHookableCall = '___';
|
||||||
|
} else if($obj && !method_exists($obj, $function) && method_exists($obj, "___$function")) {
|
||||||
|
$isHookableCall = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($type === '->' && isset($apiVars[$class])) {
|
||||||
|
// use API var name when available
|
||||||
|
if(strtolower($class) === strtolower(ltrim($apiVars[$class], '$'))) {
|
||||||
|
$class = $apiVars[$class];
|
||||||
|
} else {
|
||||||
|
$class = "$class " . $apiVars[$class];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($basename === 'Wire.php' && $class !== 'Wire') {
|
||||||
|
$ref = new \ReflectionClass($trace['class']);
|
||||||
|
$file = $ref->getFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootPath and rootPath2 can be different if one of them represented by a symlink
|
||||||
|
$file = str_replace($rootPath, '/', $file);
|
||||||
|
if($rootPath2 !== $rootPath) $file = str_replace($rootPath2, '/', $file);
|
||||||
|
|
||||||
|
if(($function === '__call' || $function == '_callMethod') && count($args)) {
|
||||||
|
$function = array_shift($args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$options['showHooks'] && $isHookableCall === '___') {
|
||||||
|
$function = substr($function, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($args)) {
|
||||||
|
$newArgs = array();
|
||||||
|
if($isHookableCall && count($args) === 1 && is_array($args[0])) {
|
||||||
|
$newArgs = $args[0];
|
||||||
|
}
|
||||||
|
foreach($args as $arg) {
|
||||||
|
if(is_object($arg)) {
|
||||||
|
$arg = wireClassName($arg) . ' $obj';
|
||||||
|
} else if(is_array($arg)) {
|
||||||
|
$count = count($arg);
|
||||||
|
if($count < 4) {
|
||||||
|
$arg = $count ? self::toStr($arg, array('maxDepth' => 2)) : '[]';
|
||||||
|
} else {
|
||||||
|
$arg = 'array(' . count($arg) . ')';
|
||||||
|
}
|
||||||
|
} else if(is_string($arg)) {
|
||||||
|
if(strlen("$arg") > $options['maxStrlen']) $arg = substr($arg, 0, $options['maxStrlen']) . ' …';
|
||||||
|
$arg = '"' . $arg . '"';
|
||||||
|
} else if(is_bool($arg)) {
|
||||||
|
$arg = $arg ? 'true' : 'false';
|
||||||
|
} else {
|
||||||
|
// leave as-is
|
||||||
|
}
|
||||||
|
$newArgs[] = $arg;
|
||||||
|
}
|
||||||
|
$argStr = implode(', ', $newArgs);
|
||||||
|
if($argStr === '[]') $argStr = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$call = "$class$type$function($argStr)";
|
||||||
|
$file = "$file:$trace[line]";
|
||||||
|
|
||||||
|
if($options['getString']) {
|
||||||
|
$result[] = "$cnt. $file » $call";
|
||||||
|
} else {
|
||||||
|
$result[] = array(
|
||||||
|
'file' => $file,
|
||||||
|
'call' => $call,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($options['getString']) $result = implode("\n", $result);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert value to string for backtrace method
|
||||||
|
*
|
||||||
|
* @param $value
|
||||||
|
* @param array $options
|
||||||
|
* @return null|string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static protected function toStr($value, array $options = array()) {
|
||||||
|
|
||||||
|
$defaults = array(
|
||||||
|
'maxCount' => 10, // max size for arrays
|
||||||
|
'maxStrlen' => 100, // max length for strings
|
||||||
|
'maxDepth' => 5,
|
||||||
|
'ellipsis' => ' …'
|
||||||
|
);
|
||||||
|
|
||||||
|
static $depth = 0;
|
||||||
|
$options = count($options) ? array_merge($defaults, $options) : $defaults;
|
||||||
|
$depth++;
|
||||||
|
|
||||||
|
if(is_object($value)) {
|
||||||
|
// object
|
||||||
|
$str = wireClassName($value);
|
||||||
|
if($str === 'HookEvent') {
|
||||||
|
$str .= ' $event';
|
||||||
|
} else if(method_exists($value, '__toString')) {
|
||||||
|
$value = (string) $value;
|
||||||
|
if($value !== $str) {
|
||||||
|
if(strlen($value) > $options['maxStrlen']) {
|
||||||
|
$value = substr($value, 0, $options['maxStrlen']) . $options['ellipsis'];
|
||||||
|
}
|
||||||
|
$str .= "($value)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(is_array($value)) {
|
||||||
|
// array
|
||||||
|
if(empty($value)) {
|
||||||
|
$str = '[]';
|
||||||
|
} else if($depth >= $options['maxDepth']) {
|
||||||
|
$str = "array(" . count($value) . ")";
|
||||||
|
} else {
|
||||||
|
$suffix = '';
|
||||||
|
if(count($value) > $options['maxCount']) {
|
||||||
|
$value = array_slice($value, 0, $options['maxCount']);
|
||||||
|
$suffix = $options['ellipsis'];
|
||||||
|
}
|
||||||
|
foreach($value as $k => $v) {
|
||||||
|
$value[$k] = self::toStr($v, $options);
|
||||||
|
}
|
||||||
|
$str = '[ ' . implode(', ', $value) . $suffix . ' ]';
|
||||||
|
}
|
||||||
|
} else if(is_string($value)) {
|
||||||
|
// string
|
||||||
|
if(strlen($value) > $options['maxStrlen']) {
|
||||||
|
$value = substr($value, 0, $options['maxStrlen']) . $options['ellipsis'];
|
||||||
|
}
|
||||||
|
$hasDQ = strpos($value, '"') !== false;
|
||||||
|
$hasSQ = strpos($value, "'") !== false;
|
||||||
|
if(($hasDQ && $hasSQ) || $hasSQ) {
|
||||||
|
$value = str_replace('"', '\\"', $value);
|
||||||
|
$str = '"' . $value . '"';
|
||||||
|
} else {
|
||||||
|
$str = "'$value'";
|
||||||
|
}
|
||||||
|
} else if(is_bool($value)) {
|
||||||
|
// true or false
|
||||||
|
$str = $value ? 'true' : 'false';
|
||||||
|
} else {
|
||||||
|
// int, float or other
|
||||||
|
$str = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$depth--;
|
||||||
|
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -90,7 +90,7 @@ abstract class Notice extends WireData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function set($key, $value) {
|
public function set($key, $value) {
|
||||||
if($key === 'text' && strpos($value, 'icon-') === 0 && strpos($value, ' ')) {
|
if($key === 'text' && is_string($value) && strpos($value, 'icon-') === 0 && strpos($value, ' ')) {
|
||||||
list($icon, $value) = explode(' ', $value, 2);
|
list($icon, $value) = explode(' ', $value, 2);
|
||||||
list(,$icon) = explode('-', $icon, 2);
|
list(,$icon) = explode('-', $icon, 2);
|
||||||
$icon = $this->wire('sanitizer')->name($icon);
|
$icon = $this->wire('sanitizer')->name($icon);
|
||||||
|
@@ -760,12 +760,13 @@ class ProcessWire extends Wire {
|
|||||||
* Get root path, check it, and optionally auto-detect it if not provided
|
* Get root path, check it, and optionally auto-detect it if not provided
|
||||||
*
|
*
|
||||||
* @param bool|string $rootPath Root path if already known, in which case we’ll just modify as needed
|
* @param bool|string $rootPath Root path if already known, in which case we’ll just modify as needed
|
||||||
|
* …or specify boolean true to get absolute root path, which disregards any symbolic links to core.
|
||||||
* @return string
|
* @return string
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected static function getRootPath($rootPath = '') {
|
public static function getRootPath($rootPath = '') {
|
||||||
|
|
||||||
if(strpos($rootPath, '..') !== false) {
|
if($rootPath !== true && strpos($rootPath, '..') !== false) {
|
||||||
$rootPath = realpath($rootPath);
|
$rootPath = realpath($rootPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,7 +778,7 @@ class ProcessWire extends Wire {
|
|||||||
if(!file_exists($rootPath . 'wire/core/ProcessWire.php')) $rootPath = '';
|
if(!file_exists($rootPath . 'wire/core/ProcessWire.php')) $rootPath = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($rootPath)) {
|
if(empty($rootPath) || $rootPath === true) {
|
||||||
// if unable to determine from script filename, attempt to determine from current file
|
// if unable to determine from script filename, attempt to determine from current file
|
||||||
$parts = explode(DIRECTORY_SEPARATOR, __FILE__);
|
$parts = explode(DIRECTORY_SEPARATOR, __FILE__);
|
||||||
$parts = array_slice($parts, 0, -3); // removes "ProcessWire.php", "core" and "wire"
|
$parts = array_slice($parts, 0, -3); // removes "ProcessWire.php", "core" and "wire"
|
||||||
|
Reference in New Issue
Block a user