商业 / phparch
PHP项目架构测试
Requires
- php: ^7.4|^8.0
- nikic/php-parser: ^4.0
- phpdocumentor/reflection-docblock: ^5.2
- phpdocumentor/type-resolver: ^1.4
- symfony/finder: ^4.1|^5.0
- thecodingmachine/safe: ^1.3
Requires (Dev)
- phpstan/phpstan: ^0.12
- phpunit/phpunit: ^9.5
- squizlabs/php_codesniffer: ^3.5
Suggests
- phpunit/phpunit: For assertion helpers to work correctly.
README
这是什么?
PHPArch是一个PHP项目的架构测试库,仍在开发中。它受到archlint (C#)和archunit (java)的启发。
它可以用来帮助强制执行应用程序中的架构边界,以防止随着时间的推移通过引入跨越先前良好定义的架构边界的依赖项而导致架构腐烂。
安装
您可以使用composer安装PHPArch。如果您不知道composer是什么,那么您可能不需要用于架构测试的库。
$ composer require toubiz/phparch
简单的命名空间验证
PHPArch可以帮助您进行的检查中最简单的一种是基于命名空间的检查:为哪些命名空间允许或禁止依赖其他命名空间设置规则。
public function testSimpleNamespaces() { (new PhpArch()) ->fromDirectory(__DIR__ . '/../../app') ->validate(new ForbiddenDependency('Lib\\', 'App\\')) ->validate(new MustBeSelfContained('App\\Utility')) ->validate(new MustOnlyDependOn('App\\Mailing', 'PHPMailer\\PHPMailer')) ->assertHasNoErrors(); }
可用的验证器
目前以下验证器可用:
ForbiddenDependency
允许您声明一个命名空间不允许依赖于另一个命名空间。MustBeSelfContained
允许您声明一个命名空间必须是自包含的,这意味着它可能没有任何外部依赖。MustOnlyDependOn
允许您声明一个命名空间必须只依赖于另一个命名空间。MustOnlyHaveAutoloadableDependencies
检查所有依赖项是否在当前环境中可自动加载。如果两个包不应该有任何依赖关系,但它们仍然悄悄进入,因为这两个包经常一起使用,这可能很有帮助。AllowInterfaces
是对允许依赖接口的验证器的包装。MustOnlyDependOnComposerDependencies
检查给定命名空间中的所有依赖项是否在给定的composer.json
文件中声明。如果包含多个包的一个存储库包含多个包,这可能有助于防止意外依赖项。ExplicitlyAllowDependency
是对允许特定依赖项的验证器的包装。
大多数架构边界都可以用这些规则描述。
定义架构
PHPArch还包含一个流畅的API,允许您定义基于组件的架构,然后进行验证。该API基于组件,由一个或多个命名空间识别,而不是层或“洋葱皮”,因为这是最简单的方式来传达任何架构 - 无论其实现细节如何。
public function testArchitecture() { $architecture = (new Architecture()) ->component('Components')->identifiedByNamespace('J6s\\PhpArch\\Component') ->mustNotDependOn('Validation')->identifiedByNamespace('J6s\\PhpArch\\Validation'); (new PhpArch()) ->fromDirectory(__DIR__ . '/../../app') ->validate($architecture) ->assertHasNoErrors(); }
定义架构的大部分内容只是对上面命名空间验证器的语法糖。以下方法允许您向组件结构添加断言
mustNotDependOn
mustNotDependDirectlyOn
mustNotBeDependedOnBy
mustOnlyDependOn
mustNotDependOnAnyOtherComponent
mustOnlyDependOnComposerDependencies
dissallowInterdependence
isAllowedToDependOn
语法糖:组件的批量定义
虽然定义架构的说话API很好,但如果您有很多组件,它可能会变得复杂且难以阅读。可以用来定义组件的components
方法,使用简单的关联数组,其中键是组件名称,值是定义组件的命名空间。这样,组件的定义和设置依赖项规则可以分成两步,以提高可读性。
// This $architecture->components([ 'Foo' => 'Vendor\\Foo', 'Bar' => [ 'Vendor\\Bar', 'Vendor\\Deep\\Bar' ] ]); // Is the same as this $architecture->component('Foo') ->identifiedByNamespace('Vendor\\Foo') ->component('Bar') ->identifierByNamespace('Vendor\\Bar') ->identifiedByNamespace('Vendor\\Deep\\Bar')
语法糖:链式依赖规则
如果在这些方法中引用了一个不存在的组件,则将创建该组件。这些方法还将引用的组件设置为当前活动的组件 - 因此,当使用 ->mustNotDependOn('FooBar')
时,所有未来的操作都将引用 FooBar
组件。
为了对单个组件链式添加多个依赖规则,有一些便捷的方法可用
andMustNotDependOn
andMustNotBeDependedOnBy
// This (new Architecture) ->component('Foo') ->mustNotDependOn('Bar') ->andMustNotDependOn('Baz') // Is this same as this: (new Architecture()) ->component('Foo')->mustNotDependOn('Bar') ->component('Foo')->mustNotDependOn('Baz')
单仓库的快捷操作:addComposerBasedComponent
如果一个仓库包含多个包,每个包都有自己的 composer.json
文件,那么很容易不小心使用不在当前包的 composer.json
文件中的方法或类。
为了防止这种情况,可以使用 Architecture->mustOnlyDependOnComposerDependencies
方法以及 MustOnlyDependOnComposerDependencies
验证器来检查所有使用的命名空间是否在给定的 composer.json
文件中声明
$architecture = (new Architecture) ->component('vendor/subpackage') ->identifierByNamespace('Vendor\\Subpackage\\') ->mustOnlyDependOnComposerDependencies('packages/subpackage/composer.json');
然而,composer.json
已经包含了关于包名和命名空间的信息。因此,可以使用 addComposerBasedComponent
方法使事情变得更加简单
$architecture = (new Architecture) ->addComposerBasedComponent('packages/subpackage/composer.json');