Passed
Branch dev (a37336)
by Michael
14:56
created

___getLegibleDetailedObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
/**
3
 * Admin Page Framework
4
 *
5
 * http://admin-page-framework.michaeluno.jp/
6
 * Copyright (c) 2013-2021, 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
     * @param   mixed   $mValue
35
     * @param   integer $iStringLengthLimit
36
     * @param   integer $iArrayDepthLimit
37
     * @return  string
38
     * @since   3.8.9
39
     */
40
    static protected function _getLegibleDetails( $mValue, $iStringLengthLimit=0, $iArrayDepthLimit=0 ) {
41
        if ( is_array( $mValue ) ) {
42
            return '(array, length: ' . count( $mValue ).') '
43
                . print_r( self::___getLegibleDetailedArray( $mValue, $iStringLengthLimit, $iArrayDepthLimit ) , true );
0 ignored issues
show
Bug introduced by
Are you sure print_r(self::___getLegi...ArrayDepthLimit), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

43
                . /** @scrutinizer ignore-type */ print_r( self::___getLegibleDetailedArray( $mValue, $iStringLengthLimit, $iArrayDepthLimit ) , true );
Loading history...
44
        }
45
        return print_r( self::getLegibleDetailedValue( $mValue, $iStringLengthLimit ), true );
0 ignored issues
show
Bug Best Practice introduced by
The expression return print_r(self::get...ringLengthLimit), true) also could return the type true which is incompatible with the documented return type string.
Loading history...
46
    }
47
48
    /**
49
     * Returns a object name if it is an object. Otherwise, the value itself.
50
     * This is used to convert objects into a string in array-walk functions
51
     * as objects tent to get large when they are converted to a string representation.
52
     * @since       3.8.9
53
     * @since       3.8.32  Changed the visibility scope to public from private to be passed as a callback for outside the current class scope.
54
     * And renamed from `___getObjectName()`.
55
     * @param mixed $mItem
56
     * @return mixed
57
     */
58
    static public function getObjectName( $mItem ) {
59
        if ( is_object( $mItem ) ) {
60
            return '(object) ' . get_class( $mItem );
61
        }
62
        return $mItem;
63
    }
64
65
    /**
66
     * Returns a string representation of the given value with no variable details.
67
     *
68
     * @param  $mValue
69
     * @param  integer $iStringLengthLimit
70
     * @param  integer $iArrayDepthLimit
71
     * @return string
72
     * @since  3.8.9
73
     * @since  3.8.22  Added the `$sStringLengthLimit` and `$iArrayDepthLimit` parameters.
74
     */
75
    static protected function _getLegible( $mValue, $iStringLengthLimit=0, $iArrayDepthLimit=0 ) {
76
77
        $iArrayDepthLimit = $iArrayDepthLimit ? $iArrayDepthLimit : self::$iLegibleArrayDepthLimit;
78
        $mValue           = is_object( $mValue )
79
            ? ( method_exists( $mValue, '__toString' )
80
                ? ( string ) $mValue          // cast string
81
                : ( array ) $mValue           // cast array
82
            )
83
            : $mValue;
84
        $mValue = is_array( $mValue )
85
            ? self::getArrayMappedRecursive(
86
                array( __CLASS__, 'getObjectName' ),
87
                self::_getSlicedByDepth( $mValue, $iArrayDepthLimit ),
88
                array()
89
            )
90
            : $mValue;
91
        $mValue = is_string( $mValue )
92
            ? self::___getLegibleString( $mValue,  $iStringLengthLimit, false )
93
            : $mValue;
94
        return self::_getArrayRepresentationSanitized( print_r( $mValue, true ) );
0 ignored issues
show
Bug introduced by
It seems like print_r($mValue, true) can also be of type true; however, parameter $sString of AdminPageFramework_Debug...presentationSanitized() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

94
        return self::_getArrayRepresentationSanitized( /** @scrutinizer ignore-type */ print_r( $mValue, true ) );
Loading history...
95
96
    }
97
98
        /**
99
         * @since       3.8.9
100
         * @param       callable     $asoCallable
101
         * @return      string
102
         */
103
        static private function ___getLegibleDetailedCallable( $asoCallable ) {
104
            return '(callable) ' . self::___getCallableName( $asoCallable );
105
        }
106
            /**
107
             * @since       3.8.9
108
             * @param       callable     $asoCallable
109
             * @return      string
110
             */
111
            static public function ___getCallableName( $asoCallable ) {
112
113
                if ( is_string( $asoCallable ) ) {
114
                    return $asoCallable;
115
                }
116
                if ( is_object( $asoCallable ) ) {
117
                    return get_class( $asoCallable );
118
                }
119
                $_sSubject = is_object( $asoCallable[ 0 ] )
120
                    ? get_class( $asoCallable[ 0 ] )
121
                    : ( string ) $asoCallable[ 0 ];
122
                return $_sSubject . '::' . ( string ) $asoCallable[ 1 ];
123
124
            }
125
126
        /**
127
         * @since       3.8.9
128
         * @param       object      $oObject
129
         * @return      string
130
         */
131
        static private function ___getLegibleDetailedObject( $oObject ) {
132
133
            if ( method_exists( $oObject, '__toString' ) ) {
134
                return ( string ) $oObject;
135
            }
136
            return '(object) ' . get_class( $oObject ) . ' ' . count( get_object_vars( $oObject ) ) . ' properties.';
137
138
        }
139
140
        /**
141
         * Returns an array representation with value types in each element.
142
         * The element deeper than 10 dimensions will be dropped.
143
         * @param  array   $aArray
144
         * @param  integer $iStringLengthLimit
145
         * @param  integer $iDepthLimit
146
         * @return array
147
         * @since  3.8.9
148
         * @since  3.8.22  Added the `$iDepthLimit` parameter
149
         * @since  3.8.22  Changed the scope to private from public.
150
         * @since  3.8.22  Renamed from `_getLegibleArray()`.
151
         */
152
        static private function ___getLegibleDetailedArray( array $aArray, $iStringLengthLimit=0, $iDepthLimit=0 ) {
153
            $_iDepthLimit = $iDepthLimit ? $iDepthLimit : self::$iLegibleArrayDepthLimit;
154
            return self::getArrayMappedRecursive(
155
                array( __CLASS__, 'getLegibleDetailedValue' ),
156
                self::_getSlicedByDepth( $aArray, $_iDepthLimit ),
157
                array( $iStringLengthLimit )
158
            );
159
        }
160
161
    /**
162
     * @param mixed $mItem
163
     * @param integer $iStringLengthLimit
164
     * @return      string
165
     * @since       3.8.22  Renamed from `_getLegibleValue()`.
166
     * @since       3.8.9
167
     * @since       3.8.32  Changed the visibility scope to public from private to be passed as a callback for outside the current class scope.
168
     * And renamed from `___getLegibleDetailedValue()`.
169
     */
170
    static public function getLegibleDetailedValue( $mItem, $iStringLengthLimit ) {
171
        if ( is_callable( $mItem ) ) {
172
            return self::___getLegibleDetailedCallable( $mItem );
173
        }
174
        return is_scalar( $mItem )
175
            ? self::___getLegibleDetailedScalar( $mItem, $iStringLengthLimit )
176
            : self::___getLegibleDetailedNonScalar( $mItem );
177
    }
178
        /**
179
         * @since       3.8.9
180
         * @since       3.8.22  Renamed from `_getLegibleNonScalar()`.
181
         * @return      string
182
         * @param       mixed   $mNonScalar
183
         */
184
        static private function ___getLegibleDetailedNonScalar( $mNonScalar ) {
185
186
            $_sType = gettype( $mNonScalar );
187
            if ( is_null( $mNonScalar ) ) {
188
                return '(null)';
189
            }
190
            if ( is_object( $mNonScalar ) ) {
191
                return self::___getLegibleDetailedObject( $mNonScalar );
192
            }
193
            if ( is_array( $mNonScalar ) ) {
194
                return '(' . $_sType . ') ' . count( $mNonScalar ) . ' elements';
195
            }
196
            return '(' . $_sType . ') ' . ( string ) $mNonScalar;
197
198
        }
199
        /**
200
         * @return      string
201
         * @param       integer|float|boolean $sScalar
202
         * @param       integer $iStringLengthLimit
203
         * @since       3.8.9
204
         * @since       3.8.22      Renamed from `_getLegibleScalar()`.
205
         */
206
        static private function ___getLegibleDetailedScalar( $sScalar, $iStringLengthLimit ) {
207
            if ( is_bool( $sScalar ) ) {
208
                return '(boolean) ' . ( $sScalar ? 'true' : 'false' );
209
            }
210
            return is_string( $sScalar )
0 ignored issues
show
introduced by
The condition is_string($sScalar) is always false.
Loading history...
211
                ? self::___getLegibleString( $sScalar, $iStringLengthLimit, true )
212
                : '(' . gettype( $sScalar ) . ', length: ' . self::___getValueLength( $sScalar ) .  ') ' . $sScalar;
213
        }
214
            /**
215
             * Returns a length of a value.
216
             * @since       3.5.3
217
             * @internal
218
             * @return      integer|null For string or integer, the string length. For array, the element lengths. For other types, null.
219
             * @param       mixed        $mValue
220
             */
221
            static private function ___getValueLength( $mValue ) {
222
                $_sVariableType = gettype( $mValue );
223
                if ( in_array( $_sVariableType, array( 'string', 'integer' ) ) ) {
224
                    return strlen( $mValue );
225
                }
226
                if ( 'array' === $_sVariableType ) {
227
                    return count( $mValue );
228
                }
229
                return null;
230
            }
231
            /**
232
             * @param       string      $sString
233
             * @param       integer     $iLengthLimit
234
             * @param       boolean     $bShowDetails
235
             * @return      string
236
             */
237
            static private function ___getLegibleString( $sString, $iLengthLimit, $bShowDetails=true ) {
238
239
                static $_iMBSupport;
240
                $_iMBSupport    = isset( $_iMBSupport ) ? $_iMBSupport : ( integer ) function_exists( 'mb_strlen' );
241
                $_aStrLenMethod = array( 'strlen', 'mb_strlen' );
242
                $_aSubstrMethod = array( 'substr', 'mb_substr' );
243
                $iCharLimit     = $iLengthLimit ? $iLengthLimit : self::$iLegibleStringCharacterLimit;
244
                $_iCharLength   = call_user_func_array( $_aStrLenMethod[ $_iMBSupport ], array( $sString ) );
245
246
                if ( $bShowDetails ) {
247
                    return $_iCharLength <= $iCharLimit
248
                        ? '(string, length: ' . $_iCharLength . ') ' . $sString
249
                        : '(string, length: ' . $_iCharLength . ') ' . call_user_func_array( $_aSubstrMethod[ $_iMBSupport ], array( $sString, 0, $iCharLimit ) )
250
                            . '...';
251
                }
252
                return $_iCharLength <= $iCharLimit
253
                    ? $sString
254
                    : call_user_func_array( $_aSubstrMethod[ $_iMBSupport ], array( $sString, 0, $iCharLimit ) );
255
256
            }
257
258
    /**
259
     * @return      string
260
     * @since       3.8.9
261
     * @param       string  $sString
262
     */
263
    static protected function _getArrayRepresentationSanitized( $sString ) {
264
265
        // Fix extra line breaks after `Array()`
266
        $sString = preg_replace(
267
            '/\)(\r\n?|\n)(?=(\r\n?|\n)\s+[\[)])/', // needle
268
            ')', // replacement
269
            $sString // subject
270
        );
271
272
        // Fix empty array output
273
        $sString = preg_replace(
274
            '/Array(\r\n?|\n)\s+\((\r\n?|\n)\s+\)/', // needle
275
            'Array()', // replacement
276
            $sString // subject
277
        );
278
        return $sString;
279
280
    }
281
282
    /**
283
     * Slices an array by the given depth.
284
     * @param array $aSubject
285
     * @param int $iDepth
286
     * @param string $sMore
287
     * @return array
288
     * @since  3.8.22
289
     */
290
    static public function getSlicedByDepth( array $aSubject, $iDepth=0, $sMore='(array truncated) ...' ) {
291
       return self::_getSlicedByDepth( $aSubject, $iDepth, $sMore );
292
    }
293
294
    /**
295
     * Slices an array by the given depth.
296
     *
297
     * @param array $aSubject
298
     * @param int $iDepth
299
     * @param string $sMore
300
     *
301
     * @return      array
302
     * @since       3.4.4
303
     * @since       3.8.9       Changed it not to convert an object into an array.
304
     * @since       3.8.9       Changed the scope to private.
305
     * @since       3.8.9       Renamed from `getSliceByDepth()`.
306
     * @since       3.8.22      Show a message when truncated by depth.
307
     * @since       3.8.22      Added the `$sMore` parameter.
308
     * @internal
309
     */
310
    static private function _getSlicedByDepth( array $aSubject, $iDepth=0, $sMore='(array truncated) ...' ) {
311
312
        foreach ( $aSubject as $_sKey => $_vValue ) {
313
314
            if ( is_array( $_vValue ) ) {
315
316
                $_iDepth = $iDepth;
317
                if ( $iDepth > 0 ) {
318
                    $aSubject[ $_sKey ] = self::_getSlicedByDepth( $_vValue, --$iDepth );
319
                    $iDepth = $_iDepth;
320
                    continue;
321
                }
322
323
                if ( strlen( $sMore ) ) {
324
                    $aSubject[ $_sKey ] = $sMore;
325
                    continue;
326
                }
327
                unset( $aSubject[ $_sKey ] );
328
329
            }
330
331
        }
332
        return $aSubject;
333
334
    }
335
336
    /**
337
     * @param   integer $iSkip    The number of skipping records. This is used when the caller does not want to include the self function/method.
338
     * @param   null    $_deprecated
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_deprecated is correct as it would always require null to be passed?
Loading history...
339
     * @return  string
340
     * @since   3.8.22
341
     * @since   3.8.23 Deprecated the `$oException` parameter.
342
     */
343
    static public function getStackTrace( $iSkip=0, $_deprecated=null ) {
344
345
        $_iSkip      = 1;   // need to skip this method trace itself
346
        $_oException = new Exception();
347
348
        // Backward compatibility.
349
        if ( is_object( $iSkip ) && $iSkip instanceof Exception ) {
0 ignored issues
show
introduced by
The condition is_object($iSkip) is always false.
Loading history...
350
            $_oException = $iSkip;
351
            $iSkip = ( integer ) $_deprecated;
352
        }
353
354
        $_iSkip      = $_iSkip + $iSkip;
355
356
        $_aTraces    = array();
357
        $_aFrames    = $_oException->getTrace();
358
        $_aFrames    = array_slice( $_aFrames, $_iSkip );
359
        foreach ( array_reverse( $_aFrames ) as $_iIndex => $_aFrame ) {
360
361
            $_aFrame     = $_aFrame + array(
362
                'file'  => null, 'line' => null, 'function' => null,
363
                'class' => null, 'args' => array(),
364
            );
365
            $_sArguments = self::___getArgumentsOfEachStackTrace( $_aFrame[ 'args' ] );
366
            $_aTraces[]  = sprintf(
367
                "#%s %s(%s): %s(%s)",
368
                $_iIndex + 1,
369
                $_aFrame[ 'file' ],
370
                $_aFrame[ 'line' ],
371
                isset( $_aFrame[ 'class' ] ) ? $_aFrame[ 'class' ] . '->' . $_aFrame[ 'function' ] : $_aFrame[ 'function' ],
372
                $_sArguments
373
            );
374
375
        }
376
        return implode( PHP_EOL, $_aTraces ) . PHP_EOL;
377
378
    }
379
        /**
380
         * @param array $aTraceArguments
381
         * @return string
382
         * @since 3.8.22
383
         * @internal
384
         */
385
        static private function ___getArgumentsOfEachStackTrace( array $aTraceArguments ) {
386
387
            $_aArguments = array();
388
            foreach ( $aTraceArguments as $_mArgument ) {
389
                $_sType        = gettype( $_mArgument );
390
                $_sType        = str_replace(
391
                    array( 'resource (closed)', 'unknown type', 'integer', 'double', ),
392
                    array( 'resource', 'unknown', 'scalar', 'scalar', ),
393
                    $_sType
394
                );
395
                $_sMethodName  = "___getStackTraceArgument_{$_sType}";
396
                $_aArguments[] = method_exists( __CLASS__, $_sMethodName )
397
                    ? self::{$_sMethodName}( $_mArgument )
398
                    : $_sType;
399
            }
400
            return join(", ",  $_aArguments );
401
        }
402
            /**
403
             * @since 3.8.22
404
             * @param mixed $mArgument
405
             * @internal
406
             * @return string
407
             */
408
            static private function ___getStackTraceArgument_string( $mArgument ) {
409
                $_sString = self::___getLegibleString( $mArgument, 200, true );
410
                return "'" . $_sString . "'";
411
            }
412
            static private function ___getStackTraceArgument_scalar( $mArgument ) {
413
                return $mArgument;
414
            }
415
            static private function ___getStackTraceArgument_boolean( $mArgument ) {
416
                return ( $mArgument ) ? "true" : "false";
417
            }
418
            static private function ___getStackTraceArgument_NULL( $mArgument ) {
419
                return 'NULL';
420
            }
421
            static private function ___getStackTraceArgument_object( $mArgument ) {
422
                return 'Object(' . get_class( $mArgument ) . ')';
423
            }
424
            static private function ___getStackTraceArgument_resource( $mArgument ) {
425
                return get_resource_type( $mArgument );
426
            }
427
            static private function ___getStackTraceArgument_unknown( $mArgument ) {
428
                return gettype( $mArgument );
429
            }
430
            static private function ___getStackTraceArgument_array( $mArgument ) {
431
                $_sOutput = '';
432
                $_iMax    = 10;
433
                $_iTotal  = count( $mArgument );
434
                $_iIndex  = 0;
435
                foreach( $mArgument as $_sKey => $_mValue ) {
436
                    $_iIndex++;
437
                    $_mValue   = is_scalar( $_mValue )
438
                        ? self::___getLegibleDetailedScalar( $_mValue, 100 )
439
                        : ucfirst( gettype( $_mValue ) ) . (
440
                            is_object( $_mValue )
441
                                ? ' (' . get_class( $_mValue ) . ')'
442
                                : ''
443
                        );
444
                    $_sOutput .= $_sKey . ': ' . $_mValue . ', ';
445
                    if ( $_iIndex > $_iMax && $_iTotal > $_iMax ) {
446
                        $_sOutput  = rtrim( $_sOutput, ','  ) . '...';
447
                        break;
448
                    }
449
                }
450
                $_sOutput = rtrim( $_sOutput, ',' );
451
                return "Array({$_sOutput})";
452
            }
453
454
}