Completed
Branch dev (2736f5)
by
unknown
01:41
created

AdminPageFramework_Debug_Base::_getLegible()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 32
nop 3
dl 0
loc 22
rs 8.9457
c 0
b 0
f 0
1
<?php
2
/**
3
 * Admin Page Framework
4
 *
5
 * http://admin-page-framework.michaeluno.jp/
6
 * Copyright (c) 2013-2020, Michael Uno; Licensed MIT
7
 *
8
 */
9
10
/**
11
 * A base class of the debug class.
12
 *
13
 * Mainly provides methods for debug outputs.
14
 *
15
 * @since           3.8.9
16
 * @extends         AdminPageFramework_FrameworkUtility
17
 * @package         AdminPageFramework/Common/Utility
18
 */
19
class AdminPageFramework_Debug_Base extends AdminPageFramework_FrameworkUtility {
20
21
    /**
22
     * @var int
23
     * @since   3.8.19
24
     */
25
    static public $iLegibleArrayDepthLimit = 50;
26
27
    /**
28
     * Character length limit to truncate.
29
     */
30
    static public $iLegibleStringCharacterLimit = 99999;
31
32
    /**
33
     * Returns a legible value representation with value details.
34
     * @since       3.8.9
35
     * @return      string
36
     */
37
    static protected function _getLegibleDetails( $mValue, $iStringLengthLimit=0, $iArrayDepthLimit=0 ) {
38
        if ( is_array( $mValue ) ) {
39
            return '(array, length: ' . count( $mValue ).') '
40
                . print_r( self::___getLegibleDetailedArray( $mValue, $iStringLengthLimit, $iArrayDepthLimit ) , true );
41
        }
42
        return print_r( self::___getLegibleDetailedValue( $mValue, $iStringLengthLimit ), true );
43
    }
44
45
    /**
46
     * Returns a string representation of the given value with no variable details.
47
     *
48
     * @since       3.8.9
49
     * @since       3.8.22  Added the `$sStringLengthLimit` and `$iArrayDepthLimit` parameters.
50
     * @return      string
51
     */
52
    static protected function _getLegible( $mValue, $iStringLengthLimit=0, $iArrayDepthLimit=0 ) {
53
54
        $iArrayDepthLimit = $iArrayDepthLimit ? $iArrayDepthLimit : self::$iLegibleArrayDepthLimit;
55
        $mValue           = is_object( $mValue )
56
            ? ( method_exists( $mValue, '__toString' )
57
                ? ( string ) $mValue          // cast string
58
                : ( array ) $mValue           // cast array
59
            )
60
            : $mValue;
61
        $mValue = is_array( $mValue )
62
            ? self::___getArrayMappedRecursive(
63
                self::_getSlicedByDepth( $mValue, $iArrayDepthLimit ),
64
                array( __CLASS__, '___getObjectName' ),
65
                array()
66
            )
67
            : $mValue;
68
        $mValue = is_string( $mValue )
69
            ? self::___getLegibleString( $mValue,  $iStringLengthLimit, false )
70
            : $mValue;
71
        return self::_getArrayRepresentationSanitized( print_r( $mValue, true ) );
72
73
    }
74
75
        /**
76
         * Returns a object name if it is an object. Otherwise, the value itself.
77
         * This is used to convert objects into a string in array-walk functions
78
         * as objects tent to get large when they are converted to a string representation.
79
         * @since       3.8.9
80
         */
81
        static private function ___getObjectName( $mItem ) {
82
            if ( is_object( $mItem ) ) {
83
                return '(object) ' . get_class( $mItem );
84
            }
85
            return $mItem;
86
        }
87
88
        /**
89
         * @since       3.8.9
90
         * @param       callable     $asoCallable
91
         * @return      string
92
         */
93
        static private function ___getLegibleDetailedCallable( $asoCallable ) {
94
            return '(callable) ' . self::___getCallableName( $asoCallable );
95
        }
96
            /**
97
             * @since       3.8.9
98
             * @param       callable     $asoCallable
99
             * @return      string
100
             */
101
            static public function ___getCallableName( $asoCallable ) {
102
103
                if ( is_string( $asoCallable ) ) {
104
                    return $asoCallable;
105
                }
106
                if ( is_object( $asoCallable ) ) {
107
                    return get_class( $asoCallable );
108
                }
109
                $_sSubject = is_object( $asoCallable[ 0 ] )
110
                    ? get_class( $asoCallable[ 0 ] )
111
                    : ( string ) $asoCallable[ 0 ];
112
                return $_sSubject . '::' . ( string ) $asoCallable[ 1 ];
113
114
            }
115
116
        /**
117
         * @since       3.8.9
118
         * @param       object      $oObject
119
         * @return      string
120
         */
121
        static private function ___getLegibleDetailedObject( $oObject ) {
122
123
            if ( method_exists( $oObject, '__toString' ) ) {
124
                return ( string ) $oObject;
125
            }
126
            return '(object) ' . get_class( $oObject ) . ' ' . count( get_object_vars( $oObject ) ) . ' properties.';
127
128
        }
129
        /**
130
         * Returns an array representation with value types in each element.
131
         * The element deeper than 10 dimensions will be dropped.
132
         * @since       3.8.9
133
         * @since       3.8.22  Added the `$iDepthLimit` parameter
134
         * @since       3.8.22  Changed the scope to private from public.
135
         * @since       3.8.22  Renamed from `_getLegibleArray()`.
136
         * @return      array
137
         */
138
        static private function ___getLegibleDetailedArray( array $aArray, $iStringLengthLimit=0, $iDepthLimit=0 ) {
139
            $_iDepthLimit = $iDepthLimit ? $iDepthLimit : self::$iLegibleArrayDepthLimit;
140
            return self::___getArrayMappedRecursive(
141
                self::_getSlicedByDepth( $aArray, $_iDepthLimit ),
142
                array( __CLASS__, '___getLegibleDetailedValue' ),
143
                array( $iStringLengthLimit )
144
            );
145
        }
146
            /**
147
             * @since       3.8.9
148
             * @since       3.8.22  Renamed from `_getLegibleValue()`.
149
             * @return      string
150
             */
151
            static private function ___getLegibleDetailedValue( $mItem, $iStringLengthLimit ) {
152
                if ( is_callable( $mItem ) ) {
153
                    return self::___getLegibleDetailedCallable( $mItem );
154
                }
155
                return is_scalar( $mItem )
156
                    ? self::___getLegibleDetailedScalar( $mItem, $iStringLengthLimit )
157
                    : self::___getLegibleDetailedNonScalar( $mItem );
158
            }
159
                /**
160
                 * @since       3.8.9
161
                 * @since       3.8.22  Renamed from `_getLegibleNonScalar()`.
162
                 * @return      string
163
                 */
164
                static private function ___getLegibleDetailedNonScalar( $mNonScalar ) {
165
166
                    $_sType = gettype( $mNonScalar );
167
                    if ( is_null( $mNonScalar ) ) {
168
                        return '(null)';
169
                    }
170
                    if ( is_object( $mNonScalar ) ) {
171
                        return self::___getLegibleDetailedObject( $mNonScalar );
172
                    }
173
                    if ( is_array( $mNonScalar ) ) {
174
                        return '(' . $_sType . ') ' . count( $mNonScalar ) . ' elements';
175
                    }
176
                    return '(' . $_sType . ') ' . ( string ) $mNonScalar;
177
178
                }
179
                /**
180
                 * @return      string
181
                 * @param       integer|float|boolean $sScalar
182
                 * @param       integer $iStringLengthLimit
183
                 * @since       3.8.9
184
                 * @since       3.8.22      Renamed from `_getLegibleScalar()`.
185
                 */
186
                static private function ___getLegibleDetailedScalar( $sScalar, $iStringLengthLimit ) {
187
                    if ( is_bool( $sScalar ) ) {
188
                        return '(boolean) ' . ( $sScalar ? 'true' : 'false' );
189
                    }
190
                    return is_string( $sScalar )
191
                        ? self::___getLegibleString( $sScalar, $iStringLengthLimit, true )
192
                        : '(' . gettype( $sScalar ) . ', length: ' . self::___getValueLength( $sScalar ) .  ') ' . $sScalar;
193
                }
194
                    /**
195
                     * Returns a length of a value.
196
                     * @since       3.5.3
197
                     * @internal
198
                     * @return      integer|null        For string or integer, the string length. For array, the element lengths. For other types, null.
199
                     */
200
                    static private function ___getValueLength( $mValue ) {
201
                        $_sVariableType = gettype( $mValue );
202
                        if ( in_array( $_sVariableType, array( 'string', 'integer' ) ) ) {
203
                            return strlen( $mValue );
204
                        }
205
                        if ( 'array' === $_sVariableType ) {
206
                            return count( $mValue );
207
                        }
208
                        return null;
209
                    }
210
                    /**
211
                     * @param       string      $sString
212
                     * @param       integer     $iLengthLimit
213
                     * @param       boolean     $bShowDetails
214
                     * @return      string
215
                     */
216
                    static private function ___getLegibleString( $sString, $iLengthLimit, $bShowDetails=true ) {
217
218
                        static $_iMBSupport;
219
                        $_iMBSupport    = isset( $_iMBSupport ) ? $_iMBSupport : ( integer ) function_exists( 'mb_strlen' );
220
                        $_aStrLenMethod = array( 'strlen', 'mb_strlen' );
221
                        $_aSubstrMethod = array( 'substr', 'mb_substr' );
222
                        $iCharLimit     = $iLengthLimit ? $iLengthLimit : self::$iLegibleStringCharacterLimit;
223
                        $_iCharLength   = call_user_func_array( $_aStrLenMethod[ $_iMBSupport ], array( $sString ) );
224
225
                        if ( $bShowDetails ) {
226
                            return $_iCharLength <= $iCharLimit
227
                                ? '(string, length: ' . $_iCharLength . ') ' . $sString
228
                                : '(string, length: ' . $_iCharLength . ') ' . call_user_func_array( $_aSubstrMethod[ $_iMBSupport ], array( $sString, 0, $iCharLimit ) )
229
                                    . '...';
230
                        }
231
                        return $_iCharLength <= $iCharLimit
232
                            ? $sString
233
                            : call_user_func_array( $_aSubstrMethod[ $_iMBSupport ], array( $sString, 0, $iCharLimit ) );
234
235
                    }
236
237
238
239
240
    /**
241
     * @return      string
242
     * @since       3.8.9
243
     */
244
    static protected function _getArrayRepresentationSanitized( $sString ) {
245
246
        // Fix extra line breaks after `Array()`
247
        $sString = preg_replace(
248
            '/\)(\r\n?|\n)(?=(\r\n?|\n)\s+[\[\)])/', // needle
249
            ')', // replacement
250
            $sString // subject
251
        );
252
253
        // Fix empty array output
254
        $sString = preg_replace(
255
            '/Array(\r\n?|\n)\s+\((\r\n?|\n)\s+\)/', // needle
256
            'Array()', // replacement
257
            $sString // subject
258
        );
259
        return $sString;
260
261
    }
262
263
    /**
264
     * Slices an array by the given depth.
265
     * @return array
266
     * @since  3.8.22
267
     */
268
    static public function getSlicedByDepth( array $aSubject, $iDepth=0, $sMore='(array truncated) ...' ) {
269
       return self::_getSlicedByDepth( $aSubject, $iDepth, $sMore );
270
    }
271
272
    /**
273
     * Slices an array by the given depth.
274
     *
275
     * @param array $aSubject
276
     * @param int $iDepth
277
     * @param string $sMore
278
     *
279
     * @return      array
280
     * @since       3.4.4
281
     * @since       3.8.9       Changed it not to convert an object into an array.
282
     * @since       3.8.9       Changed the scope to private.
283
     * @since       3.8.9       Renamed from `getSliceByDepth()`.
284
     * @since       3.8.22      Show a message when truncated by depth.
285
     * @since       3.8.22      Added the `$sMore` parameter.
286
     * @internal
287
     */
288
    static private function _getSlicedByDepth( array $aSubject, $iDepth=0, $sMore='(array truncated) ...' ) {
289
290
        foreach ( $aSubject as $_sKey => $_vValue ) {
291
292
            if ( is_array( $_vValue ) ) {
293
294
                $_iDepth = $iDepth;
295
                if ( $iDepth > 0 ) {
296
                    $aSubject[ $_sKey ] = self::_getSlicedByDepth( $_vValue, --$iDepth );
297
                    $iDepth = $_iDepth;
298
                    continue;
299
                }
300
301
                if ( strlen( $sMore ) ) {
302
                    $aSubject[ $_sKey ] = $sMore;
303
                    continue;
304
                }
305
                unset( $aSubject[ $_sKey ] );
306
307
            }
308
309
        }
310
        return $aSubject;
311
312
    }
313
314
    /**
315
     * Performs `array_map()` recursively.
316
     * @remark      Accepts arguments.
317
     * @return      array
318
     * @since       3.8.9
319
     */
320
    static private function ___getArrayMappedRecursive( array $aArray, $oCallable, array $aArguments=array() ) {
321
322
        self::$___oCurrentCallableForArrayMapRecursive = $oCallable;
323
        self::$___aArgumentsForArrayMapRecursive       = $aArguments;
324
        $_aArray = array_map( array( __CLASS__, '___getArrayMappedNested' ), $aArray );
325
        self::$___oCurrentCallableForArrayMapRecursive = null;
326
        self::$___aArgumentsForArrayMapRecursive       = array();
327
        return $_aArray;
328
329
    }
330
        static private $___oCurrentCallableForArrayMapRecursive;
331
        static private $___aArgumentsForArrayMapRecursive;
332
        /**
333
         * @internal
334
         * @return      mixed       A modified value.
335
         * @since       3.8.9
336
         */
337
        static private function ___getArrayMappedNested( $mItem ) {
338
            return is_array( $mItem )
339
                ? array_map( array( __CLASS__, '___getArrayMappedNested' ), $mItem )
340
                : call_user_func_array( 
341
                    self::$___oCurrentCallableForArrayMapRecursive, 
342
                    array_merge( array( $mItem ), self::$___aArgumentsForArrayMapRecursive )  
343
                );
344
        }
345
346
    /**
347
     * @param Exception $oException
348
     * @param integer $iSkip    The number of skipping records. This is used when the caller does not want to include the self function/method.
349
     *
350
     * @return  string
351
     * @since   3.8.22
352
     */
353
    static public function getStackTrace( Exception $oException, $iSkip=0 ) {
354
355
        $_aTraces    = array();
356
        $_aFrames    = $oException->getTrace();
357
        $_aFrames    = array_slice( $_aFrames, $iSkip );
358
        foreach ( array_reverse( $_aFrames ) as $_iIndex => $_aFrame ) {
359
360
            $_aFrame     = $_aFrame + array(
361
                'file'  => null, 'line' => null, 'function' => null,
362
                'class' => null, 'args' => array(),
363
            );
364
            $_sArguments = self::___getArgumentsOfEachStackTrace( $_aFrame[ 'args' ] );
365
            $_aTraces[]  = sprintf(
366
                "#%s %s(%s): %s(%s)",
367
                $_iIndex + 1,
368
                $_aFrame[ 'file' ],
369
                $_aFrame[ 'line' ],
370
                isset( $_aFrame[ 'class' ] ) ? $_aFrame[ 'class' ] . '->' . $_aFrame[ 'function' ] : $_aFrame[ 'function' ],
371
                $_sArguments
372
            );
373
374
        }
375
        return implode( PHP_EOL, $_aTraces ) . PHP_EOL;
376
377
    }
378
        /**
379
         * @param array $aTraceArguments
380
         * @return string
381
         * @since 3.8.22
382
         * @internal
383
         */
384
        static private function ___getArgumentsOfEachStackTrace( array $aTraceArguments ) {
385
386
            $_aArguments = array();
387
            foreach ( $aTraceArguments as $_mArgument ) {
388
                $_sType        = gettype( $_mArgument );
389
                $_sType        = str_replace(
390
                    array( 'resource (closed)', 'unknown type', 'integer', 'double', ),
391
                    array( 'resource', 'unknown', 'scalar', 'scalar', ),
392
                    $_sType
393
                );
394
                $_sMethodName  = "___getStackTraceArgument_{$_sType}";
395
                $_aArguments[] = method_exists( __CLASS__, $_sMethodName )
396
                    ? self::{$_sMethodName}( $_mArgument )
397
                    : $_sType;
398
            }
399
            return join(", ",  $_aArguments );
400
        }
401
            /**
402
             * @since 3.8.22
403
             * @param mixed $mArgument
404
             * @internal
405
             * @return string
406
             */
407
            static private function ___getStackTraceArgument_string( $mArgument ) {
408
                return "'" . $mArgument . "'";
409
            }
410
            static private function ___getStackTraceArgument_scalar( $mArgument ) {
411
                return $mArgument;
412
            }
413
            static private function ___getStackTraceArgument_boolean( $mArgument ) {
414
                return ( $mArgument ) ? "true" : "false";
415
            }
416
            static private function ___getStackTraceArgument_NULL( $mArgument ) {
0 ignored issues
show
Unused Code introduced by
The parameter $mArgument is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
417
                return 'NULL';
418
            }
419
            static private function ___getStackTraceArgument_object( $mArgument ) {
420
                return 'Object(' . get_class( $mArgument ) . ')';
421
            }
422
            static private function ___getStackTraceArgument_resource( $mArgument ) {
423
                return get_resource_type( $mArgument );
424
            }
425
            static private function ___getStackTraceArgument_unknown( $mArgument ) {
426
                return gettype( $mArgument );
427
            }
428
            static private function ___getStackTraceArgument_array( $mArgument ) {
429
                $_sOutput = '';
430
                $_iMax    = 10;
431
                $_iTotal  = count( $mArgument );
432
                $_iIndex  = 0;
433
                foreach( $mArgument as $_sKey => $_mValue ) {
434
                    $_iIndex++;
435
                    $_mValue   = is_scalar( $_mValue )
436
                        ? $_mValue
437
                        : ucfirst( gettype( $_mValue ) ) . (
438
                            is_object( $_mValue )
439
                                ? ' (' . get_class( $_mValue ) . ')'
440
                                : ''
441
                        );
442
                    $_sOutput .= $_sKey . ': ' . $_mValue . ',';
443
                    if ( $_iIndex > $_iMax && $_iTotal > $_iMax ) {
444
                        $_sOutput  = rtrim( $_sOutput, ','  ) . '...';
445
                        break;
446
                    }
447
                }
448
                $_sOutput = rtrim( $_sOutput, ',' );
449
                return "Array({$_sOutput})";
450
            }
451
452
}
453