evo / ejinn
eJinn 异常精灵
Requires
- php: >=8.3
- evo/exception: @stable
This package is not auto-updated.
Last update: 2024-10-01 16:18:40 UTC
README
异常精灵(PHP v8.3+)
概述
-
你是否喜欢每个异常类只有一个异常代码?
-
你是否喜欢使用接口来组织异常类?
-
你是否喜欢在你的整个库或项目中拥有唯一的错误代码?
-
你是否厌倦了追踪所有那些令人烦恼的异常代码?
-
你是否厌倦了创建那些无聊的样板异常和接口类?
-
你是否厌倦了寻找合适的异常,因为你记不起是否为这个或那个错误创建了一个?
如果你对上述任何一个问题回答了“是”,那么 eJinn 可能正是你所需要的。《eJinn》基于简单的灵活配置文件构建异常类和接口。《eJinn》允许你根据逻辑和易于阅读的配置来组织所有的 异常类 和 错误代码。
eJinn 为你构建 PHP 异常和接口类文件。《eJinn》不需要上传到你的实时服务器,只需上传它创建的异常即可!
以下是一个 eJinn 配置示例(作为一个 PHP 数组)。
return [ "author" => "ArtisticPhoenix", "buildpath" => ["psr"=>4], "description" => "eJinn The Exception Genie", "package" => "eJinn", "subpackage" => "", "support" => "https://github.com/ArtisticPhoenix/eJinn/issues", "version" => "1.0.0", "namespaces" => [ //{key:Namespace} => [{value:Namespace tier}] "eJinn\\Exception" => [ "subpackage" => "Exception", "interfaces" => [ //{key:numeric} => {value:Base class name OR Interface Tier} "eJinnExceptionInterface" ], "exceptions" => [ //{key:Error_Code} => {value:Base class name OR Excption Tier} 0 => "UnknownError", 1001 => "ResservedCode", 1002 => "UnknownConfKey", 1003 => "DuplateCode", 1004 => "MissingRequired", 1005 => "KeyNotAllowed", 1006 => "KeyNotAllowed", 1007 => "ConfCollision", 1100 => [ "name" => "JsonParseError", "reserved" => [[1101,1108]], //reserve range [1101-1108] @see: (json_last_error() + 1100) ] ]//end exceptions ]//end namespace "eJinn\Exception" ] //end namespaces ];//end config
eJinn 的配置基于一个基于 多级 的树,具有 自顶向下继承。这使我们能够在保持最小配置大小的同时具有最大的灵活性。属性从较高级别(父级)传递到较低级别(子级)。如果子级和父级都存在该属性,则使用子级的属性值。
配置可以是上面显示的 PHP 数组那么简单,或者你可以使用 \eJinn\Reader\{Type}
类将许多不同类型的文件转换为 PHP 数组,然后将其传递给 \eJinn\eJinnParser
类。设计 Reader 以可扩展,因此如果您喜欢的配置文件类型没有此类,请告诉我们!
配置架构
eJinn 中的属性匹配不区分大小写,因此名为 "version" 的属性将匹配(但不仅限于)以下内容:"VERSION","Verison" 和 "vErSiOn"。
配置的主要级别是 全局、命名空间 和 实体(接口或异常)。全局级别是配置数组的顶层。《命名空间》是包含在 namespaces 属性中的数组。《接口》和《异常》级别包含 eJinn 将创建的接口和异常类的最终定义。大部分时间我们将它们统称为《实体》级别。它们有很多相似之处,但也有一些显著的区别。接口比异常简单,因此包含的配置属性较少。一般来说,接口中任何额外的配置属性都将被忽略。这些将在《接口》级别显示,并在《必需》列中标记为“忽略”。
除了上述容器属性外,每个级别可以包含其上方任何级别的属性。但是,它不能包含其下方任何级别的属性。《实体》级别可以包含任何配置属性,但《全局》级别只能包含其内部定义的属性。
容器属性在下面的“必需”列中用 protected 标记。如上所述,这些属性 必须 在所示层级存在,不能放置在配置层次结构的任何其他地方。eJinn 将抛出异常并通知您配置中是否存在任何问题。
内部属性在下面的“必需”列中用 private 标记。通常,这些属性在 \eJinn\eJinnParser
类外部不可访问,这里仅为了完整性和文档目的而展示。它们 不应 包含在任何配置中。
注释属性是以 _
开头的属性。在解析过程中,这些属性(及其子属性)将被删除(忽略)。这很有用,因为它允许您在不实际删除它们的情况下排除配置的一部分。您也可以通过像这样 _coment = "eJinn 是我见过的最酷的东西"
的方式在配置中留下开发备注。
全局层级
命名空间层级
异常层级
接口层级
- buildpath 对 buildpath 属性的一些特殊考虑
- 默认值是正在处理的配置文件的当前位置。
- 如果以编程方式运行 eJinn,则这是
eJinn\eJinnParser::parse()
的第二个参数。 - 在覆盖时,这可以是相对于父层级 buildpath 的路径,或者是一个绝对路径。与其他属性简单替换不同,buildpaths 在相对路径时被附加,在绝对路径时被替换。相对路径 不应 以
/
开头。在基于 Unix 的系统上,绝对路径应以/
开头,在 Windows 上以驱动器字母c:\
开头(在 Windows 上/
或\
都是可接受的)。 - buildpath 可用一些 特殊 值,这些值专门为自动加载器设计,且为数组,而不是通常与该属性关联的字符串类型。您可能在示例配置中注意到这一点。例如
["psr" => 0]
和["psr" => 4]
。使用任一值时,当前 buildpath(在该层级)的值将附加以下命名空间,具有以下考虑:- 对于
["psr" => 0]
:类名中的任何_
下划线将被替换为目录分隔符。对于命名空间中的_
下划线不进行特殊处理。 - 对于
["psr" => 4]
:不对_
下划线进行特殊处理。
- 对于
- 文件路径必须存在,并且应该由当前用户运行的 PHP 可写。
简短的 buildpath 示例
$config = [ "buildpath" => "/home/app", //root path overide with absolute path "namespaces" => [ "Models\\Users\\Exceptions" => [ "buildpath" => ["psr" => 4], "exceptions" => [ 100 => "UnknownUser", 101 => "InvalidPasword", ] ], "Models\\Products\\Exceptions" => [ "buildpath" => "Models/Products/Exceptions", "exceptions" => [ 200 => "UnknownProduct", ] ] ] ];
这两个路径大致相同,它们将生成以下文件。
- /home/app/Models/Users/Exceptions/UnknownUser.php (类 \Models\Users\Exceptions\UnknownUser errorCode 100)
- /home/app/Models/Users/Exceptions/InvalidPasword.php (类 \Models\Users\Exceptions\InvalidPasword errorCode 101)
- /home/app/Models/Products/Exceptions/UnknownProduct.php (类 \Models\Users\Exceptions\UnknownProduct errorCode 200)
完整的 PSR-4 示例 (假设配置文件位于 /home/app
),这与上面的示例等效。
$config = [ "buildpath" => ["psr" => 4], "namespaces" => [ "Models\\Users\\Exceptions" => [ "exceptions" => [ 100 => "UnknownUser", 101 => "InvalidPasword", ] ], "Models\\Products\\Exceptions" => [ "exceptions" => [ 200 => "UnknownProduct", ] ] ] ];
简短的 PSR-0 示例 (假设配置文件位于 /home/app
)
$config = [ "buildpath" => ["psr" => 0], "namespaces" => [ "Models\\Users" => [ "exceptions" => [ 100 => "Exception_UnknownUser", 101 => "Exception_InvalidPasword", ] ] ] ];
这将创建以下两个类。
- /home/app/Models/Users/Exception/UnknownUser.php (类 \Models\Users\Exception_UnknownUser errorCode 100)
- /home/app/Models/Users/Exception/InvalidPasword.php (类 \Models\Users\Exception_InvalidPasword errorCode 101)
PSR-0 允许使用更短的命名空间,但仍然在文件级别上提供分离。这可以在一定程度上使编码更容易,因为不需要太多的 use
语句。但这也稍微让人有点困惑,不知道文件放在哪里。我个人更喜欢 PSR-4。例如,命名空间只是 Models\Users
,所以在使用 Models\Users\User
类时,你不需要为这些异常包含 use
标签,但是路径中的 _
被替换,使得异常放在各自的子目录中。调用它们时,你会这样做 throw Excption_UnknownUser()
而不是像 PSR-4 示例中的 throw UnknownUser
。然而,PSR-4 示例也需要为每个异常类添加这个 use Models\Users\Exceptions\{ClassName};
。话虽如此,你也可以在 PSR-4 中这样做 use Models\Users\Exceptions as Exception;
并使用别名,然后像这样抛出它们 throw Exeption\UnknownUser()
,这是我的首选方式。
构建选项
预读取
预读取是指打开配置文件并将其转换为上面给出的数组结构。 eJinn 解析器类只理解上面的 PHP 数组结构。通过将其分离成独立的步骤,eJinn 可以使用任何可能的配置文件类型。
解析
解析是指将 PHP 配置数组进行处理。这个步骤的主要任务是展开继承树,并将所有相关值分配给异常或接口实体。在这个步骤中,我们还会检查配置中的各种错误。
编译
编译是指为配置文件中发现的实体创建实际的 PHP 类文件。
锁定
锁定是指创建一个 eJinn.lock 文件,这可以防止多个进程同时运行解析器/编译器。当编译过程完成后,此文件将被删除。
缓存
缓存是指创建一个 eJinn.cache 文件,该文件存储配置文件的引用或哈希。这个哈希用于在编译之间检查配置文件是否有所更改。如果没有更改,解析器将不会编译配置。你可以删除 .cache 文件来强制 eJinn 重新编译所有实体。你也可以设置运行时选项 forcereCompile 为 true
以使解析器重新编译配置。
异常考虑因素
不深入细节,我将简要说明为什么使用唯一的异常是有益的。一个明显的例子是
//Catch a single exception ( based on class ) try{ throw \Excption("some really verbose message"); }catch(\Exception $e ){ echo $e->getMessage(); //prints "?" }
这可能是我能想到的最糟糕的异常示例。你几乎无法通过它知道它抑制了哪个异常,或者如果它是可接受的错误或致命错误,你应该做什么。这是一个略微改进的版本
//Catch a single exception ( based on class ) try{ throw \Excption("some really verbose message", 100); }catch(\Exception $e ){ if($e->getCode() == 200 ) echo $e->getMessage(); //prints "?" else throw \Excption("rethrow", $e->getCode(), $e); }
这仍然没有给我们提供太多关于捕获和忽略错误的选项。一个更好的方法是像这样
//Catch a single exception ( based on class ) try{ throw \Excption("some really verbose message", 100); }catch(\eJinn\Exception\ResservedCode $e ){ //catch only class \eJinn\Exception\ResservedCode }catch(\eJinn\Exception\eJinnExceptionInterface $e ){ //catch any class that implements \eJinn\Exception\eJinnExceptionInterface }
现在我们对自己的错误处理有了非常精细的控制。我们可以只捕获我们想要的错误,并且我们可以根据不同的 catch
块处理一系列错误。这种错误处理类型唯一的问题是设置异常类和跟踪它们的额外麻烦。
这正是 eJinn 被设计来解决的问题。通过简化这些异常的创建和跟踪,我们可以创建所需的任何数量的异常,并且有一个单一的地方来跟踪它们。
其他配置示例
最小配置。
return [ "version" => "1.0.0", "namespaces" => [ "" => [ "exceptions" => [ 0 => "UnknownError", ] ] ] ];
上面的配置将创建一个单独的异常文件,这个文件将在配置文件的位置创建,没有命名空间。所以如果我们配置在 /home/app/Exceptions
,那么我们会得到这个文件
- /home/app/Exceptions/UnknownError (类 \UnknownError errorCode 0)
###示例### eJinn 使用自身来创建异常文件。您可以在 src/eJinnConf.php
中查看配置文件,并可以在 src/evo/ejinn/Exceptions
中看到它创建的文件。您还可以通过主 index.php
文件来运行它。我还计划将其用于我的其他项目中!
##版本变更说明## #v2#
- 相对命名空间,现在您可以轻松地在同一命名空间中扩展异常
- 简化了调试(不再需要那么多了)
- 在本地和命名空间层级添加了描述
- 将描述正确地包装在90个字符内
- 更新了所有 eJinn 可能抛出的异常
- 添加了许多新的扩展,这些扩展扩展了PHP内置的扩展