celestriode / constructure
用于验证任何潜在类型的结构的容器,例如 JSON 或 NBT。
Requires
- php: ^7.3|^8.0
Requires (Dev)
- phpunit/phpunit: ^9.5
README
Constructure 库是一个通用模板,它标准化了由此派生的结构验证库。它具有很少的功能,更注重描述所有扩展库应遵循的最基本内容。
扩展库
以下库使用 Constructure 来验证特定数据结构
- JSON Constructure
- NBT Constructure (WIP)
Constructure 用于什么?
从用户输入中进行结构评估和验证,例如用户提交的 JSON 或 SNBT 字符串应该遵循高度特定的结构。由于这侧重于用户输入,而用户输入往往是字符串,因此通常需要与扩展库一起提供解析器,具体取决于正在验证的结构语言。
基本扩展
AbstractConstructure 对象
首先,你应该扩展这个类。它包含一个预设的 validate() 方法来比较两个结构(输入和预期结构)。当你实例化对象时,必须提供一个事件处理器。事件处理器由库提供,但你可以自由扩展它或通过 EventHandlerInterface 实现自己的。
必须向扩展的 Constructure 类添加一个方法:toStructure(),它将接受某种形式的输入(通常是字符串)并将其转换为 Constructure 定义的架构。那个结果结构必须是 StructureInterface 的实例。
$constructure = new class(new EventHandler()) extends AbstractConstructure { public function toStructure($input): StructureInterface { // Put code to transform the input into a Constructure structure. } };
结构
所有结构都需要实现 StructureInterface 或扩展 AbstractStructure 类,该类实现了大多数必需的方法。默认情况下,Constructure 不对数据结构的结构做出太多假设。使用 JSON Constructure 库,每个数据类型都是其自己的结构。它还包括其他专用结构来定义预期结构,例如用户定义键的占位符和递归结构的重定向。
AbstractStructure 不提供的方法是 toString(),你可以用它将 StructureInterface 转换回字符串。如果用户输入字符串,这应该能够还原到相同的字符串。由于 JSON Constructure 库对每个数据类型使用结构,这意味着你可以使用 toString() 方法为用户输入的不正确部分提供上下文。
$structure = new class extends AbstractStructure { public function toString(PrettifierInterface $prettifier = null): string { return ""; } };
你需要两个结构来进行验证:一个是由用户输入并使用 $constructure->toStructure() 构建的,另一个是描述用户输入必须匹配的结构。那个预期结构应该事先构建。使用 $constructure->validate() 方法进行比较。
结构验证使用审计,而反馈使用事件处理。
美化
当使用 toString() 方法时,可以提供自定义美化器以将结构转换为更漂亮的字符串(例如,向压缩的 JSON 结构添加换行符和制表符)。此库旁边没有提供美化类。
事件处理
事件可以在验证过程中触发。事件做什么完全取决于您,因为事件是一个纯粹的可调用对象。您可以使用EventHandlerInterface接口创建自己的事件处理器,或者使用预先制作的EventHandler类。给到$constructure对象的事件处理器实例将包含所有必要的事件。
可以使用addEvent()方法添加事件。匿名函数的可选输入取决于触发器,因此请确保您的事件与触发器匹配。可以添加具有相同名称的多个事件,并且它们将同时触发。
$eventHandler = (new EventHandler())->addEvent("event_name", function ($input1, $input2, $input3) { // Do something here. });
触发事件涉及在trigger()方法中调用事件名称,并根据需要提供任何输入。
$eventHandler->trigger("event_name", 1, "two", 3);
您可以在SampleEvents中找到示例事件函数。
事件静音
事件处理器可以被静音以防止触发事件,就像这些事件不存在一样。这用于运行断言,这些断言应该是静默的。
事件处理器的mute()方法将静音它,而unmute()将取消静音(而silent()将返回是否静音)。
事件捕获
与其完全忽略事件,不如捕获它们并确定是否应该忽略它们可能更有用。例如,BitwiseAudits 审计使用事件捕获来延迟触发事件,直到确定审计失败。
当使用capture()启用捕获时,任何触发的事件将被暂时搁置。完成捕获后,您执行以下两种操作之一:
- 使用
clear()方法,它将清除所有捕获的事件而不会运行它们。 - 使用
release()方法,它将禁用捕获并运行捕获期间捕获的事件。
捕获的事件通过EventHandler::CAPTURED_RELEASED默认事件触发。作为一个默认事件,您可以在释放时更改捕获事件的处理方式。
默认事件
默认事件是任何EventHandler实例默认包含的事件。使用new EventHandler(false)实例化事件处理器将禁用默认事件的包含。
目前,唯一的默认事件是用于释放捕获的事件。如果您想获取该特定事件的函数,可以使用getReleaseCapturedEvent()静态方法。
审计
审计是验证输入结构的程序。为了使输入结构被认为是正确的,必须在将其与预期结构进行比较时,使所有审计通过输入。
所有审计都必须实现AuditInterface,尽管有一个提供大多数功能的AbstractAudit类。您必须实现2个方法
audit(),它将检查输入结构,并返回true或false,具体取决于输入是否根据审计正确。Constructure对象也提供,它提供对事件处理器的访问,以及额外的验证预期结构。getName(),它返回审计的友好名称,可以显示给最终用户,如果基于失败(或成功)的审计提供反馈。
$audit = new class extends AbstractAudit { public function audit(AbstractConstructure $constructure, StructureInterface $input, StructureInterface $expected): bool { return true; } public static function getName(): string { return "user-friendly name of the audit"; } };
审计应根据对输入结构的发现触发事件。事件可用于日志记录或用户反馈。
默认情况下,AbstractStructure 类在比较过程中运行审计,不做其他操作。如果没有审计,输入和预期结构将被视为完美匹配。有关审计的更多示例,请参阅JSON Constructure库。
提供了一个示例 IsNumber 审计,展示了如何使用审计进行输入验证和事件触发。但通常不应使用它。然而,还有四个其他审计可供使用
AlwaysTrue,这是一个始终通过的审计。AlwaysFalse,这是一个始终失败的审计。TriggerEvent,它与其说是审计,不如说是事件注入器。这是一个延迟审计,它在非延迟审计之后运行。这可以在审计后分析结构时很有用。BitwiseAudits,它接收一个运算符(OR、XOR或AND)和一组审计,然后根据这组审计的结果执行运算。这个审计将根据该操作通过或失败,而不是根据每个单独的审计通过或失败。因为默认情况下审计就是按照这种方式工作的,所以不包括AND。
谓词
谓词是一个审计,作为必须通过另一个审计才能运行的条件。谓词运行时静默,不触发事件,并返回谓词是否通过。如果谓词失败,主审计将不会运行(因此不会用于通过或失败输入结构)。如果它通过了,主审计将随后运行。
可以使用addPredicates()或addPredicate()将谓词添加到审计实例中。AbstractStructure类(不是AbstractAudit)将处理谓词,所以如果你正在创建自己的StructureInterface实现,务必也要处理它。