├── .gitattributes
├── .gitignore
├── README.md
├── composer.json
├── ref.css
├── ref.js
├── ref.php
└── tests
├── example.class.php
├── file.txt
├── index.php
└── today
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 | [Dd]ebug/
46 | [Rr]elease/
47 | *_i.c
48 | *_p.c
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.vspscc
63 | .builds
64 | *.dotCover
65 |
66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this
67 | #packages/
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 |
76 | # Visual Studio profiler
77 | *.psess
78 | *.vsp
79 |
80 | # ReSharper is a .NET coding add-in
81 | _ReSharper*
82 |
83 | # Installshield output folder
84 | [Ee]xpress
85 |
86 | # DocProject is a documentation generator add-in
87 | DocProject/buildhelp/
88 | DocProject/Help/*.HxT
89 | DocProject/Help/*.HxC
90 | DocProject/Help/*.hhc
91 | DocProject/Help/*.hhk
92 | DocProject/Help/*.hhp
93 | DocProject/Help/Html2
94 | DocProject/Help/html
95 |
96 | # Click-Once directory
97 | publish
98 |
99 | # Others
100 | [Bb]in
101 | [Oo]bj
102 | sql
103 | TestResults
104 | *.Cache
105 | ClientBin
106 | stylecop.*
107 | ~$*
108 | *.dbmdl
109 | Generated_Code #added for RIA/Silverlight projects
110 |
111 | # Backup & report files from converting an old project file to a newer
112 | # Visual Studio version. Backup files are not needed, because we have git ;-)
113 | _UpgradeReport_Files/
114 | Backup*/
115 | UpgradeLog*.XML
116 |
117 |
118 |
119 | ############
120 | ## Windows
121 | ############
122 |
123 | # Windows image file caches
124 | Thumbs.db
125 |
126 | # Folder config file
127 | Desktop.ini
128 |
129 |
130 | #############
131 | ## Python
132 | #############
133 |
134 | *.py[co]
135 |
136 | # Packages
137 | *.egg
138 | *.egg-info
139 | dist
140 | build
141 | eggs
142 | parts
143 | bin
144 | var
145 | sdist
146 | develop-eggs
147 | .installed.cfg
148 |
149 | # Installer logs
150 | pip-log.txt
151 |
152 | # Unit test / coverage reports
153 | .coverage
154 | .tox
155 |
156 | #Translations
157 | *.mo
158 |
159 | #Mr Developer
160 | .mr.developer.cfg
161 |
162 | # Mac crap
163 | .DS_Store
164 |
165 |
166 | ############
167 | ## Idea
168 | ############
169 |
170 | .idea/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | REF, or `r()` is a nicer alternative to PHP's [`print_r`](http://php.net/manual/en/function.print-r.php) / [`var_dump`](http://php.net/manual/en/function.var-dump.php) functions.
2 |
3 | ## [DEMO](http://dev.digitalnature.eu/php-ref/) ##
4 |
5 | ## Requirements ##
6 |
7 | - (server) PHP 5.3+ (5.4+ displays additional info)
8 | - (client) Any browser, except IE 8 and lower of course
9 |
10 | ## Installation using Composer
11 |
12 | Add REF to your `composer.json`:
13 |
14 | ```js
15 | {
16 | "require": {
17 | "digitalnature/php-ref": "dev-master"
18 | }
19 | }
20 | ```
21 |
22 | Now tell composer to download the bundle by running:
23 |
24 | ``` bash
25 | $ php composer.phar update digitalnature/php-ref
26 | ```
27 |
28 | Composer will install the bundle to the directory `vendor/digitalnature`.
29 |
30 | ## Usage ##
31 |
32 | Basic example:
33 |
34 | // include the class (not needed if project runs with Composer because it's auto-loaded)
35 | require '/full/path/to/ref.php';
36 |
37 | // display info about defined classes
38 | r(get_declared_classes());
39 |
40 | // display info about global variables
41 | r($GLOBALS);
42 |
43 | To print in text mode you can use the `rt()` function instead:
44 |
45 | rt($var);
46 |
47 | To terminate the script after the info is dumped, prepend the bitwise NOT operator:
48 |
49 | ~r($var); // html
50 | ~rt($var); // text
51 |
52 | Prepending the error control operator (@) will return the information:
53 |
54 | $output = @r($var); // html
55 | $output = @rt($var); // text
56 |
57 | Keyboard shortcuts (javascript must be enabled):
58 |
59 | - `X` - collapses / expands all levels
60 | - `Ctrl` + `X` - toggles display state
61 |
62 | To modify the global configuration call `ref::config()`:
63 |
64 | // example: initially expand first 3 levels
65 | ref::config('expLvl', 3);
66 |
67 | You can also add configuration options in your `php.ini` file like this:
68 |
69 | [ref]
70 | ref.expLvl = 3
71 | ref.maxDepth = 4
72 |
73 | Currently available options and their default values:
74 |
75 | | Option | Default | Description
76 | |:------------------------- |:------------------- |:-----------------------------------------------
77 | | `'expLvl'` | `1` | Initially expanded levels (for HTML mode only). A negative value will expand all levels
78 | | `'maxDepth'` | `6` | Maximum depth (`0` to disable); note that disabling it or setting a high value can produce a 100+ MB page when input involves large data
79 | | `'showIteratorContents'` | `FALSE` | Display iterator data (keys and values)
80 | | `'showResourceInfo'` | `TRUE` | Display additional information about resources
81 | | `'showMethods'` | `TRUE` | Display methods and parameter information on objects
82 | | `'showPrivateMembers'` | `FALSE` | Include private properties and methods
83 | | `'showStringMatches'` | `TRUE` | Perform and display string matches for dates, files, json strings, serialized data, regex patterns etc. (SLOW)
84 | | `'formatters'` | `array()` | Custom/external formatters (as associative array: format => className)
85 | | `'shortcutFunc'` | `array('r', 'rt')` | Shortcut functions used to detect the input expression. If they are namespaced, the namespace must be present as well (methods are not supported)
86 | | `'stylePath'` | `'{:dir}/ref.css'` | Local path to a custom stylesheet (HTML only); `FALSE` means that no CSS is included.
87 | | `'scriptPath'` | `'{:dir}/ref.js'` | Local path to a custom javascript (HTML only); `FALSE` means no javascript (tooltips / toggle / kbd shortcuts require JS)
88 | | `'showUrls'` | `FALSE` | Gets information about URLs. Setting to false can improve performance (requires showStringMatches to be TRUE)
89 | | `'timeout'` | `10` | Stop execution after this amount of seconds, forcing an incomplete listing. Applies to all calls
90 | | `'validHtml'` | `FALSE` | For HTML mode only. Whether to produce W3C-valid HTML (larger code output) or unintelligible, potentially browser-incompatible but much smaller code output
91 |
92 | ## Similar projects
93 |
94 | - [Kint](http://raveren.github.io/kint/)
95 | - [dump_r](https://github.com/leeoniya/dump_r.php)
96 | - [Krumo](http://sourceforge.net/projects/krumo/)
97 | - [dBug](http://dbug.ospinto.com/)
98 | - [symfony-vardumper](http://www.sitepoint.com/var_dump-introducing-symfony-vardumper/)
99 |
100 |
101 | ## TODOs
102 |
103 | - Inherit DocBlock comments from parent or prototype, if missing
104 | - Refactor "bubbles" (for text-mode)
105 | - Correctly indent multi-line strings (text-mode)
106 | - Move separator tokens to ::before and ::after pseudo-elements (html-mode)
107 |
108 |
109 | ## License
110 |
111 | http://opensource.org/licenses/mit-license.html
112 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "digitalnature/php-ref",
3 | "description": "A nicer print_r/var_dump alternative for PHP 5.3+",
4 | "keywords": ["debug","var_dump"],
5 | "homepage": "https://github.com/digitalnature/php-ref",
6 | "license": "MIT",
7 | "require": {
8 | "php": ">=5.3.0"
9 | },
10 | "autoload": {
11 | "files": [ "ref.php" ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ref.css:
--------------------------------------------------------------------------------
1 | .ref{
2 | font: normal normal 12px/18px Consolas, "Liberation Mono", Menlo, "Courier New", Courier, monospace;
3 | color: #333;
4 | }
5 |
6 | /* reset default styles for these elements */
7 | .ref i,
8 | .ref span,
9 | .ref r,
10 | .ref a{
11 | font-style: inherit;
12 | font-weight: inherit;
13 | margin: 0;
14 | padding: 0;
15 | text-align: left;
16 | display: inline;
17 | text-decoration: inherit;
18 | white-space: normal;
19 | background: none;
20 | }
21 |
22 | /* meta content (used to generate tooltips) */
23 | .ref > div,
24 | .ref > t{
25 | display: none;
26 | }
27 |
28 | /* show help cursor when mouse is over an entity with a tooltip */
29 | .ref [data-tip],
30 | .ref [h]{
31 | cursor: help;
32 | }
33 |
34 | /* pointer if inside a link */
35 | .ref a > [data-tip],
36 | .ref a > [h]{
37 | cursor: pointer;
38 | }
39 |
40 | /* links */
41 | .ref a{
42 | color: inherit;
43 | border-bottom: 1px dotted transparent;
44 | border-color: inherit;
45 | }
46 |
47 | /* tooltip; note that the js overrides top/left properties, this is why we use margin */
48 | #rTip{
49 | display: none;
50 | position: absolute;
51 | z-index: 99999;
52 | font-size: 12px;
53 | white-space: pre;
54 | text-align: left;
55 | text-shadow: 0 -1px 0 #191919;
56 | line-height: 16px;
57 | background: #222;
58 | color: #888;
59 | border: 0;
60 | border-radius: 4px;
61 | opacity: 0.90;
62 | box-shadow:0 0 4px rgba(0,0,0, 0.25);
63 | -webkit-transition: opacity .25s, margin .25s;
64 | transition: opacity .25s, margin .25s;
65 | }
66 |
67 | #rTip.visible{
68 | display: table;
69 | margin: 10px 0 0 15px;
70 | }
71 |
72 | #rTip.visible.fadingOut{
73 | opacity: 0;
74 | margin: 20px 0 0 25px;
75 | }
76 |
77 | #rTip [data-cell],
78 | #rTip [c]{
79 | padding: 2px 7px;
80 | }
81 |
82 | #rTip [data-title], #rTip [data-desc]{
83 | padding: 8px;
84 | display: block;
85 | color: #ccc;
86 | }
87 |
88 | #rTip [data-desc]{
89 | padding-top: 0px;
90 | color: #777;
91 | }
92 |
93 | #rTip [data-cell][data-varType],
94 | #rTip [c][data-varType]{
95 | padding: 10px;
96 | background: #333;
97 | box-shadow: inset -1px 0 0 #444;
98 | border-right:1px solid #111;
99 | border-top-left-radius: 4px;
100 | border-bottom-left-radius: 4px;
101 | }
102 |
103 | #rTip [data-cell][data-sub],
104 | #rTip [c][data-sub]{
105 | padding: 8px 10px 10px 10px;
106 | background: #333;
107 | box-shadow: inset 0 1px 0 #444;
108 | border-top:1px solid #111;
109 | border-bottom-right-radius: 4px;
110 | border-bottom-left-radius: 4px;
111 | }
112 |
113 | #rTip [data-table] [data-cell]:first-child,
114 | #rTip [t] [c]:first-child{
115 | font: bold 11px Helvetica, Arial;
116 | color: #888;
117 | }
118 |
119 | #rTip [data-table] [data-cell]:nth-child(2),
120 | #rTip [t] [c]:nth-child(2){
121 | color: #edd078;
122 | }
123 |
124 |
125 |
126 |
127 | /* base entity - can be nested */
128 | .ref span, .ref r{
129 | white-space: pre;
130 | display: inline;
131 | }
132 |
133 | /* key-value dividers, property & method modifiers etc. */
134 | .ref i{
135 | white-space: pre;
136 | color: #aaa;
137 | }
138 |
139 | /* source expression (input) */
140 | .ref [data-input]{
141 | margin: 2px 0 0;
142 | padding: 2px 7px 3px 4px;
143 | display: block;
144 | color: #ccc;
145 | background-color: #333;
146 | background-image: -webkit-linear-gradient(top, #444, #333);
147 | background-image: linear-gradient(top, #444, #333);
148 | border-radius: 4px 4px 0 0;
149 | border-bottom: 1px solid #fff;
150 | }
151 |
152 | .ref [data-backtrace]{
153 | float: right;
154 | }
155 |
156 | .ref [data-output]{
157 | background: #f9f9f9;
158 | border: 1px solid #eee;
159 | border-top: 0;
160 | border-radius: 0 0 4px 4px;
161 | box-shadow: inset 0px 4px 4px #f3f3f3, inset 0px -8px 8px #fff;
162 | padding: 2px 5px;
163 | margin: 0 0 4px;
164 | text-shadow: 0 1px 0 #fff;
165 | display: block;
166 | }
167 |
168 | /* expand/collapse toggle link for groups */
169 | .ref [data-toggle]{
170 | display: inline-block;
171 | vertical-align: -3px;
172 | margin-left: 2px;
173 | width: 0px;
174 | height: 0px;
175 | border-style: solid;
176 | border-width: 7px 0 7px 10px;
177 | border-color: transparent transparent transparent #CC0033;
178 | cursor: pointer;
179 | -webkit-transition: all ease-in .15s;
180 | transition: all ease-in .15s;
181 | }
182 |
183 | /* collapse graphic */
184 | .ref [data-toggle][data-exp]{
185 | -webkit-transform: rotate(90deg);
186 | -ms-transform: rotate(90deg);
187 | transform: rotate(90deg);
188 | }
189 |
190 | .ref [data-group],
191 | .ref [g]{
192 | display: none;
193 | }
194 |
195 | .ref [data-toggle][data-exp] ~ [data-group],
196 | .ref [data-toggle][data-exp] ~ [g]{
197 | display: block;
198 | }
199 |
200 | /* group sections */
201 | .ref [data-table],
202 | .ref [t]{
203 | display: table;
204 | }
205 |
206 | /* section titles */
207 | .ref [data-tHead]{
208 | font: bold 11px Helvetica, Arial;
209 | color: #bcbcbc;
210 | text-transform: lowercase;
211 | margin: 12px 0 2px 10px;
212 | display: block;
213 | }
214 |
215 | /* emulate a table for displaying array & object members */
216 | /* section row */
217 | .ref [data-row],
218 | .ref [r]{
219 | display: table-row;
220 | }
221 |
222 | /* zebra-like rows */
223 | .ref [data-output] [data-row]:nth-child(odd){background: #f4f4f4;}
224 | .ref [data-output] [data-row]:nth-child(even){background: #f9f9f9;}
225 | .ref [data-output] [r]:nth-child(odd){background: #f4f4f4;}
226 | .ref [data-output] [r]:nth-child(even){background: #f9f9f9;}
227 |
228 | /* section cells */
229 | .ref [data-cell],
230 | .ref [c]{
231 | display: table-cell;
232 | width: auto;
233 | vertical-align: top;
234 | padding: 1px 0 1px 10px;
235 | }
236 |
237 | /* last cell of a row (forces table to adjust width like we want to) */
238 | .ref [data-output] [data-table],
239 | .ref [data-output] [t],
240 | .ref [data-output] [data-cell]:last-child,
241 | .ref [data-output] [c]:last-child{
242 | width: 100%;
243 | }
244 |
245 |
246 |
247 | /* tag-like appearance for boolean, null and resource types */
248 | .ref [data-true],
249 | .ref [data-false],
250 | .ref [data-null],
251 | .ref [data-unknown],
252 | .ref [data-resource],
253 | .ref [data-match],
254 | .ref [m]{
255 | font: bold 11px Helvetica, Arial;
256 | color: #fff;
257 | padding: 1px 3px;
258 | text-transform: lowercase;
259 | text-shadow: none;
260 | border-radius: 2px;
261 | margin-right: 5px;
262 | background-color: #eee;
263 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.1) 40%,rgba(0,0,0,0.1) 100%);
264 | background-image: linear-gradient(to bottom, rgba(255,255,255,0.1) 40%,rgba(0,0,0,0.1) 100%);
265 | }
266 |
267 | /* string matches */
268 | .ref [data-match],
269 | .ref [m]{
270 | background-color: #d78035;
271 | }
272 |
273 | /* boolean true */
274 | .ref [data-true]{
275 | background-color: #339900;
276 | }
277 |
278 | /* boolean false */
279 | .ref [data-false]{
280 | background-color: #CC0033;
281 | color: #fff;
282 | }
283 |
284 | /* null value */
285 | .ref [data-null],
286 | .ref [data-unknown]{
287 | background-color: #eee;
288 | color: #999;
289 | text-shadow: inherit;
290 | }
291 |
292 | /* resources */
293 | .ref [data-resource]{
294 | background-color: #0057ae;
295 | }
296 |
297 | .ref [data-resourceProp]{
298 | font: bold 11px Helvetica, Arial;
299 | color: #999;
300 | }
301 |
302 | /* integer or double values */
303 | .ref [data-integer],
304 | .ref [data-double]{
305 | color: #0099CC;
306 | }
307 |
308 | /* string values */
309 | .ref [data-string]{
310 | background: #e8f0e1;
311 | color: #669933;
312 | padding: 3px 1px;
313 |
314 | /* prevent long strings from breaking the page layout */
315 | white-space: -moz-pre-wrap; /* Mozilla */
316 | white-space: -hp-pre-wrap; /* HP printers */
317 | white-space: -o-pre-wrap; /* Opera 7 */
318 | white-space: -pre-wrap; /* Opera 4-6 */
319 | white-space: pre-wrap; /* CSS 2.1 */
320 | word-wrap: break-word; /* IE */
321 | word-break: break-all;
322 | }
323 |
324 | .ref [data-string][data-special]{
325 | background: none;
326 | padding: 0;
327 | }
328 |
329 | .ref [data-string][data-special] i{
330 | background: #faf3dc;
331 | color: #d78035;
332 | }
333 |
334 | /* arrays & objects */
335 | .ref [data-array],
336 | .ref [data-array] ~ i,
337 | .ref [data-object],
338 | .ref [data-object] ~ i,
339 | .ref [data-resource] ~ i{
340 | color:#CC0033;
341 | }
342 |
343 | .ref [data-method]{
344 | font-weight: bold;
345 | color: #0057ae;
346 | }
347 |
348 | .ref [data-const][data-inherited],
349 | .ref [data-prop][data-inherited]{
350 | color: #999;
351 | }
352 |
353 | .ref [data-prop][data-private],
354 | .ref [data-method][data-private]{
355 | color: #CC0033;
356 | }
357 |
358 | /* inherited methods */
359 | .ref [data-method][data-inherited]{
360 | font-weight: bold;
361 | color: #6da5de;
362 | }
363 |
364 | /* method arguments */
365 | .ref [data-param]{
366 | font-weight: normal;
367 | color: #333;
368 | }
369 |
370 | /* optional method arguments */
371 | .ref [data-param][data-optional]{
372 | font-style: italic;
373 | font-weight: normal;
374 | color: #aaa;
375 | }
376 |
377 | /* group info prefix */
378 | .ref [data-gLabel],
379 | .ref [gl]{
380 | font: bold 11px Helvetica, Arial;
381 | padding: 0 3px;
382 | color: #333;
383 | }
384 |
385 | /* tiny bubbles that indicate visibility info or class features */
386 | .ref [data-mod]{
387 | font: bold 11px Helvetica, Arial;
388 | text-shadow: none;
389 | color: #fff;
390 | }
391 |
392 | .ref [data-input] [data-mod]{
393 | color: #444;
394 | }
395 |
396 | .ref [data-mod] span,
397 | .ref [data-mod] r{
398 | display: inline-block;
399 | margin: 0 2px;
400 | width: 14px;
401 | height: 14px;
402 | text-align: center;
403 | border-radius: 30px;
404 | line-height: 15px;
405 | }
406 |
407 | .ref [data-mod-interface],
408 | .ref [data-mod-abstract]{
409 | background: #baed78;
410 | }
411 |
412 | .ref [data-mod-anonymous]{
413 | background: #444;
414 | }
415 |
416 | .ref [data-mod-protected]{
417 | background: #edd078;
418 | }
419 |
420 | .ref [data-mod-private]{
421 | background: #eea8b9;
422 | }
423 |
424 | .ref [data-mod-iterateable]{
425 | background: #d5dea5;
426 | }
427 |
428 | .ref [data-mod-cloneable]{
429 | background: #bdd7d1;
430 | }
431 |
432 | .ref [data-mod-final]{
433 | background: #78bded;
434 | }
435 |
436 | /* regular expression (colors partially match RegexBuddy and RegexPal) */
437 | .ref [data-regex]{
438 | font-weight: bold;
439 | text-shadow: none;
440 | padding: 1px 0;
441 | background: #e6e6e6;
442 | word-wrap: break-word;
443 | }
444 |
445 | /* char class */
446 | .ref [data-regex-chr]{
447 | background: #ffc080;
448 | color: #694c07;
449 | }
450 |
451 | .ref [data-regex-chr-meta]{background: #e0a060;} /* char class: metasequence */
452 | .ref [data-regex-chr-range]{background: #ffcf9b;} /* char class: range-hyphen */
453 |
454 | /* metasequence */
455 | .ref [data-regex-meta]{
456 | background: #80c0ff;
457 | color: #105f8c;
458 | }
459 |
460 | /* group: depth 1 */
461 | .ref [data-regex-g1]{
462 | background: #00c000;
463 | color: #fff;
464 | }
465 |
466 | /* group: depth 2 */
467 | .ref [data-regex-g2]{
468 | background: #c3e86c;
469 | color: #648c1c;
470 | }
471 |
472 | /* group: depth 3 */
473 | .ref [data-regex-g3]{
474 | background: #008000;
475 | color: #fff;
476 | }
477 |
478 | /* group: depth 4 */
479 | .ref [data-regex-g4]{
480 | background: #6dcb99;
481 | color: #fff;
482 | }
483 |
484 | /* group: depth 5 */
485 | .ref [data-regex-g5]{
486 | background: #00ff00;
487 | color: #2c8e24;
488 | }
489 |
490 | .ref [data-error]{
491 | background: #CC0033;
492 | color: #fff;
493 | border-radius: 0 0 4px 4px;
494 | padding: 2px 5px;
495 | margin: 0 0 4px;
496 | display: block;
497 | }
498 |
499 | /* make labels and less-relevant text non-selectable */
500 | .ref [data-match],
501 | .ref [m],
502 | .ref [data-tHead],
503 | .ref [data-gLabel],
504 | .ref [gl],
505 | .ref [data-mod]{
506 | -webkit-user-select: none;
507 | -moz-user-select: none;
508 | -ms-user-select: none;
509 | user-select: none;
510 | }
511 |
--------------------------------------------------------------------------------
/ref.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('load', function(){
2 |
3 | var tip = document.createElement('div'),
4 | refs = document.querySelectorAll('.ref');
5 |
6 | for(var i = 0, m = refs.length; i < m; i++){
7 | var kbds = refs[i].querySelectorAll('[data-toggle]'),
8 | tippable = refs[i].querySelectorAll('[data-tip],[h]'),
9 | tips = refs[i].querySelectorAll('div, t');
10 |
11 | for(var j = 0, n = kbds.length; j < n; j++){
12 | if(kbds[j].parentNode !== refs[i])
13 | kbds[j].onclick = function(e){
14 | ('exp' in this.dataset) ? delete this.dataset.exp : this.dataset.exp = 1;
15 | }
16 | }
17 |
18 | [].filter.call(tips, function(node){
19 | return node.parentNode == refs[i];
20 | });
21 |
22 | for(var j = 0, n = tippable.length; j < n; j++){
23 | tippable[j].tipRef = tips[tippable[j].dataset.tip] || tips[tippable[j].getAttribute('h')];
24 | tippable[j].onmouseover = function(){
25 | tip.className = 'ref visible';
26 | tip.innerHTML = this.tipRef.innerHTML;
27 | window.clearTimeout(tip.fadeOut);
28 | };
29 | tippable[j].onmouseout = function(){
30 | tip.className = 'ref visible fadingOut';
31 | tip.fadeOut = window.setTimeout(function(){
32 | tip.innerHTML = '';
33 | tip.className = '';
34 | }, 250);
35 | };
36 | }
37 |
38 | refs[i].onmousemove = function(e){
39 | if(tip.className.indexOf('visible') < 0)
40 | return;
41 | tip.style.top = ((document.documentElement.clientHeight - e.clientY) < tip.offsetHeight + 20 ? Math.max(e.pageY - tip.offsetHeight, 0) : e.pageY) + 'px';
42 | tip.style.left = ((document.documentElement.clientWidth - e.clientX) < tip.offsetWidth + 20 ? Math.max(e.pageX - tip.offsetWidth, 0) : e.pageX) + 'px';
43 | };
44 | }
45 |
46 | tip.id = 'rTip';
47 | document.body.appendChild(tip);
48 |
49 | window.addEventListener('keydown', function(e){
50 | if((e.keyCode != 88) || (['input', 'textarea', 'select'].indexOf(e.target.tagName.toLowerCase()) > -1))
51 | return;
52 |
53 | e.preventDefault();
54 |
55 | if(e.ctrlKey && e.keyCode == 88){
56 | var d = refs[0].style.display !== 'none' ? 'none' : 'block';
57 | for(var i = 0, n = refs.length; i < n; i++)
58 | refs[i].style.display = d;
59 |
60 | return;
61 | }
62 |
63 | var kbds = document.querySelectorAll('.ref [data-toggle]'),
64 | m = kbds.length,
65 | partlyExp = document.querySelectorAll('.ref [data-toggle][data-exp]').length !== m;
66 |
67 | for(var i = 0; i < m; i++)
68 | partlyExp ? (kbds[i].dataset.exp = 1) : (delete kbds[i].dataset.exp);
69 |
70 | });
71 |
72 | });
73 |
74 |
--------------------------------------------------------------------------------
/ref.php:
--------------------------------------------------------------------------------
1 |
REF';
33 |
34 | $ref = new ref($format);
35 |
36 | if($capture)
37 | ob_start();
38 |
39 | foreach($args as $index => $arg)
40 | $ref->query($arg, $expressions ? $expressions[$index] : null);
41 |
42 | // return the results if this function was called with the error suppression operator
43 | if($capture)
44 | return ob_get_clean();
45 |
46 | // stop the script if this function was called with the bitwise not operator
47 | if(in_array('~', $options, true) && ($format === 'html')){
48 | print '';
49 | exit(0);
50 | }
51 | }
52 |
53 |
54 |
55 | /**
56 | * Shortcut to ref, plain text mode
57 | *
58 | * @param mixed $args
59 | * @return void|string
60 | */
61 | function rt(){
62 | $args = func_get_args();
63 | $options = array();
64 | $output = '';
65 | $expressions = ref::getInputExpressions($options);
66 | $capture = in_array('@', $options, true);
67 | $ref = new ref((php_sapi_name() !== 'cli') || $capture ? 'text' : 'cliText');
68 |
69 | if(func_num_args() !== count($expressions))
70 | $expressions = null;
71 |
72 | if(!headers_sent())
73 | header('Content-Type: text/plain; charset=utf-8');
74 |
75 | if($capture)
76 | ob_start();
77 |
78 | foreach($args as $index => $arg)
79 | $ref->query($arg, $expressions ? $expressions[$index] : null);
80 |
81 | if($capture)
82 | return ob_get_clean();
83 |
84 | if(in_array('~', $options, true))
85 | exit(0);
86 | }
87 |
88 |
89 |
90 | /**
91 | * REF is a nicer alternative to PHP's print_r() / var_dump().
92 | *
93 | * @version 1.0
94 | * @author digitalnature - http://digitalnature.eu
95 | */
96 | class ref{
97 |
98 | const
99 |
100 | MARKER_KEY = '_phpRefArrayMarker_';
101 |
102 |
103 |
104 | protected static
105 |
106 | /**
107 | * CPU time used for processing
108 | *
109 | * @var array
110 | */
111 | $time = 0,
112 |
113 | /**
114 | * Configuration (+ default values)
115 | *
116 | * @var array
117 | */
118 | $config = array(
119 |
120 | // initially expanded levels (for HTML mode only)
121 | 'expLvl' => 1,
122 |
123 | // depth limit (0 = no limit);
124 | // this is not related to recursion
125 | 'maxDepth' => 6,
126 |
127 | // show the place where r() has been called from
128 | 'showBacktrace' => true,
129 |
130 | // display iterator contents
131 | 'showIteratorContents' => false,
132 |
133 | // display extra information about resources
134 | 'showResourceInfo' => true,
135 |
136 | // display method and parameter list on objects
137 | 'showMethods' => true,
138 |
139 | // display private properties / methods
140 | 'showPrivateMembers' => false,
141 |
142 | // peform string matches (date, file, functions, classes, json, serialized data, regex etc.)
143 | // note: seriously slows down queries on large amounts of data
144 | 'showStringMatches' => true,
145 |
146 | // shortcut functions used to access the query method below;
147 | // if they are namespaced, the namespace must be present as well (methods are not supported)
148 | 'shortcutFunc' => array('r', 'rt'),
149 |
150 | // custom/external formatters (as associative array: format => className)
151 | 'formatters' => array(),
152 |
153 | // stylesheet path (for HTML only);
154 | // 'false' means no styles
155 | 'stylePath' => '{:dir}/ref.css',
156 |
157 | // javascript path (for HTML only);
158 | // 'false' means no js
159 | 'scriptPath' => '{:dir}/ref.js',
160 |
161 | // display url info via cURL
162 | 'showUrls' => false,
163 |
164 | // stop evaluation after this amount of time (seconds)
165 | 'timeout' => 10,
166 |
167 | // whether to produce W3c-valid HTML,
168 | // or unintelligible, but optimized markup that takes less space
169 | 'validHtml' => false,
170 | ),
171 |
172 | /**
173 | * Some environment variables
174 | * used to determine feature support
175 | *
176 | * @var array
177 | */
178 | $env = array(),
179 |
180 | /**
181 | * Timeout point
182 | *
183 | * @var bool
184 | */
185 | $timeout = -1,
186 |
187 | $debug = array(
188 | 'cacheHits' => 0,
189 | 'objects' => 0,
190 | 'arrays' => 0,
191 | 'scalars' => 0,
192 | );
193 |
194 |
195 | protected
196 |
197 | /**
198 | * Output formatter of this instance
199 | *
200 | * @var RFormatter
201 | */
202 | $fmt = null,
203 |
204 | /**
205 | * Start time of the current instance
206 | *
207 | * @var float
208 | */
209 | $startTime = 0,
210 |
211 | /**
212 | * Internally created objects
213 | *
214 | * @var SplObjectStorage
215 | */
216 | $intObjects = null;
217 |
218 |
219 |
220 | /**
221 | * Constructor
222 | *
223 | * @param string|RFormatter $format Output format ID, or formatter instance defaults to 'html'
224 | */
225 | public function __construct($format = 'html'){
226 |
227 | static $didIni = false;
228 |
229 | if(!$didIni){
230 | $didIni = true;
231 | foreach(array_keys(static::$config) as $key){
232 | $iniVal = get_cfg_var('ref.' . $key);
233 | if($iniVal !== false)
234 | static::$config[$key] = $iniVal;
235 | }
236 |
237 | }
238 |
239 | if($format instanceof RFormatter){
240 | $this->fmt = $format;
241 |
242 | }else{
243 | $format = isset(static::$config['formatters'][$format]) ? static::$config['formatters'][$format] : 'R' . ucfirst($format) . 'Formatter';
244 |
245 | if(!class_exists($format, false))
246 | throw new \Exception(sprintf('%s class not found', $format));
247 |
248 | $this->fmt = new $format();
249 | }
250 |
251 | if(static::$env)
252 | return;
253 |
254 | static::$env = array(
255 |
256 | // php 5.4+ ?
257 | 'is54' => version_compare(PHP_VERSION, '5.4') >= 0,
258 |
259 | // php 5.4.6+ ?
260 | 'is546' => version_compare(PHP_VERSION, '5.4.6') >= 0,
261 |
262 | // php 5.6+
263 | 'is56' => version_compare(PHP_VERSION, '5.6') >= 0,
264 |
265 | // php 7.0+ ?
266 | 'is7' => version_compare(PHP_VERSION, '7.0') >= 0,
267 |
268 | // curl extension running?
269 | 'curlActive' => function_exists('curl_version'),
270 |
271 | // is the 'mbstring' extension active?
272 | 'mbStr' => function_exists('mb_detect_encoding'),
273 |
274 | // @see: https://bugs.php.net/bug.php?id=52469
275 | 'supportsDate' => (strncasecmp(PHP_OS, 'WIN', 3) !== 0) || (version_compare(PHP_VERSION, '5.3.10') >= 0),
276 | );
277 | }
278 |
279 |
280 |
281 | /**
282 | * Enforce proper use of this class
283 | *
284 | * @param string $name
285 | */
286 | public function __get($name){
287 | throw new \Exception(sprintf('No such property: %s', $name));
288 | }
289 |
290 |
291 |
292 | /**
293 | * Enforce proper use of this class
294 | *
295 | * @param string $name
296 | * @param mixed $value
297 | */
298 | public function __set($name, $value){
299 | throw new \Exception(sprintf('Cannot set %s. Not allowed', $name));
300 | }
301 |
302 |
303 |
304 | /**
305 | * Generate structured information about a variable/value/expression (subject)
306 | *
307 | * Output is flushed to the screen
308 | *
309 | * @param mixed $subject
310 | * @param string $expression
311 | */
312 | public function query($subject, $expression = null){
313 |
314 | if(static::$timeout > 0)
315 | return;
316 |
317 | $this->startTime = microtime(true);
318 |
319 | $this->intObjects = new \SplObjectStorage();
320 |
321 | $this->fmt->startRoot();
322 | $this->fmt->startExp();
323 | $this->evaluateExp($expression);
324 | $this->fmt->endExp();
325 | $this->evaluate($subject);
326 | $this->fmt->endRoot();
327 | $this->fmt->flush();
328 |
329 | static::$time += microtime(true) - $this->startTime;
330 | }
331 |
332 |
333 |
334 |
335 | /**
336 | * Executes a function the given number of times and returns the elapsed time.
337 | *
338 | * Keep in mind that the returned time includes function call overhead (including
339 | * microtime calls) x iteration count. This is why this is better suited for
340 | * determining which of two or more functions is the fastest, rather than
341 | * finding out how fast is a single function.
342 | *
343 | * @param int $iterations Number of times the function will be executed
344 | * @param callable $function Function to execute
345 | * @param mixed &$output If given, last return value will be available in this variable
346 | * @return double Elapsed time
347 | */
348 | public static function timeFunc($iterations, $function, &$output = null){
349 |
350 | $time = 0;
351 |
352 | for($i = 0; $i < $iterations; $i++){
353 | $start = microtime(true);
354 | $output = call_user_func($function);
355 | $time += microtime(true) - $start;
356 | }
357 |
358 | return round($time, 4);
359 | }
360 |
361 |
362 |
363 | /**
364 | * Timer utility
365 | *
366 | * First call of this function will start the timer.
367 | * The second call will stop the timer and return the elapsed time
368 | * since the timer started.
369 | *
370 | * Multiple timers can be controlled simultaneously by specifying a timer ID.
371 | *
372 | * @since 1.0
373 | * @param int $id Timer ID, optional
374 | * @param int $precision Precision of the result, optional
375 | * @return void|double Elapsed time, or void if the timer was just started
376 | */
377 | public static function timer($id = 1, $precision = 4){
378 |
379 | static
380 | $timers = array();
381 |
382 | // check if this timer was started, and display the elapsed time if so
383 | if(isset($timers[$id])){
384 | $elapsed = round(microtime(true) - $timers[$id], $precision);
385 | unset($timers[$id]);
386 | return $elapsed;
387 | }
388 |
389 | // ID doesn't exist, start new timer
390 | $timers[$id] = microtime(true);
391 | }
392 |
393 |
394 |
395 | /**
396 | * Parses a DocBlock comment into a data structure.
397 | *
398 | * @link http://pear.php.net/manual/en/standards.sample.php
399 | * @param string $comment DocBlock comment (must start with /**)
400 | * @param string|null $key Field to return (optional)
401 | * @return array|string|null Array containing all fields, array/string with the contents of
402 | * the requested field, or null if the comment is empty/invalid
403 | */
404 | public static function parseComment($comment, $key = null){
405 |
406 | $description = '';
407 | $tags = array();
408 | $tag = null;
409 | $pointer = '';
410 | $padding = 0;
411 | $comment = preg_split('/\r\n|\r|\n/', '* ' . trim($comment, "/* \t\n\r\0\x0B"));
412 |
413 | // analyze each line
414 | foreach($comment as $line){
415 |
416 | // drop any wrapping spaces
417 | $line = trim($line);
418 |
419 | // drop "* "
420 | if($line !== '')
421 | $line = substr($line, 2);
422 |
423 | if(strpos($line, '@') !== 0){
424 |
425 | // preserve formatting of tag descriptions,
426 | // because they may span across multiple lines
427 | if($tag !== null){
428 | $trimmed = trim($line);
429 |
430 | if($padding !== 0)
431 | $trimmed = static::strPad($trimmed, static::strLen($line) - $padding, ' ', STR_PAD_LEFT);
432 | else
433 | $padding = static::strLen($line) - static::strLen($trimmed);
434 |
435 | $pointer .= "\n{$trimmed}";
436 | continue;
437 | }
438 |
439 | // tag definitions have not started yet; assume this is part of the description text
440 | $description .= "\n{$line}";
441 | continue;
442 | }
443 |
444 | $padding = 0;
445 | $parts = explode(' ', $line, 2);
446 |
447 | // invalid tag? (should we include it as an empty array?)
448 | if(!isset($parts[1]))
449 | continue;
450 |
451 | $tag = substr($parts[0], 1);
452 | $line = ltrim($parts[1]);
453 |
454 | // tags that have a single component (eg. link, license, author, throws...);
455 | // note that @throws may have 2 components, however most people use it like "@throws ExceptionClass if whatever...",
456 | // which, if broken into two values, leads to an inconsistent description sentence
457 | if(!in_array($tag, array('global', 'param', 'return', 'var'))){
458 | $tags[$tag][] = $line;
459 | end($tags[$tag]);
460 | $pointer = &$tags[$tag][key($tags[$tag])];
461 | continue;
462 | }
463 |
464 | // tags with 2 or 3 components (var, param, return);
465 | $parts = explode(' ', $line, 2);
466 | $parts[1] = isset($parts[1]) ? ltrim($parts[1]) : null;
467 | $lastIdx = 1;
468 |
469 | // expecting 3 components on the 'param' tag: type varName varDescription
470 | if($tag === 'param'){
471 | $lastIdx = 2;
472 | if(in_array($parts[1][0], array('&', '$'), true)){
473 | $line = ltrim(array_pop($parts));
474 | $parts = array_merge($parts, explode(' ', $line, 2));
475 | $parts[2] = isset($parts[2]) ? ltrim($parts[2]) : null;
476 | }else{
477 | $parts[2] = $parts[1];
478 | $parts[1] = null;
479 | }
480 | }
481 |
482 | $tags[$tag][] = $parts;
483 | end($tags[$tag]);
484 | $pointer = &$tags[$tag][key($tags[$tag])][$lastIdx];
485 | }
486 |
487 | // split title from the description texts at the nearest 2x new-line combination
488 | // (note: loose check because 0 isn't valid as well)
489 | if(strpos($description, "\n\n")){
490 | list($title, $description) = explode("\n\n", $description, 2);
491 |
492 | // if we don't have 2 new lines, try to extract first sentence
493 | }else{
494 | // in order for a sentence to be considered valid,
495 | // the next one must start with an uppercase letter
496 | $sentences = preg_split('/(?<=[.?!])\s+(?=[A-Z])/', $description, 2, PREG_SPLIT_NO_EMPTY);
497 |
498 | // failed to detect a second sentence? then assume there's only title and no description text
499 | $title = isset($sentences[0]) ? $sentences[0] : $description;
500 | $description = isset($sentences[1]) ? $sentences[1] : '';
501 | }
502 |
503 | $title = ltrim($title);
504 | $description = ltrim($description);
505 |
506 | $data = compact('title', 'description', 'tags');
507 |
508 | if(!array_filter($data))
509 | return null;
510 |
511 | if($key !== null)
512 | return isset($data[$key]) ? $data[$key] : null;
513 |
514 | return $data;
515 | }
516 |
517 |
518 |
519 | /**
520 | * Split a regex into its components
521 | *
522 | * Based on "Regex Colorizer" by Steven Levithan (this is a translation from javascript)
523 | *
524 | * @link https://github.com/slevithan/regex-colorizer
525 | * @link https://github.com/symfony/Finder/blob/master/Expression/Regex.php#L64-74
526 | * @param string $pattern
527 | * @return array
528 | */
529 | public static function splitRegex($pattern){
530 |
531 | // detection attempt code from the Symfony Finder component
532 | $maybeValid = false;
533 | if(preg_match('/^(.{3,}?)([imsxuADU]*)$/', $pattern, $m)) {
534 | $start = substr($m[1], 0, 1);
535 | $end = substr($m[1], -1);
536 |
537 | if(($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}'))
538 | $maybeValid = true;
539 | }
540 |
541 | if(!$maybeValid)
542 | throw new \Exception('Pattern does not appear to be a valid PHP regex');
543 |
544 | $output = array();
545 | $capturingGroupCount = 0;
546 | $groupStyleDepth = 0;
547 | $openGroups = array();
548 | $lastIsQuant = false;
549 | $lastType = 1; // 1 = none; 2 = alternator
550 | $lastStyle = null;
551 |
552 | preg_match_all('/\[\^?]?(?:[^\\\\\]]+|\\\\[\S\s]?)*]?|\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\\\]+|./', $pattern, $matches);
553 |
554 | $matches = $matches[0];
555 |
556 | $getTokenCharCode = function($token){
557 | if(strlen($token) > 1 && $token[0] === '\\'){
558 | $t1 = substr($token, 1);
559 |
560 | if(preg_match('/^c[A-Za-z]$/', $t1))
561 | return strpos("ABCDEFGHIJKLMNOPQRSTUVWXYZ", strtoupper($t1[1])) + 1;
562 |
563 | if(preg_match('/^(?:x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})$/', $t1))
564 | return intval(substr($t1, 1), 16);
565 |
566 | if(preg_match('/^(?:[0-3][0-7]{0,2}|[4-7][0-7]?)$/', $t1))
567 | return intval($t1, 8);
568 |
569 | $len = strlen($t1);
570 |
571 | if($len === 1 && strpos('cuxDdSsWw', $t1) !== false)
572 | return null;
573 |
574 | if($len === 1){
575 | switch ($t1) {
576 | case 'b': return 8;
577 | case 'f': return 12;
578 | case 'n': return 10;
579 | case 'r': return 13;
580 | case 't': return 9;
581 | case 'v': return 11;
582 | default: return $t1[0];
583 | }
584 | }
585 | }
586 |
587 | return ($token !== '\\') ? $token[0] : null;
588 | };
589 |
590 | foreach($matches as $m){
591 |
592 | if($m[0] === '['){
593 | $lastCC = null;
594 | $cLastRangeable = false;
595 | $cLastType = 0; // 0 = none; 1 = range hyphen; 2 = short class
596 |
597 | preg_match('/^(\[\^?)(]?(?:[^\\\\\]]+|\\\\[\S\s]?)*)(]?)$/', $m, $parts);
598 |
599 | array_shift($parts);
600 | list($opening, $content, $closing) = $parts;
601 |
602 | if(!$closing)
603 | throw new \Exception('Unclosed character class');
604 |
605 | preg_match_all('/[^\\\\-]+|-|\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)/', $content, $ccTokens);
606 | $ccTokens = $ccTokens[0];
607 | $ccTokenCount = count($ccTokens);
608 | $output[] = array('chr' => $opening);
609 |
610 | foreach($ccTokens as $i => $cm) {
611 |
612 | if($cm[0] === '\\'){
613 | if(preg_match('/^\\\\[cux]$/', $cm))
614 | throw new \Exception('Incomplete regex token');
615 |
616 | if(preg_match('/^\\\\[dsw]$/i', $cm)) {
617 | $output[] = array('chr-meta' => $cm);
618 | $cLastRangeable = ($cLastType !== 1);
619 | $cLastType = 2;
620 |
621 | }elseif($cm === '\\'){
622 | throw new \Exception('Incomplete regex token');
623 |
624 | }else{
625 | $output[] = array('chr-meta' => $cm);
626 | $cLastRangeable = $cLastType !== 1;
627 | $lastCC = $getTokenCharCode($cm);
628 | }
629 |
630 | }elseif($cm === '-'){
631 | if($cLastRangeable){
632 | $nextToken = ($i + 1 < $ccTokenCount) ? $ccTokens[$i + 1] : false;
633 |
634 | if($nextToken){
635 | $nextTokenCharCode = $getTokenCharCode($nextToken[0]);
636 |
637 | if((!is_null($nextTokenCharCode) && $lastCC > $nextTokenCharCode) || $cLastType === 2 || preg_match('/^\\\\[dsw]$/i', $nextToken[0]))
638 | throw new \Exception('Reversed or invalid range');
639 |
640 | $output[] = array('chr-range' => '-');
641 | $cLastRangeable = false;
642 | $cLastType = 1;
643 |
644 | }else{
645 | $output[] = $closing ? array('chr' => '-') : array('chr-range' => '-');
646 | }
647 |
648 | }else{
649 | $output[] = array('chr' => '-');
650 | $cLastRangeable = ($cLastType !== 1);
651 | }
652 |
653 | }else{
654 | $output[] = array('chr' => $cm);
655 | $cLastRangeable = strlen($cm) > 1 || ($cLastType !== 1);
656 | $lastCC = $cm[strlen($cm) - 1];
657 | }
658 | }
659 |
660 | $output[] = array('chr' => $closing);
661 | $lastIsQuant = true;
662 |
663 | }elseif($m[0] === '('){
664 | if(strlen($m) === 2)
665 | throw new \Exception('Invalid or unsupported group type');
666 |
667 | if(strlen($m) === 1)
668 | $capturingGroupCount++;
669 |
670 | $groupStyleDepth = ($groupStyleDepth !== 5) ? $groupStyleDepth + 1 : 1;
671 | $openGroups[] = $m; // opening
672 | $lastIsQuant = false;
673 | $output[] = array("g{$groupStyleDepth}" => $m);
674 |
675 | }elseif($m[0] === ')'){
676 | if(!count($openGroups))
677 | throw new \Exception('No matching opening parenthesis');
678 |
679 | $output[] = array('g' . $groupStyleDepth => ')');
680 | $prevGroup = $openGroups[count($openGroups) - 1];
681 | $prevGroup = isset($prevGroup[2]) ? $prevGroup[2] : '';
682 | $lastIsQuant = !preg_match('/^[=!]/', $prevGroup);
683 | $lastStyle = "g{$groupStyleDepth}";
684 | $lastType = 0;
685 | $groupStyleDepth = ($groupStyleDepth !== 1) ? $groupStyleDepth - 1 : 5;
686 |
687 | array_pop($openGroups);
688 | continue;
689 |
690 | }elseif($m[0] === '\\'){
691 | if(isset($m[1]) && preg_match('/^[1-9]/', $m[1])){
692 | $nonBackrefDigits = '';
693 | $num = substr(+$m, 1);
694 |
695 | while($num > $capturingGroupCount){
696 | preg_match('/[0-9]$/', $num, $digits);
697 | $nonBackrefDigits = $digits[0] . $nonBackrefDigits;
698 | $num = floor($num / 10);
699 | }
700 |
701 | if($num > 0){
702 | $output[] = array('meta' => "\\{$num}", 'text' => $nonBackrefDigits);
703 |
704 | }else{
705 | preg_match('/^\\\\([0-3][0-7]{0,2}|[4-7][0-7]?|[89])([0-9]*)/', $m, $pts);
706 | $output[] = array('meta' => '\\' . $pts[1], 'text' => $pts[2]);
707 | }
708 |
709 | $lastIsQuant = true;
710 |
711 | }elseif(isset($m[1]) && preg_match('/^[0bBcdDfnrsStuvwWx]/', $m[1])){
712 |
713 | if(preg_match('/^\\\\[cux]$/', $m))
714 | throw new \Exception('Incomplete regex token');
715 |
716 | $output[] = array('meta' => $m);
717 | $lastIsQuant = (strpos('bB', $m[1]) === false);
718 |
719 | }elseif($m === '\\'){
720 | throw new \Exception('Incomplete regex token');
721 |
722 | }else{
723 | $output[] = array('text' => $m);
724 | $lastIsQuant = true;
725 | }
726 |
727 | }elseif(preg_match('/^(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??$/', $m)){
728 | if(!$lastIsQuant)
729 | throw new \Exception('Quantifiers must be preceded by a token that can be repeated');
730 |
731 | preg_match('/^\{([0-9]+)(?:,([0-9]*))?/', $m, $interval);
732 |
733 | if($interval && (+$interval[1] > 65535 || (isset($interval[2]) && (+$interval[2] > 65535))))
734 | throw new \Exception('Interval quantifier cannot use value over 65,535');
735 |
736 | if($interval && isset($interval[2]) && (+$interval[1] > +$interval[2]))
737 | throw new \Exception('Interval quantifier range is reversed');
738 |
739 | $output[] = array($lastStyle ? $lastStyle : 'meta' => $m);
740 | $lastIsQuant = false;
741 |
742 | }elseif($m === '|'){
743 | if($lastType === 1 || ($lastType === 2 && !count($openGroups)))
744 | throw new \Exception('Empty alternative effectively truncates the regex here');
745 |
746 | $output[] = count($openGroups) ? array("g{$groupStyleDepth}" => '|') : array('meta' => '|');
747 | $lastIsQuant = false;
748 | $lastType = 2;
749 | $lastStyle = '';
750 | continue;
751 |
752 | }elseif($m === '^' || $m === '$'){
753 | $output[] = array('meta' => $m);
754 | $lastIsQuant = false;
755 |
756 | }elseif($m === '.'){
757 | $output[] = array('meta' => '.');
758 | $lastIsQuant = true;
759 |
760 | }else{
761 | $output[] = array('text' => $m);
762 | $lastIsQuant = true;
763 | }
764 |
765 | $lastType = 0;
766 | $lastStyle = '';
767 | }
768 |
769 | if($openGroups)
770 | throw new \Exception('Unclosed grouping');
771 |
772 | return $output;
773 | }
774 |
775 |
776 |
777 | /**
778 | * Set or get configuration options
779 | *
780 | * @param string $key
781 | * @param mixed|null $value
782 | * @return mixed
783 | */
784 | public static function config($key, $value = null){
785 |
786 | if(!array_key_exists($key, static::$config))
787 | throw new \Exception(sprintf('Unrecognized option: "%s". Valid options are: %s', $key, implode(', ', array_keys(static::$config))));
788 |
789 | if($value === null)
790 | return static::$config[$key];
791 |
792 | if(is_array(static::$config[$key]))
793 | return static::$config[$key] = (array)$value;
794 |
795 | return static::$config[$key] = $value;
796 | }
797 |
798 |
799 |
800 | /**
801 | * Total CPU time used by the class
802 | *
803 | * @param int precision
804 | * @return double
805 | */
806 | public static function getTime($precision = 4){
807 | return round(static::$time, $precision);
808 | }
809 |
810 |
811 |
812 | /**
813 | * Get relevant backtrace info for last ref call
814 | *
815 | * @return array|false
816 | */
817 | public static function getBacktrace(){
818 |
819 | // pull only basic info with php 5.3.6+ to save some memory
820 | $trace = defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace();
821 |
822 | while($callee = array_pop($trace)){
823 |
824 | // extract only the information we neeed
825 | $callee = array_intersect_key($callee, array_fill_keys(array('file', 'function', 'line'), false));
826 | extract($callee, EXTR_OVERWRITE);
827 |
828 | // skip, if the called function doesn't match the shortcut function name
829 | if(!$function || !in_array(mb_strtolower((string)$function), static::$config['shortcutFunc']))
830 | continue;
831 |
832 | return compact('file', 'function', 'line');
833 | }
834 |
835 | return false;
836 | }
837 |
838 |
839 |
840 | /**
841 | * Determines the input expression(s) passed to the shortcut function
842 | *
843 | * @param array &$options Optional, options to gather (from operators)
844 | * @return array Array of string expressions
845 | */
846 | public static function getInputExpressions(array &$options = null){
847 |
848 | // used to determine the position of the current call,
849 | // if more queries calls were made on the same line
850 | static $lineInst = array();
851 |
852 | $trace = static::getBacktrace();
853 |
854 | if(!$trace)
855 | return array();
856 |
857 | extract($trace);
858 |
859 | $code = file($file);
860 | $code = $code[$line - 1]; // multiline expressions not supported!
861 | $instIndx = 0;
862 | $tokens = token_get_all(" $token){
866 |
867 | // match token with our shortcut function name
868 | if(is_string($token) || ($token[0] !== T_STRING) || (strcasecmp($token[1], $function) !== 0))
869 | continue;
870 |
871 | // is this some method that happens to have the same name as the shortcut function?
872 | if(isset($tokens[$i - 1]) && is_array($tokens[$i - 1]) && in_array($tokens[$i - 1][0], array(T_DOUBLE_COLON, T_OBJECT_OPERATOR), true))
873 | continue;
874 |
875 | // find argument definition start, just after '('
876 | if(isset($tokens[$i + 1]) && ($tokens[$i + 1][0] === '(')){
877 | $instIndx++;
878 |
879 | if(!isset($lineInst[$line]))
880 | $lineInst[$line] = 0;
881 |
882 | if($instIndx <= $lineInst[$line])
883 | continue;
884 |
885 | $lineInst[$line]++;
886 |
887 | // gather options
888 | if($options !== null){
889 | $j = $i - 1;
890 | while(isset($tokens[$j]) && is_string($tokens[$j]) && in_array($tokens[$j], array('@', '+', '-', '!', '~')))
891 | $options[] = $tokens[$j--];
892 | }
893 |
894 | $lvl = $index = $curlies = 0;
895 | $expressions = array();
896 |
897 | // get the expressions
898 | foreach(array_slice($tokens, $i + 2) as $token){
899 |
900 | if(is_array($token)){
901 | if($token[0] !== T_COMMENT)
902 | $expressions[$index][] = ($token[0] !== T_WHITESPACE) ? $token[1] : ' ';
903 |
904 | continue;
905 | }
906 |
907 | if($token === '{')
908 | $curlies++;
909 |
910 | if($token === '}')
911 | $curlies--;
912 |
913 | if($token === '(')
914 | $lvl++;
915 |
916 | if($token === ')')
917 | $lvl--;
918 |
919 | // assume next argument if a comma was encountered,
920 | // and we're not insde a curly bracket or inner parentheses
921 | if(($curlies < 1) && ($lvl === 0) && ($token === ',')){
922 | $index++;
923 | continue;
924 | }
925 |
926 | // negative parentheses count means we reached the end of argument definitions
927 | if($lvl < 0){
928 | foreach($expressions as &$expression)
929 | $expression = trim(implode('', $expression));
930 |
931 | return $expressions;
932 | }
933 |
934 | $expressions[$index][] = $token;
935 | }
936 |
937 | break;
938 | }
939 | }
940 |
941 | return array();
942 | }
943 |
944 |
945 |
946 | /**
947 | * Get all parent classes of a class
948 | *
949 | * @param Reflector $class Reflection object
950 | * @return array Array of ReflectionClass objects (starts with the ancestor, ends with the given class)
951 | */
952 | protected static function getParentClasses(\Reflector $class){
953 |
954 | $parents = array($class);
955 | while(($class = $class->getParentClass()) !== false)
956 | $parents[] = $class;
957 |
958 | return array_reverse($parents);
959 | }
960 |
961 |
962 |
963 | /**
964 | * Generate class / function info
965 | *
966 | * @param Reflector $reflector Class name or reflection object
967 | * @param string $single Skip parent classes
968 | * @param Reflector|null $context Object context (for methods)
969 | * @return string
970 | */
971 | protected function fromReflector(\Reflector $reflector, $single = '', \Reflector $context = null){
972 |
973 | // @todo: test this
974 | $hash = var_export(func_get_args(), true);
975 | //$hash = $reflector->getName() . ';' . $single . ';' . ($context ? $context->getName() : '');
976 |
977 | if($this->fmt->didCache($hash)){
978 | static::$debug['cacheHits']++;
979 | return;
980 | }
981 |
982 | $items = array($reflector);
983 |
984 | if(($single === '') && ($reflector instanceof \ReflectionClass))
985 | $items = static::getParentClasses($reflector);
986 |
987 | $first = true;
988 | foreach($items as $item){
989 |
990 | if(!$first)
991 | $this->fmt->sep(' :: ');
992 |
993 | $first = false;
994 | $name = ($single !== '') ? $single : $item->getName();
995 | $comments = $item->isInternal() ? array() : static::parseComment($item->getDocComment());
996 | $meta = array('sub' => array());
997 | $bubbles = array();
998 |
999 | if($item->isInternal()){
1000 | $extension = $item->getExtension();
1001 | $meta['title'] = ($extension instanceof \ReflectionExtension) ? sprintf('Internal - part of %s (%s)', $extension->getName(), $extension->getVersion()) : 'Internal';
1002 |
1003 | }else{
1004 | $comments = static::parseComment($item->getDocComment());
1005 |
1006 | if($comments)
1007 | $meta += $comments;
1008 |
1009 | $meta['sub'][] = array('Defined in', basename($item->getFileName()) . ':' . $item->getStartLine());
1010 | }
1011 |
1012 | if(($item instanceof \ReflectionFunction) || ($item instanceof \ReflectionMethod)){
1013 | if(($context !== null) && ($context->getShortName() !== $item->getDeclaringClass()->getShortName()))
1014 | $meta['sub'][] = array('Inherited from', $item->getDeclaringClass()->getShortName());
1015 |
1016 | // @note: PHP 7 seems to crash when calling getPrototype on Closure::__invoke()
1017 | if(($item instanceof \ReflectionMethod) && !$item->isInternal()){
1018 | try{
1019 | $proto = $item->getPrototype();
1020 | $meta['sub'][] = array('Prototype defined by', $proto->class);
1021 | }catch(\Exception $e){}
1022 | }
1023 |
1024 | $this->fmt->text('name', $name, $meta, $this->linkify($item));
1025 | continue;
1026 | }
1027 |
1028 | // @todo: maybe - list interface methods
1029 | if(!($item->isInterface() || (static::$env['is54'] && $item->isTrait()))){
1030 |
1031 | if($item->isAbstract())
1032 | $bubbles[] = array('A', 'Abstract');
1033 |
1034 | if(static::$env['is7'] && $item->isAnonymous())
1035 | $bubbles[] = array('?', 'Anonymous');
1036 |
1037 | if($item->isFinal())
1038 | $bubbles[] = array('F', 'Final');
1039 |
1040 | // php 5.4+ only
1041 | if(static::$env['is54'] && $item->isCloneable())
1042 | $bubbles[] = array('C', 'Cloneable');
1043 |
1044 | if($item->isIterateable())
1045 | $bubbles[] = array('X', 'Iterateable');
1046 |
1047 | }
1048 |
1049 | if($item->isInterface() && $single !== '')
1050 | $bubbles[] = array('I', 'Interface');
1051 |
1052 | if($bubbles)
1053 | $this->fmt->bubbles($bubbles);
1054 |
1055 | if($item->isInterface() && $single === '')
1056 | $name .= sprintf(' (%d)', count($item->getMethods()));
1057 |
1058 | $this->fmt->text('name', $name, $meta, $this->linkify($item));
1059 | }
1060 |
1061 | $this->fmt->cacheLock($hash);
1062 | }
1063 |
1064 |
1065 |
1066 | /**
1067 | * Generates an URL that points to the documentation page relevant for the requested context
1068 | *
1069 | * For internal functions and classes, the URI will point to the local PHP manual
1070 | * if installed and configured, otherwise to php.net/manual (the english one)
1071 | *
1072 | * @param Reflector $reflector Reflector object (used to determine the URL scheme for internal stuff)
1073 | * @param string|null $constant Constant name, if this is a request to linkify a constant
1074 | * @return string|null URL
1075 | */
1076 | protected function linkify(\Reflector $reflector, $constant = null){
1077 |
1078 | static $docRefRoot = null, $docRefExt = null;
1079 |
1080 | // most people don't have this set
1081 | if(!$docRefRoot)
1082 | $docRefRoot = ($docRefRoot = rtrim(ini_get('docref_root'), '/')) ? $docRefRoot : 'http://php.net/manual/en';
1083 |
1084 | if(!$docRefExt)
1085 | $docRefExt = ($docRefExt = ini_get('docref_ext')) ? $docRefExt : '.php';
1086 |
1087 | $phpNetSchemes = array(
1088 | 'class' => $docRefRoot . '/class.%s' . $docRefExt,
1089 | 'function' => $docRefRoot . '/function.%s' . $docRefExt,
1090 | 'method' => $docRefRoot . '/%2$s.%1$s' . $docRefExt,
1091 | 'property' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.props.%1$s',
1092 | 'constant' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.constants.%1$s',
1093 | );
1094 |
1095 | $url = null;
1096 | $args = array();
1097 |
1098 | // determine scheme
1099 | if($constant !== null){
1100 | $type = 'constant';
1101 | $args[] = $constant;
1102 |
1103 | }else{
1104 | $type = explode('\\', get_class($reflector));
1105 | $type = strtolower(ltrim(end($type), 'Reflection'));
1106 |
1107 | if($type === 'object')
1108 | $type = 'class';
1109 | }
1110 |
1111 | // properties don't have the internal flag;
1112 | // also note that many internal classes use some kind of magic as properties (eg. DateTime);
1113 | // these will only get linkifed if the declared class is internal one, and not an extension :(
1114 | $parent = ($type !== 'property') ? $reflector : $reflector->getDeclaringClass();
1115 |
1116 | // internal function/method/class/property/constant
1117 | if($parent->isInternal()){
1118 | $args[] = $reflector->name;
1119 |
1120 | if(in_array($type, array('method', 'property'), true))
1121 | $args[] = $reflector->getDeclaringClass()->getName();
1122 |
1123 | $args = array_map(function($text){
1124 | return str_replace('_', '-', ltrim(strtolower($text), '\\_'));
1125 | }, $args);
1126 |
1127 | // check for some special cases that have no links
1128 | $valid = (($type === 'method') || (strcasecmp($parent->name, 'stdClass') !== 0))
1129 | && (($type !== 'method') || (($reflector->name === '__construct') || strpos($reflector->name, '__') !== 0));
1130 |
1131 | if($valid)
1132 | $url = vsprintf($phpNetSchemes[$type], $args);
1133 |
1134 | // custom
1135 | }else{
1136 | switch(true){
1137 |
1138 | // WordPress function;
1139 | // like pretty much everything else in WordPress, API links are inconsistent as well;
1140 | // so we're using queryposts.com as doc source for API
1141 | case ($type === 'function') && class_exists('WP', false) && defined('ABSPATH') && defined('WPINC'):
1142 | if(strpos($reflector->getFileName(), realpath(ABSPATH . WPINC)) === 0){
1143 | $url = sprintf('http://queryposts.com/function/%s', urlencode(strtolower($reflector->getName())));
1144 | break;
1145 | }
1146 |
1147 | // @todo: handle more apps
1148 | }
1149 |
1150 | }
1151 |
1152 | return $url;
1153 | }
1154 |
1155 |
1156 | public static function getTimeoutPoint(){
1157 | return static::$timeout;
1158 | }
1159 |
1160 |
1161 | public static function getDebugInfo(){
1162 | return static::$debug;
1163 | }
1164 |
1165 |
1166 |
1167 | protected function hasInstanceTimedOut(){
1168 |
1169 | if(static::$timeout > 0)
1170 | return true;
1171 |
1172 | $timeout = static::$config['timeout'];
1173 |
1174 | if(($timeout > 0) && ((microtime(true) - $this->startTime) > $timeout))
1175 | return (static::$timeout = (microtime(true) - $this->startTime));
1176 |
1177 | return false;
1178 | }
1179 |
1180 |
1181 |
1182 | /**
1183 | * Evaluates the given variable
1184 | *
1185 | * @param mixed &$subject Variable to query
1186 | * @param bool $specialStr Should this be interpreted as a special string?
1187 | * @return mixed Result (both HTML and text modes generate strings)
1188 | */
1189 | protected function evaluate(&$subject, $specialStr = false){
1190 |
1191 | switch($type = gettype($subject)){
1192 |
1193 | // https://github.com/digitalnature/php-ref/issues/13
1194 | case 'unknown type':
1195 | return $this->fmt->text('unknown');
1196 |
1197 | // null value
1198 | case 'NULL':
1199 | return $this->fmt->text('null');
1200 |
1201 | // integer/double/float
1202 | case 'integer':
1203 | case 'double':
1204 | return $this->fmt->text($type, $subject, $type);
1205 |
1206 | // boolean
1207 | case 'boolean':
1208 | $text = $subject ? 'true' : 'false';
1209 | return $this->fmt->text($text, $text, $type);
1210 |
1211 | // arrays
1212 | case 'array':
1213 |
1214 | // empty array?
1215 | if(empty($subject)){
1216 | $this->fmt->text('array');
1217 | return $this->fmt->emptyGroup();
1218 | }
1219 |
1220 | if(isset($subject[static::MARKER_KEY])){
1221 | unset($subject[static::MARKER_KEY]);
1222 | $this->fmt->text('array');
1223 | $this->fmt->emptyGroup('recursion');
1224 | return;
1225 | }
1226 |
1227 | // first recursion level detection;
1228 | // this is optional (used to print consistent recursion info)
1229 | foreach($subject as $key => &$value){
1230 |
1231 | if(!is_array($value))
1232 | continue;
1233 |
1234 | // save current value in a temporary variable
1235 | $buffer = $value;
1236 |
1237 | // assign new value
1238 | $value = ($value !== 1) ? 1 : 2;
1239 |
1240 | // if they're still equal, then we have a reference
1241 | if($value === $subject){
1242 | $value = $buffer;
1243 | $value[static::MARKER_KEY] = true;
1244 | $this->evaluate($value);
1245 | return;
1246 | }
1247 |
1248 | // restoring original value
1249 | $value = $buffer;
1250 | }
1251 |
1252 | $this->fmt->text('array');
1253 | $count = count($subject);
1254 | if(!$this->fmt->startGroup($count))
1255 | return;
1256 |
1257 | $max = max(array_map('static::strLen', array_keys($subject)));
1258 | $subject[static::MARKER_KEY] = true;
1259 |
1260 | foreach($subject as $key => &$value){
1261 |
1262 | // ignore our temporary marker
1263 | if($key === static::MARKER_KEY)
1264 | continue;
1265 |
1266 | if($this->hasInstanceTimedOut())
1267 | break;
1268 |
1269 | $keyInfo = gettype($key);
1270 |
1271 | if($keyInfo === 'string'){
1272 | $encoding = static::$env['mbStr'] ? mb_detect_encoding($key) : '';
1273 | $keyLen = static::strLen($key);
1274 | $keyLenInfo = $encoding && ($encoding !== 'ASCII') ? $keyLen . '; ' . $encoding : $keyLen;
1275 | $keyInfo = "{$keyInfo}({$keyLenInfo})";
1276 | }else{
1277 | $keyLen = strlen($key);
1278 | }
1279 |
1280 | $this->fmt->startRow();
1281 | $this->fmt->text('key', $key, "Key: {$keyInfo}");
1282 | $this->fmt->colDiv($max - $keyLen);
1283 | $this->fmt->sep('=>');
1284 | $this->fmt->colDiv();
1285 | $this->evaluate($value, $specialStr);
1286 | $this->fmt->endRow();
1287 | }
1288 |
1289 | unset($subject[static::MARKER_KEY]);
1290 |
1291 | $this->fmt->endGroup();
1292 | return;
1293 |
1294 | // resource
1295 | case 'resource':
1296 | case 'resource (closed)':
1297 | $meta = array();
1298 | $resType = get_resource_type($subject);
1299 |
1300 | $this->fmt->text('resource', strval($subject));
1301 |
1302 | if(!static::$config['showResourceInfo'])
1303 | return $this->fmt->emptyGroup($resType);
1304 |
1305 | // @see: http://php.net/manual/en/resource.php
1306 | // need to add more...
1307 | switch($resType){
1308 |
1309 | // curl extension resource
1310 | case 'curl':
1311 | $meta = curl_getinfo($subject);
1312 | break;
1313 |
1314 | case 'FTP Buffer':
1315 | $meta = array(
1316 | 'time_out' => ftp_get_option($subject, FTP_TIMEOUT_SEC),
1317 | 'auto_seek' => ftp_get_option($subject, FTP_AUTOSEEK),
1318 | );
1319 |
1320 | break;
1321 |
1322 | // gd image extension resource
1323 | case 'gd':
1324 | $meta = array(
1325 | 'size' => sprintf('%d x %d', imagesx($subject), imagesy($subject)),
1326 | 'true_color' => imageistruecolor($subject),
1327 | );
1328 |
1329 | break;
1330 |
1331 | case 'ldap link':
1332 | $constants = get_defined_constants();
1333 |
1334 | array_walk($constants, function($value, $key) use(&$constants){
1335 | if(strpos($key, 'LDAP_OPT_') !== 0)
1336 | unset($constants[$key]);
1337 | });
1338 |
1339 | // this seems to fail on my setup :(
1340 | unset($constants['LDAP_OPT_NETWORK_TIMEOUT']);
1341 |
1342 | foreach(array_slice($constants, 3) as $key => $value)
1343 | if(ldap_get_option($subject, (int)$value, $ret))
1344 | $meta[strtolower(substr($key, 9))] = $ret;
1345 |
1346 | break;
1347 |
1348 | // mysql connection (mysql extension is deprecated from php 5.4/5.5)
1349 | case 'mysql link':
1350 | case 'mysql link persistent':
1351 | $dbs = array();
1352 | $query = @mysql_list_dbs($subject);
1353 | while($row = @mysql_fetch_array($query))
1354 | $dbs[] = $row['Database'];
1355 |
1356 | $meta = array(
1357 | 'host' => ltrim(@mysql_get_host_info ($subject), 'MySQL host info: '),
1358 | 'server_version' => @mysql_get_server_info($subject),
1359 | 'protocol_version' => @mysql_get_proto_info($subject),
1360 | 'databases' => $dbs,
1361 | );
1362 |
1363 | break;
1364 |
1365 | // mysql result
1366 | case 'mysql result':
1367 | while($row = @mysql_fetch_object($subject)){
1368 | $meta[] = (array)$row;
1369 |
1370 | if($this->hasInstanceTimedOut())
1371 | break;
1372 | }
1373 |
1374 | break;
1375 |
1376 | // stream resource (fopen, fsockopen, popen, opendir etc)
1377 | case 'stream':
1378 | $meta = stream_get_meta_data($subject);
1379 | break;
1380 |
1381 | }
1382 |
1383 | if(!$meta)
1384 | return $this->fmt->emptyGroup($resType);
1385 |
1386 |
1387 | if(!$this->fmt->startGroup($resType))
1388 | return;
1389 |
1390 | $max = max(array_map('static::strLen', array_keys($meta)));
1391 | foreach($meta as $key => $value){
1392 | $this->fmt->startRow();
1393 | $this->fmt->text('resourceProp', ucwords(str_replace('_', ' ', $key)));
1394 | $this->fmt->colDiv($max - static::strLen($key));
1395 | $this->fmt->sep(':');
1396 | $this->fmt->colDiv();
1397 | $this->evaluate($value);
1398 | $this->fmt->endRow();
1399 | }
1400 | $this->fmt->endGroup();
1401 | return;
1402 |
1403 | // string
1404 | case 'string':
1405 | $length = static::strLen($subject);
1406 | $encoding = static::$env['mbStr'] ? mb_detect_encoding($subject) : false;
1407 | $info = $encoding && ($encoding !== 'ASCII') ? $length . '; ' . $encoding : $length;
1408 |
1409 | if($specialStr){
1410 | $this->fmt->sep('"');
1411 | $this->fmt->text(array('string', 'special'), $subject, "string({$info})");
1412 | $this->fmt->sep('"');
1413 | return;
1414 | }
1415 |
1416 | $this->fmt->text('string', $subject, "string({$info})");
1417 |
1418 | // advanced checks only if there are 3 characteres or more
1419 | if(static::$config['showStringMatches'] && ($length > 2) && (trim($subject) !== '')){
1420 |
1421 | $isNumeric = is_numeric($subject);
1422 |
1423 | // very simple check to determine if the string could match a file path
1424 | // @note: this part of the code is very expensive
1425 | $isFile = ($length < 2048)
1426 | && (max(array_map('strlen', explode('/', str_replace('\\', '/', $subject)))) < 128)
1427 | && !preg_match('/[^\w\.\-\/\\\\:]|\..*\.|\.$|:(?!(?<=^[a-zA-Z]:)[\/\\\\])/', $subject);
1428 |
1429 | if($isFile){
1430 | try{
1431 | $file = new \SplFileInfo($subject);
1432 | $flags = array();
1433 | $perms = $file->getPerms();
1434 |
1435 | if(($perms & 0xC000) === 0xC000) // socket
1436 | $flags[] = 's';
1437 | elseif(($perms & 0xA000) === 0xA000) // symlink
1438 | $flags[] = 'l';
1439 | elseif(($perms & 0x8000) === 0x8000) // regular
1440 | $flags[] = '-';
1441 | elseif(($perms & 0x6000) === 0x6000) // block special
1442 | $flags[] = 'b';
1443 | elseif(($perms & 0x4000) === 0x4000) // directory
1444 | $flags[] = 'd';
1445 | elseif(($perms & 0x2000) === 0x2000) // character special
1446 | $flags[] = 'c';
1447 | elseif(($perms & 0x1000) === 0x1000) // FIFO pipe
1448 | $flags[] = 'p';
1449 | else // unknown
1450 | $flags[] = 'u';
1451 |
1452 | // owner
1453 | $flags[] = (($perms & 0x0100) ? 'r' : '-');
1454 | $flags[] = (($perms & 0x0080) ? 'w' : '-');
1455 | $flags[] = (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-'));
1456 |
1457 | // group
1458 | $flags[] = (($perms & 0x0020) ? 'r' : '-');
1459 | $flags[] = (($perms & 0x0010) ? 'w' : '-');
1460 | $flags[] = (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-'));
1461 |
1462 | // world
1463 | $flags[] = (($perms & 0x0004) ? 'r' : '-');
1464 | $flags[] = (($perms & 0x0002) ? 'w' : '-');
1465 | $flags[] = (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-'));
1466 |
1467 | $size = is_dir($subject) ? '' : sprintf(' %.2fK', $file->getSize() / 1024);
1468 |
1469 | $this->fmt->startContain('file', true);
1470 | $this->fmt->text('file', implode('', $flags) . $size);
1471 | $this->fmt->endContain();
1472 |
1473 | }catch(\Exception $e){
1474 | $isFile = false;
1475 | }
1476 | }
1477 |
1478 | // class/interface/function
1479 | if(!preg_match('/[^\w+\\\\]/', $subject) && ($length < 96)){
1480 | $isClass = class_exists($subject, false);
1481 | if($isClass){
1482 | $this->fmt->startContain('class', true);
1483 | $this->fromReflector(new \ReflectionClass($subject));
1484 | $this->fmt->endContain();
1485 | }
1486 |
1487 | if(!$isClass && interface_exists($subject, false)){
1488 | $this->fmt->startContain('interface', true);
1489 | $this->fromReflector(new \ReflectionClass($subject));
1490 | $this->fmt->endContain('interface');
1491 | }
1492 |
1493 | if(function_exists($subject)){
1494 | $this->fmt->startContain('function', true);
1495 | $this->fromReflector(new \ReflectionFunction($subject));
1496 | $this->fmt->endContain('function');
1497 | }
1498 | }
1499 |
1500 |
1501 | // skip serialization/json/date checks if the string appears to be numeric,
1502 | // or if it's shorter than 5 characters
1503 | if(!$isNumeric && ($length > 4)){
1504 |
1505 | // url
1506 | if(static::$config['showUrls'] && static::$env['curlActive'] && filter_var($subject, FILTER_VALIDATE_URL)){
1507 | $ch = curl_init($subject);
1508 | curl_setopt($ch, CURLOPT_NOBODY, true);
1509 | curl_exec($ch);
1510 | $nfo = curl_getinfo($ch);
1511 | curl_close($ch);
1512 |
1513 | if($nfo['http_code']){
1514 | $this->fmt->startContain('url', true);
1515 | $contentType = explode(';', $nfo['content_type']);
1516 | $this->fmt->text('url', sprintf('%s:%d %s %.2fms (%d)', !empty($nfo['primary_ip']) ? $nfo['primary_ip'] : null, !empty($nfo['primary_port']) ? $nfo['primary_port'] : null, $contentType[0], $nfo['total_time'], $nfo['http_code']));
1517 | $this->fmt->endContain();
1518 | }
1519 |
1520 | }
1521 |
1522 | // date
1523 | if(($length < 128) && static::$env['supportsDate'] && !preg_match('/[^A-Za-z0-9.:+\s\-\/]/', $subject)){
1524 | try{
1525 | $date = new \DateTime($subject);
1526 | $errors = \DateTime::getLastErrors();
1527 |
1528 | if(($errors['warning_count'] < 1) && ($errors['error_count'] < 1)){
1529 | $now = new \Datetime('now');
1530 | $nowUtc = new \Datetime('now', new \DateTimeZone('UTC'));
1531 | $diff = $now->diff($date);
1532 |
1533 | $map = array(
1534 | 'y' => 'yr',
1535 | 'm' => 'mo',
1536 | 'd' => 'da',
1537 | 'h' => 'hr',
1538 | 'i' => 'min',
1539 | 's' => 'sec',
1540 | );
1541 |
1542 | $timeAgo = 'now';
1543 | foreach($map as $k => $label){
1544 | if($diff->{$k} > 0){
1545 | $timeAgo = $diff->format("%R%{$k}{$label}");
1546 | break;
1547 | }
1548 | }
1549 |
1550 | $tz = $date->getTimezone();
1551 | $offs = round($tz->getOffset($nowUtc) / 3600);
1552 |
1553 | if($offs > 0)
1554 | $offs = "+{$offs}";
1555 |
1556 | $timeAgo .= ((int)$offs !== 0) ? ' ' . sprintf('%s (UTC%s)', $tz->getName(), $offs) : ' UTC';
1557 | $this->fmt->startContain('date', true);
1558 | $this->fmt->text('date', $timeAgo);
1559 | $this->fmt->endContain();
1560 |
1561 | }
1562 | }catch(\Exception $e){
1563 | // not a date
1564 | }
1565 |
1566 | }
1567 |
1568 | // attempt to detect if this is a serialized string
1569 | static $unserializing = 0;
1570 | $isSerialized = ($unserializing < 3)
1571 | && (($subject[$length - 1] === ';') || ($subject[$length - 1] === '}'))
1572 | && in_array($subject[0], array('s', 'a', 'O'), true)
1573 | && ((($subject[0] === 's') && ($subject[$length - 2] !== '"')) || preg_match("/^{$subject[0]}:[0-9]+:/s", $subject))
1574 | && (($unserialized = @unserialize($subject)) !== false);
1575 |
1576 | if($isSerialized){
1577 | $unserializing++;
1578 | $this->fmt->startContain('serialized', true);
1579 | $this->evaluate($unserialized);
1580 | $this->fmt->endContain();
1581 | $unserializing--;
1582 | }
1583 |
1584 | // try to find out if it's a json-encoded string;
1585 | // only do this for json-encoded arrays or objects, because other types have too generic formats
1586 | static $decodingJson = 0;
1587 | $isJson = !$isSerialized && ($decodingJson < 3) && in_array($subject[0], array('{', '['), true);
1588 |
1589 | if($isJson){
1590 | $decodingJson++;
1591 | $data = json_decode($subject);
1592 |
1593 | // ensure created objects live enough for PHP to provide a unique hash
1594 | if(is_object($data))
1595 | $this->intObjects->attach($data);
1596 |
1597 | if($isJson = (json_last_error() === JSON_ERROR_NONE)){
1598 | $this->fmt->startContain('json', true);
1599 | $this->evaluate($data);
1600 | $this->fmt->endContain();
1601 | }
1602 |
1603 | $decodingJson--;
1604 | }
1605 |
1606 | // attempt to match a regex
1607 | if(!$isSerialized && !$isJson && $length < 768){
1608 | try{
1609 | $components = $this->splitRegex($subject);
1610 | if($components){
1611 | $regex = '';
1612 |
1613 | $this->fmt->startContain('regex', true);
1614 | foreach($components as $component)
1615 | $this->fmt->text('regex-' . key($component), reset($component));
1616 | $this->fmt->endContain();
1617 | }
1618 |
1619 | }catch(\Exception $e){
1620 | // not a regex
1621 | }
1622 |
1623 | }
1624 | }
1625 | }
1626 |
1627 | return;
1628 | }
1629 |
1630 | // if we reached this point, $subject must be an object
1631 |
1632 | // track objects to detect recursion
1633 | static $hashes = array();
1634 |
1635 | // hash ID of this object
1636 | $hash = spl_object_hash($subject);
1637 | $recursion = isset($hashes[$hash]);
1638 |
1639 | // sometimes incomplete objects may be created from string unserialization,
1640 | // if the class to which the object belongs wasn't included until the unserialization stage...
1641 | if($subject instanceof \__PHP_Incomplete_Class){
1642 | $this->fmt->text('object');
1643 | $this->fmt->emptyGroup('incomplete');
1644 | return;
1645 | }
1646 |
1647 | // check cache at this point
1648 | if(!$recursion && $this->fmt->didCache($hash)){
1649 | static::$debug['cacheHits']++;
1650 | return;
1651 | }
1652 |
1653 | $reflector = new \ReflectionObject($subject);
1654 | $this->fmt->startContain('class');
1655 | $this->fromReflector($reflector);
1656 | $this->fmt->text('object', ' object');
1657 | $this->fmt->endContain();
1658 |
1659 | // already been here?
1660 | if($recursion)
1661 | return $this->fmt->emptyGroup('recursion');
1662 |
1663 | $hashes[$hash] = 1;
1664 |
1665 | $flags = \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED;
1666 |
1667 | if(static::$config['showPrivateMembers'])
1668 | $flags |= \ReflectionProperty::IS_PRIVATE;
1669 |
1670 | $props = $magicProps = $methods = array();
1671 |
1672 | if($reflector->hasMethod('__debugInfo')){
1673 | $magicProps = $subject->__debugInfo();
1674 | }else{
1675 | $props = $reflector->getProperties($flags);
1676 | }
1677 |
1678 | if(static::$config['showMethods']){
1679 | $flags = \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED;
1680 |
1681 | if(static::$config['showPrivateMembers'])
1682 | $flags |= \ReflectionMethod::IS_PRIVATE;
1683 |
1684 | $methods = $reflector->getMethods($flags);
1685 | }
1686 |
1687 | $constants = $reflector->getConstants();
1688 | $interfaces = $reflector->getInterfaces();
1689 | $traits = static::$env['is54'] ? $reflector->getTraits() : array();
1690 | $parents = static::getParentClasses($reflector);
1691 |
1692 | // work-around for https://bugs.php.net/bug.php?id=49154
1693 | // @see http://stackoverflow.com/questions/15672287/strange-behavior-of-reflectiongetproperties-with-numeric-keys
1694 | if(!static::$env['is54']){
1695 | $props = array_values(array_filter($props, function($prop) use($subject){
1696 | return !$prop->isPublic() || property_exists($subject, $prop->name);
1697 | }));
1698 | }
1699 |
1700 | // no data to display?
1701 | if(!$props && !$methods && !$constants && !$interfaces && !$traits){
1702 | unset($hashes[$hash]);
1703 | return $this->fmt->emptyGroup();
1704 | }
1705 |
1706 | if(!$this->fmt->startGroup())
1707 | return;
1708 |
1709 | // show contents for iterators
1710 | if(static::$config['showIteratorContents'] && $reflector->isIterateable()){
1711 |
1712 | $itContents = iterator_to_array($subject);
1713 | $this->fmt->sectionTitle(sprintf('Contents (%d)', count($itContents)));
1714 |
1715 | foreach($itContents as $key => $value){
1716 | $keyInfo = gettype($key);
1717 | if($keyInfo === 'string'){
1718 | $encoding = static::$env['mbStr'] ? mb_detect_encoding($key) : '';
1719 | $length = $encoding && ($encoding !== 'ASCII') ? static::strLen($key) . '; ' . $encoding : static::strLen($key);
1720 | $keyInfo = sprintf('%s(%s)', $keyInfo, $length);
1721 | }
1722 |
1723 | $this->fmt->startRow();
1724 | $this->fmt->text(array('key', 'iterator'), $key, sprintf('Iterator key: %s', $keyInfo));
1725 | $this->fmt->colDiv();
1726 | $this->fmt->sep('=>');
1727 | $this->fmt->colDiv();
1728 | $this->evaluate($value);
1729 | //$this->evaluate($value instanceof \Traversable ? ((count($value) > 0) ? $value : (string)$value) : $value);
1730 | $this->fmt->endRow();
1731 | }
1732 | }
1733 |
1734 | // display the interfaces this objects' class implements
1735 | if($interfaces){
1736 | $items = array();
1737 | $this->fmt->sectionTitle('Implements');
1738 | $this->fmt->startRow();
1739 | $this->fmt->startContain('interfaces');
1740 |
1741 | $i = 0;
1742 | $count = count($interfaces);
1743 |
1744 | foreach($interfaces as $name => $interface){
1745 | $this->fromReflector($interface);
1746 |
1747 | if(++$i < $count)
1748 | $this->fmt->sep(', ');
1749 | }
1750 |
1751 | $this->fmt->endContain();
1752 | $this->fmt->endRow();
1753 | }
1754 |
1755 | // traits this objects' class uses
1756 | if($traits){
1757 | $items = array();
1758 | $this->fmt->sectionTitle('Uses');
1759 | $this->fmt->startRow();
1760 | $this->fmt->startContain('traits');
1761 |
1762 | $i = 0;
1763 | $count = count($traits);
1764 |
1765 | foreach($traits as $name => $trait){
1766 | $this->fromReflector($trait);
1767 |
1768 | if(++$i < $count)
1769 | $this->fmt->sep(', ');
1770 | }
1771 |
1772 | $this->fmt->endContain();
1773 | $this->fmt->endRow();
1774 | }
1775 |
1776 | // class constants
1777 | if($constants){
1778 | $this->fmt->sectionTitle('Constants');
1779 | $max = max(array_map('static::strLen', array_keys($constants)));
1780 | foreach($constants as $name => $value){
1781 | $meta = null;
1782 | $type = array('const');
1783 | foreach($parents as $parent){
1784 | if($parent->hasConstant($name)){
1785 | if($parent !== $reflector){
1786 | $type[] = 'inherited';
1787 | $meta = array('sub' => array(array('Prototype defined by', $parent->name)));
1788 | }
1789 | break;
1790 | }
1791 | }
1792 |
1793 | $this->fmt->startRow();
1794 | $this->fmt->sep('::');
1795 | $this->fmt->colDiv();
1796 | $this->fmt->startContain($type);
1797 | $this->fmt->text('name', $name, $meta, $this->linkify($parent, $name));
1798 | $this->fmt->endContain();
1799 | $this->fmt->colDiv($max - static::strLen($name));
1800 | $this->fmt->sep('=');
1801 | $this->fmt->colDiv();
1802 | $this->evaluate($value);
1803 | $this->fmt->endRow();
1804 | }
1805 | }
1806 |
1807 | // object/class properties
1808 | if($props){
1809 | $this->fmt->sectionTitle('Properties');
1810 |
1811 | $max = 0;
1812 | foreach($props as $idx => $prop)
1813 | if(($propNameLen = static::strLen($prop->name)) > $max)
1814 | $max = $propNameLen;
1815 |
1816 | foreach($props as $idx => $prop){
1817 |
1818 | if($this->hasInstanceTimedOut())
1819 | break;
1820 |
1821 | $bubbles = array();
1822 | $sourceClass = $prop->getDeclaringClass();
1823 | $inherited = $reflector->getShortName() !== $sourceClass->getShortName();
1824 | $meta = $sourceClass->isInternal() ? null : static::parseComment($prop->getDocComment());
1825 |
1826 | if($meta){
1827 | if($inherited)
1828 | $meta['sub'] = array(array('Declared in', $sourceClass->getShortName()));
1829 |
1830 | if(isset($meta['tags']['var'][0]))
1831 | $meta['left'] = $meta['tags']['var'][0][0];
1832 |
1833 | unset($meta['tags']);
1834 | }
1835 |
1836 | if($prop->isProtected() || $prop->isPrivate())
1837 | $prop->setAccessible(true);
1838 |
1839 | $value = $prop->getValue($subject);
1840 |
1841 | $this->fmt->startRow();
1842 | $this->fmt->sep($prop->isStatic() ? '::' : '->');
1843 | $this->fmt->colDiv();
1844 |
1845 | $bubbles = array();
1846 | if($prop->isProtected())
1847 | $bubbles[] = array('P', 'Protected');
1848 |
1849 | if($prop->isPrivate())
1850 | $bubbles[] = array('!', 'Private');
1851 |
1852 | $this->fmt->bubbles($bubbles);
1853 |
1854 | $type = array('prop');
1855 |
1856 | if($inherited)
1857 | $type[] = 'inherited';
1858 |
1859 | if($prop->isPrivate())
1860 | $type[] = 'private';
1861 |
1862 | $this->fmt->colDiv(2 - count($bubbles));
1863 | $this->fmt->startContain($type);
1864 | $this->fmt->text('name', $prop->name, $meta, $this->linkify($prop));
1865 | $this->fmt->endContain();
1866 | $this->fmt->colDiv($max - static::strLen($prop->name));
1867 | $this->fmt->sep('=');
1868 | $this->fmt->colDiv();
1869 | $this->evaluate($value);
1870 | $this->fmt->endRow();
1871 | }
1872 | }
1873 |
1874 | // __debugInfo()
1875 | if($magicProps){
1876 | $this->fmt->sectionTitle('Properties (magic)');
1877 |
1878 | $max = 0;
1879 | foreach($magicProps as $name => $value)
1880 | if(($propNameLen = static::strLen($name)) > $max)
1881 | $max = $propNameLen;
1882 |
1883 | foreach($magicProps as $name => $value){
1884 |
1885 | if($this->hasInstanceTimedOut())
1886 | break;
1887 |
1888 | // attempt to pull out doc comment from the "regular" property definition
1889 | try{
1890 | $prop = $reflector->getProperty($name);
1891 | $meta = static::parseComment($prop->getDocComment());
1892 |
1893 | }catch(\Exception $e){
1894 | $meta = null;
1895 | }
1896 |
1897 | $this->fmt->startRow();
1898 | $this->fmt->sep('->');
1899 | $this->fmt->colDiv();
1900 |
1901 | $type = array('prop');
1902 |
1903 | $this->fmt->startContain($type);
1904 | $this->fmt->text('name', $name, $meta);
1905 | $this->fmt->endContain();
1906 | $this->fmt->colDiv($max - static::strLen($name));
1907 | $this->fmt->sep('=');
1908 | $this->fmt->colDiv();
1909 | $this->evaluate($value);
1910 | $this->fmt->endRow();
1911 | }
1912 | }
1913 |
1914 | // class methods
1915 | if($methods && !$this->hasInstanceTimedOut()){
1916 |
1917 | $this->fmt->sectionTitle('Methods');
1918 | foreach($methods as $idx => $method){
1919 |
1920 | $this->fmt->startRow();
1921 | $this->fmt->sep($method->isStatic() ? '::' : '->');
1922 | $this->fmt->colDiv();
1923 |
1924 | $bubbles = array();
1925 | if($method->isAbstract())
1926 | $bubbles[] = array('A', 'Abstract');
1927 |
1928 | if($method->isFinal())
1929 | $bubbles[] = array('F', 'Final');
1930 |
1931 | if($method->isProtected())
1932 | $bubbles[] = array('P', 'Protected');
1933 |
1934 | if($method->isPrivate())
1935 | $bubbles[] = array('!', 'Private');
1936 |
1937 | $this->fmt->bubbles($bubbles);
1938 |
1939 | $this->fmt->colDiv(4 - count($bubbles));
1940 |
1941 | // is this method inherited?
1942 | $inherited = $reflector->getShortName() !== $method->getDeclaringClass()->getShortName();
1943 |
1944 | $type = array('method');
1945 |
1946 | if($inherited)
1947 | $type[] = 'inherited';
1948 |
1949 | if($method->isPrivate())
1950 | $type[] = 'private';
1951 |
1952 | $this->fmt->startContain($type);
1953 |
1954 | $name = $method->name;
1955 | if($method->returnsReference())
1956 | $name = "&{$name}";
1957 |
1958 | $this->fromReflector($method, $name, $reflector);
1959 |
1960 | $paramCom = $method->isInternal() ? array() : static::parseComment($method->getDocComment(), 'tags');
1961 | $paramCom = empty($paramCom['param']) ? array() : $paramCom['param'];
1962 | $paramCount = $method->getNumberOfParameters();
1963 |
1964 | $this->fmt->sep('(');
1965 |
1966 | // process arguments
1967 | foreach($method->getParameters() as $idx => $parameter){
1968 | $meta = null;
1969 | $paramName = "\${$parameter->name}";
1970 | $optional = $parameter->isOptional();
1971 | $variadic = static::$env['is56'] && $parameter->isVariadic();
1972 |
1973 | if($parameter->isPassedByReference())
1974 | $paramName = "&{$paramName}";
1975 |
1976 | if($variadic)
1977 | $paramName = "...{$paramName}";
1978 |
1979 | $type = array('param');
1980 |
1981 | if($optional)
1982 | $type[] = 'optional';
1983 |
1984 | $this->fmt->startContain($type);
1985 |
1986 | // attempt to build meta
1987 | foreach($paramCom as $tag){
1988 | list($pcTypes, $pcName, $pcDescription) = $tag;
1989 | if($pcName !== $paramName)
1990 | continue;
1991 |
1992 | $meta = array('title' => $pcDescription);
1993 |
1994 | if($pcTypes)
1995 | $meta['left'] = $pcTypes;
1996 |
1997 | break;
1998 | }
1999 |
2000 | try{
2001 | $paramClass = $parameter->getClass();
2002 | }catch(\Exception $e){
2003 | // @see https://bugs.php.net/bug.php?id=32177&edit=1
2004 | }
2005 |
2006 | if(!empty($paramClass)){
2007 | $this->fmt->startContain('hint');
2008 | $this->fromReflector($paramClass, $paramClass->name);
2009 | $this->fmt->endContain();
2010 | $this->fmt->sep(' ');
2011 |
2012 | }elseif($parameter->isArray()){
2013 | $this->fmt->text('hint', 'array');
2014 | $this->fmt->sep(' ');
2015 |
2016 | }else{
2017 | $hasType = static::$env['is7'] && $parameter->hasType();
2018 | if($hasType){
2019 | $type = $parameter->getType();
2020 | $this->fmt->text('hint', (string)$type);
2021 | $this->fmt->sep(' ');
2022 | }
2023 | }
2024 |
2025 | $this->fmt->text('name', $paramName, $meta);
2026 |
2027 | if($optional){
2028 | try{
2029 | $paramValue = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
2030 | if($paramValue !== null){
2031 | $this->fmt->sep(' = ');
2032 |
2033 | if(static::$env['is546'] && !$parameter->getDeclaringFunction()->isInternal() && $parameter->isDefaultValueConstant()){
2034 | $this->fmt->text('constant', $parameter->getDefaultValueConstantName(), 'Constant');
2035 |
2036 | }else{
2037 | $this->evaluate($paramValue, true);
2038 | }
2039 | }
2040 |
2041 | }catch(\Exception $e){
2042 | // unable to retrieve default value?
2043 | }
2044 |
2045 | }
2046 |
2047 | $this->fmt->endContain();
2048 |
2049 | if($idx < $paramCount - 1)
2050 | $this->fmt->sep(', ');
2051 | }
2052 | $this->fmt->sep(')');
2053 | $this->fmt->endContain();
2054 |
2055 | $hasReturnType = static::$env['is7'] && $method->hasReturnType();
2056 | if($hasReturnType){
2057 | $type = $method->getReturnType();
2058 | $this->fmt->startContain('ret');
2059 | $this->fmt->sep(':');
2060 | $this->fmt->text('hint', (string)$type);
2061 | $this->fmt->endContain();
2062 | }
2063 |
2064 | $this->fmt->endRow();
2065 | }
2066 | }
2067 |
2068 | unset($hashes[$hash]);
2069 | $this->fmt->endGroup();
2070 |
2071 | $this->fmt->cacheLock($hash);
2072 | }
2073 |
2074 |
2075 |
2076 | /**
2077 | * Scans for known classes and functions inside the provided expression,
2078 | * and linkifies them when possible
2079 | *
2080 | * @param string $expression Expression to format
2081 | * @return string Formatted output
2082 | */
2083 | protected function evaluateExp($expression = null){
2084 |
2085 | if($expression === null)
2086 | return;
2087 |
2088 | if(static::strLen($expression) > 120)
2089 | $expression = substr($expression, 0, 120) . '...';
2090 |
2091 | $this->fmt->sep('> ');
2092 |
2093 | if(strpos($expression, '(') === false)
2094 | return $this->fmt->text('expTxt', $expression);
2095 |
2096 | $keywords = array_map('trim', explode('(', $expression, 2));
2097 | $parts = array();
2098 |
2099 | // try to find out if this is a function
2100 | try{
2101 | $reflector = new \ReflectionFunction($keywords[0]);
2102 | $parts[] = array($keywords[0], $reflector, '');
2103 |
2104 | }catch(\Exception $e){
2105 |
2106 | if(stripos($keywords[0], 'new ') === 0){
2107 | $cn = explode(' ' , $keywords[0], 2);
2108 |
2109 | // linkify 'new keyword' (as constructor)
2110 | try{
2111 | $reflector = new \ReflectionMethod($cn[1], '__construct');
2112 | $parts[] = array($cn[0], $reflector, '');
2113 |
2114 | }catch(\Exception $e){
2115 | $reflector = null;
2116 | $parts[] = $cn[0];
2117 | }
2118 |
2119 | // class name...
2120 | try{
2121 | $reflector = new \ReflectionClass($cn[1]);
2122 | $parts[] = array($cn[1], $reflector, ' ');
2123 |
2124 | }catch(\Exception $e){
2125 | $reflector = null;
2126 | $parts[] = $cn[1];
2127 | }
2128 |
2129 | }else{
2130 |
2131 | // we can only linkify methods called statically
2132 | if(strpos($keywords[0], '::') === false)
2133 | return $this->fmt->text('expTxt', $expression);
2134 |
2135 | $cn = explode('::', $keywords[0], 2);
2136 |
2137 | // attempt to linkify class name
2138 | try{
2139 | $reflector = new \ReflectionClass($cn[0]);
2140 | $parts[] = array($cn[0], $reflector, '');
2141 |
2142 | }catch(\Exception $e){
2143 | $reflector = null;
2144 | $parts[] = $cn[0];
2145 | }
2146 |
2147 | // perhaps it's a static class method; try to linkify method
2148 | try{
2149 | $reflector = new \ReflectionMethod($cn[0], $cn[1]);
2150 | $parts[] = array($cn[1], $reflector, '::');
2151 |
2152 | }catch(\Exception $e){
2153 | $reflector = null;
2154 | $parts[] = $cn[1];
2155 | }
2156 | }
2157 | }
2158 |
2159 | $parts[] = "({$keywords[1]}";
2160 |
2161 | foreach($parts as $element){
2162 | if(!is_array($element)){
2163 | $this->fmt->text('expTxt', $element);
2164 | continue;
2165 | }
2166 |
2167 | list($text, $reflector, $prefix) = $element;
2168 |
2169 | if($prefix !== '')
2170 | $this->fmt->text('expTxt', $prefix);
2171 |
2172 | $this->fromReflector($reflector, $text);
2173 | }
2174 |
2175 | }
2176 |
2177 |
2178 |
2179 | /**
2180 | * Calculates real string length
2181 | *
2182 | * @param string $string
2183 | * @return int
2184 | */
2185 | protected static function strLen($string){
2186 | $encoding = function_exists('mb_detect_encoding') ? mb_detect_encoding($string) : false;
2187 | return $encoding ? mb_strlen($string, $encoding) : strlen($string);
2188 | }
2189 |
2190 |
2191 |
2192 | /**
2193 | * Safe str_pad alternative
2194 | *
2195 | * @param string $string
2196 | * @param int $padLen
2197 | * @param string $padStr
2198 | * @param int $padType
2199 | * @return string
2200 | */
2201 | protected static function strPad($input, $padLen, $padStr = ' ', $padType = STR_PAD_RIGHT){
2202 | $diff = strlen($input) - static::strLen($input);
2203 | return str_pad($input, $padLen + $diff, $padStr, $padType);
2204 | }
2205 |
2206 | }
2207 |
2208 |
2209 |
2210 | /**
2211 | * Formatter abstraction
2212 | */
2213 | abstract class RFormatter{
2214 |
2215 | /**
2216 | * Flush output and send contents to the output device
2217 | */
2218 | abstract public function flush();
2219 |
2220 | /**
2221 | * Generate a base entity
2222 | *
2223 | * @param string|array $type
2224 | * @param string|null $text
2225 | * @param string|array|null $meta
2226 | * @param string|null $uri
2227 | */
2228 | abstract public function text($type, $text = null, $meta = null, $uri = null);
2229 |
2230 | /**
2231 | * Generate container start token
2232 | *
2233 | * @param string|array $type
2234 | * @param string|bool $label
2235 | */
2236 | public function startContain($type, $label = false){}
2237 |
2238 | /**
2239 | * Generate container ending token
2240 | */
2241 | public function endContain(){}
2242 |
2243 | /**
2244 | * Generate empty group token
2245 | *
2246 | * @param string $prefix
2247 | */
2248 | public function emptyGroup($prefix = ''){}
2249 |
2250 | /**
2251 | * Generate group start token
2252 | *
2253 | * This method must return boolean TRUE on success, false otherwise (eg. max depth reached).
2254 | * The evaluator will skip this group on FALSE
2255 | *
2256 | * @param string $prefix
2257 | * @return bool
2258 | */
2259 | public function startGroup($prefix = ''){}
2260 |
2261 | /**
2262 | * Generate group ending token
2263 | */
2264 | public function endGroup(){}
2265 |
2266 | /**
2267 | * Generate section title
2268 | *
2269 | * @param string $title
2270 | */
2271 | public function sectionTitle($title){}
2272 |
2273 | /**
2274 | * Generate row start token
2275 | */
2276 | public function startRow(){}
2277 |
2278 | /**
2279 | * Generate row ending token
2280 | */
2281 | public function endRow(){}
2282 |
2283 | /**
2284 | * Column divider (cell delimiter)
2285 | *
2286 | * @param int $padLen
2287 | */
2288 | public function colDiv($padLen = null){}
2289 |
2290 | /**
2291 | * Generate modifier tokens
2292 | *
2293 | * @param array $items
2294 | */
2295 | public function bubbles(array $items){}
2296 |
2297 | /**
2298 | * Input expression start
2299 | */
2300 | public function startExp(){}
2301 |
2302 | /**
2303 | * Input expression end
2304 | */
2305 | public function endExp(){}
2306 |
2307 | /**
2308 | * Root starting token
2309 | */
2310 | public function startRoot(){}
2311 |
2312 | /**
2313 | * Root ending token
2314 | */
2315 | public function endRoot(){}
2316 |
2317 | /**
2318 | * Separator token
2319 | *
2320 | * @param string $label
2321 | */
2322 | public function sep($label = ' '){}
2323 |
2324 | /**
2325 | * Resolve cache request
2326 | *
2327 | * If the ID is not present in the cache, then a new cache entry is created
2328 | * for the given ID, and string offsets are captured until cacheLock is called
2329 | *
2330 | * This method must return TRUE if the ID exists in the cache, and append the cached item
2331 | * to the output, FALSE otherwise.
2332 | *
2333 | * @param string $id
2334 | * @return bool
2335 | */
2336 | public function didCache($id){
2337 | return false;
2338 | }
2339 |
2340 | /**
2341 | * Ends cache capturing for the given ID
2342 | *
2343 | * @param string $id
2344 | */
2345 | public function cacheLock($id){}
2346 |
2347 | }
2348 |
2349 |
2350 |
2351 |
2352 | /**
2353 | * Generates the output in HTML5 format
2354 | *
2355 | */
2356 | class RHtmlFormatter extends RFormatter{
2357 |
2358 | protected
2359 |
2360 | /**
2361 | * Actual output
2362 | *
2363 | * @var string
2364 | */
2365 | $out = '',
2366 |
2367 | /**
2368 | * Tracks current nesting level
2369 | *
2370 | * @var int
2371 | */
2372 | $level = 0,
2373 |
2374 | /**
2375 | * Stores tooltip content for all entries
2376 | *
2377 | * To avoid having duplicate tooltip data in the HTML, we generate them once,
2378 | * and use references (the Q index) to pull data when required;
2379 | * this improves performance significantly
2380 | *
2381 | * @var array
2382 | */
2383 | $tips = array(),
2384 |
2385 | /**
2386 | * Used to cache output to speed up processing.
2387 | *
2388 | * Contains hashes as keys and string offsets as values.
2389 | * Cached objects will not be processed again in the same query
2390 | *
2391 | * @var array
2392 | */
2393 | $cache = array(),
2394 |
2395 | /**
2396 | * Map of used HTML tag and attributes
2397 | *
2398 | * @var string
2399 | */
2400 | $def = array();
2401 |
2402 |
2403 |
2404 | protected static
2405 |
2406 | /**
2407 | * Instance counter
2408 | *
2409 | * @var int
2410 | */
2411 | $counter = 0,
2412 |
2413 | /**
2414 | * Tracks style/jscript inclusion state
2415 | *
2416 | * @var bool
2417 | */
2418 | $didAssets = false;
2419 |
2420 |
2421 | public function __construct(){
2422 |
2423 | if(ref::config('validHtml')){
2424 |
2425 | $this->def = array(
2426 | 'base' => 'span',
2427 | 'tip' => 'div',
2428 | 'cell' => 'data-cell',
2429 | 'table' => 'data-table',
2430 | 'row' => 'data-row',
2431 | 'group' => 'data-group',
2432 | 'gLabel' => 'data-gLabel',
2433 | 'match' => 'data-match',
2434 | 'tipRef' => 'data-tip',
2435 | );
2436 |
2437 |
2438 | }else{
2439 |
2440 | $this->def = array(
2441 | 'base' => 'r',
2442 | 'tip' => 't',
2443 | 'cell' => 'c',
2444 | 'table' => 't',
2445 | 'row' => 'r',
2446 | 'group' => 'g',
2447 | 'gLabel' => 'gl',
2448 | 'match' => 'm',
2449 | 'tipRef' => 'h',
2450 | );
2451 |
2452 | }
2453 |
2454 | }
2455 |
2456 |
2457 |
2458 | public function flush(){
2459 | print $this->out;
2460 | $this->out = '';
2461 | $this->cache = array();
2462 | $this->tips = array();
2463 | }
2464 |
2465 |
2466 | public function didCache($id){
2467 |
2468 | if(!isset($this->cache[$id])){
2469 | $this->cache[$id] = array();
2470 | $this->cache[$id][] = strlen($this->out);
2471 | return false;
2472 | }
2473 |
2474 | if(!isset($this->cache[$id][1])){
2475 | $this->cache[$id][0] = strlen($this->out);
2476 | return false;
2477 | }
2478 |
2479 | $this->out .= substr($this->out, $this->cache[$id][0], $this->cache[$id][1]);
2480 | return true;
2481 | }
2482 |
2483 | public function cacheLock($id){
2484 | $this->cache[$id][] = strlen($this->out) - $this->cache[$id][0];
2485 | }
2486 |
2487 |
2488 | public function sep($label = ' '){
2489 | $this->out .= $label !== ' ' ? '' . static::escape($label) . '' : $label;
2490 | }
2491 |
2492 | public function text($type, $text = null, $meta = null, $uri = null){
2493 |
2494 | if(!is_array($type))
2495 | $type = (array)$type;
2496 |
2497 | $tip = '';
2498 | $text = ($text !== null) ? static::escape($text) : static::escape($type[0]);
2499 |
2500 | if(in_array('special', $type)){
2501 | $text = strtr($text, array(
2502 | "\r" => '\r', // carriage return
2503 | "\t" => '\t', // horizontal tab
2504 | "\n" => '\n', // linefeed (new line)
2505 | "\v" => '\v', // vertical tab
2506 | "\e" => '\e', // escape
2507 | "\f" => '\f', // form feed
2508 | "\0" => '\0',
2509 | ));
2510 | }
2511 |
2512 | // generate tooltip reference (probably the slowest part of the code ;)
2513 | if($meta !== null){
2514 | $tipIdx = array_search($meta, $this->tips, true);
2515 |
2516 | if($tipIdx === false)
2517 | $tipIdx = array_push($this->tips, $meta) - 1;
2518 |
2519 | $tip = " {$this->def['tipRef']}=\"{$tipIdx}\"";
2520 | //$tip = sprintf('%s="%d"', $this->def['tipRef'], $tipIdx);
2521 | }
2522 |
2523 | // wrap text in a link?
2524 | if($uri !== null)
2525 | $text = '' . $text . '';
2526 |
2527 | $typeStr = '';
2528 | foreach($type as $part)
2529 | $typeStr .= " data-{$part}";
2530 |
2531 | $this->out .= "<{$this->def['base']}{$typeStr}{$tip}>{$text}{$this->def['base']}>";
2532 | //$this->out .= sprintf('<%1$s%2$s %3$s>%4$s%1$s>', $this->def['base'], $typeStr, $tip, $text);
2533 | }
2534 |
2535 | public function startContain($type, $label = false){
2536 |
2537 | if(!is_array($type))
2538 | $type = (array)$type;
2539 |
2540 | if($label)
2541 | $this->out .= '
';
2542 |
2543 | $typeStr = '';
2544 | foreach($type as $part)
2545 | $typeStr .= " data-{$part}";
2546 |
2547 | $this->out .= "<{$this->def['base']}{$typeStr}>";
2548 |
2549 | if($label)
2550 | $this->out .= "<{$this->def['base']} {$this->def['match']}>{$type[0]}{$this->def['base']}>";
2551 | }
2552 |
2553 | public function endContain(){
2554 | $this->out .= "{$this->def['base']}>";
2555 | }
2556 |
2557 | public function emptyGroup($prefix = ''){
2558 |
2559 | if($prefix !== '')
2560 | $prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape($prefix) . "{$this->def['base']}>";
2561 |
2562 | $this->out .= "({$prefix})";
2563 | }
2564 |
2565 |
2566 | public function startGroup($prefix = ''){
2567 |
2568 | $maxDepth = ref::config('maxDepth');
2569 |
2570 | if(($maxDepth > 0) && (($this->level + 1) > $maxDepth)){
2571 | $this->emptyGroup('...');
2572 | return false;
2573 | }
2574 |
2575 | $this->level++;
2576 |
2577 | $expLvl = ref::config('expLvl');
2578 | $exp = ($expLvl < 0) || (($expLvl > 0) && ($this->level <= $expLvl)) ? ' data-exp' : '';
2579 |
2580 | if($prefix !== '')
2581 | $prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape($prefix) . "{$this->def['base']}>";
2582 |
2583 | $this->out .= "({$prefix}<{$this->def['base']} data-toggle{$exp}>{$this->def['base']}><{$this->def['base']} {$this->def['group']}><{$this->def['base']} {$this->def['table']}>";
2584 |
2585 | return true;
2586 | }
2587 |
2588 | public function endGroup(){
2589 | $this->out .= "{$this->def['base']}>{$this->def['base']}>)";
2590 | $this->level--;
2591 | }
2592 |
2593 | public function sectionTitle($title){
2594 | $this->out .= "{$this->def['base']}><{$this->def['base']} data-tHead>{$title}{$this->def['base']}><{$this->def['base']} {$this->def['table']}>";
2595 | }
2596 |
2597 | public function startRow(){
2598 | $this->out .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>";
2599 | }
2600 |
2601 | public function endRow(){
2602 | $this->out .= "{$this->def['base']}>{$this->def['base']}>";
2603 | }
2604 |
2605 | public function colDiv($padLen = null){
2606 | $this->out .= "{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>";
2607 | }
2608 |
2609 | public function bubbles(array $items){
2610 |
2611 | if(!$items)
2612 | return;
2613 |
2614 | $this->out .= "<{$this->def['base']} data-mod>";
2615 |
2616 | foreach($items as $info)
2617 | $this->out .= $this->text('mod-' . strtolower($info[1]), $info[0], $info[1]);
2618 |
2619 | $this->out .= "{$this->def['base']}>";
2620 | }
2621 |
2622 | public function startExp(){
2623 | $this->out .= "<{$this->def['base']} data-input>";
2624 | }
2625 |
2626 | public function endExp(){
2627 | if(ref::config('showBacktrace') && ($trace = ref::getBacktrace())){
2628 | $docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
2629 | $path = strpos($trace['file'], $docRoot) !== 0 ? $trace['file'] : ltrim(str_replace($docRoot, '', $trace['file']), '/');
2630 | $this->out .= "<{$this->def['base']} data-backtrace>{$path}:{$trace['line']}{$this->def['base']}>";
2631 | }
2632 |
2633 | $this->out .= "{$this->def['base']}><{$this->def['base']} data-output>";
2634 | }
2635 |
2636 | public function startRoot(){
2637 | $this->out .= '' . static::getAssets() . '
';
2638 | }
2639 |
2640 | public function endRoot(){
2641 | $this->out .= "{$this->def['base']}>";
2642 |
2643 | // process tooltips
2644 | $tipHtml = '';
2645 | foreach($this->tips as $idx => $meta){
2646 |
2647 | $tip = '';
2648 | if(!is_array($meta))
2649 | $meta = array('title' => $meta);
2650 |
2651 | $meta += array(
2652 | 'title' => '',
2653 | 'left' => '',
2654 | 'description' => '',
2655 | 'tags' => array(),
2656 | 'sub' => array(),
2657 | );
2658 |
2659 | $meta = static::escape($meta);
2660 | $cols = array();
2661 |
2662 | if($meta['left'])
2663 | $cols[] = "<{$this->def['base']} {$this->def['cell']} data-varType>{$meta['left']}{$this->def['base']}>";
2664 |
2665 | $title = $meta['title'] ? "<{$this->def['base']} data-title>{$meta['title']}{$this->def['base']}>" : '';
2666 | $desc = $meta['description'] ? "<{$this->def['base']} data-desc>{$meta['description']}{$this->def['base']}>" : '';
2667 | $tags = '';
2668 |
2669 | foreach($meta['tags'] as $tag => $values){
2670 | foreach($values as $value){
2671 | if($tag === 'param'){
2672 | $value[0] = "{$value[0]} {$value[1]}";
2673 | unset($value[1]);
2674 | }
2675 |
2676 | $value = is_array($value) ? implode("{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>", $value) : $value;
2677 | $tags .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>@{$tag}{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>{$value}{$this->def['base']}>{$this->def['base']}>";
2678 | }
2679 | }
2680 |
2681 | if($tags)
2682 | $tags = "<{$this->def['base']} {$this->def['table']}>{$tags}{$this->def['base']}>";
2683 |
2684 | if($title || $desc || $tags)
2685 | $cols[] = "<{$this->def['base']} {$this->def['cell']}>{$title}{$desc}{$tags}{$this->def['base']}>";
2686 |
2687 | if($cols)
2688 | $tip = "<{$this->def['base']} {$this->def['row']}>" . implode('', $cols) . "{$this->def['base']}>";
2689 |
2690 | $sub = '';
2691 | foreach($meta['sub'] as $line)
2692 | $sub .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>" . implode("{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>", $line) . "{$this->def['base']}>{$this->def['base']}>";
2693 |
2694 | if($sub)
2695 | $tip .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']} data-sub><{$this->def['base']} {$this->def['table']}>{$sub}{$this->def['base']}>{$this->def['base']}>{$this->def['base']}>";
2696 |
2697 | if($tip)
2698 | $this->out .= "<{$this->def['tip']}>{$tip}{$this->def['tip']}>";
2699 | }
2700 |
2701 | if(($timeout = ref::getTimeoutPoint()) > 0)
2702 | $this->out .= sprintf("<{$this->def['base']} data-error>Listing incomplete. Timed-out after %4.2fs{$this->def['base']}>", $timeout);
2703 |
2704 | $this->out .= '
';
2705 | }
2706 |
2707 |
2708 |
2709 | /**
2710 | * Get styles and javascript (only generated for the 1st call)
2711 | *
2712 | * @return string
2713 | */
2714 | public static function getAssets(){
2715 |
2716 | // first call? include styles and javascript
2717 | if(static::$didAssets)
2718 | return '';
2719 |
2720 | ob_start();
2721 |
2722 | if(ref::config('stylePath') !== false){
2723 | ?>
2724 |
2727 |
2732 |
2735 | out;
2799 | $this->out = '';
2800 | $this->cache = array();
2801 | }
2802 |
2803 | public function sep($label = ' '){
2804 | $this->out .= $label;
2805 | }
2806 |
2807 | public function text($type, $text = null, $meta = null, $uri = null){
2808 |
2809 | if(!is_array($type))
2810 | $type = (array)$type;
2811 |
2812 | if($text === null)
2813 | $text = $type[0];
2814 |
2815 | if(in_array('special', $type, true)){
2816 | $text = strtr($text, array(
2817 | "\r" => '\r', // carriage return
2818 | "\t" => '\t', // horizontal tab
2819 | "\n" => '\n', // linefeed (new line)
2820 | "\v" => '\v', // vertical tab
2821 | "\e" => '\e', // escape
2822 | "\f" => '\f', // form feed
2823 | "\0" => '\0',
2824 | ));
2825 |
2826 | $this->out .= $text;
2827 | return;
2828 | }
2829 |
2830 | $formatMap = array(
2831 | 'string' => '%3$s "%2$s"',
2832 | 'integer' => 'int(%2$s)',
2833 | 'double' => 'double(%2$s)',
2834 | 'true' => 'bool(%2$s)',
2835 | 'false' => 'bool(%2$s)',
2836 | 'key' => '[%2$s]',
2837 | );
2838 |
2839 | if(!is_string($meta))
2840 | $meta = '';
2841 |
2842 | $this->out .= isset($formatMap[$type[0]]) ? sprintf($formatMap[$type[0]], $type[0], $text, $meta) : $text;
2843 | }
2844 |
2845 | public function startContain($type, $label = false){
2846 |
2847 | if(!is_array($type))
2848 | $type = (array)$type;
2849 |
2850 | if($label)
2851 | $this->out .= "\n" . str_repeat(' ', $this->indent + $this->levelPad[$this->level]) . "┗ {$type[0]} ~ ";
2852 | }
2853 |
2854 | public function emptyGroup($prefix = ''){
2855 | $this->out .= "({$prefix})";
2856 | }
2857 |
2858 | public function startGroup($prefix = ''){
2859 |
2860 | $maxDepth = ref::config('maxDepth');
2861 |
2862 | if(($maxDepth > 0) && (($this->level + 1) > $maxDepth)){
2863 | $this->emptyGroup('...');
2864 | return false;
2865 | }
2866 |
2867 | $this->level++;
2868 | $this->out .= '(';
2869 |
2870 | $this->indent += $this->levelPad[$this->level - 1];
2871 | return true;
2872 | }
2873 |
2874 | public function endGroup(){
2875 | $this->out .= "\n" . str_repeat(' ', $this->indent) . ')';
2876 | $this->indent -= $this->levelPad[$this->level - 1];
2877 | $this->level--;
2878 | }
2879 |
2880 | public function sectionTitle($title){
2881 | $pad = str_repeat(' ', $this->indent + 2);
2882 | $this->out .= sprintf("\n\n%s%s\n%s%s", $pad, $title, $pad, str_repeat('-', strlen($title)));
2883 | }
2884 |
2885 | public function startRow(){
2886 | $this->out .= "\n " . str_repeat(' ', $this->indent);
2887 | $this->lastLineSt = strlen($this->out);
2888 | }
2889 |
2890 | public function endRow(){
2891 | }
2892 |
2893 | public function colDiv($padLen = null){
2894 | $padLen = ($padLen !== null) ? $padLen + 1 : 1;
2895 | $this->out .= str_repeat(' ', $padLen);
2896 |
2897 | $this->lastIdx = strlen($this->out);
2898 | $this->levelPad[$this->level] = $this->lastIdx - $this->lastLineSt + 2;
2899 | }
2900 |
2901 | public function bubbles(array $items){
2902 |
2903 | if(!$items){
2904 | $this->out .= ' ';
2905 | return;
2906 | }
2907 |
2908 | $this->out .= '<';
2909 |
2910 | foreach($items as $item)
2911 | $this->out .= $item[0];
2912 |
2913 | $this->out .= '>';
2914 | }
2915 |
2916 | public function endExp(){
2917 |
2918 | if(ref::config('showBacktrace') && ($trace = ref::getBacktrace()))
2919 | $this->out .= ' - ' . $trace['file'] . ':' . $trace['line'];
2920 |
2921 | $this->out .= "\n" . str_repeat('=', strlen($this->out)) . "\n";
2922 | }
2923 |
2924 | public function startRoot(){
2925 | $this->out .= "\n\n";
2926 |
2927 | }
2928 |
2929 | public function endRoot(){
2930 | $this->out .= "\n";
2931 | if(($timeout = ref::getTimeoutPoint()) > 0)
2932 | $this->out .= sprintf("\n-- Listing incomplete. Timed-out after %4.2fs -- \n", $timeout);
2933 | }
2934 |
2935 | }
2936 |
2937 |
2938 |
2939 | /**
2940 | * Text formatter with color support for CLI -- unfinished
2941 | *
2942 | */
2943 | class RCliTextFormatter extends RTextFormatter{
2944 |
2945 | public function sectionTitle($title){
2946 | $pad = str_repeat(' ', $this->indent + 2);
2947 | $this->out .= sprintf("\n\n%s\x1b[4;97m%s\x1b[0m", $pad, $title);
2948 | }
2949 |
2950 | public function startExp(){
2951 | $this->out .= "\x1b[1;44;96m ";
2952 | }
2953 |
2954 | public function endExp(){
2955 | if(ref::config('showBacktrace') && ($trace = ref::getBacktrace()))
2956 | $this->out .= "\x1b[0m\x1b[44;36m " . $trace['file'] . ':' . $trace['line'];
2957 |
2958 | $this->out .= " \x1b[0m\n";
2959 | }
2960 |
2961 | public function endRoot(){
2962 | $this->out .= "\n";
2963 | if(($timeout = ref::getTimeoutPoint()) > 0)
2964 | $this->out .= sprintf("\n\x1b[3;91m-- Listing incomplete. Timed-out after %4.2fs --\x1b[0m\n", $timeout);
2965 | }
2966 |
2967 | }
2968 |
--------------------------------------------------------------------------------
/tests/example.class.php:
--------------------------------------------------------------------------------
1 | stuff = $list;
188 | $this->pubVarB = $this;
189 | $this->currentDate = \DateTime::createFromFormat('U', time(), new \DateTimeZone('Europe/London'));
190 |
191 | if(extension_loaded('gd'))
192 | $this->image = imagecreate(1, 1);
193 |
194 | if(extension_loaded('curl')){
195 | $curl = curl_init();
196 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
197 | curl_setopt($curl, CURLOPT_URL, 'http://localhost');
198 | curl_setopt($curl, CURLOPT_HEADER, 0);
199 | curl_exec($curl);
200 |
201 | $this->curl = $curl;
202 | }
203 |
204 | $this->jsonString = json_encode($this->currentDate);
205 | }
206 |
207 |
208 |
209 | /**
210 | * The destructor destroys the created image resource and the curl connection
211 | *
212 | * @since 1.0
213 | */
214 | public function __destruct(){
215 |
216 | if(isset($this->curl))
217 | curl_close($this->curl);
218 |
219 | if(isset($this->image))
220 | imagedestroy($this->image);
221 | }
222 |
223 |
224 |
225 | /**
226 | * A private method
227 | *
228 | * @since 1.0
229 | * @return array Normalized list
230 | */
231 | private function normalizeList(){}
232 |
233 |
234 |
235 | /**
236 | * A public getter method
237 | *
238 | * @since 1.0
239 | * @return array Indexed array containing list items
240 | */
241 | public function getList(ClassTest $x = null, $regexToIgnore = "#special\tabc\n#", $const = self::BAR){}
242 |
243 |
244 |
245 | /**
246 | * A protected setter method that returns a reference
247 | *
248 | * Accessible only from classes that extend this class
249 | * or from parent classes
250 | *
251 | * @since 1.0
252 | * @param array $list List as indexed array
253 | */
254 | final protected function &setList(array $list){}
255 |
256 |
257 |
258 | /**
259 | * A static method that creates a new instance
260 | *
261 | * @since 1.0
262 | * @param array $list Indexed array containing list items
263 | * @return static A new instance of this class
264 | */
265 | final public static function factory(array $list){}
266 |
267 |
268 |
269 | /**
270 | * A method that overrides parent::rewind()
271 | *
272 | * @since 1.0
273 | */
274 | public function rewind(){
275 |
276 | }
277 |
278 | }
279 |
280 |
--------------------------------------------------------------------------------
/tests/file.txt:
--------------------------------------------------------------------------------
1 | test
2 | test
3 | test
4 | test
5 | test
6 | test
7 | test
8 | test
9 | test
10 | test
11 | test
12 | test
13 | test
14 | test
15 | test
16 | test
17 | test
18 | test
19 | test
20 | test
21 | test
22 | test
23 | test
24 | test
25 | test
26 | test
27 | test
28 | test
29 | test
30 | test
31 | test
32 | test
33 | test
34 | test
35 | test
36 | test
37 | test
38 | test
39 | test
40 | test
41 | test
42 | test
43 | test
44 | test
45 | test
46 |
--------------------------------------------------------------------------------
/tests/index.php:
--------------------------------------------------------------------------------
1 | '(͡°͜ʖ͡°)',
39 | 'empty string' => '',
40 | 'multiline string' => "first line and some padding \nsecond line",
41 | 'infinity' => INF,
42 | 'regular expression (pcre)' => '/^([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$/',
43 | 'multi' => array(1, 2, 3, array(4, 5, 6), 'FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'),
44 | 'matching class' => 'DateTime',
45 | 'matching file' => 'file.txt',
46 | 'incomplete object' => unserialize('O:3:"Foo":1:{s:3:"bar";i:5;}'),
47 | 'empty object' => new \StdClass(),
48 | 'closed CURL resource' => $closedCurlRes,
49 | 'matching date/file/function/class' => 'today',
50 | 'url' => 'http://google.com',
51 | );
52 |
53 | $array['reference to self'] = &$array;
54 |
55 | $obj = new \Tests\ClassTest(array('foo', 'bar'), $array);
56 |
57 | if($htmlMode){
58 | r(true, false, 'I can haz a 강남스타일 string', '1492-10-14 04:20:00 America/Nassau', null, 4.20);
59 | r(array(), $array, serialize(array('A', 'serialized', 'string')));
60 | r(fopen('php://stdin', 'r'), function($x, $d){});
61 | r(new \DateTimeZone('Pacific/Honolulu'));
62 | r($obj, new ref());
63 |
64 | }else{
65 |
66 | rt(true, false, 'I can haz a 강남스타일 string', '1492-10-14 04:20:00 America/Nassau', null, 17, 4.20);
67 | rt(array(), $array, serialize(array('A', 'serialized', 'string')));
68 | rt(fopen('php://stdin', 'r'), function($x, $d){});
69 | rt(new \DateTimeZone('Pacific/Honolulu'));
70 | rt($obj, new ref());
71 |
72 | }
73 |
74 | exit(0);
75 | }
76 |
77 | ?>
78 |
79 |
80 |
81 |
82 | REF by digitalnature
83 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/tests/today:
--------------------------------------------------------------------------------
1 | test
2 | test
3 | test
4 | test
5 | test
6 | test
7 | test
8 | test
9 | test
10 | test
11 | test
12 | test
13 | test
14 | test
15 | test
16 | test
17 | test
18 | test
19 |
--------------------------------------------------------------------------------