8fold / php-shoop
用于创建与原始数据类型交互的更通用API的包装器。
Requires
- php: ~7.4|^8.0
- 8fold/php-foldable: ~1.3.1
- nikic/iter: ^2.0
Requires (Dev)
- phpunit/phpunit: ~9.1
- dev-main
- 0.10.19
- 0.10.18
- 0.10.17
- 0.10.16
- 0.10.15
- 0.10.14
- 0.10.13
- 0.10.12
- 0.10.11
- 0.10.10
- 0.10.9
- 0.10.8
- 0.10.7
- 0.10.6
- 0.10.5
- 0.10.4
- 0.10.3
- 0.10.2
- 0.10.1
- 0.10.0
- 0.9.1
- 0.9.0
- 0.8.0
- 0.7.1
- 0.7.0
- 0.6.10
- 0.6.9
- 0.6.8
- 0.6.7
- 0.6.6
- 0.6.5
- 0.6.4
- 0.6.3
- 0.6.2
- 0.6.1
- 0.6.0
- 0.5.4
- 0.5.3
- 0.5.2
- 0.5.1
- 0.5.0.x-dev
- 0.5.0
- 0.4.10
- 0.4.9
- 0.4.8
- 0.4.7
- 0.4.6
- 0.4.5
- 0.4.4
- 0.4.3
- 0.4.2
- 0.4.1
- 0.4.0
- 0.3.14
- 0.3.13
- 0.3.12
- 0.3.11
- 0.3.10
- 0.3.9
- 0.3.8
- 0.3.7
- 0.3.6
- 0.3.5
- 0.3.4
- 0.3.3
- 0.1.0
- dev-sort+php8
- dev-working
- dev-yaml
This package is auto-updated.
Last update: 2023-03-05 22:31:41 UTC
README
Shoop是一个水平一致的PHP接口,而PHP可以描述为一个垂直一致的C接口。
Shoop建立在8fold Foldable之上,允许普遍构建数据对象。
安装
composer require 8fold/php-shoop
用法
构造示例...实时示例即将推出,可在测试文件夹中找到。
应用单个过滤器。
Apply::plus(1)->unfoldUsing(2); // indirect call to output: 3 Plus::applyWith(1)->unfoldUsing(2); // direct call to output: 3
管道多个过滤器。
Shoop::pipe(2, Apply::plus(1), Apply::divide(1) )->unfold(); // output: 3
嵌套管道和过滤器。(PlusAt
过滤器的部分变体。)
Shoop::pipe([1, 2, 3], Apply::from(0, 1), // output: [1, 2] Apply::plus("hello"), // output: [1, 2, "hello"] Apply::plus( Apply::from(1)->unfoldUsing([1, 2, 3]) // output: [3] ) )->unfold(); // output [1, 2, "hello", 3]
使用方法链的流畅性。
Shoop::this(2)->plus(1)->divide(2)->unfold(); // output 1.5 Shooped::fold(2)->plus(1)->divide(2)->unfold(); // output: 1.5
类型和类型转换
Shoop定义了抽象和具体类型。
抽象Shoop类型直接映射到PHP类型。
类型名称 | PHP类型(s) |
---|---|
内容 | PHP布尔值、浮点数、整数和字符串。 |
集合 | PHP数组、stdClass、没有公共方法的对象。 |
列表 | PHP数组。 |
顺序 | PHP索引数组、整数、字符串。 |
对象 | 只能从中转换,而不能到转换的PHP对象。 |
具体Shoop类型
- 必须根据它们是什么或像什么来自定义,而不是根据它们不是什么来定义。
- 必须合理地与其他所有Shoop具体类型(除对象外)进行转换。
类型名称 | 抽象类型(s) | 定义 |
---|---|---|
布尔值 | 内容 | 与PHP相同 |
数字 | '' | 所有实数 |
整数 | '' | 所有整数 |
字符串 | '' | 与PHP相同 |
字典 | 集合,列表 | 具有字符串键的PHP数组(关联数组) |
数组 | 集合,列表 | 具有从0开始的顺序索引的整数索引的PHP数组(索引数组) |
元组 | 集合 | 仅包含非空公共属性的PHP对象 |
JSON | 内容,集合 | 请参阅字符串和元组 |
对象 | 对象 | 请参阅元组,至少有一个定义的公共方法 - 或实现Shoop类型接口 |
注意:Shoop类型不是作为PHP类实现的,而是对您实现的PHP类型的解释。Shoop类型简化了类型转换、转换和应用过滤器。
过滤器
过滤器是继承自Shoop抽象过滤器并实现该继承所需接口方法的PHP类。
过滤器通常充当Shoopland和PHP之间的桥梁。它们可以被视为用于操作PHP类型的低级函数。要成为过滤器,必须至少满足以下三个条件之一
- 在至少一个或多个生产项目中使用至少三次,这可能包括Shoop本身。例如:Reversed::fromBoolean
- 全面测试所提出的过滤器结果会导致测试多个其他过滤器。例如:IsEmpty::fromTuple
- 建议的过滤器不需要测试,因为它直接使用PHP。例如:IsEmpty::fromString
- 建议的过滤器不需要测试,因为它使用已批准的过滤器,这些过滤器不需要测试。例如:AsBoolean::*
注意:如果第四项被用作批准建议过滤器的原因,它应该有具体的原因。例如,AsBoolean过滤器简化了对该具体类型的操作,并返回IsNotEmpty的结果,IsNotEmpty返回Reversed的IsEmpty结果。AsBoolean和IsNotEmpty都不需要测试;然而,这是一种对大多数PHP类型实现PHP布尔类型转换操作的DRY方法。
遵守上述要求,以下要求应自动满足
- 建议的过滤器必须进行全面测试,直接或间接。
注意:在Shoop中,对象是可完全指定的PHP类型,因为你可以实现它们的表示为任何Shoop支持的PHP类型。
与PHP的差异
为了使Shoop易于采用,我们尽量不偏离您习惯的内容。以下详细说明了已知与PHP标准行为的差异
Shoop | PHP |
---|---|
您可以转换(转换)任何Shoop类型到任何其他Shoop类型,除了对象。 | 某些转换会导致错误;例如,数组转换为字符串。 |
接口用于定义自定义类型或实现该接口的实例的PHP类型表示。 | PHP在这方面有限制:请参阅Stringable和JsonSerializable接口以及一些魔术方法。 |
布尔值作为字符串返回其英文等价物。例如:"true" | PHP将其转换为整数,然后对结果进行字符串化。例如:"1" |
布尔值作为字典返回一个包含true和false值的两个键字典。 | PHP使用布尔值作为第一个索引。 |
布尔值作为数组使用字典的数组值,其中0包含false的值。 | PHP区分字典和数组。 |
JSON可以转换为字典,但不能以这种方式创建。转换是非递归的;因此,内部对象仍然是对象。字符串表示必须以开括号和闭括号开始和结束。 | PHP的json_decode()函数可以返回一个PHP关联数组,其中内部对象被转换为关联数组。字符串表示可以开始和结束于开方括号和闭方括号。 |
性能
我们编写的每个测试都涵盖了速度和内存使用的性能。
在速度方面,我们的目标是每个过滤器都可以在不到一毫秒(在我们的测试中代表为1.0)的时间内应用和返回。
我们的速度检查起点为1微秒(在我们的测试中为0.001)。如果一个测试超过1微秒,但不到5微秒,我们将速度检查增加1微秒(例如,0.001001变为0.002),同时将较短的运行作为注释说明。如果一个测试超过5微秒(0.005),我们切换到10微秒间隔(例如,0.005变为0.01)。在其他所有情况下,我们将结果四舍五入到最接近的10微秒标记(例如,0.010001变为0.02)。
在内存方面,我们的目标是每个过滤器都可以在不到1千字节(在我们的测试中为1)的内存中使用来应用和返回。这将在一定程度上取决于传递给过滤器的参数。我们的测试使用最小的值传递来测量,以验证测试产生预期的结果。
我们的测试也是使用过滤器模式编写的
AssertEquals::applyWith( // expected return value // expected return type // max time allowed in milliseconds // max memory allowed in kilobytes )->unfoldUsing( // the filter being tested );
项目
目标
Shoop 的主要目标,不分先后顺序:
- 使用浅显易懂的语言(易于接近):PHP 对那些可能没有计算机科学背景的新开发者来说相当容易接近;Shoop 继续延续这一主题。
- 语法和语义轻量级:PHP 在语法上(用于帮助解析器的特殊字符)和功能上(简短的功能名,但很多)上可以理解地较为庞大。我们根据生产需求审查过滤器和功能,而不是根据直觉和“因为我们能做”。
- 不可变:尽可能返回新实例和值,而不是改变状态。
- 类型安全:Shoop 的灵活性意味着我们不必在每一步都检查类型,但在返回请求的结果之前检查类型。
- 延迟处理:尽可能延迟处理,直到最后一刻。
- 类型间的普遍性:我们倾向于使用少量过滤器,然后通过参数进行最小配置。
- DRY(不要重复自己):我们努力利用 Shoop 中已有的功能,而不是实现 PHP 解决方案;大多数过滤器都是通过开发不同的过滤器而产生的。
- 让什么都不代表什么:作为开发者,我们花了大量时间解释、规避和防御那些代表无意义的东西。
null
可能是代表无意义最广为人知的东西,Shoop 不使用也不考虑null
。false
也等于零,它代表无意义。零代表无意义的观点是 Shoop 数组从一开始的根本论点,因为请求“无意义索引”应该总是得到无意义的结果...某物不能被无意义所包含。
PHP 经常被批评的是其不一致的 API。PHP 的创造者,Rasmus 这样解释过(概括):
PHP 完全一致,只是不是你预期的样子。它是垂直一致的。所以,对于 PHP 中的每一个函数,如果你看看它的下面,比如某些字符串函数下的
libc
函数,参数顺序和命名与它们所基于的东西相匹配。所以,在水平上没有一致性,但在垂直上挖掘到堆栈中是完美的。
如果你使用 Laravel 的 Illuminate Support 部分的类或一些 Symfony Components,你熟悉了对水平一致 API 的需求问题(甚至超出 PHP 本身)。
虽然这种实现是针对特定语言的,但基本概念、模式和命名都力求语言无关。
PHP 对于新开发者来说非常 简单,Shoop 继承了这一传统。
贡献
有关更多一般性建议,请参阅我们的 贡献 文档。
名字的含义
作为缩写词,Shoop 指的是库的灵感来源
- Swift、Smalltalk 和 Squeak;
- Haskell(函数式编程和不可变性);
- 面向对象(封装、组合和通信);以及
- 过程式(顺序、逻辑编程)。
作为单词,Shoop 与“photoshopping”相似(听起来比“Foops”好)。
Shoop这个名称,是Salt-N-Pepa在1993年发布的一首歌曲的标题,并在2016年用于《死侍》系列电影的第一部。
其他
历史
这个库从2019年初开始开发,并于2019年中开始被大多数8fold项目使用。每当创建一个新项目时,我们都试图不用它,但发现这让我们感到烦恼,因此我们决定将其转变为一个更正式的项目和库,供他人使用。
我们提供了多个接口和默认实现,以便在支持的具体类型之间进行操作。每个接口提供两种方法:一种返回实现该接口的对象,另一种返回PHP类型。前者以"as"为前缀,用于Fluent接口。后者以"ef"为前缀,可以看作类似于以双下划线(__
)为前缀的PHP魔术方法。
?? - 抽象和具体过滤器 - ??
- 抽象过滤器使用其他过滤器来生成输出。
- 具体过滤器不使用其他过滤器来生成输出。
PHP偏差
布尔值
Shoop | Shoop结果 | PHP等价 | PHP结果 |
---|---|---|---|
TypeAsInteger::apply()->unfoldUsing(false) |
0 | count(false) |
PHP警告 |
TypeAsString::apply()->unfoldUsing(false) |
"false" | (string) false |
"0" |
TypeAsArray::apply()->unfoldUsing(false) |
[0 => true, 1 => false] | (array) false |
[0] => false |
TypeAsDictionary::apply()->unfoldUsing(false) |
["false" => true, "true" => false] | '' | '' |
TypeAsTuple::apply()->unfoldUsing(false) |
object(["false"] => true, ["true"] => false) | (object) false |
object(["scalar"] => false) |
数字和整数
Shoop | Shoop结果 | PHP等价 | PHP结果 |
---|---|---|---|
TypeAsInteger::apply()->unfoldUsing(2) |
2 | count(2) |
PHP警告 |
TypeAsArray::apply()->unfoldUsing(2) |
[0 => 0, 1 => 1, 2 => 2] | (array) 2 |
[0 => 2] |
TypeAsDictionary::apply()->unfoldUsing(2) |
["i0" => 0, "i1" => 1, "i2" => 2] | '' | '' |
TypeAsTuple::apply()->unfoldUsing(2) |
object(["i0" => 0, "i1" => 1, "i2" => 2]) | (object) 2 |
object(["scalar" => 2]) |
数组转换为元组应该是PHP的默认行为吗?这减少了偏差。
字符串
Shoop | Shoop结果 | PHP等价 | PHP结果 |
---|---|---|---|
TypeAsInteger::apply()->unfoldUsing("Hi!") |
3 | (int) "Hi!" |
0 |
TypeAsInteger::apply()->unfoldUsing("Hi!") |
3 | count("Hi!") |
PHP警告 |
TypeAsArray::apply()->unfoldUsing("Hi!") |
[0 => "H", 1 => "i", 2 => "!"] | (array) "Hi!" |
[0 => "Hi!"] |
TypeAsDictionary::apply()->unfoldUsing("Hi!") |
["content" => "Hi!"] | '' | '' |
TypeAsTuple::apply()->unfoldUsing("Hi!") |
object(["content" => "Hi!"]) | (object) "Hi!" |
object(["scalar" => "Hi!"]) |
字典、元组和JSON
字典和元组在偏差方式上与PHP相似,语法可能不同。
Shoop | Shoop结果 | PHP等价 | PHP结果 |
---|---|---|---|
TypeAsInteger::apply()->unfoldUsing(["a" => 1, "b" => 2]) |
2 | (int) ["a" => 1, "b" => 2] |
1 |
TypeAsInteger::apply()->unfoldUsing(["a" => 1, "b" => 2]) |
2 | count(["a" => 1, "b" => 2]) |
2 |
TypeAsString::apply()->unfoldUsing(["a" => 1, "b" => 2]) |
"", 可配置 | (string) ["a" => 1, "b" => 2] |
PHP Notice: Array to string... |
TypeAsArray::apply()->unfoldUsing(["a" => 1, "b" => 2]) |
[0 => 1, 1 => 2] | (array) ["a" => 1, "b" => 2] |
["a" => 1, "b" => 2] |
注意:JSON字符串被转换为元组,元组被转换为字典。
注意:PHP JsonSerialize接口的默认实现会导致PHP类型被转换为Shoop元组,然后进行编码。
数组
Shoop | Shoop结果 | PHP等价 | PHP结果 |
---|---|---|---|
TypeAsInteger::apply()->unfoldUsing(["a", "b"]) |
2 | (int) ["a", "b"] |
1 |
TypeAsString::apply()->unfoldUsing(["a", "b"]) |
"ab", 可配置的 | (string) ["a", "b"] |
PHP Notice: Array to string... |
TypeAsDictionary::apply()->unfoldUsing(["a", "b"]) |
["i0" => "a", "i1" => "b"] | (array) ["a", "b"] |
["a", "b"] |
TypeAsTuple::apply()->unfoldUsing(["a", "b"]) |
object(["i0"] => "a", ["i1"] => "b") | (object) ["a", "b"] |
object(["0"] => "a", ["1"] => "b") |
数组转换为元组是否应该是PHP的默认行为?这减少了偏差。访问这些属性时表现不佳。
例如:$object = object(["0" => 1, "1" => 2])
- $object->0 = 解析错误
- $object->"0" = 解析错误
- $object->{0} = 预期行为
对象
例如:$using = new class {}
Shoop | Shoop结果 | PHP等价 | PHP结果 |
---|---|---|---|
TypeAsBoolean::apply()->unfoldUsing($using) |
false:IsEmpty的相反数,可以重写 | (bool) $using |
true,不能重写 |
IsEmpty::apply()->unfoldUsing($using) |
true:TypeAsInteger的布尔值,可以重写 | empty($using) |
false,不能重写 |
`TypeAsInteger::apply()->unfoldUsing($using) | 0(公共属性的计数),可以重写 | (int) $using |
PHP Notice... |
`TypeAsNumber::apply()->unfoldUsing($using) | 0.0(公共属性的计数) | (float) $using |
'' |
`TypeAsString::apply()->unfoldUsing($using) | ""(连接的字符串属性),可以重写 | (string) $using |
'' |
`TypeAsArray::apply()->unfoldUsing($using) | [] | (array) $using |
[] |
`TypeAsTuple::apply()->unfoldUsing($using) | object():删除所有方法和私有属性 | (object) $using |
object():删除所有方法,保留私有属性 |