Passed
Pull Request — develop (#27)
by Glynn
06:28 queued 03:19
created

ifElse()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 1
nop 3
dl 0
loc 10
rs 10
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
            $data,
252
            getProperty($property),
253
            \PinkCrab\FunctionConstructors\Comparisons\isEqualTo($value)
254
        );
255
    };
256
}
257
258
/**
259
 * Sets a property in an object or array.
260
 *
261
 * All object properties are passed as $object->{$property} = $value.
262
 * So no methods can be called using set property.
263
 * Will throw error is the property is protect or private.
264
 * Only works for public or dynamic properties.
265
 *
266
 * @param array<string,mixed>|ArrayObject<string,mixed>|object $store
267
     * @param string $property The property key.
268
 * @return Closure(mixed):(array<string,mixed>|ArrayObject<string,mixed>|object)
269
 */
270
function setProperty($store, string $property): Closure
271
{
272
273
    // If passed store is not an array or object, throw exception.
274
    if (! isArrayAccess($store) && ! is_object($store)) {
275
        throw new TypeError('Only objects or arrays can be constructed using setProperty.');
276
    }
277
278
    /**
279
     * @param mixed $value The value to set to keu.
280
     * @return array<string,mixed>|ArrayObject<string,mixed>|object The datastore.
281
     */
282
    return function ($value) use ($store, $property) {
283
        if (isArrayAccess($store)) {
284
            /** @phpstan-ignore-next-line */
285
            $store[ $property ] = $value;
286
        } else {
287
            $store->{$property} = $value;
288
        }
289
290
        return $store;
291
    };
292
}
293
294
/**
295
 * Returns a callable for created an array with a set key
296
 * sets the value as the result of a callable being passed some data.
297
 *
298
 * @param string $key
299
 * @param callable(mixed):mixed $value
300
 * @return Closure(mixed):mixed
301
 */
302
function encodeProperty(string $key, callable $value): Closure
303
{
304
    /**
305
     * @param mixed $data The data to pass through the callable
306
     * @return array
307
     */
308
    return function ($data) use ($key, $value): array {
309
        return array( $key => $value($data) );
310
    };
311
}
312
313
/**
314
 * Creates a callable for encoding an array or object,
315
 * from an array of encodeProperty functions.
316
 *
317
 * @param  array<string,mixed>|ArrayObject<string,mixed>|object $dataType
318
 * @return Closure(Closure(mixed):mixed ...$e):(array<string,mixed>|ArrayObject<string,mixed>|object)
319
 */
320
function recordEncoder($dataType): Closure
321
{
322
    /**
323
     * @param callable(mixed):mixed ...$encoders encodeProperty() functions.
324
     * @return Closure
325
     */
326
    return function (...$encoders) use ($dataType): Closure {
327
        /**
328
         * @param mixed $data The data to pass through the encoders.
329
         * @return array<string,mixed>|object The encoded object/array.
330
         */
331
        return function ($data) use ($dataType, $encoders) {
332
            foreach ($encoders as $encoder) {
333
                $key = array_keys($encoder($data))[0];
334
                // throw exception if key is int
335
                if (is_int($key)) {
336
                    throw new TypeError('Encoders must user an array with a string key.');
337
                }
338
339
                $dataType = setProperty($dataType, $key)(array_values($encoder($data))[0]);
340
            }
341
            return $dataType;
342
        };
343
    };
344
}
345
346
/**
347
 * Partially applied callable invoker.
348
 *
349
 * @param callable(mixed):mixed $fn
350
 * @return Closure(mixed ...$a):mixed
351
 */
352
function invoker(callable $fn): Closure
353
{
354
    /**
355
     * @param mixed ...$args
356
     * @return mixed
357
     */
358
    return function (...$args) use ($fn) {
359
        return $fn(...$args);
360
    };
361
}
362
363
/**
364
 * Returns a function which always returns the value you created it with
365
 *
366
 * @param mixed $value The value you always want to return.
367
 * @return Closure(mixed):mixed
368
 */
369
function always($value): Closure
370
{
371
    /**
372
     * @param mixed $ignored Any values can be passed and ignored.
373
     * @return mixed
374
     */
375
    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

375
    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...
376
        return $value;
377
    };
378
}
379
380
/**
381
 * Returns a function for turning objects into arrays.
382
 * Only takes public properties.
383
 *
384
 * @return Closure(object):array<string, mixed>
385
 */
386
function toArray(): Closure
387
{
388
    /**
389
     * @param object $object
390
     * @return array<string, mixed>
391
     */
392
    return function ($object): array {
393
394
        // If not object, return empty array.
395
        if (! is_object($object)) {
396
            return array();
397
        }
398
399
        $objectVars = get_object_vars($object);
400
        return array_reduce(
401
            array_keys($objectVars),
402
            function (array $array, $key) use ($objectVars): array {
403
                $array[ ltrim((string) $key, '_') ] = $objectVars[ $key ];
404
                return $array;
405
            },
406
            array()
407
        );
408
    };
409
}
410
411
/**
412
 * Creates a function which will validate the data through a condition callable, then return
413
 * the results of passing the data through the callback.
414
 *
415
 * @param callable(mixed):bool  $condition
416
 * @param callable(mixed):mixed $then
417
 * @return \Closure(mixed):mixed
418
 */
419
function ifThen(callable $condition, callable $then): Closure
420
{
421
    /**
422
     * @param  mixed $value
423
     * @return mixed
424
     */
425
    return function ($value) use ($condition, $then) {
426
        return true === (bool) $condition($value)
427
            ? $then($value)
428
            : $value;
429
    };
430
}
431
432
/**
433
 * Creates a function which will validate the data through a condition callable, then return
434
 * the results of passing the data through the callback.
435
 * Has a required callback required for failing condition.
436
 *
437
 * @param callable(mixed):bool  $condition
438
 * @param callable(mixed):mixed $then
439
 * @param callable(mixed):mixed $else
440
 * @return \Closure
441
 */
442
function ifElse(callable $condition, callable $then, callable $else): Closure
443
{
444
    /**
445
     * @param  mixed $value
446
     * @return mixed
447
     */
448
    return function ($value) use ($condition, $then, $else) {
449
        return true === (bool) $condition($value)
450
            ? $then($value)
451
            : $else($value);
452
    };
453
}
454