recordEncoder()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 25
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 1
nop 1
dl 0
loc 25
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Mixture of general functions, including compose, pipe, invoke and property
5
 * accessors.
6
 *
7
 * This file is part of PinkCrab Function Constructors.
8
 *
9
 * PinkCrab Function Constructors is free software: you can redistribute it and/or modify it under the terms of the
10
 * GNU General Public License as published by the Free Software Foundation, either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * PinkCrab Function Constructors is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
 * See the GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along with PinkCrab Function Constructors.
18
 * If not, see <https://www.gnu.org/licenses/>.
19
 *
20
 * @author Glynn Quelch <[email protected]>
21
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
22
 * @package PinkCrab\FunctionConstructors
23
 * @since 0.0.1
24
 *
25
 * @template Number of int|float
26
 * @phpstan-template Number of int|float
27
 * @psalm-template Number of int|float
28
 */
29
30
declare(strict_types=1);
31
32
namespace PinkCrab\FunctionConstructors\GeneralFunctions;
33
34
use Closure;
35
use TypeError;
36
use ArrayObject;
37
38
/**
39
 * Composes a function based on a set of callbacks.
40
 * All functions passed should have matching parameters.
41
 *
42
 * @param callable(mixed):mixed ...$callables
43
 * @return Closure(mixed):mixed
44
 */
45
function compose(callable ...$callables): Closure
46
{
47
    /**
48
     * @param mixed The value passed into the functions
49
     * @return mixed The final result.
50
     */
51
    return function ($e) use ($callables) {
52
        foreach ($callables as $callable) {
53
            $e = $callable($e);
54
        }
55
        return $e;
56
    };
57
}
58
59
/**
60
 * Composes a function based on a set of callbacks in reverse
61
 * All functions passed should have matching parameters.
62
 *
63
 * @param callable(mixed):mixed ...$callables
64
 * @return Closure(mixed):mixed
65
 */
66
function composeR(callable ...$callables): Closure
67
{
68
    /**
69
     * @param mixed The value passed into the functions
70
     * @return mixed The final result.
71
     */
72
    return function ($e) use ($callables) {
73
        foreach (\array_reverse($callables) as $callable) {
74
            $e = $callable($e);
75
        }
76
        return $e;
77
    };
78
}
79
80
/**
81
 * Compose a function which is escaped if the value returns as null.
82
 * This allows for safer composition.
83
 *
84
 * @param callable(mixed):mixed ...$callables
85
 * @return Closure(mixed):mixed
86
 */
87
function composeSafe(callable ...$callables): Closure
88
{
89
    /**
90
     * @param mixed The value passed into the functions
91
     * @return mixed|null The final result or null.
92
     */
93
    return function ($e) use ($callables) {
94
        foreach ($callables as $callable) {
95
            if (! is_null($e)) {
96
                $e = $callable($e);
97
            }
98
        }
99
        return $e;
100
    };
101
}
102
103
/**
104
 * Creates a strictly pure function.
105
 * Every return from every class, must pass a validation function
106
 * If any fail validation, null is returned.
107
 *
108
 * @param callable(mixed):bool $validator The validation function (b -> bool)
109
 * @param callable(mixed):mixed ...$callables The functions to execute (a -> a)
110
 * @return Closure(mixed):mixed
111
 */
112
function composeTypeSafe(callable $validator, callable ...$callables): Closure
113
{
114
    /**
115
     * @param mixed $e The value being passed through the functions.
116
     * @return mixed The final result.
117
     */
118
    return function ($e) use ($validator, $callables) {
119
        foreach ($callables as $callable) {
120
            // If invalid, abort and return null
121
            if (! $validator($e)) {
122
                return null;
123
            }
124
            // Run through callable.
125
            $e = $callable($e);
126
127
            // Check results and bail if invalid type.
128
            if (! $validator($e)) {
129
                return null;
130
            }
131
        }
132
        // Return the final result.
133
        return $e;
134
    };
135
}
136
137
/**
138
 * Alias for compose()
139
 *
140
 * @param mixed $value Value to be passed through the functions.
141
 * @param callable(mixed):mixed ...$callables Functions to be called.
142
 * @return mixed
143
 */
144
function pipe($value, callable ...$callables)
145
{
146
    return compose(...$callables)($value);
147
}
148
149
/**
150
 * The reverse of the functions passed into compose() (pipe())).
151
 * To give a more functional feel to some piped calls.
152
 *
153
 * @param mixed $value Value to be passed through the functions.
154
 * @param callable(mixed):mixed ...$callables
155
 * @return mixed
156
 */
157
function pipeR($value, callable ...$callables)
158
{
159
    return compose(...\array_reverse($callables))($value);
160
}
161
162
/**
163
 * Returns a callback for getting a property or element from an array/object.
164
 *
165
 * @param string $property
166
 * @return Closure(mixed):mixed
167
 */
168
function getProperty(string $property): Closure
169
{
170
    /**
171
     * @param mixed $data The array or object to attempt to get param.
172
     * @return mixed|null
173
     */
174
    return function ($data) use ($property) {
175
        if (is_array($data)) {
176
            return array_key_exists($property, $data) ? $data[ $property ] : null;
177
        } elseif (is_object($data)) {
178
            return property_exists($data, $property) ? $data->{$property} : null;
179
        } else {
180
            return null;
181
        }
182
    };
183
}
184
185
/**
186
 * Walks an array or object tree based on the nodes passed.
187
 * Will return whatever value at final node passed.
188
 * If any level returns null, process aborts.
189
 *
190
 * @param string ...$nodes
191
 * @return Closure(mixed[]|object):mixed
192
 */
193
function pluckProperty(string ...$nodes): Closure
194
{
195
    /**
196
     * @param mixed[]|object $data The array or object to attempt to get param.
197
     * @return mixed|null
198
     */
199
    return function ($data) use ($nodes) {
200
        foreach ($nodes as $node) {
201
            $data = getProperty($node)($data);
202
203
            if (is_null($data)) {
204
                return $data;
205
            }
206
        }
207
        return $data;
208
    };
209
}
210
211
/**
212
 * Returns a callable for a checking if a property exists.
213
 * Works for both arrays and objects (with public properties).
214
 *
215
 * @param string $property
216
 * @return Closure(mixed[]|object):mixed
217
 */
218
function hasProperty(string $property): Closure
219
{
220
    /**
221
     * @param mixed $data The array or object to attempt to get param.
222
     * @return mixed|null
223
     */
224
    return function ($data) use ($property): bool {
225
        if (is_array($data)) {
226
            return array_key_exists($property, $data);
227
        } elseif (is_object($data)) {
228
            return property_exists($data, $property);
229
        } else {
230
            return false;
231
        }
232
    };
233
}
234
235
/**
236
 * Returns a callable for a checking if a property exists and matches the passed value
237
 * Works for both arrays and objects (with public properties).
238
 *
239
 * @param string $property
240
 * @param mixed $value
241
 * @return Closure(mixed[]|object):bool
242
 */
243
function propertyEquals(string $property, $value): Closure
244
{
245
    /**
246
     * @param mixed $data The array or object to attempt to get param.
247
     * @return bool
248
     */
249
    return function ($data) use ($property, $value): bool {
250
        return pipe(
251
            getProperty($property),
252
            \PinkCrab\FunctionConstructors\Comparisons\isEqualTo($value)
253
        )($data);
254
    };
255
}
256
257
/**
258
 * Sets a property in an object or array.
259
 *
260
 * All object properties are passed as $object->{$property} = $value.
261
 * So no methods can be called using set property.
262
 * Will throw error is the property is protect or private.
263
 * Only works for public or dynamic properties.
264
 *
265
 * @param array<string,mixed>|ArrayObject<string,mixed>|object $store
266
 * @return Closure(string,mixed):(array<string,mixed>|ArrayObject<string,mixed>|object)
267
 */
268
function setProperty($store): Closure
269
{
270
271
    // If passed store is not an array or object, throw exception.
272
    if (! isArrayAccess($store) && ! is_object($store)) {
273
        throw new TypeError('Only objects or arrays can be constructed using setProperty.');
274
    }
275
276
    /**
277
     * @param string $property The property key.
278
     * @param mixed $value The value to set to keu.
279
     * @return array<string,mixed>|ArrayObject<string,mixed>|object The datastore.
280
     */
281
    return function (string $property, $value) use ($store) {
282
        if (isArrayAccess($store)) {
283
            /** @phpstan-ignore-next-line */
284
            $store[ $property ] = $value;
285
        } else {
286
            $store->{$property} = $value;
287
        }
288
289
        return $store;
290
    };
291
}
292
293
/**
294
 * Returns a callable for created an array with a set key
295
 * sets the value as the result of a callable being passed some data.
296
 *
297
 * @param string $key
298
 * @param callable(mixed):mixed $value
299
 * @return Closure(mixed):mixed
300
 */
301
function encodeProperty(string $key, callable $value): Closure
302
{
303
    /**
304
     * @param mixed $data The data to pass through the callable
305
     * @return array
306
     */
307
    return function ($data) use ($key, $value): array {
308
        return array( $key => $value($data) );
309
    };
310
}
311
312
/**
313
 * Creates a callable for encoding an array or object,
314
 * from an array of encodeProperty functions.
315
 *
316
 * @param  array<string,mixed>|ArrayObject<string,mixed>|object $dataType
317
 * @return Closure(Closure(mixed):mixed ...$e):(array<string,mixed>|ArrayObject<string,mixed>|object)
318
 */
319
function recordEncoder($dataType): Closure
320
{
321
    /**
322
     * @param callable(mixed):mixed ...$encoders encodeProperty() functions.
323
     * @return Closure
324
     */
325
    return function (...$encoders) use ($dataType): Closure {
326
        /**
327
         * @param mixed $data The data to pass through the encoders.
328
         * @return array<string,mixed>|object The encoded object/array.
329
         */
330
        return function ($data) use ($dataType, $encoders) {
331
            foreach ($encoders as $encoder) {
332
                $key = array_keys($encoder($data))[0];
333
                // throw exception if key is int
334
                if (is_int($key)) {
335
                    throw new TypeError('Encoders must user an array with a string key.');
336
                }
337
338
                $dataType = setProperty($dataType)(
339
                    $key,
340
                    array_values($encoder($data))[0]
341
                );
342
            }
343
            return $dataType;
344
        };
345
    };
346
}
347
348
/**
349
 * Partially applied callable invoker.
350
 *
351
 * @param callable(mixed):mixed $fn
352
 * @return Closure(mixed ...$a):mixed
353
 */
354
function invoker(callable $fn): Closure
355
{
356
    /**
357
     * @param mixed ...$args
358
     * @return mixed
359
     */
360
    return function (...$args) use ($fn) {
361
        return $fn(...$args);
362
    };
363
}
364
365
/**
366
 * Returns a function which always returns the value you created it with
367
 *
368
 * @param mixed $value The value you always want to return.
369
 * @return Closure(mixed):mixed
370
 */
371
function always($value): Closure
372
{
373
    /**
374
     * @param mixed $ignored Any values can be passed and ignored.
375
     * @return mixed
376
     */
377
    return function (...$ignored) use ($value) {
1 ignored issue
show
Unused Code introduced by
The parameter $ignored is not used and could be removed. ( Ignorable by Annotation )

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

377
    return function (/** @scrutinizer ignore-unused */ ...$ignored) use ($value) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
378
        return $value;
379
    };
380
}
381
382
/**
383
 * Returns a function for turning objects into arrays.
384
 * Only takes public properties.
385
 *
386
 * @return Closure(object):array<string, mixed>
387
 */
388
function toArray(): Closure
389
{
390
    /**
391
     * @param object $object
392
     * @return array<string, mixed>
393
     */
394
    return function ($object): array {
395
396
        // If not object, return empty array.
397
        if (! is_object($object)) {
398
            return array();
399
        }
400
401
        $objectVars = get_object_vars($object);
402
        return array_reduce(
403
            array_keys($objectVars),
404
            function (array $array, $key) use ($objectVars): array {
405
                $array[ ltrim((string) $key, '_') ] = $objectVars[ $key ];
406
                return $array;
407
            },
408
            array()
409
        );
410
    };
411
}
412