RSI helpers  0.1
RSI helpers
Dump.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi;
4 
5 class Dump extends Thing{
6 
7  public $styleFile = __DIR__ . '/dump.css'; //!< Stylsheet filename.
8  public $styleNamespace = 'dump';
9  public $scriptFile = __DIR__ . '/dump.js'; //!< Javascript filename.
10  public $scriptNamespace = 'dump';
11  public $extraBefore = 10; //!< Extra lines to show before focus in source code dump.
12  public $extraAfter = 5; //!< Extra lines to show after focus line in source code dump.
13  public $sourceFormat = [
14  'php' => [
15  '/\\$\\w+/' => '<i>$0</i>', //var
16  '/(?<!\\$)(?<!::)(?<!-&gt;)\\b(' .
17  'abstract|and|array|as|break|callable|case|catch|class|clone|const|continue|declare|default|die|do|echo|else(if)?|empty|end(declare|for|foreach|' .
18  'if|switch|while)|eval|exit|extends|final|finally|for(each)?|function|global|goto|if|implements|include(_once)?|in(stance|stead)of|interface|isset|list|' .
19  'namespace|new|or|print|private|protected|public|require(_once)?|return|' .
20  'static|switch|throw|trait|try|unset|use|var|while|xor|yield|' .
21  '__\\w+__' .
22  ')\\b/' => '<u>$0</u>', //reserved
23  '/(&lt;\\?php|\\?\\&gt;)/' => '<u><b>$0</b></u>', //PHP tags
24  '/(?<!\\w)\\d+(\\.\\d+)?/' => '<b>$0</b>', //number
25  '/(\'|").*?(?<!\\\\)\\1/' => '<p>$0</p>', //string
26  '/(^\\s*(\\/\\/|\\/\\*|\\*).*|\\/\\/[^\'"]*)$/' => '<em>$0</em>' //comment
27  ],
28  'sql' => [
29  '/\\b(' .
30  'add( constraint)?|alter( column| table)?|all|and|any|as|asc|backup database|between|case|check|column|constraint|create (database|index|view|table|procedure|(unique )?index|view)|database|default|delete|desc|distinct|drop (column|constraint|database|default|index|table|view)|' .
31  'exec|exists|(foreign |primary )?key|from|((full|left|right) )?(outer )?join|group by|having|in|index|inner join|insert into|is (not )?null|like|limit|not|or|order by|' .
32  'procedure|rownum|select( (distinct|into|top))?|set|(truncate )?table|top|union( all)?|unique|update|values|view|where' .
33  ')\\b/i' => '<u>$0</u>', //reserved
34  '/(?<!\\w)\\d+(\\.\\d+)?/' => '<b>$0</b>', //number
35  '/(\'|").*?(?<!\\\\)\\1/' => '<p>$0</p>', //string
36  ]
37  ];
38  public $memoryLimit = null; //!< Maximum memory usage (empty = 20% of script memory limit).
39  public $maxDepth = 16; //!< Maximum recursion depth.
40  public $maxLength = 128; //!< Maximum single line length (chop+expand if longer).
41  public $timeWindow = 315360000; //!< Window (seconds) for integer to time conversion (default = 10 years).
42  public $timeFormat = 'Y-m-d H:i:s';
43  public $validFilename = '/^([A-Z]:[\\/\\\\]|\\/|\\\\\\\\)[^\\*\\?\\:\\|\\<\\>\\&\\r\\n]*\\.\\w+$/'; //!< Regex to determine if a string might be a filename (empty = do not check).
44  public $groupProperties = true; //!< Group properties by (parent) class.
45  public $sortProperties = true; //!< Sort properties by name (ignoring leading underscores).
46  public $remoteDocs = 'https://php.net/manual/en/'; //!< Remote location for PHP documentation.
47  public $localDocs = null; //!< Local location for PHP documentation (https://www.php.net/download-docs.php, "Many HTML files").
48  public $paramConstsFile = __DIR__ . '/../../data/consts.php'; //!< File with const data for parameters.
49 
50  protected $_index = 0;
51  protected $_propertyDocs = [];
52  protected $_paramConsts = null;
53 
54  public function __construct($config = null){
55  $this->configure($config);
56  if(!$this->memoryLimit) $this->memoryLimit = \Rsi::memoryLimit() / 5;
57  }
58  /**
59  * Head for dumps (style and script tags).
60  * @return string
61  */
62  public function head(){
63  return
64  "<style>\n" . file_get_contents($this->styleFile) . "\n</style>\n" .
65  "<script>\n" . file_get_contents($this->scriptFile) . "\n</script>\n";
66  }
67  /**
68  * Dump source code.
69  * @param string|array $filename Filename or array with lines.
70  * @param int $line_no Line to focus (empty = complete file, no focus).
71  * @param int $line_to Show source from $line_no to $line_to (no focus).
72  * @param string $type Formatting type (empty = use filename extension).
73  * @param string $header Header (defaults to filename; may contain HTML / no escaping).
74  * @return string Returns false if the file is not readable.
75  */
76  public function source($filename,$line_no = null,$line_to = null,$type = null,$header = null){
77  if(is_array($filename)){
78  $lines = array_values($filename);
79  $filename = null;
80  }
81  elseif(!is_readable($filename)) return false;
82  else $lines = file($filename);
83  $focus = $min = -1;
84  $max = $count = count($lines);
85  if($line_to){
86  $min = $line_no - 1;
87  $max = $line_to - 1;
88  }
89  elseif($line_no){
90  $focus = $line_no - 1;
91  $min = $focus - $this->extraBefore;
92  $max = $focus + $this->extraAfter;
93  }
94  $max = min($count - 1,$max);
95  $table = '';
96  for($i = max(0,$min); $i <= $max; $i++){
97  $line = htmlspecialchars(rtrim($lines[$i]),ENT_NOQUOTES);
98  foreach(($this->sourceFormat[$type ?: File::ext($filename)] ?? []) as $search => $replace) $line = preg_replace($search,$replace,$line);
99  $table .= "\t\t<tr" . ($i == $focus ? " class='{$this->styleNamespace}-active'" : '') . "><th>" . ($i + 1) . "</th><td>$line</td></tr>\n";
100  }
101  return
102  "<table class='{$this->styleNamespace}-source'>\n" . ($filename || $header ?
103  "\t<thead><tr>" . ($header ? "<th colspan='2'>$header</th>" : "<th><input class='{$this->styleNamespace}-copy' value='" . htmlspecialchars($filename,ENT_QUOTES) . "' onclick='{$this->scriptNamespace}.copy(this)'></th><th>" . htmlspecialchars($filename) . "<span>" . date($this->timeFormat,filemtime($filename)) . ", $count lines</span></th>") . "</tr></thead>\n" : '' ) .
104  "\t<tbody>\n$table\t</tbody>\n" .
105  "</table>\n";
106  }
107 
108  protected function valueAsTime($value){
109  return ($value > time() - $this->timeWindow) && ($value < time() + $this->timeWindow) ? htmlspecialchars(date($this->timeFormat,$value)) : null;
110  }
111 
112  protected function escapeDoc($doc){
113  $doc = htmlspecialchars($doc);
114  $doc = preg_replace('/\\n\\s*\\* /',"\n",$doc);
115  $doc = preg_replace('/\\n\\s*\\*\\s*\\n/',"\n\n",$doc);
116  $doc = preg_replace('/(^\\s*\\/\\*\\*\\s*|\\s*\\*\\/\\s*$)/','',$doc);
117  $doc = preg_replace('/\\n@(.*? |param \\S+ \\$\\w+( )?|return \\S+( )?)/',"\n<code>@\$1</code>",$doc);
118  $doc = str_replace("\n ",' ',$doc);
119  return $doc;
120  }
121 
122  protected function propertyDoc($filename,$property){
123  if(!$filename) return false;
124  if(!array_key_exists($key = $filename . '#' . $property,$this->_propertyDocs)){
125  $docs = false;
126  if(!array_key_exists($filename,$this->_propertyDocs)){
127  $this->_propertyDocs[$filename] = [];
128  if(is_readable($filename)) foreach(token_get_all(file_get_contents($filename)) as $token) if(is_array($token)) switch($token[0]){
129  case T_FUNCTION: break 2;
130  case T_WHITESPACE: break;
131  default: $this->_propertyDocs[$filename][] = $token;
132  }
133  $this->_propertyDocs[$filename][] = [null];
134  $this->_propertyDocs[$filename][] = [null];
135  }
136  foreach(($tokens = $this->_propertyDocs[$filename]) as $index => $token) if(($token[0] == T_VARIABLE) && ($token[1] == '$' . $property)){
137  if((($tokens[++$index][0] == T_COMMENT) || ($tokens[++$index][0] == T_COMMENT)) && (substr($tokens[$index][1],0,4) == '//!<')){
138  $docs = trim(substr($tokens[$index][1],4));
139  while(($tokens[++$index][0] == T_COMMENT) && (substr($tokens[$index][1],0,2) == '//')) $docs .= ' ' . trim(substr($tokens[$index][1],2));
140  }
141  break;
142  }
143  $this->_propertyDocs[$key] = $docs;
144  }
145  return $this->_propertyDocs[$key];
146  }
147 
148  protected function reflectVisibility($reflect){
149  $visibility = [];
150  if($reflect->isPrivate()) $visibility[] = 'private';
151  if($reflect->isProtected()) $visibility[] = 'protected';
152  if($reflect->isPublic()) $visibility[] = 'public';
153  if($reflect->isStatic()) $visibility[] = 'static';
154  return $visibility;
155  }
156 
157  protected function varConstTable($constants){
158  $table =
159  "<div class='{$this->styleNamespace}-const'><table class='{$this->styleNamespace}-var'>\n" .
160  "\t<thead><tr>" .
161  "<th onclick='{$this->scriptNamespace}.sort({$this->_index}," . json_encode(array_keys(Record::sort($names = array_keys($constants)))) . ")'>Name</th>" .
162  "<th onclick='{$this->scriptNamespace}.sort({$this->_index}," . json_encode(array_values($names = array_flip($names))) . ")'>Type</th>" .
163  "<th onclick='{$this->scriptNamespace}.sort({$this->_index}," . json_encode(array_values(array_merge(Record::sort($constants),$names))) . ")'>Value</th>" .
164  "</tr></thead>\n\t<tbody id='dump-sort-{$this->_index}'>\n";
165  foreach($constants as $name => $value){
166  $i = strpos($row = $this->varRow($name,null,$value),'<tr');
167  $table .= substr($row,0,$i) . "<tr id='dump-sort-{$this->_index}-{$names[$name]}'" . substr($row,$i + 3);
168  }
169  return $table . "\t</tbody>\n</table></div>\n";
170  }
171 
172  protected function varTypeArray($value,&$size,&$sub,$depth,$hashes){
173  $size = count($value);
174  if($value){
175  if($depth > $this->maxDepth) $sub = "<div class='{$this->styleNamespace}-depth'>depth too great</div>";
176  else{
177  $keys = null;
178  $types = [];
179  foreach($value as $key => $record){
180  if(!is_array($record) || (($keys !== null) && (array_keys($record) !== $keys))){
181  $keys = false;
182  break;
183  }
184  else $keys = array_keys($record);
185  if($keys){
186  $str = $key;
187  foreach($record as $k => $v){
188  $type = strtolower(gettype($v));
189  if(
190  !is_scalar($v) ||
191  (strpos($v,"\n") !== false) ||
192  (mb_strlen($str .= $v) > $this->maxLength) ||
193  (array_key_exists($k,$types) && ($type != ($types[$k])))
194  ){
195  $keys = false;
196  break 2;
197  }
198  else $types[$k] = $type;
199  }
200  }
201  }
202  if($keys){
203  $sub .= "\t<thead>\n\t\t<tr><th class='empty' rowspan='2'></th>";
204  foreach($keys as $key) $sub .= "<th>" . htmlspecialchars($key) . "</th>";
205  $sub .= "</tr>\n\t\t<tr>";
206  foreach($keys as $key) $sub .= "<td class='{$this->styleNamespace}-type'>{$types[$key]}</td>";
207  $sub .= "</tr>\n\t</thead>\n\t<tbody>\n";
208  foreach($value as $key => $record){
209  $sub .= "\t\t<tr><th>" . htmlspecialchars($key) . "</th>";
210  foreach($record as $v) $sub .= "<td class='{$this->styleNamespace}-value'>" . $this->varValue($v) . "</td>";
211  $sub .= "</tr>\n";
212  }
213  $sub .= "\t</tbody>\n";
214  }
215  else{
216  $type = null;
217  if((count($keys = array_keys($value)) > 1) && ($keys == array_keys($keys)) && (strlen(implode($keys)) < $this->maxLength)) foreach($value as $k => $v){
218  if(!is_scalar($v) || (($type !== null) && (gettype($v) != $type))){
219  $type = false;
220  break;
221  }
222  else $type = gettype($v);
223  }
224  if(($type = strtolower($type)) && (mb_strlen($str = implode($value)) < $this->maxLength) && (strpos($str,"\n") === false)){
225  $sub .= "\t<tr><thead><th class='empty'>";
226  foreach($value as $k => $v) $sub .= "<th>" . htmlspecialchars($k) . "</th>";
227  $sub .= "</thad></tr>\n\t<tr class='{$this->styleNamespace}-var-$type'><tbody><td class='{$this->styleNamespace}-type'>$type</td>";
228  foreach($value as $v) $sub .= "<td class='{$this->styleNamespace}-value'>" . $this->varValue($v) . "</td>";
229  $sub .= "</tr></tbody>\n";
230  }
231  else{
232  $keys = false;
233  foreach($value as $k => $v) $sub .= $this->varRow($k,null,$v,$depth + 1,$hashes);
234  }
235  }
236  $sub = "<table" . ($keys ? " class='{$this->styleNamespace}-records'" : '') . ">\n$sub</table>\n";
237  }
238  }
239  return null;
240  }
241 
242  protected function varTypeBoolean($value,&$size,&$sub,$depth,$hashes){
243  return $value ? 'true' : 'false';
244  }
245 
246  protected function varTypeDouble($value,&$size,&$sub,$depth,$hashes){
247  if($time = $this->valueAsTime($value)) $value .= "<span>$time</span>";
248  return $value;
249  }
250 
251  protected function varTypeInteger($value,&$size,&$sub,$depth,$hashes){
252  if($time = $this->valueAsTime($value)) $value .= "<span>$time</span>";
253  return $value;
254  }
255 
256  protected function varTypeNull($value,&$size,&$sub,$depth,$hashes){
257  return 'null';
258  }
259 
260  protected function varTypeObject($value,&$size,&$sub,$depth,$hashes){
261  $class = $reflect = new \ReflectionObject($value);
262  if(memory_get_usage() > $this->memoryLimit) $sub .= "<div class='{$this->styleNamespace}-memory'>out of memory</div>";
263  elseif(array_key_exists($hash = md5(print_r($value,true)),$hashes)) $sub .= "<a href='#dump-sub-{$hashes[$hash]}' class='{$this->styleNamespace}-recursion'>recursion</a>";
264  elseif($depth > $this->maxDepth) $sub .= "<div class='{$this->styleNamespace}-depth'>depth too great</div>";
265  else{
266  $docs = ($doc = $reflect->getDocComment()) ? $this->escapeDoc($doc) : null;
267  $hashes[$hash] = Str::random();
268  $properties = [];
269  $default = ['type' => null,'docs' => null,'class' => null,'depth' => 0];
270  foreach($reflect->getProperties() as $property){
271  $property->setAccessible(true);
272  $properties[$property->name] = [
273  'type' => 'property',
274  'visibility' => array_merge($this->reflectVisibility($property),$property->isDefault() ? [] : ['dynamic']),
275  'value' => $property->getValue($value),
276  'docs' => $property->getDocComment() ?: $this->propertyDoc($reflect->getFilename(),$property->name),
277  'sort' => $this->sortProperties ? strtolower(ltrim($property->name,'_')) : null
278  ] + $default;
279  }
280  foreach($reflect->getMethods() as $method) if(preg_match('/^get[A-Z]/',$method->name) && !$method->getNumberOfParameters() && $method->isInternal()) try{
281  $method->setAccessible(true);
282  $properties[$method->name] = [
283  'type' => 'method',
284  'visibility' => $this->reflectVisibility($method),
285  'value' => $method->invoke($value),
286  'docs' => $method->getDocComment(),
287  'sort' => $this->sortProperties ? strtolower(substr($method->name,4)) : null
288  ] + $default;
289  }
290  catch(\Exception $e){}
291  while($class = $class->getParentClass()){
292  if($doc = $class->GetDocComment()) $docs .= "<h1>{$class->name}</h1>" . $this->escapeDoc($doc);
293  foreach($properties as $name => &$property) if(call_user_func([$class,'has' . ucfirst($property['type'])],$name)){
294  if($this->groupProperties){
295  $property['class'] = $class->name;
296  $property['depth']++;
297  }
298  if(!$property['docs']) $property['docs'] = ($property['type'] == 'property' ? $class->getProperty($name) : $class->getMethod($name))->getDocComment() ?: ($property['type'] == 'property' ? $this->propertyDoc($class->getFilename(),$name) : null);
299  }
300  }
301  unset($property);
302  if($reflect->hasMethod('__toString')) try{
303  $properties['{string}'] = [
304  'visibility' => ['public'],
305  'value' => (string)$value,
306  'sort' => '{string}'
307  ] + $default;
308  }
309  catch(\Exception $e){}
310  if($properties){
311  uasort($properties,function($a,$b){
312  return ($a['depth'] <=> $b['depth']) ?: ($a['sort'] <=> $b['sort']);
313  });
314  if($docs) $sub .= "\t<tr class='{$this->styleNamespace}-docs'><td colspan='4'><div>$docs</div></td></tr>\n";
315  $class = null;
316  foreach($properties as $name => $property){
317  if($property['class'] != $class) $sub .= "\t<tr class='{$this->styleNamespace}-class'><td colspan='4'>" . htmlspecialchars($class = $property['class']) . "</td></tr>\n";
318  $sub .= $this->varRow($name . ($property['type'] == 'method' ? '()' : ''),$property['visibility'],$property['value'],$depth + 1,$hashes,$property['docs']);
319  }
320  $sub = "<table>\n" . str_replace($hashes[$hash],$this->_index + 1,$sub) . "</table>\n";
321  }
322  }
323  if($constants = $reflect->getConstants()) $sub .= $this->varConstTable($constants);
324  return $reflect->name;
325  }
326 
327  protected function varTypeResource($value,&$size,&$sub,$depth,$hashes){
328  $info = null;
329  switch($type = get_resource_type($value)){
330  case 'curl': $info = curl_getinfo($value); break;
331  case 'gd': $info = ['width' => imagesx($value),'height' => imagesy($value),'colors' => imagecolorstotal($value)]; break;
332  case 'stream': $info = stream_get_meta_data($value); break;
333  }
334  if($info){
335  if($this->sortProperties) ksort($info);
336  foreach($info as $k => $v) $sub .= $this->varRow($k,null,$v);
337  $sub = "<table>\n$sub</table>\n";
338  }
339  return $type;
340  }
341 
342  protected function varTypeString($value,&$size,&$sub,$depth,$hashes){
343  $size = strlen($value);
344  $filename = null;
345  if(is_array($data = json_decode($value, true))) $this->varTypeArray($data,$skip,$sub,$depth,$hashes);
346  elseif($this->validFilename && preg_match($this->validFilename,$value) && is_file($value)) $filename = $value;
347  if((mb_strlen($value) > $this->maxLength) || (strpos($value,"\n") !== false)){
348  if(!$sub) $sub = "<pre>" . htmlspecialchars($value) . "</pre>";
349  $value = mb_substr($value,0,$this->maxLength);
350  }
351  $value = htmlspecialchars($value);
352  foreach(["\n" => '\\n',"\r" => '\\r',"\t" => '\\t'] as $s => $r) $value = str_replace($s,"<code>$r</code>",$value);
353  if($filename) $value .= "<span>" . htmlspecialchars(implode(', ',[date($this->timeFormat,filemtime($filename)),Number::formatBytes(filesize($filename))])) . "</span>";
354  return $value;
355  }
356 
357  protected function varTypeUnknown($value,&$size,&$sub,$depth,$hashes){
358  return 'unknown type';
359  }
360 
361  protected function varValue($value,&$size = null,&$sub = null,$depth = 0,$hashes = []){
362  $size = $sub = null;
363  return call_user_func_array(
364  [$this,method_exists($this,$method = 'varType' . ucfirst(strtolower(gettype($value)))) ? $method : 'varTypeUnknown'],
365  [$value,&$size,&$sub,$depth,$hashes]
366  );
367  }
368 
369  protected function varRow($name,$visibility,$value,$depth = 0,$hashes = [],$docs = null){
370  $from = $this->_index + 1;
371  $type = strtolower(gettype($value));
372  $value = $this->varValue($value,$size,$sub,$depth,$hashes);
373  if($sub){
374  $index = ++$this->_index;
375  $value .=
376  "<div class='{$this->styleNamespace}-actions'>" .
377  "<button type='button' class='{$this->styleNamespace}-show' id='dump-show-$index'" . ($index >= $from ? " onclick='{$this->scriptNamespace}.showSubs($from,$index,event)'" : '') . ">+</button>" .
378  "<button type='button' class='{$this->styleNamespace}-hide' id='dump-hide-$index' onclick='{$this->scriptNamespace}.hideSub($index,event)'>-</button>" .
379  "</div>";
380  $sub =
381  "\t<tr class='{$this->styleNamespace}-sub' id='dump-sub-$index'><td colspan='" . ($visibility ? 4 : 3) . "' class='{$this->styleNamespace}-sub'>" .
382  "<div class='{$this->styleNamespace}-name'>" . (substr_count($sub,'<tr') * 2 >= strlen($name) ? "<div>" . htmlspecialchars($name) . "</div>" : '') . "</div>" .
383  $sub .
384  "</td></tr>\n";
385  }
386  return
387  "\t<tr class='{$this->styleNamespace}-var-$type'>" .
388  "<th" . ($docs ? " title='" . htmlspecialchars($this->escapeDoc($docs),ENT_QUOTES) . "'" : '') . ">" . htmlspecialchars($name) . "</th>" .
389  ($visibility ? "<td class='{$this->styleNamespace}-visibility" . implode(" {$this->styleNamespace}-visibility-",array_merge([null],$visibility)) . "'>" . implode(' ',$visibility) . "</td>" : '') .
390  "<td class='{$this->styleNamespace}-type'>$type" . ($size === null ? '' : "<span>$size</span>") . "</td>" .
391  "<td class='{$this->styleNamespace}-value'" . ($sub ? " onclick='{$this->scriptNamespace}.showSub($index,event)'" : '') . ">$value</td>" .
392  "</tr>\n" .
393  $sub;
394  }
395  /**
396  * Dump a variable.
397  * @param string $name Name of the variable.
398  * @param mixed $value
399  * @param string $docs Documentation for the variable.
400  * @return string
401  */
402  public function var($name,$value,$docs = null){
403  return "<table class='{$this->styleNamespace}-var'>\n" . $this->varRow($name,null,$value,0,[],$docs) . "</table>\n";
404  }
405 
406  protected function traceTabs($step,$reflect){
407  if(array_key_exists('file',$step)) $tabs['source'] = ['caption' => 'Source','tab' => $this->source($step['file'],$step['line'])];
408  $filename = $reflect ? strtolower($step['class'] ?? 'function') . '.' . str_replace('_','-',$reflect->name) : null;
409  if($args = $step['args'] ?? null){
410  $params = [];
411  if($reflect) foreach($reflect->getParameters() as $param) $params[] = $param->name;
412  $tab = null;
413  foreach($args as $index => $arg){
414  $consts = [];
415  if(is_numeric($arg)){
416  if($this->_paramConsts === null) $this->_paramConsts = $this->paramConstsFile ? require($this->paramConstsFile) : [];
417  foreach(($this->_paramConsts[$filename][$params[$index] ?? null] ?? []) as $const => $value) if($arg & $value) $consts[$const] = $value;
418  if($consts && (array_sum($consts) != $arg)) $consts['???'] = null;
419  }
420  $tab .= $this->var(array_key_exists($index,$params) ? '$' . $params[$index] : '#' . $index,$arg,implode(' + ',array_keys($consts)));
421  }
422  $tabs['args'] = ['caption' => 'Parameters','tab' => $tab];
423  }
424  if(array_key_exists('object',$step)) $tabs['object'] = ['caption' => 'Object','tab' => $this->var($step['class'] ?? null,$step['object'])];
425  if($reflect){
426  $docs = [($prev = null) => $reflect->isInternal() ? true : $reflect->getDocComment()];
427  $protos = [];
428  if($reflect instanceof \ReflectionMethod){
429  $class = $reflect->getDeclaringClass();
430  while($class = $class->getParentClass()){
431  if($method = $class->hasMethod($reflect->name)){
432  if(($docs[$class->name] = $class->getMethod($reflect->name)->getDocComment()) == $docs[$prev]) $docs[$prev] = false;
433  $prev = $class->name;
434  }
435  $protos[] =
436  "<span title='" . htmlspecialchars($class->getFileName()) . "'" . ($method ? '' : " class='{$this->styleNamespace}-parent-base'") . ">" .
437  ($class->isInternal() ? "<a href='{$this->remoteDocs}class." . strtolower($class->name) . ".php' target='_blank'>" : '') .
438  $class->name .
439  ($class->isInternal() ? "</a>" : '') .
440  (($traits = $class->getTraitNames()) ? " <span class='{$this->styleNamespace}-traits'>(traits: " . implode(', ',$traits) . ")</span>" : '') .
441  (($interfaces = $class->getInterfaceNames()) ? " <span class='{$this->styleNamespace}-interfaces'>(interfaces: " . implode(', ',$interfaces) . ")</span>" : '') .
442  "</span>";
443  }
444  }
445  if($docs = array_filter($docs)){
446  foreach($docs as &$doc) $doc = $doc === true
447  ? "<a href='{$this->remoteDocs}$filename.php' target='_blank'>" . preg_replace('~^.*?://~','',$this->remoteDocs) . "$filename.php</a>" .
448  ($this->localDocs ? " / " . preg_replace('~^.*?://~','',$this->localDocs) . "$filename.html: <iframe src='{$this->localDocs}$filename.html'></iframe>" : '')
449  : $this->escapeDoc($doc);
450  unset($doc);
451  $tabs['docs'] = ['caption' => 'Documentation','tab' => "<div class='{$this->styleNamespace}-docs'><h1>" . Record::implode($docs,"</p>\n\t<h1>","</h1>\n\t<p>") . "</p></div>"];
452  }
453  if($filename = $reflect->getFilename()) $tabs['function'] = ['caption' => 'Function','tab' => $this->source($filename,$reflect->getStartLine(),$reflect->getEndLine())];
454  }
455  if(array_key_exists('class',$step)){
456  $class = new \ReflectionClass($step['class']);
457  if($constants = $class->getConstants()) $tabs['const'] = ['caption' => 'Constants','tab' => $this->varConstTable($constants)];
458  }
459  if($reflect && ($misc = array_filter($misc = [
460  'Return type' => $reflect->getReturnType(),
461  'Parents' => implode(', ',array_filter($protos)),
462  'Deprecated' => $reflect->isDeprecated() ? 'yes' : null
463  ]))) $tabs['misc'] = ['caption' => 'Other','tab' => "<ul class='{$this->styleNamespace}-misc'><li><b>" . Record::implode($misc,"</li>\n\t<li><b>",':</b> ') . "</li></ul>"];
464  return $tabs;
465  }
466 
467  protected function traceShowTab($tabs,$index,$key){
468  return array_key_exists($key,$tabs) ? " onclick='{$this->scriptNamespace}.showTab($index,\"$key\"," . json_encode(array_keys($tabs)) . ")'" : null;
469  }
470  /**
471  * Dump a (back-) trace.
472  * @param array $trace Current trace when empty.
473  * @return string
474  */
475  public function trace($trace = null){
476  if($trace === null) $trace = debug_backtrace();
477  $table = null;
478  foreach($trace as $step){
479  $index = ++$this->_index;
480  $actions = $sub = $nav = $params = $reflect = null;
481  if(array_key_exists('function',$step)) try{
482  $reflect = array_key_exists('class',$step) ? new \ReflectionMethod($step['class'],$step['function']) : new \ReflectionFunction($step['function']);
483  }
484  catch(\Exception $e){
485  $reflect = false;
486  }
487  if($reflect && ($args = $step['args'] ?? null)) foreach($reflect->getParameters() as $param) $params .=
488  ($params ? ',' : '') .
489  ($param->isPassedByReference() ? '&amp;' : '') .
490  "<span class='{$this->styleNamespace}-name'>\$" . htmlspecialchars($param->name) . "</span>" .
491  ($param->isDefaultValueAvailable() ? ' = ' . htmlspecialchars(var_export($param->getDefaultValue(),true)) : '');
492  if($tabs = $this->traceTabs($step,$reflect)){
493  $actions .=
494  "<div class='{$this->styleNamespace}-actions'>" .
495  "<button type='button' class='{$this->styleNamespace}-show' id='dump-show-$index' onclick='{$this->scriptNamespace}.showSub($index)'>+</button>" .
496  "<button type='button' class='{$this->styleNamespace}-hide' id='dump-hide-$index' onclick='{$this->scriptNamespace}.hideSub($index)'>-</button>" .
497  "</div>";
498  foreach($tabs as $key => $tab){
499  $nav .= "<button type='button' class='{$this->styleNamespace}-tab-$key" . ($nav ? '' : " {$this->styleNamespace}-active") . "' id='dump-nav-$index-$key'" . $this->traceShowTab($tabs,$index,$key) . ">" . ($tab['caption'] ?? $key) . "</button>";
500  $sub .= "\t\t<div id='dump-tab-$index-$key' class='{$this->styleNamespace}-tab-$key'>{$tab['tab']}</div>\n";
501  }
502  $sub = "\t<tr class='{$this->styleNamespace}-sub' id='dump-sub-$index'><td colspan='3' class='{$this->styleNamespace}-tabs'>\n\t\t<nav>$nav</nav>\n$sub\t</td></tr>\n";
503  }
504  $table .=
505  "\t<tr class='{$this->styleNamespace}-step'>\n" .
506  "\t\t<td>$actions</td>\n" .
507  "\t\t<td>" . (array_key_exists('file',$step) ? "<span class='{$this->styleNamespace}-file'" . $this->traceShowTab($tabs,$index,'source') . ">" . htmlspecialchars($step['file'] . "#" . $step['line']) . " <input class='{$this->styleNamespace}-copy' value='" . htmlspecialchars($step['file'],ENT_QUOTES) . "' onclick='{$this->scriptNamespace}.copy(this)'></span>" : '') . "</td>\n" .
508  "\t\t<td>" .
509  (array_key_exists('class',$step) ? "<span class='{$this->styleNamespace}-class'" . $this->traceShowTab($tabs,$index,'object') . ">" . htmlspecialchars($step['class'] . ($step['type'] ?? null)) . "</span>" : '') .
510  (array_key_exists('function',$step)
511  ? "<span class='{$this->styleNamespace}-function'" . ($this->traceShowTab($tabs,$index,'docs') ?: $this->traceShowTab($tabs,$index,'function')) . ">" . htmlspecialchars($step['function']) . "</span>" .
512  "<span class='{$this->styleNamespace}-args'" . $this->traceShowTab($tabs,$index,'args') . ">($params)</span>"
513  : ''
514  ) .
515  "</td>\n" .
516  "\t</tr>\n" .
517  $sub;
518  }
519  return "<table class='{$this->styleNamespace}-trace'>\n$table</table>\n";
520  }
521  /**
522  * Dump and die.
523  * @param mixed $value Trace when empty.
524  */
525  public static function die($value = null){
526  $dump = new self();
527  die(
528  "<!DOCTYPE html>\n<html>\n" .
529  "\t<head><title>Dump</title>" . $dump->head() . "</head>\n" .
530  "\t<body>" . (func_num_args() ? $dump->var('value',$value) : $dump->trace()) . "</body>\n" .
531  "</html>"
532  );
533  }
534 
535 }
traceShowTab($tabs, $index, $key)
Definition: Dump.php:467
head()
Head for dumps (style and script tags).
Definition: Dump.php:62
varTypeNull($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:256
varConstTable($constants)
Definition: Dump.php:157
valueAsTime($value)
Definition: Dump.php:108
reflectVisibility($reflect)
Definition: Dump.php:148
Basic object.
Definition: Thing.php:13
varTypeString($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:342
__construct($config=null)
Definition: Dump.php:54
varTypeInteger($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:251
varTypeDouble($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:246
escapeDoc($doc)
Definition: Dump.php:112
trace($trace=null)
Dump a (back-) trace.
Definition: Dump.php:475
varRow($name, $visibility, $value, $depth=0, $hashes=[], $docs=null)
Definition: Dump.php:369
var($name, $value, $docs=null)
Dump a variable.
Definition: Dump.php:402
static memoryLimit()
Script memory limit.
Definition: Rsi.php:51
Definition: Color.php:3
varTypeObject($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:260
varValue($value, &$size=null, &$sub=null, $depth=0, $hashes=[])
Definition: Dump.php:361
source($filename, $line_no=null, $line_to=null, $type=null, $header=null)
Dump source code.
Definition: Dump.php:76
Definition: Dump.php:5
varTypeArray($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:172
static die($value=null)
Dump and die.
Definition: Dump.php:525
propertyDoc($filename, $property)
Definition: Dump.php:122
varTypeUnknown($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:357
varTypeBoolean($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:242
traceTabs($step, $reflect)
Definition: Dump.php:406
varTypeResource($value, &$size, &$sub, $depth, $hashes)
Definition: Dump.php:327