Completed
Push — master ( dac48e...0d5905 )
by Rasmus
02:47
created

readable   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 91.6%

Importance

Changes 0
Metric Value
wmc 67
lcom 2
cbo 0
dl 0
loc 257
ccs 109
cts 119
cp 0.916
rs 3.04
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
D value() 0 57 19
A values() 0 12 3
C typeof() 0 25 14
A callback() 0 12 6
B error() 0 26 7
A severity() 0 6 2
F trace() 0 47 16

How to fix   Complexity   

Complex Class

Complex classes like readable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use readable, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace mindplay;
4
5
use Closure;
6
use Error;
7
use ErrorException;
8
use Exception;
9
use ReflectionFunction;
10
use Throwable;
11
12
/**
13
 * Pseudo-namespace for functions that generate human-readable string representations
14
 * of most types of PHP values.
15
 */
16
abstract class readable
17
{
18
    /**
19
     * @var int strings longer than this number of characters will be truncated when formatting string-values
20
     */
21
    public static $max_string_length = 120;
22
23
    /**
24
     * @var string[] map where PHP error severity code => constant name
25
     */
26
    public static $severity_names = [
27
        E_ERROR             => "E_ERROR",
28
        E_USER_ERROR        => "E_USER_ERROR",
29
        E_CORE_ERROR        => "E_CORE_ERROR",
30
        E_COMPILE_ERROR     => "E_COMPILE_ERROR",
31
        E_PARSE             => "E_PARSE",
32
        E_WARNING           => "E_WARNING",
33
        E_USER_WARNING      => "E_USER_WARNING",
34
        E_CORE_WARNING      => "E_CORE_WARNING",
35
        E_COMPILE_WARNING   => "E_COMPILE_WARNING",
36
        E_NOTICE            => "E_NOTICE",
37
        E_USER_NOTICE       => "E_USER_NOTICE",
38
        E_STRICT            => "E_STRICT",
39
        E_RECOVERABLE_ERROR => "E_RECOVERABLE_ERROR",
40
        E_DEPRECATED        => "E_DEPRECATED",
41
        E_USER_DEPRECATED   => "E_USER_DEPRECATED",
42
    ];
43
44
    /**
45
     * @param mixed $value any type of PHP value
46
     *
47
     * @return string readable representation of the given value
48
     */
49 1
    public static function value($value)
50
    {
51 1
        $type = is_array($value) && is_callable($value)
52 1
            ? "callable"
53 1
            : strtolower(gettype($value));
54
55 1
        switch ($type) {
56 1
            case "boolean":
57 1
                return $value ? "true" : "false";
58
59 1
            case "integer":
60 1
                return number_format($value, 0, "", "");
61
62 1
            case "double": // (for historical reasons "double" is returned in case of a float, and not simply "float")
63 1
                $formatted = sprintf("%.6g", $value);
64
65 1
                return $value == $formatted
66 1
                    ? "{$formatted}"
67 1
                    : "~{$formatted}";
68
69 1
            case "string":
70 1
                $string = strlen($value) > self::$max_string_length
71 1
                    ? substr($value, 0, self::$max_string_length) . "...[" . strlen($value) . "]"
72 1
                    : $value;
73
74 1
                return '"' . addslashes($string) . '"';
75
76 1
            case "array":
77 1
                return "[" . self::values($value) . "]";
78
79 1
            case "object":
80 1
                if ($value instanceof Closure) {
81 1
                    $reflection = new ReflectionFunction($value);
82
83 1
                    return "{Closure in " . $reflection->getFileName() . "({$reflection->getStartLine()})}";
84
                }
85
86 1
                return "{" . ($value instanceof \stdClass ? "object" : get_class($value)) . "}";
87
88 1
            case "resource":
89 1
                return "{" . get_resource_type($value) . "}";
90
91 1
            case "resource (closed)":
92
                // TODO this provides BC for breaking changes in PHP 7.2 (should be changed in a major release)
93 1
                return "{unknown type}";
94
95 1
            case "callable":
96 1
                return is_object($value[0])
97 1
                    ? '{' . get_class($value[0]) . "}->{$value[1]}()"
98 1
                    : "{$value[0]}::{$value[1]}()";
99
100 1
            case "null":
101 1
                return "null";
102
        }
103
104
        return "{{$type}}"; // "unknown type" and possibly unsupported (future) types
105
    }
106
107
    /**
108
     * @param array $array array containing any type of PHP values
109
     *
110
     * @return string comma-separated human-readable representation of the given values
111
     */
112 1
    public static function values(array $array)
113
    {
114 1
        $formatted = array_map(['mindplay\\readable', 'value'], $array);
115
116 1
        if (array_keys($array) !== range(0, count($array) - 1)) {
117 1
            foreach ($formatted as $name => $value) {
118 1
                $formatted[$name] = self::value($name) . " => {$value}";
119
            }
120
        }
121
122 1
        return implode(", ", $formatted);
123
    }
124
125
    /**
126
     * @param mixed $value any type of PHP value
127
     *
128
     * @return string human-readable type-name
129
     */
130 1
    public static function typeof($value)
131
    {
132 1
        $type = ! is_string($value) && ! is_object($value) && is_callable($value)
133 1
            ? "callable"
134 1
            : strtolower(gettype($value));
135
136 1
        switch ($type) {
137 1
            case "boolean":
138 1
                return "bool";
139 1
            case "integer":
140 1
                return "int";
141 1
            case "double":
142 1
                return "float";
143 1
            case "object":
144 1
                return $value instanceof \stdClass ? "object" : get_class($value);
145 1
            case "string":
146 1
            case "array":
147 1
            case "resource":
148 1
            case "callable":
149 1
            case "null":
150 1
                return $type;
151
        }
152
153 1
        return "unknown";
154
    }
155
156
    /**
157
     * @param mixed $callable
158
     *
159
     * @return string human-readable description of callback
160
     */
161 1
    public static function callback($callable)
162
    {
163 1
        if (is_string($callable) && is_callable($callable)) {
164 1
            return "{$callable}()";
165 1
        } elseif (is_object($callable) && method_exists($callable, "__invoke")) {
166 1
            return $callable instanceof Closure
167 1
                ? self::value($callable)
168 1
                : "{" . get_class($callable) . "}->__invoke()";
169
        }
170
171 1
        return self::value($callable);
172
    }
173
174
    /**
175
     * @param Exception|Error|Throwable|int $error an Exception, Error (or one of the E_* error severity constants)
176
     *
177
     * @return string
178
     */
179 1
    public static function error($error)
180
    {
181 1
        if (is_int($error)) {
182 1
            return static::severity($error);
183
        }
184
185 1
        $type = get_class($error);
186
187 1
        if ($error instanceof ErrorException) {
188
            $severity = static::severity($error->getSeverity());
189
190
            $type = "{$type}: {$severity}";
191
        }
192
193 1
        if ($error instanceof Exception || $error instanceof Error) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
194 1
            $message = $error->getMessage() ?: '{none}';
195
196 1
            $file = $error->getFile()
197 1
                ? $error->getFile() . "(" . $error->getLine() . ")"
198 1
                : "{no file}";
199
200 1
            return "{$type} with message: {$message} in {$file}";
201
        }
202
203
        return $type;
204
    }
205
206
    /**
207
     * @param int $severity one of the E_* error severity constants
208
     *
209
     * @return string
210
     */
211 1
    public static function severity($severity)
212
    {
213 1
        return isset(self::$severity_names[$severity])
214 1
            ? self::$severity_names[$severity]
215 1
            : "{unknown error-code}";
216
    }
217
218
    /**
219
     * @param array|Exception|Error|Throwable $source      Exception, Error or stack-trace data as provided
220
     *                                                     by Exception::getTrace() or by debug_backtrace()
221
     * @param bool                            $with_params if TRUE, calls will be formatted with parameters
222
     *
223
     * @return string
224
     */
225 1
    public static function trace($source, $with_params = true)
226
    {
227 1
        if ($source instanceof Exception || $source instanceof Error) {
0 ignored issues
show
Bug introduced by
The class Error does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
228 1
            $trace = $source->getTrace();
229
        } elseif (is_array($source)) {
230
            $trace = $source;
231
        } else {
232
            return "{stack-trace unavailable}";
233
        }
234
235 1
        $formatted = [];
236
237 1
        foreach ($trace as $index => $entry) {
238 1
            $line = array_key_exists("line", $entry)
239 1
                ? ":" . $entry["line"]
240 1
                : "";
241
242 1
            $file = isset($entry["file"])
243 1
                ? $entry["file"]
244 1
                : "{no file}";
245
246 1
            $function = isset($entry["class"])
247 1
                ? $entry["class"] . @$entry["type"] . @$entry["function"]
248 1
                : @$entry["function"];
249
250 1
            if ($function === "require" || $function === "include") {
251
                // bypass argument formatting for include and require statements
252
                $args = isset($entry["args"]) && is_array($entry["args"])
253
                    ? reset($entry["args"])
254
                    : "";
255
            } else {
256 1
                $args = $with_params && isset($entry["args"]) && is_array($entry["args"])
257 1
                    ? static::values($entry["args"])
258 1
                    : "";
259
            }
260
261 1
            $call = $function
262 1
                ? "{$function}({$args})"
263 1
                : "";
264
265 1
            $depth = $index + 1;
266
267 1
            $formatted[] = sprintf("%6s", "{$depth}.") . " {$file}{$line} {$call}";
268
        }
269
270 1
        return implode("\n", $formatted);
271
    }
272
}
273