8fold/php-shoop

此包已被弃用且不再维护。没有建议的替代包。

用于创建与原始数据类型交互的更通用API的包装器。

0.10.19 2021-07-14 02:02 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类型

  1. 必须根据它们什么或什么来自定义,而不是根据它们不是什么来定义。
  2. 必须合理地与其他所有Shoop具体类型(除对象外)进行转换。
类型名称 抽象类型(s) 定义
布尔值 内容 与PHP相同
数字 '' 所有实数
整数 '' 所有整数
字符串 '' 与PHP相同
字典 集合,列表 具有字符串键的PHP数组(关联数组)
数组 集合,列表 具有从0开始的顺序索引的整数索引的PHP数组(索引数组)
元组 集合 仅包含非空公共属性的PHP对象
JSON 内容,集合 请参阅字符串和元组
对象 对象 请参阅元组,至少有一个定义的公共方法 - 或实现Shoop类型接口

注意:Shoop类型不是作为PHP类实现的,而是对您实现的PHP类型的解释。Shoop类型简化了类型转换、转换和应用过滤器。

过滤器

过滤器是继承自Shoop抽象过滤器并实现该继承所需接口方法的PHP类。

过滤器通常充当Shoopland和PHP之间的桥梁。它们可以被视为用于操作PHP类型的低级函数。要成为过滤器,必须至少满足以下三个条件之一

  1. 在至少一个或多个生产项目中使用至少三次,这可能包括Shoop本身。例如:Reversed::fromBoolean
  2. 全面测试所提出的过滤器结果会导致测试多个其他过滤器。例如:IsEmpty::fromTuple
  3. 建议的过滤器不需要测试,因为它直接使用PHP。例如:IsEmpty::fromString
  4. 建议的过滤器不需要测试,因为它使用已批准的过滤器,这些过滤器不需要测试。例如:AsBoolean::*

注意:如果第四项被用作批准建议过滤器的原因,它应该有具体的原因。例如,AsBoolean过滤器简化了对该具体类型的操作,并返回IsNotEmpty的结果,IsNotEmpty返回Reversed的IsEmpty结果。AsBoolean和IsNotEmpty都不需要测试;然而,这是一种对大多数PHP类型实现PHP布尔类型转换操作的DRY方法。

遵守上述要求,以下要求应自动满足

  1. 建议的过滤器必须进行全面测试,直接或间接。

注意:在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 不使用也不考虑 nullfalse 也等于零,它代表无意义。零代表无意义的观点是 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():删除所有方法,保留私有属性