mlg / shovel
A PHP 库,用于操作数组、字符串和对象 - 受 ramda.js 启发
Requires
- php: >=7.2
README
A PHP 库,用于操作数组、字符串和对象 - 受 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']
API
数组
A::of
将每个参数作为数组拼接在一起
参见: A::concat()
A::of(1, 2, [3]); // [1, 2, [3]]
A::isArray
检查给定参数是否为数组(对于数字和关联数组都返回 true)
A::isArray([1, 2, 3]); // true
A::isArray(["a" => 10]); // true
A::isArray("asdf"); // false
A::isArray(50); // false
A::isArray(new stdClass()); // false
A::isAssoc
检查给定参数是否为关联数组。空数组被视为普通数组,函数将返回 false
该方法基于此解决方案: https://stackoverflow.com/a/173479/1806628
A::isAssoc([]); // false
A::isAssoc([1, 2, 3]); // false;
A::isAssoc(["x" => 10, "y" => 20]); // true
A::reduce
从左到右遍历数组中的每个元素,并传递返回值到后续的 $fn 调用中
参见: A::reduceRight()
A::reduce($fn, $init, [1, 2, 3]); // same as $fn(3, $fn(2, $fn(1, $init)))
A::reverse
翻转数组中的值顺序
A::reverse([1, 2, 3]); // [3, 2, 1]
A::reduceRight
从右到左遍历数组中的每个元素,并传递返回值到后续的 $fn 调用中
参见: A::reduce()
A::reduceRight($fn, $init, [1, 2, 3]); // same as $fn(1, $fn(2, $fn(3, $init)))
A::sum
将给定数组中的数字相加并返回总和
A::sum([1, 2, 3, 4, 5]); // 15
A::map
对所有元素调用 $fn 并返回结果数组
$numbers = [1, 2, 3, 4, 5]; function double(int $n){ return $n * 2; } $doubles = A::map(double, $numbers); // [2, 4, 6, 8, 10]
A::keys
返回数组的索引
参见: O::keys()
A::keys([3, 6, 9]); // [0, 1, 2]
A::values
返回数组的值
参见: O::values()
A::values([3, 6, 9]); // [3, 6, 9]
A::equals
比较两个数字数组,当它们的内 容相同时返回 true
A::equals([1, 2, 3], [1, 2, 3]); // true
A::equals([1, 2, 3], [1, 2, 3, 4, 5]); // false
A::length
返回数组的大小
A::length([1, 2, 3]); // 3
A::isEmpty
当给定数组内部没有元素时返回 true
参见: A::isNotEmpty()
A::isEmpty([]); // true
A::isEmpty([1, 2, 3]); // false
A::isNotEmpty
当给定数组内部有元素时返回 true
参见: A::isEmpty()
A::isNotEmpty([1, 2, 3]); // true
A::isNotEmpty([]); // false
A::ensureArray
将给定值包裹在数组中(即使是关联数组),除非它已经是一个数组
A::ensureArray([10]) // [10]
A::ensureArray(['a' => 10]) // [['a' => 10]]
A::ensureArray(123); // [123]
A::ensureArray([4, 5, 6]); // [4, 5, 6]
A::append
A::prepend
A::pluck
A::uniq
A::uniqByKey
A::sortBy
A::sortByKey
A::unnest
A::forEach
A::head
返回数组的第一个元素,如果为空则返回 null
A::head([1, 2, 3]) // 1
A::head([]) // null
A::first
A::head() 的别名
参见: A::head()
A::last
返回数组的最后一个元素,如果为空则返回 null
A::last([1, 2, 3, 4, 5]) // 5
A::last([]) // null
A::init
返回一个不含最后一个元素的给定数组的副本
A::init([1, 2, 3, 4, 5]) // [1, 2, 3, 4]
A::tail
返回一个不含第一个元素的给定数组的副本
A::tail([1, 2, 3, 4, 5]) // [2, 3, 4, 5]
A::filter
在数组的元素上调用给定的函数,并返回所有函数返回真值的值
$numbers = [1, 2, 3, 4, 5, 6]; function isOdd($n){ return $n % 2 === 0; } A::filter('isOdd', $numbers); // [2, 4, 6]
A::reject
在数组的元素上调用给定的函数,并移除所有函数返回真值的值
$numbers = [1, 2, 3, 4, 5, 6]; function isOdd($n){ return $n % 2 === 0; } A::reject('isOdd', $numbers); // [1, 3, 5]
A::find
在数组元素上调用给定的函数,并返回第一个匹配的值。如果没有匹配,则返回 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
A::findLast
在数组元素上调用给定的函数,并返回最后一个匹配的值。如果没有匹配,则返回 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
A::findIndex
在数组元素上调用给定的函数,并返回第一个匹配的关键字。如果没有匹配,则返回 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
A::findLastIndex
在数组元素上调用给定的函数,并返回最后一个匹配的关键字。如果没有匹配,则返回 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
A::any
在给定数组中的元素上调用给定的谓词函数,如果至少有一个元素使得谓词返回 true,则返回 true
$data = [2, 3, 5, 6, 7, 9, 10]; $result = A::any(fn($x) => $x % 5 === 0, $data); // $result = true
A::none
A::all
A::includes
A::contains
A::slice
返回从 fromIndex(包含)到 toIndex(不包含)的给定列表元素
A::join
A::pickRandom
从给定数组中选择一个随机项目
A::concat
将每个参数连接到一个数组中。如果任何参数是数字数组,则这些数组将被展开
另请参阅:A::of()
A::concat([1, 2], 3, [4, 5]); // [1, 2, 3, 4, 5]
A::zipObj
A::without
通过第二个数组中的值从第二个数组中删除项目。如果第一个值不是数组,则将其转换为数组
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']
String
大多数字符串操作都包含一个可选的第三个参数,称为 $caseSensitivity,它可以是
S::CASE_SENSITIVE(默认)或S::CASE_INSENSITIVE。
所有字符串操作都是多字节安全的!
S::isString
检查给定的参数是否是字符串
S::isString('hello'); // true
S::isString(['hello']); // false
S::isString(304.2); // false
S::length
计算给定参数中的字符数
S::length('őz'); // 2 -- strlen('őz') returns 3
S::isEmpty
检查给定的字符串是否没有字符
S::isEmpty(''); // true
S::isEmpty('caterpillar'); // false
S::isNotEmpty
检查给定的字符串是否包含任何字符
S::isNotEmpty(''); // false
S::isNotEmpty('caterpillar'); // true
S::toLower
将字符串中的每个字符转换为小写
S::toLower('AsDf JkLÉ'); // "asdf jklé"
S::toUpper
将字符串中的每个字符转换为大写
S::toUpper('AsDf JkLÉ'); // "ASDF JKLÉ"
S::includes
检查作为第一个参数的字符串是否是第二个参数字符串的子串
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
S::contains
别名:S::includes()
另请参阅:S::includes
S::split
在匹配另一个字符串的点将字符串分割成多个部分
S::split("/", "foo/bar/baz") // ["foo", "bar", "baz"]
S::splitAt
在给定位置将字符串分割成两部分
S::splitAt(3, "abcdef") // ["abc", "def"]
S::equals
比较两个字符串,以查看它们是否匹配
S::equals('asdf', 'asdf'); // true
S::equals('asdf', 'ASDF', S::CASE_INSENSITIVE); // true
S::equals('asdf', 'ASDF', S::CASE_SENSITIVE); // false
S::slice
从开始(包含)到结束(不包含)的位置复制子字符串
S::slice(2, 5, "abcdefgh"); // "cde"
S::slice(-3, PHP_INT_MAX, "abcdefgh") // "fgh"
S::startsWith
检查第二个参数是否以第一个参数开头
S::startsWith("inf", "infinity"); // true
S::startsWith("inf", "iNFinItY", S::CASE_INSENSITIVE); // true
S::startsWith("inf", "iNFinItY", S::CASE_SENSITIVE); // false
S::endsWith
检查第二个参数是否以第一个参数结尾
S::endsWith("ed", "throwed"); // true
S::endsWith("ed", "tHRoWeD", S::CASE_SENSITIVE); // false
S::endsWith("ed", "tHRoWeD", S::CASE_INSENSITIVE); // true
S::trim
从字符串中删除前导和尾随空格
S::trim(" asd f "); // "asd f"
S::replace
用另一个子串替换子串
S::replace("a", "e", "alabama"); // "elebeme"
Object
O::isObject
检查传入的参数是否是对象
$point = new stdClass(); $point->x = 10; $point->y = 20; O::isObject($point); // true
O::isObject("asdf"); // false
O::toPairs
获取数组或对象的所有键和值,并作为键值对数组返回
$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]]
O::pick
O::assoc
通过给定的键将值分配给对象。已存在的键将被覆盖
$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]
不适用于具有数字键的数组!
O::dissoc
从对象中删除一个键
$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]
不适用于具有数字键的数组!
O::has
检查对象和关联数组中是否存在键
内部使用 array_key_exists()
$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
O::keys
O::values
O::prop
从对象和关联数组中读取给定键的给定值。如果没有找到,则返回 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
Functions
F::complement
以这种方式包装传入的函数:当调用它时,它反转返回值
function isOdd(int $n):boolean { return $n % 2 === 1; } $isEven = F::complement(isOdd);
Concepts
每种方法都遵循以下规则(或者至少应该是这样的。如果不符合,那么1)欢迎提交PR,2)欢迎提交Issue)
无状态
每个方法应该从参数中获取所有必要的信息,而不应依赖于任何外部参数或状态
静态
由于每个方法都是无状态的,因此没有必要创建类实例
纯净
不使用除了传入的参数之外的任何内容
不可变
不会改变任何参数,没有引用或类似的东西
最后一个参数应该是你正在处理的数据...
就像在Lodash FP或Ramda中一样
除非参数列表有可选参数!
欢迎提出关于放置可选参数的建议
不对参数进行任何验证
如果你正在使用来自 A 的方法,那么你最好发送一个数组给它。PHP是一种弱类型语言,你可能需要花费整天的时间验证输入参数。
不对任何输入参数进行类型转换
这与验证相同,你应该在将数据传递给函数之前检查数据
只做一件明确的事情
小而美,易于维护——并且可能更容易在我有动力为这个库编写测试的时候进行测试
错误时返回null
当发生错误并且底层PHP函数返回false时(例如,end或strpos),它将被规范化为null
驼峰命名法
纯数字数组最好通过A中的方法处理,而关联数组和对象则通过O中的方法处理。
未来计划
我会随着需要添加方法,所以如果你缺少一个你可能会使用的方法,那么1)欢迎提交PR,2)欢迎提交Issue。
如果所有方法都是静态和无状态的,那么为什么不只写简单的函数呢?
存在多个具有相同参数签名的函数,但操作不同的参数类型。为了避免在每次函数调用时检查参数类型,我选择根据它们操作的类型将函数命名空间化为静态方法。
例如,看看Array和String的includes实现。它们的实现相对简单,因为它们的类型预期分别是数组和字符串(类型提示即将到来)。
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; } }
如果我要有一个单一的、合并的includes函数,那么我必须每次都进行类型检查,这将使代码变得不必要地嘈杂。
此外,有时我想使用的函数名已经被PHP占用,比如在S::split的情况下
致谢
https://www.geeksforgeeks.org/php-startswith-and-endswith-functions/ - 用于S::startsWith()和S::endsWith()
https://stackoverflow.com/a/173479/1806628 - 用于A::isAssoc()
https://php.ac.cn/manual/en/function.array-unique.php#116302 - 用于A::uniqByKey()