Passed
Push — master ( 5d4ebc...774f1e )
by Marwan
01:18
created

Misc::getArrayValueByKey()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 11
c 1
b 0
f 0
nc 6
nop 3
dl 0
loc 23
rs 9.2222
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
    public static function getNormalizedPath(string $directory, string $filename, string $extension = ''): string
31
    {
32
        $filename = substr($filename, -strlen($extension)) === $extension ? $filename : $filename . $extension;
33
        $directory = $directory . '/';
34
35
        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
    public static function getArrayValueByKey(array &$array, string $key, $default = null)
48
    {
49
        if (!count($array)) {
50
            return $default;
51
        }
52
53
        $data = &$array;
54
55
        if (strpos($key, '.') !== false) {
56
            $parts = explode('.', $key);
57
58
            foreach ($parts as $part) {
59
                if (!array_key_exists($part, $data)) {
60
                    return $default;
61
                }
62
63
                $data = &$data[$part];
64
            }
65
66
            return $data;
67
        }
68
69
        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
    public static function setArrayValueByKey(array &$array, string $key, $value): bool
82
    {
83
        if (!strlen($key)) {
84
            return false;
85
        }
86
87
        $parts = explode('.', $key);
88
        $lastPart = array_pop($parts);
89
90
        $data = &$array;
91
92
        if (!empty($parts)) {
93
            foreach ($parts as $part) {
94
                if (!isset($data[$part])) {
95
                    $data[$part] = [];
96
                }
97
98
                $data = &$data[$part];
99
            }
100
        }
101
102
        $data[$lastPart] = $value;
103
104
        return true;
105
    }
106
107
    /**
108
     * Interpolates context values into the message placeholders.
109
     *
110
     * @param string $text The text to interpolate.
111
     * @param array $context An associative array like `['varName' => 'varValue']`.
112
     * @param string $placeholderIndicator The wrapper that indicate a variable. Max 2 chars, anything else will be ignored and "{}" will be used instead.
113
     *
114
     * @return string The interpolated string.
115
     */
116
    public static function interpolate(string $text, array $context = [], string $placeholderIndicator = '{}'): string
117
    {
118
        if (strlen($placeholderIndicator) !== 2) {
119
            $placeholderIndicator = '{}';
120
        }
121
122
        $replacements = [];
123
        foreach ($context as $key => $value) {
124
            // check that the value can be cast to string
125
            if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
126
                $replacements[$placeholderIndicator[0] . $key . $placeholderIndicator[1]] = $value;
127
            }
128
        }
129
130
        return strtr($text, $replacements);
131
    }
132
133
    /**
134
     * Returns the passed key(s) from the backtrace.
135
     *
136
     * @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.
137
     * @param int $offset [optional] The offset of the backtrace, passing `-1` will reference the last item in the backtrace.
138
     *
139
     * @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.
140
     */
141
    public static function backtrace($pluck = null, ?int $offset = 0)
142
    {
143
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
144
        $plucked = null;
145
146
        if ($pluck === null) {
147
            return $backtrace;
148
        }
149
150
        if ($offset === -1) {
151
            $offset = 0;
152
            $backtrace = array_reverse($backtrace);
153
        }
154
155
        if (count($backtrace) < $offset + 1) {
156
            return null;
157
        } elseif (is_string($pluck)) {
158
            $plucked = isset($backtrace[$offset][$pluck]) ? $backtrace[$offset][$pluck] : null;
159
        } elseif (is_array($pluck)) {
0 ignored issues
show
introduced by
The condition is_array($pluck) is always true.
Loading history...
160
            $plucked = [];
161
            foreach ($pluck as $key) {
162
                !isset($backtrace[$offset][$key]) ?: $plucked[$key] = $backtrace[$offset][$key];
163
            }
164
        }
165
166
        return is_string($plucked) || is_array($plucked) && count($plucked, COUNT_RECURSIVE) ? $plucked : null;
167
    }
168
169
    /**
170
     * Logs a message to a file and generates it if it does not exist.
171
     *
172
     * @param string $message The message wished to be logged.
173
     * @param array|null $context An associative array of values where key = {key} in the message.
174
     * @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".
175
     * @param string|null $directory [optional] The directory where the log file should be written. If not the file will be written in "/storage/logs/".
176
     *
177
     * @return bool True if message was written.
178
     */
179
    public static function log(string $message, ?array $context = [], ?string $filename = null, ?string $directory = null): bool
180
    {
181
        if (!Config::get('global.loggingEnabled')) {
182
            return true;
183
        }
184
185
        $hasPassed = false;
186
187
        if (!$filename) {
188
            $filename = 'autogenerated-' . date('Ymd');
189
        }
190
191
        if (!$directory) {
192
            $directory = BASE_PATH . '/storage/logs/';
193
        }
194
195
        $file = self::getNormalizedPath($directory, $filename, '.log');
196
197
        if (!file_exists($directory)) {
198
            mkdir($directory, 0744, true);
199
        }
200
201
        // create log file if it does not exist
202
        if (!is_file($file) && is_writable($directory)) {
203
            $signature = 'Created by ' . __METHOD__ . date('() \o\\n l jS \of F Y h:i:s A (Ymdhis)') . PHP_EOL . PHP_EOL;
204
            file_put_contents($file, $signature, 0, stream_context_create());
205
            chmod($file, 0775);
206
        }
207
208
        // write in the log file
209
        if (is_writable($file)) {
210
            clearstatcache(true, $file);
211
            // empty the file if it exceeds 64MB
212
            if (filesize($file) > 6.4e+7) {
213
                $stream = fopen($file, 'r');
214
                if (is_resource($stream)) {
215
                    $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;
216
                    fclose($stream);
217
                    file_put_contents($file, $signature, 0, stream_context_create());
218
                    chmod($file, 0775);
219
                }
220
            }
221
222
            $timestamp = (new \DateTime())->format(DATE_ISO8601);
223
            $message   = self::interpolate($message, $context ?? []);
224
225
            $log = "$timestamp\t$message\n";
226
227
            $stream = fopen($file, 'a+');
228
            if (is_resource($stream)) {
229
                fwrite($stream, $log);
230
                fclose($stream);
231
                $hasPassed = true;
232
            }
233
        }
234
235
        return $hasPassed;
236
    }
237
}
238