Passed
Push — master ( fab1ff...bbc3bd )
by Marwan
09:15
created

Misc::log()   B

Complexity

Conditions 11
Paths 113

Size

Total Lines 57
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 11.727

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 32
c 1
b 0
f 0
nc 113
nop 4
dl 0
loc 57
ccs 27
cts 33
cp 0.8182
crap 11.727
rs 7.2083

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @author Marwan Al-Soltany <[email protected]>
5
 * @copyright Marwan Al-Soltany 2021
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace MAKS\Velox\Helper;
13
14
use MAKS\Velox\Backend\Config;
15
16
/**
17
 * A class that serves as a holder for various miscellaneous utility function.
18
 */
19
final class Misc
20
{
21
    /**
22
     * Returns a normalized path based on OS.
23
     *
24
     * @param string $directory
25
     * @param string $filename
26
     * @param string $extension
27
     *
28
     * @return string
29
     */
30 9
    public static function getNormalizedPath(string $directory, string $filename, string $extension = ''): string
31
    {
32 9
        $filename = substr($filename, -strlen($extension)) === $extension ? $filename : $filename . $extension;
33 9
        $directory = $directory . '/';
34
35 9
        return preg_replace('/\/+|\\+/', DIRECTORY_SEPARATOR, $directory . $filename);
36
    }
37
38
    /**
39
     * Gets a value from an array via dot-notation.
40
     *
41
     * @param array &$array The array to get the value from.
42
     * @param string $key The dotted key representation.
43
     * @param mixed $default [optional] The default fallback value.
44
     *
45
     * @return mixed The requested value if found otherwise the default parameter.
46
     */
47 35
    public static function getArrayValueByKey(array &$array, string $key, $default = null)
48
    {
49 35
        if (!count($array)) {
50 3
            return $default;
51
        }
52
53 35
        $data = &$array;
54
55 35
        if (strpos($key, '.') !== false) {
56 21
            $parts = explode('.', $key);
57
58 21
            foreach ($parts as $part) {
59 21
                if (!array_key_exists($part, $data)) {
60 4
                    return $default;
61
                }
62
63 21
                $data = &$data[$part];
64
            }
65
66 21
            return $data;
67
        }
68
69 19
        return array_key_exists($key, $data) ? $data[$key] : $default;
70
    }
71
72
    /**
73
     * Sets a value of an array via dot-notation.
74
     *
75
     * @param array $array The array to set the value in.
76
     * @param string $key The string key representation.
77
     * @param mixed $value The value to set.
78
     *
79
     * @return bool True on success.
80
     */
81 31
    public static function setArrayValueByKey(array &$array, string $key, $value): bool
82
    {
83 31
        if (!strlen($key)) {
84 1
            return false;
85
        }
86
87 31
        $parts = explode('.', $key);
88 31
        $lastPart = array_pop($parts);
89
90 31
        $data = &$array;
91
92 31
        if (!empty($parts)) {
93 6
            foreach ($parts as $part) {
94 6
                if (!isset($data[$part])) {
95 4
                    $data[$part] = [];
96
                }
97
98 6
                $data = &$data[$part];
99
            }
100
        }
101
102 31
        $data[$lastPart] = $value;
103
104 31
        return true;
105
    }
106
107
108
    /**
109
     * Gets a private, protected, or public property (default, static, or constant) of an object.
110
     *
111
     * @param object $object Class instance.
112
     * @param string $property Property name.
113
     *
114
     * @return mixed The property value.
115
     *
116
     * @throws \Exception On failure.
117
     */
118 6
    public static function getObjectProperty($object, string $property)
119
    {
120 6
        return \Closure::bind(function ($object, $property) {
121 6
            $return = null;
122
123
            try {
124 6
                $class = get_class($object);
125 6
                if (defined($class . '::' . $property)) {
126 1
                    $return = constant($class . '::' . $property);
127 6
                } elseif (isset($object::$$property)) {
128 5
                    $return = $object::$$property;
129 3
                } elseif (isset($object->{$property})) {
130 3
                    $return = $object->{$property};
131
                } else {
132 6
                    throw new \Exception("No default, static, or constant property with the name '{$property}' exists!");
133
                }
134 1
            } catch (\Exception $error) {
135 1
                throw new \Exception(sprintf('%s() failed!', __METHOD__), $error->getCode(), $error);
136
            }
137
138 6
            return $return;
139 6
        }, null, $object)($object, $property);
140
    }
141
142
    /**
143
     * Sets a private, protected, or public property (default or static) of an object.
144
     *
145
     * @param object $object Class instance.
146
     * @param string $property Property name.
147
     * @param string $value Property value.
148
     *
149
     * @return mixed The new property value.
150
     *
151
     * @throws \Exception On failure.
152
     */
153 12
    public static function setObjectProperty($object, string $property, $value)
154
    {
155 12
        return \Closure::bind(function ($object, $property, $value) {
156 12
            $return = null;
157
158
            try {
159 12
                if (isset($object::$$property)) {
160 12
                    $return = $object::$$property = $value;
161 1
                } elseif (isset($object->{$property})) {
162 1
                    $return = $object->{$property} = $value;
163
                } else {
164 12
                    throw new \Exception("No default, static, or constant property with the name '{$property}' exists!");
165
                }
166 1
            } catch (\Exception $error) {
167 1
                throw new \Exception(sprintf('%s() failed!', __METHOD__), $error->getCode(), $error);
168
            }
169
170 12
            return $return;
171 12
        }, null, $object)($object, $property, $value);
172
    }
173
174
    /**
175
     * Calls a private, protected, or public method on an object.
176
     *
177
     * @param object $object Class instance.
178
     * @param string $method Method name.
179
     * @param mixed ...$arguments
180
     *
181
     * @return mixed The function result, or false on error.
182
     *
183
     * @throws \Exception On failure or if the called function threw an exception.
184
     */
185 1
    public static function callObjectMethod($object, string $method, ...$arguments)
186
    {
187 1
        return \Closure::bind(function ($object, $method, $arguments) {
188
            try {
189 1
                return call_user_func_array([$object, $method], $arguments);
190 1
            } catch (\Exception $error) {
191 1
                throw new \Exception(sprintf('%s() failed!', __METHOD__), $error->getCode(), $error);
192
            }
193 1
        }, null, $object)($object, $method, $arguments);
194
    }
195
196
    /**
197
     * Interpolates context values into text placeholders.
198
     *
199
     * @param string $text The text to interpolate.
200
     * @param array $context An associative array like `['varName' => 'varValue']`.
201
     * @param string $placeholderIndicator The wrapper that indicate a variable. Max 2 chars, anything else will be ignored and "{}" will be used instead.
202
     *
203
     * @return string The interpolated string.
204
     */
205 10
    public static function interpolate(string $text, array $context = [], string $placeholderIndicator = '{}'): string
206
    {
207 10
        if (strlen($placeholderIndicator) !== 2) {
208 1
            $placeholderIndicator = '{}';
209
        }
210
211 10
        $replacements = [];
212 10
        foreach ($context as $key => $value) {
213
            // check that the value can be cast to string
214 10
            if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
215 10
                $replacements[$placeholderIndicator[0] . $key . $placeholderIndicator[1]] = $value;
216
            }
217
        }
218
219 10
        return strtr($text, $replacements);
220
    }
221
222
    /**
223
     * Returns the passed key(s) from the backtrace.
224
     *
225
     * @param null|string|string[] $pluck [optional] $pluck The key to to get as a string or an array of strings (keys) from this list `[file, line, function, class, type, args]`, passing `null` will return the entire backtrace.
226
     * @param int $offset [optional] The offset of the backtrace, passing `-1` will reference the last item in the backtrace.
227
     *
228
     * @return string|int|array|null A string or int if a string is passed, an array if an array or null is passed, and null if no match was found.
229
     */
230 5
    public static function backtrace($pluck = null, ?int $offset = 0)
231
    {
232 5
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
233 5
        $plucked = null;
234
235 5
        if ($pluck === null) {
236 1
            return $backtrace;
237
        }
238
239 5
        if ($offset === -1) {
240 1
            $offset = 0;
241 1
            $backtrace = array_reverse($backtrace);
242
        }
243
244 5
        if (count($backtrace) < $offset + 1) {
245 1
            return null;
246 5
        } elseif (is_string($pluck)) {
247 3
            $plucked = isset($backtrace[$offset][$pluck]) ? $backtrace[$offset][$pluck] : null;
248 3
        } elseif (is_array($pluck)) {
0 ignored issues
show
introduced by
The condition is_array($pluck) is always true.
Loading history...
249 3
            $plucked = [];
250 3
            foreach ($pluck as $key) {
251 3
                !isset($backtrace[$offset][$key]) ?: $plucked[$key] = $backtrace[$offset][$key];
252
            }
253
        }
254
255 5
        return is_string($plucked) || is_array($plucked) && count($plucked, COUNT_RECURSIVE) ? $plucked : null;
256
    }
257
258
    /**
259
     * Logs a message to a file and generates it if it does not exist.
260
     *
261
     * @param string $message The message wished to be logged.
262
     * @param array|null $context An associative array of values where key = {key} in the message (context).
263
     * @param string|null $filename [optional] The name wished to be given to the file. If not provided the message will be logged in "autogenerated-{Ymd}.log".
264
     * @param string|null $directory [optional] The directory where the log file should be written. If not the file will be written in "/storage/logs/".
265
     *
266
     * @return bool True if message was written.
267
     */
268 4
    public static function log(string $message, ?array $context = [], ?string $filename = null, ?string $directory = null): bool
269
    {
270 4
        if (!Config::get('global.loggingEnabled')) {
271 1
            return true;
272
        }
273
274 4
        $hasPassed = false;
275
276 4
        if (!$filename) {
277 1
            $filename = 'autogenerated-' . date('Ymd');
278
        }
279
280 4
        if (!$directory) {
281 4
            $directory = BASE_PATH . '/storage/logs/';
282
        }
283
284 4
        $file = self::getNormalizedPath($directory, $filename, '.log');
285
286 4
        if (!file_exists($directory)) {
287 2
            mkdir($directory, 0744, true);
288
        }
289
290
        // create log file if it does not exist
291 4
        if (!is_file($file) && is_writable($directory)) {
292 2
            $signature = 'Created by ' . __METHOD__ . date('() \o\\n l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL . PHP_EOL;
293 2
            file_put_contents($file, $signature, 0, stream_context_create());
294 2
            chmod($file, 0775);
295
        }
296
297
        // write in the log file
298 4
        if (is_writable($file)) {
299 4
            clearstatcache(true, $file);
300
            // empty the file if it exceeds 64MB
301 4
            if (filesize($file) > 6.4e+7) {
302
                $stream = fopen($file, 'r');
303
                if (is_resource($stream)) {
304
                    $signature = fgets($stream) . 'For exceeding 64MB, it was overwritten on ' . date('l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL . PHP_EOL;
305
                    fclose($stream);
306
                    file_put_contents($file, $signature, 0, stream_context_create());
307
                    chmod($file, 0775);
308
                }
309
            }
310
311 4
            $timestamp = (new \DateTime())->format(DATE_ISO8601);
312 4
            $message   = self::interpolate($message, $context ?? []);
313
314 4
            $log = "$timestamp\t$message\n";
315
316 4
            $stream = fopen($file, 'a+');
317 4
            if (is_resource($stream)) {
318 4
                fwrite($stream, $log);
319 4
                fclose($stream);
320 4
                $hasPassed = true;
321
            }
322
        }
323
324 4
        return $hasPassed;
325
    }
326
}
327