WIP on pipeable array functions
This commit is contained in:
43
README.md
43
README.md
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
This project contains PHP utility classes whose functionality is inspired by their F# counterparts.
|
This project contains PHP utility classes whose functionality is inspired by their F# counterparts.
|
||||||
|
|
||||||
The v2 series requires at least PHP 8.4. A similar API exists for PHP 8.2 - 8.3 in version 1 of this project; see its README for specifics.
|
The v3 series requires at least PHP 8.5, and includes pipeable `array_*` functions. The v2 series requires at least PHP 8.4. A similar v2 API exists for PHP 8.2 - 8.3 in version 1 of this project; see its README for specifics.
|
||||||
|
|
||||||
## What It Provides
|
## What It Provides
|
||||||
|
|
||||||
This early-stage library currently provides two classes, both of which are designed to wrap values and indicate the state of the action that produced them. `Option<T>` represents a variable that may or may not have a value. `Result<TOK, TError>` represents the result of an action; the "ok" and "error" states both provide a value.
|
This library currently provides three classes.
|
||||||
|
|
||||||
|
### `Option`s and `Result`s
|
||||||
|
|
||||||
|
Two of them are designed to wrap values and indicate the state of the action that produced them. `Option<T>` represents a variable that may or may not have a value. `Result<TOK, TError>` represents the result of an action; the "ok" and "error" states both provide a value.
|
||||||
|
|
||||||
| | `Option<T>`<br>Replaces `null` checks | `Result<TOK, TError>`<br>Replaces exception-based error handling |
|
| | `Option<T>`<br>Replaces `null` checks | `Result<TOK, TError>`<br>Replaces exception-based error handling |
|
||||||
|---------------------------------------------------|-------------------------------------------------------|-----------------------------------------------------------------------------|
|
|---------------------------------------------------|-------------------------------------------------------|-----------------------------------------------------------------------------|
|
||||||
@@ -34,7 +38,38 @@ In addition to this, `Option<T>` provides:
|
|||||||
- `->filter(callable(T): bool)` will compare a Some value against the callable, and if it returns `true`, will remain Some; if it returns `false`, the value will become None.
|
- `->filter(callable(T): bool)` will compare a Some value against the callable, and if it returns `true`, will remain Some; if it returns `false`, the value will become None.
|
||||||
- `->unwrap()` will return `null` for None options and the value for Some options.
|
- `->unwrap()` will return `null` for None options and the value for Some options.
|
||||||
|
|
||||||
Finally, we would be remiss to not acknowledge some really cool prior art in this area - the [PhpOption](https://github.com/schmittjoh/php-option) project. `Option::of` recognizes their options and converts them properly, and `Option<T>` instances have a `->toPhpOption()` method that will convert these back into PhpOption's `Some<T>` and `None` instances. There is also a [ResultType](https://github.com/GrahamCampbell/Result-Type) project from the same team, though this project's result does not (yet) have any conversion methods for it.
|
> We would be remiss to not acknowledge some excellent prior art in this area - the [PhpOption](https://github.com/schmittjoh/php-option) project. `Option::of` recognizes their options and converts them properly, and `Option<T>` instances have a `->toPhpOption()` method that will convert these back into PhpOption's `Some<T>` and `None` instances. There is also a [ResultType](https://github.com/GrahamCampbell/Result-Type) project from the same team, though this project's result does not (yet) have any conversion methods for it.
|
||||||
|
|
||||||
|
### Pipeable `array_*` Functions
|
||||||
|
|
||||||
|
The final class, exclusive to v3, is `Arr`, which wraps the `array_*` functions with versions that return a single-arity (AKA "one parameter") function which takes the array and returns the result.
|
||||||
|
|
||||||
|
The `array_map` example is easy to understand. This function requires the array first, and the transformation callback second. This does not play nicely with PHP 8.5's pipeline operator (`|>`); to pipe to `array_map`, one has to create an anonymous function to reverse the parameters. `Arr::map()` does this for you.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// The result of both calls is [2, 4, 6]
|
||||||
|
|
||||||
|
// Without this library
|
||||||
|
$withoutLibrary =
|
||||||
|
[1, 2, 3]
|
||||||
|
|> fn ($arr) => array_map($arr, fn ($x) => x * 2);
|
||||||
|
|
||||||
|
// With this library
|
||||||
|
$withLibrary =
|
||||||
|
[1, 2, 3]
|
||||||
|
|> Arr::map(fn ($x) => $x * 2);
|
||||||
|
```
|
||||||
|
All `array_*` functions which expect the target array as the first parameter have a matching `Arr::*` function which provides a version of the function with the array last. `array_*` functions which only take the array (such as `array_count_values` or `array_keys`) have corresponding functions in `Arr` for completeness; when piping to these, call the `Arr` function vs. using it as a `callable`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Use this
|
||||||
|
$theKeys = $array |> Arr::keys();
|
||||||
|
// ...or this
|
||||||
|
$theKeys = $array |> array_keys(...);
|
||||||
|
|
||||||
|
// Not this
|
||||||
|
$theKeys = $array |> Arr::keys(...);
|
||||||
|
```
|
||||||
|
|
||||||
## The Inspiration
|
## The Inspiration
|
||||||
|
|
||||||
@@ -65,7 +100,7 @@ $value = Option::of($myVar)
|
|||||||
->getOrDefault('There was no string');
|
->getOrDefault('There was no string');
|
||||||
```
|
```
|
||||||
|
|
||||||
If PHP gets a pipeline operator, we'll revisit lots of stuff here (in a non-breaking way, of course).
|
If PHP gets a pipeline operator (TODO: !!!!), we'll revisit lots of stuff here (in a non-breaking way, of course).
|
||||||
|
|
||||||
## Ideas
|
## Ideas
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
"rss": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp.rss"
|
"rss": "https://git.bitbadger.solutions/bit-badger/inspired-by-fsharp.rss"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.4"
|
"php": ">=8.5"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpoption/phpoption": "^1",
|
"phpoption/phpoption": "^1",
|
||||||
"pestphp/pest": "^3.5"
|
"pestphp/pest": "^4"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
1401
composer.lock
generated
1401
composer.lock
generated
File diff suppressed because it is too large
Load Diff
129
src/Arr.php
Normal file
129
src/Arr.php
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
||||||
|
* @license MIT
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace BitBadger\InspiredByFSharp;
|
||||||
|
|
||||||
|
use Error;
|
||||||
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Arr` has static functions that wrap PHP's native `array_*` functions with versions that provide a function which
|
||||||
|
* takes the input array. This makes these functions pipeable using PHP 8.5's pipe operator (ex.
|
||||||
|
* `array(1, 2, 3) |> Arr::map(fn ($x) => $x * 2)` would provide the array `[2, 4, 6]`).
|
||||||
|
*/
|
||||||
|
class Arr
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if all elements of an array match the given boolean callback.
|
||||||
|
*
|
||||||
|
* @param callable<mixed, ?mixed> $callback The callback function to call to check each element. If this function
|
||||||
|
* returns `false`, `false` is returned and the callback will not be called for further elements.
|
||||||
|
* @return callable<array, array> A function that calls `array_all` with the given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-all.php `array_all` Documentation
|
||||||
|
*/
|
||||||
|
public static function all(callable $callback): callable
|
||||||
|
{
|
||||||
|
return fn (array $array) => array_all($array, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if any elements of an array match the given boolean callback.
|
||||||
|
*
|
||||||
|
* @param callable<mixed, ?mixed> $callback The callback function to call to check each element. If this function
|
||||||
|
* returns `true`, `true` is returned and the callback will not be called for further elements.
|
||||||
|
* @return callable<array, array> A function that calls `array_any` with the given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-any.php array_any Documentation
|
||||||
|
*/
|
||||||
|
public static function any(callable $callback): callable
|
||||||
|
{
|
||||||
|
return fn (array $array) => array_any($array, $callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with all keys from array lowercased or uppercased. Numbered indices are left as is.
|
||||||
|
*
|
||||||
|
* @param int $case Either `CASE_UPPER` or `CASE_LOWER` (default)
|
||||||
|
* @return callable<array, array> A function that calls `array_change_key_case` with the given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-change-key-case.php array_change_key_case Documentation
|
||||||
|
*/
|
||||||
|
public static function changeKeyCase(int $case = CASE_LOWER): callable
|
||||||
|
{
|
||||||
|
return fn (array $array) => array_change_key_case($array, $case);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunks an array into arrays with `$length` elements. The last chunk may contain less than `$length` elements.
|
||||||
|
*
|
||||||
|
* @param int $length The size of each chunk.
|
||||||
|
* @param bool $preserveKeys When set to `true` keys will be preserved. Default is `false` which will reindex the
|
||||||
|
* chunk numerically.
|
||||||
|
* @return callable<array, array<array>> A function that calls `array_chunk` with the given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-chunk.php array_chunk Documentation
|
||||||
|
*/
|
||||||
|
public static function chunk(int $length, bool $preserveKeys = false): callable
|
||||||
|
{
|
||||||
|
return fn (array $array) => array_chunk($array, $length, $preserveKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values from a single column of the array, identified by the `$columnKey`. Optionally, an `$indexKey`
|
||||||
|
* may be provided to index the values in the returned array by the values from the `$indexKey` column of the input
|
||||||
|
* array.
|
||||||
|
*
|
||||||
|
* @param int|string|null $columnKey The column of values to return. This value may be an integer key of the column
|
||||||
|
* you wish to retrieve, or it may be a string key name for an associative array or property name. It may also
|
||||||
|
* be `null` to return complete arrays or objects (this is useful together with `$indexKey` to reindex the
|
||||||
|
* array).
|
||||||
|
* @param int|string|null $indexKey The column to use as the index/keys for the returned array. This value may be
|
||||||
|
* the integer key of the column, or it may be the string key name. The value is cast as usual for array keys.
|
||||||
|
* @return callable A function that calls `array_column` with the given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-column.php array_column Documentation
|
||||||
|
*/
|
||||||
|
public static function column(int|string|null $columnKey, int|string|null $indexKey = null): callable
|
||||||
|
{
|
||||||
|
return fn (array $array) => array_column($array, $columnKey, $indexKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the occurrences of each distinct value in an array.
|
||||||
|
*
|
||||||
|
* @return callable<array<mixed, int|string>, array<int|string, int>> A function that calls `array_count` with the
|
||||||
|
* given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-count-values.php array_count_values Documentation
|
||||||
|
*/
|
||||||
|
public static function countValues(): callable
|
||||||
|
{
|
||||||
|
return array_count_values(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform each element of an array to a different element (optionally merging other mapped arrays).
|
||||||
|
*
|
||||||
|
* @param callable|null $callback A callable to run for each element in each array.<br><br>
|
||||||
|
* `null` can be passed as a value to `$callback` to perform a zip operation on multiple arrays and return
|
||||||
|
* an array where each element is an array containing the elements from the input arrays at the same position of
|
||||||
|
* the internal array pointer. If only array is provided, `Arr::map()` will return the input array.
|
||||||
|
* @param array ...$arrays Supplementary variable list of array arguments to run through the callback function.
|
||||||
|
* @return callable<array, array> A function that calls `array_map` with the given array.
|
||||||
|
* @see https://www.php.net/manual/en/function.array-map.php array_map Documentation
|
||||||
|
*/
|
||||||
|
public static function map(?callable $callback, array ...$arrays): callable
|
||||||
|
{
|
||||||
|
return fn (array $array) => array_map($callback, $array, ...$arrays);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception This class should not be constructed
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
throw new Exception('This is a static class; do not instantiate it');
|
||||||
|
}
|
||||||
|
}
|
||||||
58
tests/Unit/ArrTest.php
Normal file
58
tests/Unit/ArrTest.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use BitBadger\InspiredByFSharp\Arr;
|
||||||
|
|
||||||
|
describe('::all()', function () {
|
||||||
|
test('succeeds when all elements match', function () {
|
||||||
|
expect([2, 4, 6] |> Arr::all(fn ($it) => $it % 2 === 0))->toBeTrue();
|
||||||
|
});
|
||||||
|
test('succeeds when not all elements match', function () {
|
||||||
|
expect([2, 5, 6] |> Arr::all(fn ($it) => $it % 2 === 0))->toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('::any()', function () {
|
||||||
|
test('succeeds when no elements match', function () {
|
||||||
|
expect([2, 4, 6] |> Arr::any(fn ($it) => $it % 2 === 1))->toBeFalse();
|
||||||
|
});
|
||||||
|
test('succeeds when an element matches', function () {
|
||||||
|
expect([2, 5, 6] |> Arr::any(fn ($it) => $it % 2 === 1))->toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('::changeKeyCase()', function () {
|
||||||
|
test('succeeds', function () {
|
||||||
|
$test = ['ONE' => 1, 'two' => 2, 'Three' => 3] |> Arr::changeKeyCase();
|
||||||
|
expect(array_keys($test))->toEqual(['one', 'two', 'three']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('::chunk()', function () {
|
||||||
|
test('succeeds', function () {
|
||||||
|
expect([1, 2, 3, 4, 5] |> Arr::chunk(2))->toBeArray()->toHaveLength(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('::countValues', function () {
|
||||||
|
test('succeeds', function () {
|
||||||
|
$arr = ['this' => 1, 'that' => 2, 'theOther' => 3, 'somewhere' => 1, 'else' => 5] |> Arr::countValues();
|
||||||
|
expect($arr)->toBeArray()
|
||||||
|
->and(array_keys($arr))->toHaveLength(4)
|
||||||
|
->and($arr[1])->toBe(2)
|
||||||
|
->and($arr[2])->toBe(1)
|
||||||
|
->and($arr[3])->toBe(1)
|
||||||
|
->and($arr[5])->toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('::map()', function () {
|
||||||
|
test('maps an array', function () {
|
||||||
|
expect([1, 2, 3] |> Arr::map(fn ($it) => $it * 2))->toEqual([2, 4, 6]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user