Completed
Push — v5 ( a506ca...8f36b1 )
by Mihail
11:57
created

functions.php (3 issues)

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 FilesystemIterator;
16
use JsonException;
17
use Koded\Stdlib\Serializer\{JsonSerializer, XmlSerializer};
18
use RecursiveDirectoryIterator;
19
use RecursiveIteratorIterator;
20
21
/**
22
 * Creates a new Argument instance
23
 * with optional arbitrary number of arguments.
24
 *
25
 * @param array ...$values
26
 *
27
 * @return Argument
28
 */
29
function arguments(...$values): Argument
30
{
31
    return new Arguments(...$values);
32
}
33
34
/**
35
 * Creates a new Immutable instance
36
 * with optional arbitrary number of arguments.
37
 *
38
 * @param array ...$values
39
 *
40
 * @return Data
41
 */
42
function value(...$values): Data
43
{
44
    return new Immutable(...$values);
45
}
46
47
/**
48
 * HTML encodes a string.
49
 * Useful for escaping the input values in HTML templates.
50
 *
51
 * @param string $input    The input string
52
 * @param string $encoding The encoding
53
 *
54
 * @return string
55
 */
56
function htmlencode(string $input, string $encoding = 'UTF-8'): string
57
{
58
    return htmlentities($input, ENT_QUOTES | ENT_HTML5, $encoding);
59
}
60
61
/**
62
 * Creates a random generated string with optional prefix and/or suffix.
63
 *
64
 * NOTE: DO NOT use it for passwords or any data that requires cryptographic secureness!
65
 *
66
 * @param int    $length [optional]
67
 * @param string $prefix [optional]
68
 * @param string $suffix [optional]
69
 *
70
 * @return string
71
 * @throws \Exception if it was not possible to gather sufficient entropy
72
 * @since 1.10.0
73
 */
74
function randomstring(int $length = 16, string $prefix = '', string $suffix = ''): string
75
{
76
    $buffer = '';
77
    for ($x = 0; $x < $length; ++$x) {
78
        $buffer .= '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[random_int(0, 61)];
79
    }
80
81
    return $prefix . $buffer . $suffix;
82
}
83
84
/**
85
 * Transforms the simple snake_case string into CamelCaseName.
86
 *
87
 * @param string $string
88
 *
89
 * @return string Camel-cased string
90
 */
91
function snake_to_camel_case(string $string): string
92
{
93
    $string = preg_replace('/[\W\_]++/', ' ', $string);
94
    return str_replace(' ', '', ucwords($string));
95
}
96
97
/**
98
 * Transforms simple CamelCaseName into camel_case_name (lower case underscored).
99
 *
100
 * @param string $string CamelCase string to be underscored
101
 *
102
 * @return string Transformed string (for weird strings, you get what you deserve)
103
 */
104
function camel_to_snake_case(string $string): string
105
{
106
    $string = snake_to_camel_case($string);
107
    return strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', trim($string)));
108
}
109
110
/**
111
 * Converts the string with desired delimiter character.
112
 *
113
 * @param string $string
114
 * @param int    $delimiter chr() of the delimiter character
115
 *
116
 * @return string The converted string with the provided delimiter
117
 */
118
function to_delimited_string(string $string, int $delimiter): string
119
{
120
    $str = preg_split('~[^\p{L}\p{N}\']+~u', trim($string));
121
    return join(chr($delimiter), $str);
0 ignored issues
show
It seems like $str can also be of type false; however, parameter $pieces of join() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
    return join(chr($delimiter), /** @scrutinizer ignore-type */ $str);
Loading history...
122
}
123
124
/**
125
 * Converts the string to-kebab-case
126
 *
127
 * @param string $string
128
 *
129
 * @return string
130
 */
131
function to_kebab_string(string $string): string
132
{
133
    return strtolower(to_delimited_string($string, ord('-')));
134
}
135
136
/**
137
 * Returns the JSON representation of a value.
138
 *
139
 * @param mixed $value   The data to be serialized
140
 * @param int   $options [optional] JSON bitmask options for JSON encoding.
141
 *                       Warning: uses {@JsonSerializer::OPTIONS} as defaults;
142
 *                       instead of adding, it may remove the option (if set in OPTIONS)
143
 *
144
 * @return string JSON encoded string, or EMPTY STRING if encoding failed
145
 * @see http://php.net/manual/en/function.json-encode.php
146
 */
147
function json_serialize($value, int $options = JsonSerializer::OPTIONS): string
148
{
149
    try {
150
        return json_encode($value, $options);
151
    } catch (JsonException $e) {
152
        error_log(__FUNCTION__, $e->getMessage(), $value);
153
        return '';
154
    }
155
}
156
157
/**
158
 * Decodes a JSON string into appropriate PHP type.
159
 *
160
 * @param string $json        A JSON string
161
 * @param bool   $associative When TRUE, returned objects will be
162
 *                            converted into associative arrays
163
 *
164
 * @return mixed The decoded value, or EMPTY STRING on error
165
 */
166
function json_unserialize(string $json, bool $associative = false)
167
{
168
    try {
169
        return json_decode($json, $associative, 512, JSON_OBJECT_AS_ARRAY | JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR);
170
    } catch (JsonException $e) {
171
        error_log(__FUNCTION__, $e->getMessage(), $json);
172
        return '';
173
    }
174
}
175
176
/**
177
 * Serializes the data into XML document.
178
 *
179
 * @param string   $root The XML document root name
180
 * @param iterable $data The data to be encoded
181
 *
182
 * @return string XML document
183
 */
184
function xml_serialize(string $root, iterable $data): string
185
{
186
    return (new XmlSerializer($root))->serialize($data);
187
}
188
189
/**
190
 * Unserialize an XML document into PHP array.
191
 * This function does not deal with magical conversions
192
 * of complicated XML structures.
193
 *
194
 * @param string $xml The XML document to be decoded into array
195
 *
196
 * @return array Decoded version of the XML string,
197
 *               or empty array on malformed XML
198
 */
199
function xml_unserialize(string $xml): array
200
{
201
    return (new XmlSerializer(null))->unserialize($xml) ?: [];
202
}
203
204
/**
205
 * Send a formatted error message to PHP's system logger.
206
 *
207
 * @param string $func    The function name where error occurred
208
 * @param string $message The error message
209
 * @param mixed  $data    Original data passed into function
210
 */
211
function error_log(string $func, string $message, $data): void
212
{
213
    \error_log(sprintf("(%s) %s:\n%s", $func, $message, var_export($data, true)));
214
}
215
216
/**
217
 * Checks if the array is an associative array.
218
 *
219
 * Simple rules:
220
 *
221
 * - If all keys are sequential starting from 0..n, it is not an associative array
222
 * - empty array is not associative
223
 *
224
 * Unfortunately, the internal typecast to integer on the keys makes
225
 * the sane programming an ugly Array Oriented Programming hackery.
226
 *
227
 * @param array $array
228
 *
229
 * @return bool
230
 */
231
function is_associative(array $array): bool
232
{
233
    return (bool)array_diff_assoc($array, array_values($array));
234
}
235
236
/**
237
 * Gets an instance of DateTimeImmutable in UTC.
238
 *
239
 * @return DateTimeImmutable
240
 */
241
function now(): DateTimeImmutable
242
{
243
    return date_create_immutable('now', timezone_open('UTC'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return date_create_immut..., timezone_open('UTC')) could return the type false which is incompatible with the type-hinted return DateTimeImmutable. Consider adding an additional type-check to rule them out.
Loading history...
It seems like timezone_open('UTC') can also be of type false; however, parameter $timezone of date_create_immutable() does only seem to accept DateTimeZone|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

243
    return date_create_immutable('now', /** @scrutinizer ignore-type */ timezone_open('UTC'));
Loading history...
244
}
245
246
/**
247
 * Removes a directory.
248
 *
249
 * @param string $dirname The folder name
250
 *
251
 * @return bool TRUE on success, FALSE otherwise
252
 */
253
function rmdir(string $dirname): bool
254
{
255
    $deleted = [];
256
257
    /** @var \SplFileInfo $path */
258
    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirname, FilesystemIterator::SKIP_DOTS),
259
        RecursiveIteratorIterator::CHILD_FIRST) as $path) {
260
        $deleted[] = ($path->isDir() && false === $path->isLink()) ? \rmdir($path->getPathname()) : \unlink($path->getPathname());
261
    }
262
263
    return (bool)array_product($deleted);
264
}
265