miniature / component
PHP-classes组件的模板,用于组件架构。依赖注入的黑盒方法。
Requires
- php: >=7.4
- miniature/di_container: *
This package is auto-updated.
Last update: 2024-09-24 21:59:59 UTC
README
组件
警告!
这仍然处于实验阶段。我们不知道目前是否已经把所有东西都整合好了。还需要进行广泛的测试。
目的

作为门面,该组件提供了访问限制机制。这种访问限制反过来是连接组件耦合和所需瓶颈,以便容器中的类与外部世界进行通信。还有一个违规检测程序确保黑盒保持关闭。
可以通过某些注入和覆盖轻松更改行为。DI-Container是其自己的独立包。
在此上下文中“组件”是什么意思?
我们正在寻找一组工具来构建组合组件架构。我们正在尝试构建一个微小的框架,使我们能够相对容易地设置此类内容。
如果您对此不熟悉,并且《内聚性与耦合性问题》不在您的议程上,那么您可能使用的是不同的术语,并且这个包绝对不适合您。
您可能首先想访问扩展图,以获得更好的理解。
设置组件的基本步骤
- 从
Miniature\Component\Component
单例继承,并给它一个独特的自我描述性名称 - 设置一个配置文件夹,并将目录路径信息注入到组件中
- 提供配置文件(PHP数组或YAML)...
- 包含依赖注入连接
- 以及组件耦合连接
- 提供配置文件(PHP数组或YAML)...
- 然后您就可以出发了
在继续之前有一件事需要注意
此包提供某些依赖连接和黑/灰/白盒机制,这可能对发布组件架构很有用。这主要关于结构、访问控制和依赖管理。这并不意味着这些对于组件架构的任务来说是足够的。其他方法可能更加可持续。首先考虑捆绑、独立部署和版本控制。
安装
使用Composer
composer require miniature/component
下载包
您还需要DI-Container包。
将两个包解压缩到名为Miniature
的目录中。将以下内容添加到您的autoload中
<?php function miniature_autoload($class) { $fileName = str_replace('\\', '/', realpath(__DIR__) . '/' . $class ) . '.php'; if (preg_match('/^(.*\/Miniature)\/(\w+)\/((\w+\/)*)(\w+)\.php/', $fileName)) { $newFileName = preg_replace( '/^(.*\/Miniature)\/(\w+)\/((\w+\/)*)(\w+)\.php/', '$1/$2/src/$3$5.php', $fileName ); if (is_file($newFileName)) { require $newFileName; } } } spl_autoload_register('miniature_autoload');
可能您需要调整filePath
的文件路径拼接,通过在filepath()
语句中设置相对路径。
组件的实例
基本实例化
基本类是一个抽象的单例。这样做的目的是
- 您的组件在PHP环境中被认为是唯一的
- 将会有多个组件
- 您的组件在任何时候都应该是全局可访问的
话虽如此,如果您已经想好了自己的名字,您可能只需要几行代码就能完成
<?php use Miniature\Component\Component; class SelfSpeakingComponent extends Component { protected static ?Component $instance = null; } $selfSpeakingComponentInstance = SelfSpeakingComponent::getInstance();
这里最重要的是protected static $instance
属性。它确保始终使用您创建的继承者的相同实例。否则,当您处理多个组件实例时,您将面临意外的行为,尽管在使用唯一实例时一切似乎都很正常。
这将没有错误,但它什么也不会做。
参数注入:配置目录的路径
组件实例需要知道在哪里读取配置。并且很可能还有更多信息和数据需要提供给组件。因此,存在一个参数类Miniature\Component\InitParameters
,用于注入组件构造函数。在这个第一个例子中,配置目录的路径可能就足够了。
$paramObject = (new Miniature\Component\InitParameters()) ->setConfigDirectory( __DIR__ . '/../config'); $selfSpeakingComponentInstance = SelfSpeakingComponent::getInstance($paramObject);
这可能对您来说看起来不太好,因为您不知道组件是在请求处理的哪个阶段第一次被调用的。因此,存在一个可重写的静态自动注入方法。
<?php use Miniature\Component\Component; use Miniature\Component\InitParametersInterface; use Miniature\Component\InitParameters; class SelfSpeakingComponent extends Component { protected static ?Component $instance = null; protected static function autoInject() : ?InitParametersInterface { return (new InitParameters()) ->setConfigDirectory( __DIR__ . '/../config'); } }
这个操作使您能够“随时随地”访问组件实例。
调用始终看起来一样。即使当您创建它时。您永远不会知道您在创建它。
$instantlyNeededInstance = SelfSpeakingComponent::getInstance();
有关配置目录的内容和依赖关系的详细信息,请在此处了解。
您几乎完成组件
假设您想要为在DI-Container中生存的选定实例提供类型安全的或契约安全的访问,您的完成组件可能看起来像这样
<?php namespace YourOwnExamleApp; use Miniature\Component\Component; use Miniature\Component\InitParametersInterface; use Miniature\Component\InitParameters; use YourOwnExamleApp\The2ndComponent; use YourOwnExamleApp\ProductAccessInterface; use YourOwnExamleApp\PersonAccessInterface; use YourOwnExamleApp\AddressAccessInterface; class SelfSpeakingComponent extends Component { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * INIT * * * * * * * * * * * * * * * * * * * * * * * * * * * */ protected static ?Component $instance = null; protected static function autoInject() : ?InitParametersInterface { return (new InitParameters()) ->setConfigDirectory( __DIR__ . '/../config'); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * PROVIDE * * * * * * * * * * * * * * * * * * * * * * * * * * * */ public function providePerson() : PersonAccessInterface { return $this->container->get('person_access'); } public function provideAddress() : AddressAccessInterface { return $this->container->get('address_access'); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * CONSUME * * * * * * * * * * * * * * * * * * * * * * * * * * * */ public function consumeProduct() : ProductAccessInterface { return The2ndComponent::getInstance()->provideProduct(); } }
有两个公共方法,通过从DI-Container检索的对象提供某种访问。有一个方法从另一个组件检索另一个访问对象,最有可能将其返回到组件内部的一个类,从而消耗契约。
但您还没有真正完成。尽管您通过在公共方法中返回接口实现提供了契约,但您无法控制“谁”在使用它。没有控制,很容易就会在组件之间产生多个交叉连接,从而产生非常不希望的结构。这是一个架构任务的失败。
还有关于DI-Container内部哪些类有意义的限制。
因此,Miniature\Component
提供了一种简单的连接机制。
耦合检测和保护
Miniature\Component\Component
提供了一种保护方法,确保只有通过连接耦合配置获得许可的类和方法才能访问。不需要返回值。insureCoupling()
简单地将异常抛出。
$this->insureCoupling();
在下面的示例中,提供调用,找到提供者和消费者方法。没有连接,它们将不会做任何事情,只会抛出异常。
<?php namespace YourOwnExamleApp; use Miniature\Component\Component; use Miniature\Component\InitParametersInterface; use Miniature\Component\InitParameters; use YourOwnExamleApp\The2ndComponent; use YourOwnExamleApp\ProductAccessInterface; use YourOwnExamleApp\PersonAccessInterface; use YourOwnExamleApp\AddressAccessInterface; class SelfSpeakingComponent extends Component { /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * INIT * * * * * * * * * * * * * * * * * * * * * * * * * * * */ protected static ?Component $instance = null; protected static function autoInject() : ?InitParametersInterface { return (new InitParameters()) ->setConfigDirectory( __DIR__ . '/../config'); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * PROVIDE * * * * * * * * * * * * * * * * * * * * * * * * * * * */ public function providePerson() : PersonAccessInterface { $this->insureCoupling(); return $this->container->get('person_access'); } public function provideAddress() : AddressAccessInterface { $this->insureCoupling(); return $this->container->get('address_access'); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * CONSUME * * * * * * * * * * * * * * * * * * * * * * * * * * * */ public function consumeProduct() : ProductAccessInterface { $this->insureCoupling(); return The2ndComponent::getInstance()->provideProduct(); } }
连接耦合
您可能首先想观察概述图,以便更好地理解。
这些布线示例大致基于上面显示的组件类示例所示。我们只假设存在一个The2ndComponent
和一些来自容器内部的类。示例是简化的。当然,类名将在这里以完全限定形式出现。
请注意,YAML支持默认不可用。
示例中解释的第一个布线
组件耦合部分开始
coupling:
方法SelfSpeakingComponent->providePerson()
...
SelfSpeakingComponent::getInstance()->providePerson();
SelfSpeakingComponent: providePerson:
... 允许通过The2ndComponent->consumePerson()
调用。
The2ndComponent::getInstance()->consumePerson();
The2ndComponent: consumePerson: true
可以通过将值更改为false
立即关闭此功能。
通用布线功能
通常,可以有多个类和多个方法的赋值。通过观察YAML结构应该会变得清楚。
除非证明这些赋值总体上实现了一些“一个接口”,否则应考虑组件之间的大量赋值是不受欢迎的。对于从DI-容器内部访问组件的类,可能会有所不同。
通配符表示法
还有授予一个类一般权限的选项。可能是为了测试或开发,或者它是一个位于容器内、专门用于与组件通信的类。
OnlyInTestingEnvironment: true
DI映射
仅硬布线
抱歉,到目前为止,将不会有自动布线。除了缺乏开发时间和使用反射类可能引起的问题外,我们还认为,在接口到实现映射方面,您已经有所有手段了。
死锁保护
Miniature\DiContainer
通过在实例化过程中构建的Miniature\DiContainer\DiNode
-树提供死锁保护。未来可能需要一个整个布线冒泡的测试模式。
示例
让我们从PHP和YAML的简单示例开始。请注意,YAML支持默认不可用。另一方面,PHP可能具有产生动态结构的优势(或不确定性)。
如果您习惯于在流行的MVC框架中使用服务容器配置,这可能会让您感到熟悉。的确,我们曾考虑过从Symfony调整语法,但最终得出结论,使用键service
在术语上会引起很多混淆。所以映射分支被命名为它所代表的内容:di_mapping
。如果您想更改语法,可以通过覆盖来完成。
键的解释
类键
- '@person'
在args
子分支中查找重复的二级键,如person
、address
、person_manager
、mysql_wrapper
和person_access
,前面加上一个@
前缀。这就是如何将类映射为构造函数注入的参数(args
)。命名完全由您选择。考虑名称空间。如上所述,相等的键将导致覆盖。这是为了实现基于环境的覆盖。
对于每个类映射,必须使用键class
,这很可能是指一个完全限定的类名。
参数数据的键
- '%mysql_person_connection'
查找带有标记为%mysql_person_connection
的参数的mysql_wrapper
。您将在params
分支中找到这个键(mysql_person_connection
)。这将不经过任何更改传递给构造函数。同样,关于键access_token
(分别对应%access_token
)也是如此。
DI容器的键
- '@miniature.di_container'
映射 person_access
展示了如何通过参数列表注入 DI 容器本身:@miniature.di_container
。
请注意,构造函数注入在单元测试中更容易处理。但有些情况下,你可能甚至需要它,因为这是直接访问容器的唯一方式。
键:'static'
static: getInstance
另一个来自 person_access
的例子:键 static
。
这应该包含一个字符串,该字符串是用于代替普通构造函数调用的静态生成/访问方法的名字(例如 new ClassName($param)
)。在我们的例子中,这是用于单例模式的标准调用,它将导致
$instance = AppDemo\PersonAccess::getInstance($container);
键:'singleton'
singleton: true
singleton
键的使用与同名的设计模式完全独立。通过将其赋值为 true
,你可以强制类只实例化一次。这不仅适用于你可能会使用设计模式的场景,而且在你可以确保不会在成员变量中存储结果的所有场景中都有用。
这是访问实例最快且节省资源的方式。内部键 instance
总是会被检查。如果有内容,它将被返回,并跳过所有其他进程。要真正地说,这不是真正的单例模式的替代品。(你可能会更喜欢称之为缓存。)你可以使用不同的键在两个不同的地方实例化相同的类,也许使用不同的参数。这可能就是你的意图?
键 'public'
public: true
将键 public
设置为 true
使实例可以通过组件从外部访问。默认情况下,此功能仅适用于 'dev' 和 'test' 环境。你可以通过设置允许 public
的环境来控制此行为。
$paramObject = (new Miniature\Component\InitParameters()) // other configurations ->setEnvAllowingPublicAccess('dev', 'test', 'prod');
在我们的情况下,可能是这样的(假设我们正在使用 自动注入)
SelfSpeakingComponent::getInstance()->person_access->somePublicMethod('string_parameter');
如果你不喜欢魔法访问,你可能更喜欢像这里所示的 get($string)
SelfSpeakingComponent::getInstance()->get('person_access')->somePublicMethod('string_parameter');
如果你倾向于将所有内容都声明为 public
或者质疑这些繁琐的东西有什么用,我建议你了解凝聚性与耦合问题以及组件架构。
键 'skipViolationScan'
skipViolationScan: true
此键不包括在上面的示例中。将此键设置为 true
将在违规扫描例程中禁用 违规检测。
例如,当将供应商类包含到映射中时,这很有用。
动态覆盖参数
请注意:此功能与 singleton 功能不兼容。尝试组合这两个功能将导致异常。
这种覆盖参数的方式仅适用于已注入容器的类。从容器中检索实例是通过 get
来实现的。
$instanceFromMapping = $this->container->get('class_key_string');
动态覆盖是通过第二个参数实现的,这是一个数组,其字段与原始参数数组索引相同。你可以再次提供所有参数,或者你可能只想提供某些参数。在后一种情况下,字符串索引在参数中很有用。
考虑上述示例,假设 person_access
-实例想要使用 @person
和 @adress
的替换类检索一个 person_manager
实例,它可能看起来像这样
$instanceWithOverrides = $this->container->get( 'person_manager', [ 'address' => '@other_address_key', 'person' => '@other_person_key', ] );
由于 person_manager
保留一个关联数组作为参数,因此甚至不需要关心顺序。基本上,这种方法也可以与数字键一起使用,但你是否感到舒适呢?
读取配置目录
读取行为
配置目录及其子目录中所有文件都将被递归读取,前提是文件格式受支持。目前,这包括
- PHP:PHP文件始终应返回一个数组
- YAML:这取决于YAML PECL扩展是否加载。或者,可以注入一个装饰器,该装饰器包含您选择的基于PHP的YAML解释器。更多关于这方面的信息请点击此处
第1次递归级别:选择解释器
有三个主要键,用于三个主要目的:di_mapping
、params
和coupling
。
(顺便说一下:如果您不喜欢命名方式,可以通过注入DI容器来更改它,但coupling
键的命名除外。)
第2次递归级别:访问类和参数的键
下一递归级别上的键都是您选择的。这些是您的类和参数将被使用的名称。具有相同键的内容将被覆盖。这是使我们能够根据特定环境的需求进行覆盖的基本机制。
YAML支持
YAML不是PHP标准安装的一部分。另一方面,我们努力使Miniature免受外部依赖。尽管如此,为了所有配置工作,YAML非常受欢迎。
因此,目前有两种选择
- 安装YAML的PECL扩展
- 注入一个实现
Miniature\Component\Reader\YamlParserDecoratorInterface
的装饰器
配置读取器将检测YAML PECL扩展是否已安装,并将其作为首选。如果没有,它将检查是否注入了装饰器,并使用它。如果没有,它会在遇到没有可用解析器的YAML文件时抛出一个警告级错误。
我们假设,YamlParserDecoratorInterface本身说明了一切。InitParameters类提供了一个专门的设置器。
$paramObject = (new Miniature\Component\InitParameters()) // other settings ->setYamlParserDecorator(new MyOwnAPP\YamlParserDecorator());
基于环境的覆盖
如上所述,配置读取器在覆盖第2次递归级别的键方面是无情的。它将继续这样做,通过递归地读取配置目录的每个子目录。您可以通过列出可用环境中的子目录来更改这一点。该列表的包含将防止以相同名称命名的目录被读取,除非当前环境碰巧与目录名称相同。
这就是基于环境的覆盖实现。没有更多。
配置环境
一般参数注入
InitParameters类
基本配置是通过一个名为InitParameters
的参数对象完成的,该对象被传递给容器实例。(我们可能会在未来添加一些更多配置选项,以便使其更加方便。)
$paramObject = (new Miniature\Component\InitParameters()) ->setConfigDirectory( __DIR__ . '/../config'); $selfSpeakingComponentInstance = SelfSpeakingComponent::getInstance($paramObject);
从长远来看,自动注入是更好的选择...
class SelfSpeakingComponent extends Component { protected static ?Component $instance = null; protected static function autoInject() : ?InitParametersInterface { return (new InitParameters()) ->setConfigDirectory( __DIR__ . '/../config'); } }
...因为这样您可以随时从任何地方访问
$instantlyNeededInstance = SelfSpeakingComponent::getInstance();
InitParameters支持方法链
$paramObject = (new Miniature\Component\InitParameters()) ->setAppRootPath(__DIR__ . '/..') ->setConfigDirectory('config') ->setDotEnvPath('');
路径配置
所有与路径相关的设置器都接受基于入口脚本脚本的相对路径。通常我们假设这是位于 public
目录中的 index.php
。设置器方法会将它们转换为相对路径并检查其有效性。这也适用于绝对路径。
setAppRootPath()
这并非必需,但它可能会使事情更加方便。一旦设置,其他路径相关方法将连接到根路径字符串。
所以,而不是设置配置路径的相对路径
$paramObject = (new Miniature\Component\InitParameters()) ->setAppRootPath(__DIR__ . '/..') ->setConfigDirectory(__DIR__ . '/../config');
可以直接这样做
$paramObject = (new Miniature\Component\InitParameters()) ->setAppRootPath(__DIR__ . '/..') ->setConfigDirectory('config');
setConfigDirectory()
自解释,这是所有映射所在的目录。请注意 读取行为 和 基于环境的覆盖。
$paramObject->setConfigDirectory(__DIR__ . '/../config');
setDotEnvPath()
自解释,这是 .env 文件所在的目录路径。了解更多关于结果的 行为。
$paramObject->setDotEnvPath(__DIR__ . '/..');
环境设置
setEnv()
直接使用字符串参数设置环境名称。在没有 .env
文件的情况下很有用。请注意,在不确定的情况下,这会导致覆盖从 .env
文件 中读取的值。这对于想要模拟生产行为的发展情况可能很有用。
setAvailableEnv()
这是一个已知于您系统的环境名称列表。这个列表尤其与 目录读取行为 相关。任何被命名为列表中名称之一的目录将不会自动递归扫描。
它可以接受环境名称的数组 ...
$paramObject->setAvailableEnv( ['dev', 'test', 'prod', 'another'] );
... 以及字符串参数列表。
$paramObject->setAvailableEnv('dev', 'test', 'prod', 'another');
setEnvAllowingPublicAccess()
正如在 setAvailableEnv() 中,这是一个环境名称列表。它可以作为数组传递,也可以传递可变数量的字符串参数。
$paramObject->setEnvAllowingPublicAccess('dev', 'test', 'another');
了解 这里 的用途。
行为注入
setYamlParserDecorator()
您可以通过实现 Miniature\Component\Reader\YamlParserDecoratorInterface
来提供基于 PHP 的 YAML 解析器。在有关 YAML 支持 的部分了解更多信息。
$paramObject->setYamlParserDecorator(new MyOwnAPP\YamlParserDecorator());
setDiSyntaxMapper()
$paramObject->setDiSyntaxMapper(new Miniature\DiContainer\Syntax\MapperSymfonyStyle());
设置可用的环境
默认情况下,Component 类知道三个环境 dev
、prod
和 test
。您可以完全根据您的需求更改它。您可以有尽可能多的环境,并且可以按照您想要的命名它们。
$paramObject = (new Miniature\Component\InitParameters()) ->setAvailableEnv('develop', 'testing', 'production', 'another');
读取 .env 文件
.env 已成为写入全局变量的配置值的标准。Miniature 不会写入全局变量,也不会从全局变量中读取。但它会在您想要的情况下读取 .env。目前,这的唯一目的是 APP_ENV
的值,它将被传递到您的组件类的 $env
标志,代表 当前环境。
请注意,调用 setEnv() 将覆盖此值。
当前环境
当前环境是通过一个字符串命名的,该字符串命名了本地机器提供的环境。通常它类似于 developement
、production
或 test
。
此值可能来自 .env
文件,或者它可能是 直接设置 的。无论如何,它具有某些影响,尤其是在扫描 配置目录 期间的 读取行为。