1: <?php
2:
3: namespace GAubry\Helpers;
4:
5: /**
6: * Debug class useful for don't forgetting where debug traces are.
7: *
8: * Automatically decorates print_r() and var_dump() with following information:
9: * – file and line of the caller
10: * – name of function/method containing the call
11: * – name of the parameter passed during call
12: *
13: * Example:
14: * <code>
15: * function f($value) {Debug::printr($value);}
16: * </code>
17: *
18: * Result:
19: * <code>
20: * [function f() in file /path/to/file.php, line 31]
21: * $value =
22: * Array
23: * (
24: * [0] => 'xyz'
25: * )
26: * </code>
27: *
28: * See examples/debug.php for a complete example.
29: */
30: class Debug
31: {
32:
33: /**
34: * Patterns for both HTML and CLI rendering:
35: * %1$s = function name or '∅' if no function
36: * %2$s = filename,
37: * %3$d = line in filename,
38: * %4$s = varname in parameter,
39: * %5$s = value of parameter
40: *
41: * @var array
42: * @see self::displayTrace()
43: */
44: public static $sDisplayPatterns = array(
45: 'html' => '<pre><i>[function %1$s in file %2$s, line %3$d]</i><br /><b>%4$s</b> = %5$s</pre>',
46: // @codingStandardsIgnoreStart HEREDOC syntax is not supported in array by pdepend…
47: 'cli' => "\033[2;33;40m[function \033[1m%1\$s\033[2m in file \033[1m%2\$s\033[2m, line \033[1m%3\$d\033[2m]\n\033[1m%4\$s\033[2m = \033[0m\n%5\$s\n"
48: // @codingStandardsIgnoreEnd
49: );
50:
51: /**
52: * Constructor.
53: *
54: * @codeCoverageIgnore
55: */
56: private function __construct()
57: {
58: }
59:
60: /**
61: * Returns an array containing function name, filename and line in filename of the caller.
62: * If called out of any function, then return '' as function name.
63: * To return the caller of your function, either call get_caller(), or get_caller(__FUNCTION__).
64: *
65: * @see http://stackoverflow.com/a/4767754
66: * @author Aram Kocharyan
67: * @author Geoffroy Aubry
68: *
69: * @param string $sFunctionName function whose caller is searched
70: * @param array stack trace, or debug_backtrace() by default
71: * @return array triplet: (string)function name or '', (string)filename or '', (int)line in filename or 0
72: */
73: public static function getCaller ($sFunctionName = '', $aStack = array())
74: {
75: if ($aStack == array()) {
76: $aStack = debug_backtrace();
77: }
78:
79: if ($sFunctionName == '') {
80: // We need $sFunctionName to be a function name to retrieve its caller. If it is omitted, then
81: // we need to first find what function called get_caller(), and substitute that as the
82: // default $sFunctionName. Remember that invoking get_caller() recursively will add another
83: // instance of it to the function stack, so tell get_caller() to use the current stack.
84: list($sFunctionName, , ) = self::getCaller(__FUNCTION__, $aStack);
85: }
86:
87: // If we are given a function name as a string, go through the function stack and find
88: // it's caller.
89: for ($i = 0; $i < count($aStack); $i++) {
90: $aCurrFunction = $aStack[$i];
91: // Make sure that a caller exists, a function being called within the main script won't have a caller.
92: if ($aCurrFunction['function'] == $sFunctionName && ($i + 1) < count($aStack)) {
93: if (preg_match("/^(.*?)\((\d+)\) : eval\(\)\\'d code$/i", $aStack[$i]['file'], $aMatches) === 1) {
94: return array('eval', $aMatches[1], $aMatches[2]);
95: } else {
96: return array($aStack[$i + 1]['function'], $aStack[$i]['file'], $aStack[$i]['line']);
97: }
98: }
99: }
100:
101: // If out of any function:
102: if ($aCurrFunction['function'] == $sFunctionName) {
103: return array('', $aCurrFunction['file'], $aCurrFunction['line']);
104: } else {
105: // At this stage, no caller has been found, bummer.
106: return array('', '', 0);
107: }
108: }
109:
110: /**
111: * Return the name of the first parameter of the penultimate function call.
112: *
113: * TODO bug if multiple calls in the same line…
114: *
115: * @see http://stackoverflow.com/a/6837836
116: * @author Sebastián Grignoli
117: * @author Geoffroy Aubry
118: *
119: * @param string $sFunction function called
120: * @param string $sFile file containing a call to $sFunction
121: * @param int $iLine line in $sFile containing a call to $sFunction
122: * @return string the name of the first parameter of the penultimate function call.
123: */
124: private static function getVarName ($sFunction, $sFile, $iLine)
125: {
126: $sContent = file($sFile);
127: $sLine = $sContent[$iLine - 1];
128: preg_match("#$sFunction\s*\((.+)\)#", $sLine, $aMatches);
129:
130: // Let's count brackets to see how many of them actually belongs to the var name.
131: // e.g.: die(catch_param($this->getUser()->hasCredential("delete")));
132: // We want: $this->getUser()->hasCredential("delete")
133: $iMax = strlen($aMatches[1]);
134: $sVarname = '';
135: $iNb = 0;
136: for ($i = 0; $i < $iMax; $i++) {
137: $char = substr($aMatches[1], $i, 1);
138: if ($char == '(') {
139: $iNb++;
140: } elseif ($char == ')') {
141: $iNb--;
142: if ($iNb < 0) {
143: break;
144: }
145: }
146: $sVarname .= $char;
147: }
148:
149: // $varname now holds the name of the passed variable ('$' included)
150: // e.g.: catch_param($hello)
151: // => $sVarname = "$hello"
152: // or the whole expression evaluated
153: // e.g.: catch_param($this->getUser()->hasCredential("delete"))
154: // => $sVarname = "$this->getUser()->hasCredential(\"delete\")"
155: return $sVarname;
156: }
157:
158: /**
159: * Use specified pattern to display function name, filename, line in filename,
160: * varname in parameter and value of this parameter of the caller.
161: *
162: * @param string $sPattern key of self::$sDisplayPatterns
163: * @param string $sValue value of the parameter of the caller
164: * @see self::$sDisplayPatterns
165: */
166: private static function displayTrace ($sPattern, $sValue)
167: {
168: list($sDebugFunction, , ) = self::getCaller();
169: list($sFunction, $sFile, $sLine) = self::getCaller($sDebugFunction);
170: $sFunction = (empty($sFunction) ? '∅' : "$sFunction()");
171: $sVarName = self::getVarName($sDebugFunction, $sFile, $sLine);
172: echo sprintf(self::$sDisplayPatterns[$sPattern], $sFunction, $sFile, $sLine, $sVarName, $sValue);
173: }
174:
175: /**
176: * Display an HTML trace containing a var_dump() of the specified value.
177: *
178: * @param mixed $mValue value to pass to var_dump()
179: */
180: public static function htmlVarDump ($mValue)
181: {
182: ob_start();
183: var_dump($mValue);
184: $sOut = ob_get_contents();
185: ob_end_clean();
186: self::displayTrace('html', htmlspecialchars($sOut, ENT_QUOTES));
187: }
188:
189: /**
190: * Display an HTML trace containing a print_r() of the specified value.
191: *
192: * @param mixed $mValue value to pass to print_r()
193: */
194: public static function htmlPrintr ($mValue)
195: {
196: self::displayTrace('html', htmlspecialchars(print_r($mValue, true), ENT_QUOTES));
197: }
198:
199: /**
200: * Display a CLI trace containing a var_dump() of the specified value.
201: *
202: * @param mixed $mValue value to pass to var_dump()
203: */
204: public static function varDump ($mValue)
205: {
206: ob_start();
207: var_dump($mValue);
208: $sOut = ob_get_contents();
209: ob_end_clean();
210: self::displayTrace('cli', $sOut);
211: }
212:
213: /**
214: * Display a CLI trace containing a print_r() of the specified value.
215: *
216: * @param mixed $mValue value to pass to print_r()
217: */
218: public static function printr ($mValue)
219: {
220: self::displayTrace('cli', print_r($mValue, true));
221: }
222: }
223: