A PHP library for manipulating Arrays, Strings and Objects - inspired by ramda.js
PHP 7.2
composer require mlg/shovel
<?php
use Shovel\A;
use Shovel\S;
$fruits = ['banana', 'raspberry', 'orange', 'strawberry', 'apple', 'blueberry'];
$berries = A::filter(function($fruit) {
return S::endsWith('berry', $fruit);
}, $fruits);
print_r($berries); // ['raspberry', 'strawberry', 'blueberry']
See also: A::concat()
A::of(1, 2, [3]); // [1, 2, [3]]
A::isArray([1, 2, 3]); // true
A::isArray(["a" => 10]); // true
A::isArray("asdf"); // false
A::isArray(50); // false
A::isArray(new stdClass()); // false
checks whether the given parameter is an associative array. empty arrays are treated as normal arrays and the function will return false for them
The method is based on this solution: https://stackoverflow.com/a/173479/1806628
A::isAssoc([]); // false
A::isAssoc([1, 2, 3]); // false;
A::isAssoc(["x" => 10, "y" => 20]); // true
Calls $fn with each element from left to right in the array and passes the returned value to the subsequent $fn calls
See also: A::reduceRight()
A::reduce($fn, $init, [1, 2, 3]); // same as $fn(3, $fn(2, $fn(1, $init)))
A::reverse([1, 2, 3]); // [3, 2, 1]
Calls $fn with each element from right to left in the array and passes the returned value to the subsequent $fn calls
See also: A::reduce()
A::reduceRight($fn, $init, [1, 2, 3]); // same as $fn(1, $fn(2, $fn(3, $init)))
A::sum([1, 2, 3, 4, 5]); // 15
$numbers = [1, 2, 3, 4, 5];
function double(int $n){
return $n * 2;
}
$doubles = A::map(double, $numbers); // [2, 4, 6, 8, 10]
See also: O::keys()
A::keys([3, 6, 9]); // [0, 1, 2]
See also: O::values()
A::values([3, 6, 9]); // [3, 6, 9]
A::equals([1, 2, 3], [1, 2, 3]); // true
A::equals([1, 2, 3], [1, 2, 3, 4, 5]); // false
A::length([1, 2, 3]); // 3
See also: A::isNotEmpty()
A::isEmpty([]); // true
A::isEmpty([1, 2, 3]); // false
See also: A::isEmpty()
A::isNotEmpty([1, 2, 3]); // true
A::isNotEmpty([]); // false
A::ensureArray([10]) // [10]
A::ensureArray(['a' => 10]) // [['a' => 10]]
A::ensureArray(123); // [123]
A::ensureArray([4, 5, 6]); // [4, 5, 6]
A::head([1, 2, 3]) // 1
A::head([]) // null
See also: A::head()
A::last([1, 2, 3, 4, 5]) // 5
A::last([]) // null
A::init([1, 2, 3, 4, 5]) // [1, 2, 3, 4]
A::tail([1, 2, 3, 4, 5]) // [2, 3, 4, 5]
calls the given function on the elements of an array and returns every value where the function gave truthy value
$numbers = [1, 2, 3, 4, 5, 6];
function isOdd($n){
return $n % 2 === 0;
}
A::filter('isOdd', $numbers); // [2, 4, 6]
calls the given function on the elements of an array and removes every value where the function gave truthy value
$numbers = [1, 2, 3, 4, 5, 6];
function isOdd($n){
return $n % 2 === 0;
}
A::reject('isOdd', $numbers); // [1, 3, 5]
calls the given function on the elements of an array and returns the value for the first match. if there's no match, it will return null
$data = [
["a" => 8],
["a" => 10],
["a" => 12]
];
$result = A::find(fn($x) => $x["a"] > 3, $data);
// $result = ["a" => 8]
$data = [
["a" => 8],
["a" => 10],
["a" => 12]
];
$result = A::find(fn($x) => $x["a"] === -4, $data);
// $result = null
calls the given function on the elements of an array and returns the value for the last match. if there's no match, it will return null
$data = [
["a" => 8],
["a" => 10],
["a" => 12]
];
$result = A::findLast(fn($x) => $x["a"] > 3, $data);
// $result = ["a" => 12]
$data = [
["a" => 8],
["a" => 10],
["a" => 12]
];
$result = A::findLast(fn($x) => $x["a"] === -4, $data);
// $result = null
calls the given function on the elements of an array and returns the key for the first match. if there's no match it will return null
$data = [1, 1, 1, 0, 0, 0, 0, 0];
$result = A::findIndex(fn($x) => $x === 0, $data);
// $result = 3
$data = [1, 1, 1, 0, 0, 0, 0, 0];
$result = A::findIndex(fn($x) => $x === 2, $data);
// $result = null
calls the given function on the elements of an array and returns the key for the last match. if there's no match it will return null
$data = [1, 1, 1, 0, 0, 0, 0, 0];
$result = A::findLastIndex(fn($x) => $x === 1, $data);
// $result = 2
$data = [1, 1, 1, 0, 0, 0, 0, 0];
$result = A::findLastIndex(fn($x) => $x === 2, $data);
// $result = null
calls the given predicate function on the elements in the given array and returns true if for at least one of them the predicate returns true
$data = [2, 3, 5, 6, 7, 9, 10];
$result = A::any(fn($x) => $x % 5 === 0, $data);
// $result = true
concatenates every argument into an array. if any of the arguments are numeric arrays, then those will get unnested
See also: A::of()
A::concat([1, 2], 3, [4, 5]); // [1, 2, 3, 4, 5]
removes items from the second array by values in the first array. if first value is not an array, then it is transformed into one
A::without([1, 3], [1, 2, 3, 4, 5]); // [2, 4, 5]
A::without(['a' => 12], [1, 2, 3, 4, ['a' => 12]]); // [1, 2, 3, 4]
A::without('t', ['t', 'f', 'f', 't', 'f']); // ['f', 'f', 'f']
Most string operations come with an optional 3rd parameter called $caseSensitivity, which can be either
S::CASE_SENSITIVE
(default) orS::CASE_INSENSITIVE
.
All string operations are multibyte safe!
S::isString('hello'); // true
S::isString(['hello']); // false
S::isString(304.2); // false
S::length('Ĺ‘z'); // 2 -- strlen('Ĺ‘z') returns 3
S::isEmpty(''); // true
S::isEmpty('caterpillar'); // false
S::isNotEmpty(''); // false
S::isNotEmpty('caterpillar'); // true
S::toLower('AsDf JkLÉ'); // "asdf jklé"
S::toUpper('AsDf JkLÉ'); // "ASDF JKLÉ"
S::includes('erf', 'butterfly'); // true
S::includes('ERF', 'butterfly', S::CASE_INSENSITIVE); // true
S::includes('ERF', 'butterfly', S::CASE_SENSITIVE); // false
S::includes('', 'butterfly'); // false -- edge case
See also: S::includes
S::split("/", "foo/bar/baz") // ["foo", "bar", "baz"]
S::splitAt(3, "abcdef") // ["abc", "def"]
S::equals('asdf', 'asdf'); // true
S::equals('asdf', 'ASDF', S::CASE_INSENSITIVE); // true
S::equals('asdf', 'ASDF', S::CASE_SENSITIVE); // false
S::slice(2, 5, "abcdefgh"); // "cde"
S::slice(-3, PHP_INT_MAX, "abcdefgh") // "fgh"
S::startsWith("inf", "infinity"); // true
S::startsWith("inf", "iNFinItY", S::CASE_INSENSITIVE); // true
S::startsWith("inf", "iNFinItY", S::CASE_SENSITIVE); // false
S::endsWith("ed", "throwed"); // true
S::endsWith("ed", "tHRoWeD", S::CASE_SENSITIVE); // false
S::endsWith("ed", "tHRoWeD", S::CASE_INSENSITIVE); // true
S::trim(" asd f "); // "asd f"
S::replace("a", "e", "alabama"); // "elebeme"
$point = new stdClass();
$point->x = 10;
$point->y = 20;
O::isObject($point); // true
O::isObject("asdf"); // false
$point = new stdClass();
$point->x = 10;
$point->y = 20;
O::toPairs($point); // [["x", 10], ["y", 20]]
$user = [
"firstName" => "John",
"lastName" => "Doe"
];
O::toPairs($user); // [["firstName", "John"], ["lastName", "Doe"]]
$temperatures = [75, 44, 36];
O::toPairs($temperatures); // [[0, 75], [1, 44], [2, 36]]
$point2d = new stdClass();
$point2d->x = 10;
$point2d->y = 20;
$point3d = O::assoc("z", 30, $point2d); // {"x": 10, "y": 20, "z": 30}
$point2d = [
"x" => 10,
"y" => 20
];
$point3d = O::assoc("z", 30, $point2d); // ["x" => 10, "y" => 20, "z" => 30]
Does not work on arrays with numeric keys!
$point3d = new stdClass();
$point3d->x = 10;
$point3d->y = 20;
$point3d->z = 30;
$point2d = O::dissoc("z", 30, $point3d); // {"x": 10, "y": 20}
$point3d = [
"x" => 10,
"y" => 20,
"z" => 30
];
$point2d = O::dissoc("z", 30, $point3d); // ["x" => 10, "y" => 30]
Does not work on arrays with numeric keys!
uses array_key_exists()
internally
$data = new stdClass();
$data->x = 10;
O::has('x', $data); // true
O::has('y', $data); // false
$data = ['x' => 10];
O::has('x', $data); // true
O::has('y', $data); // false
Reads the given value for the given key from objects and associative arrays. If not found, then returns null.
$data = new stdClass();
$data->x = 10;
O::prop('x', $data); // 10
O::prop('y', $data); // null
$data = ['x' => 10];
O::prop('x', $data); // 10
O::prop('y', $data); // null
function isOdd(int $n):boolean {
return $n % 2 === 1;
}
$isEven = F::complement(isOdd);
Every method is abide to the following rules ( or at least they should. if they don't, then 1) PRs are welcome, 2) Issues are welcome ):
each method should get all the necessary info from the parameters and should not rely on any external parameters or state
since every method is stateless, there is no need to create class instances
not using anything apart from the passed in parameters
not going to change any of the parameters, no & references or stuff like that
like in Lodash FP or Ramda
suggestions are welcome on where to place optional parameters
if you are using a method from A
, then you better be sending it an array. PHP is a loosely typed language and you could spend all day validating input parameters.
it's the same as for the validation, you should check the data you pass to the function beforehand
small is beautiful, and maintainable - and probably easier to test later on when I'll get the willpower to write tests for this lib
when an error happens and the underlying php function returns false (eg. end or strpos), then it's being normalized to null
Plain numeric arrays are handled best via the methods in A, while associative arrays and objects are handled via the methods in O.
I keep adding methods as I come across the need for them, so if you're missing a method you'd use, then 1) PRs are welcome, 2) Issues are welcome.
There are multiple functions, which have the same parameter signature, but operate with different parameter types. To avoid having to type check the parameters at every function call I've chose to namespace the functions based on the types they work on into static methods.
For example take a look at includes for both Arrays and Strings. Their implementation is relative simple, because their types are expected to be arrays and strings respectively (type hinting will come soon).
class A {
public static function includes($value, $data)
{
return in_array($value, $data, true);
}
}
class S {
public static function includes($value, $data)
{
return mb_strpos($data, $str) !== false;
}
}
If I were to have a single, combined includes
function, then I would have to do type checking every time and it would make the code unnecessarily noisy.
Plus, sometimes the function name I would like to use is already taken by PHP, like in the case of S::split
https://www.geeksforgeeks.org/php-startswith-and-endswith-functions/ - used for S::startsWith() and S::endsWith()
https://stackoverflow.com/a/173479/1806628 - used for A::isAssoc()
https://www.php.net/manual/en/function.array-unique.php#116302 - used for A::uniqByKey()