Completed
Push — master ( 995897...f89623 )
by Rasmus
04:23
created

readable   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 0

Test Coverage

Coverage 92.31%

Importance

Changes 0
Metric Value
wmc 66
lcom 2
cbo 0
dl 0
loc 253
ccs 108
cts 117
cp 0.9231
rs 3.12
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
D value() 0 53 18
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 "callable":
92 1
                return is_object($value[0])
93 1
                    ? '{' . get_class($value[0]) . "}->{$value[1]}()"
94 1
                    : "{$value[0]}::{$value[1]}()";
95
96 1
            case "null":
97 1
                return "null";
98
        }
99
100 1
        return "{{$type}}"; // "unknown type" and possibly unsupported (future) types
101
    }
102
103
    /**
104
     * @param array $array array containing any type of PHP values
105
     *
106
     * @return string comma-separated human-readable representation of the given values
107
     */
108 1
    public static function values(array $array)
109
    {
110 1
        $formatted = array_map(['mindplay\\readable', 'value'], $array);
111
112 1
        if (array_keys($array) !== range(0, count($array) - 1)) {
113 1
            foreach ($formatted as $name => $value) {
114 1
                $formatted[$name] = self::value($name) . " => {$value}";
115
            }
116
        }
117
118 1
        return implode(", ", $formatted);
119
    }
120
121
    /**
122
     * @param mixed $value any type of PHP value
123
     *
124
     * @return string human-readable type-name
125
     */
126 1
    public static function typeof($value)
127
    {
128 1
        $type = ! is_string($value) && ! is_object($value) && is_callable($value)
129 1
            ? "callable"
130 1
            : strtolower(gettype($value));
131
132 1
        switch ($type) {
133 1
            case "boolean":
134 1
                return "bool";
135 1
            case "integer":
136 1
                return "int";
137 1
            case "double":
138 1
                return "float";
139 1
            case "object":
140 1
                return $value instanceof \stdClass ? "object" : get_class($value);
141 1
            case "string":
142 1
            case "array":
143 1
            case "resource":
144 1
            case "callable":
145 1
            case "null":
146 1
                return $type;
147
        }
148
149 1
        return "unknown";
150
    }
151
152
    /**
153
     * @param mixed $callable
154
     *
155
     * @return string human-readable description of callback
156
     */
157 1
    public static function callback($callable)
158
    {
159 1
        if (is_string($callable) && is_callable($callable)) {
160 1
            return "{$callable}()";
161 1
        } elseif (is_object($callable) && method_exists($callable, "__invoke")) {
162 1
            return $callable instanceof Closure
163 1
                ? self::value($callable)
164 1
                : "{" . get_class($callable) . "}->__invoke()";
165
        }
166
167 1
        return self::value($callable);
168
    }
169
170
    /**
171
     * @param Exception|Error|Throwable|int $error an Exception, Error (or one of the E_* error severity constants)
172
     *
173
     * @return string
174
     */
175 1
    public static function error($error)
176
    {
177 1
        if (is_int($error)) {
178 1
            return static::severity($error);
179
        }
180
181 1
        $type = get_class($error);
182
183 1
        if ($error instanceof ErrorException) {
184
            $severity = static::severity($error->getSeverity());
185
186
            $type = "{$type}: {$severity}";
187
        }
188
189 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...
190 1
            $message = $error->getMessage() ?: '{none}';
191
192 1
            $file = $error->getFile()
193 1
                ? $error->getFile() . "(" . $error->getLine() . ")"
194 1
                : "{no file}";
195
196 1
            return "{$type} with message: {$message} in {$file}";
197
        }
198
199
        return $type;
200
    }
201
202
    /**
203
     * @param int $severity one of the E_* error severity constants
204
     *
205
     * @return string
206
     */
207 1
    public static function severity($severity)
208
    {
209 1
        return isset(self::$severity_names[$severity])
210 1
            ? self::$severity_names[$severity]
211 1
            : "{unknown error-code}";
212
    }
213
214
    /**
215
     * @param array|Exception|Error|Throwable $source      Exception, Error or stack-trace data as provided
216
     *                                                     by Exception::getTrace() or by debug_backtrace()
217
     * @param bool                            $with_params if TRUE, calls will be formatted with parameters
218
     *
219
     * @return string
220
     */
221 1
    public static function trace($source, $with_params = true)
222
    {
223 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...
224 1
            $trace = $source->getTrace();
225
        } elseif (is_array($source)) {
226
            $trace = $source;
227
        } else {
228
            return "{stack-trace unavailable}";
229
        }
230
231 1
        $formatted = [];
232
233 1
        foreach ($trace as $index => $entry) {
234 1
            $line = array_key_exists("line", $entry)
235 1
                ? ":" . $entry["line"]
236 1
                : "";
237
238 1
            $file = isset($entry["file"])
239 1
                ? $entry["file"]
240 1
                : "{no file}";
241
242 1
            $function = isset($entry["class"])
243 1
                ? $entry["class"] . @$entry["type"] . @$entry["function"]
244 1
                : @$entry["function"];
245
246 1
            if ($function === "require" || $function === "include") {
247
                // bypass argument formatting for include and require statements
248
                $args = isset($entry["args"]) && is_array($entry["args"])
249
                    ? reset($entry["args"])
250
                    : "";
251
            } else {
252 1
                $args = $with_params && isset($entry["args"]) && is_array($entry["args"])
253 1
                    ? static::values($entry["args"])
254 1
                    : "";
255
            }
256
257 1
            $call = $function
258 1
                ? "{$function}({$args})"
259 1
                : "";
260
261 1
            $depth = $index + 1;
262
263 1
            $formatted[] = sprintf("%6s", "{$depth}.") . " {$file}{$line} {$call}";
264
        }
265
266 1
        return implode("\n", $formatted);
267
    }
268
}
269