Code

< 40 %
40-60 %
> 60 %
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
*/
11
12
namespace Koded\Stdlib;
13
14
use DateTimeImmutable;
15
use Exception;
16
use FilesystemIterator;
17
use Koded\Stdlib\Serializer\{JsonSerializer, XmlSerializer};
18
use RecursiveDirectoryIterator;
19
use RecursiveIteratorIterator;
20
use stdClass;
21
use function array_diff_assoc;
22
use function array_key_exists;
23
use function array_product;
24
use function array_values;
25
use function chr;
26
use function date_create_immutable;
27
use function getenv;
28
use function htmlentities;
29
use function join;
30
use function ord;
31
use function preg_replace;
32
use function preg_split;
33
use function putenv;
34
use function random_int;
35
use function sprintf;
36
use function str_replace;
37
use function strtolower;
38
use function timezone_open;
39
use function trim;
40
use function ucwords;
41
use function unlink;
42
use function var_export;
43
44
/**
45
 * Creates a new Arguments instance
46
 * with optional arbitrary number of arguments.
47
 *
48
 * @param array ...$values
49
 * @return Argument
50
 */
51
function arguments(...$values): Argument
52
{
53 1
    return new Arguments(...$values);
54
}
55
56
/**
57
 * Creates a new ExtendedArguments instance
58
 * with optional arbitrary number of arguments.
59
 *
60
 * @param array ...$values
61
 * @return ExtendedArguments
62
 */
63
function extended(...$values): ExtendedArguments
64
{
65 1
    return new ExtendedArguments(...$values);
66
}
67
68
/**
69
 * Creates a new Data instance (Immutable)
70
 * with optional arbitrary number of arguments.
71
 *
72
 * @param array ...$values
73
 * @return Data
74
 */
75
function value(...$values): Data
76
{
77 1
    return new Immutable(...$values);
78
}
79
80
/**
81
 * Do something with an object or array inside the callable,
82
 * and return that value.
83
 *
84
 * @param mixed $value The value that is tapped into the callback
85
 * @param callable|null $callable Callback that can modify the value
86
 * @return mixed The tapped value
87
 */
88
function tap(mixed $value, callable $callable = null): mixed
89
{
90 6
    if (false === is_null($callable)) {
91 5
        $callable($value);
92 4
        return $value;
93
    }
94 1
    return new class($value) extends stdClass implements Tapped {
95
        public function __construct(private mixed $value) {}
96
        public function __call($method, $arguments) {
97
            // @codeCoverageIgnoreStart
98
            ([$this->value, $method])(...$arguments);
99
            return $this->value;
100
            // @codeCoverageIgnoreEnd
101
        }
102 1
    };
103
}
104
105
/**
106
 * Transforms simple CamelCaseName into camel_case_name (lower case underscored).
107
 *
108
 * @param string $string CamelCase string to be underscored
109
 * @return string Transformed string (for weird strings, you get what you deserve)
110
 */
111
function camel_to_snake_case(string $string): string
112
{
113 5
    $string = snake_to_camel_case($string);
114 5
    return strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', trim($string)));
115
}
116
117
/**
118
 * Send a formatted error message to PHP's system logger.
119
 *
120
 * @param string $func The function name where error occurred
121
 * @param string $message The error message
122
 * @param mixed $data Original data passed into function
123
 */
124
function error_log(string $func, string $message, mixed $data): void
125
{
126 3
    \error_log(sprintf("(%s) %s:\n%s", $func, $message, var_export($data, true)));
127
}
128
129
/**
130
 * Gets or sets environment variables.
131
 *
132
 * @param string|null $name
133
 * @param string [optional] $name The name of the env variable
134
 * @param array|null $initialState
135
 * @return mixed The value for the env variable,
136
 *               or all variables if $name is not provided
137
 */
138
function env(
139
    string $name = null,
140
    mixed  $default = null,
141
    array  $initialState = null): mixed
142
{
143 26
    static $state = [];
144 26
    if (null !== $initialState) {
145 26
        foreach ($initialState as $k => $v) {
146 3
            null === $v || putenv($k . '=' . $v);
147
        }
148 26
        return $state = $initialState;
149
    }
150 3
    if (null === $name) {
151 3
        return $state;
152
    }
153 1
    return array_key_exists($name, $state)
154 1
        ? $state[$name]
155 1
        : (getenv($name) ?: $default);
156
}
157
158
/**
159
 * HTML encodes a string.
160
 * Useful for escaping the input values in HTML templates.
161
 *
162
 * @param string $input The input string
163
 * @param string $encoding The encoding
164
 * @return string
165
 */
166
function htmlencode(string $input, string $encoding = 'UTF-8'): string
167
{
168 1
    return htmlentities($input, ENT_QUOTES | ENT_HTML5, $encoding);
169
}
170
171
/**
172
 * Checks if the array is an associative array.
173
 *
174
 * Simple rules:
175
 *
176
 * - If all keys are sequential starting from 0..n, it is not an associative array
177
 * - empty array is not associative
178
 *
179
 * Unfortunately, the internal typecast to integer on the keys makes
180
 * the sane programming an ugly Array Oriented Programming hackery.
181
 *
182
 * @param array $array
183
 * @return bool
184
 */
185
function is_associative(array $array): bool
186
{
187 25
    return (bool)array_diff_assoc($array, array_values($array));
188
}
189
190
/**
191
 * Returns the JSON representation of a value.
192
 *
193
 * @param mixed $value The data to be serialized
194
 * @param int $options [optional] JSON bitmask options for JSON encoding.
195
 *                       [WARNING]: uses {@JsonSerializer::OPTIONS} as defaults;
196
 *                       instead of adding, it may remove the option (if set in OPTIONS)
197
 * @return string JSON encoded string, or EMPTY STRING if encoding failed
198
 * @see http://php.net/manual/en/function.json-encode.php
199
 */
200
function json_serialize(mixed $value, int $options = 0): string
201
{
202 11
    return (new JsonSerializer($options))->serialize($value);
203
}
204
205
/**
206
 * Decodes a JSON string into appropriate PHP type.
207
 *
208
 * @param string $json A JSON string
209
 * @param bool $associative When TRUE, returned objects will be
210
 *                            converted into associative arrays
211
 * @return mixed The decoded value, or EMPTY STRING on error
212
 */
213
function json_unserialize(string $json, bool $associative = false): mixed
214
{
215 7
    return (new JsonSerializer(0, $associative))->unserialize($json);
216
}
217
218
/**
219
 * Gets an instance of DateTimeImmutable in UTC.
220
 *
221
 * @return DateTimeImmutable
222
 */
223
function now(): DateTimeImmutable
224
{
225 1
    return date_create_immutable('now', timezone_open('UTC'));
226
}
227
228
/**
229
 * Creates a random generated string with optional prefix and/or suffix.
230
 *
231
 * NOTE: DO NOT use it for passwords or any data that requires cryptographic secureness!
232
 *
233
 * @param int $length [optional]
234
 * @param string $prefix [optional]
235
 * @param string $suffix [optional]
236
 *
237
 * @return string
238
 * @throws Exception if it was not possible to gather sufficient entropy
239
 * @since 1.10.0
240
 */
241
function randomstring(
242
    int    $length = 16,
243
    string $prefix = '',
244
    string $suffix = ''): string
245
{
246 5
    $buffer = '';
247 5
    for ($x = 0; $x < $length; ++$x) {
248 5
        $buffer .= '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[random_int(0, 61)];
249
    }
250 5
    return $prefix . $buffer . $suffix;
251
}
252
253
/**
254
 * Removes a directory.
255
 *
256
 * @param string $dirname The folder name
257
 * @return bool TRUE on success, FALSE otherwise
258
 */
259
function rmdir(string $dirname): bool
260
{
261 1
    $deleted = [];
262
263
    /** @var \SplFileInfo $path */
264 1
    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirname, FilesystemIterator::SKIP_DOTS),
265 1
        RecursiveIteratorIterator::CHILD_FIRST) as $path) {
266 1
        $deleted[] = ($path->isDir() && false === $path->isLink())
267 1
            ? \rmdir($path->getPathname()) : unlink($path->getPathname());
268
    }
269 1
    return (bool)array_product($deleted);
270
}
271
272
/**
273
 * Transforms the simple snake_case string into CamelCaseName.
274
 *
275
 * @param string $string
276
 * @return string Camel-cased string
277
 */
278
function snake_to_camel_case(string $string): string
279
{
280 10
    $string = preg_replace('/[\W_]++/', ' ', $string);
281 10
    return str_replace(' ', '', ucwords($string));
282
}
283
284
/**
285
 * Converts the string with desired delimiter character.
286
 *
287
 * @param string $string
288
 * @param int $delimiter chr() of the delimiter character
289
 * @return string The converted string with the provided delimiter
290
 */
291
function to_delimited_string(string $string, int $delimiter): string
292
{
293 3
    $str = preg_split('~[^\p{L}\p{N}\']+~u', trim($string));
294 3
    return join(chr($delimiter), $str);
295
}
296
297
/**
298
 * Converts the string to-kebab-case
299
 *
300
 * @param string $string
301
 * @return string
302
 */
303
function to_kebab_string(string $string): string
304
{
305 1
    return strtolower(to_delimited_string($string, ord('-')));
306
}
307
308
/**
309
 * Serializes the data into XML document.
310
 *
311
 * @param string $root The XML document root name
312
 * @param iterable $data The data to be encoded
313
 * @return string XML document
314
 */
315
function xml_serialize(string $root, iterable $data): string
316
{
317 2
    return (new XmlSerializer($root))->serialize($data);
318
}
319
320
/**
321
 * Unserialize an XML document into PHP array.
322
 * This function does not deal with magical conversions
323
 * of complicated XML structures.
324
 *
325
 * @param string $xml The XML document to be decoded into array
326
 * @return array Decoded version of the XML string,
327
 *               or empty array on malformed XML
328
 */
329
function xml_unserialize(string $xml): array
330
{
331 2
    return (new XmlSerializer(null))->unserialize($xml) ?: [];
332
}
333