mlg/shovel

A PHP 库,用于操作数组、字符串和对象 - 受 ramda.js 启发

v3.4.1 2023-07-08 20:49 UTC

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()