evo/ejinn

eJinn 异常精灵

2.0.1 2024-09-17 16:12 UTC

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 是我见过的最酷的东西" 的方式在配置中留下开发备注。

全局层级

命名空间层级

异常层级

接口层级

  • buildpathbuildpath 属性的一些特殊考虑
    • 默认值是正在处理的配置文件的当前位置。
    • 如果以编程方式运行 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 重新编译所有实体。你也可以设置运行时选项 forcereCompiletrue 以使解析器重新编译配置。

异常考虑因素

不深入细节,我将简要说明为什么使用唯一的异常是有益的。一个明显的例子是

   //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内置的扩展