j6s/phparch

此软件包已被弃用且不再维护。作者建议使用 phpat/phpat 软件包代替。

PHP项目架构测试

3.1.1 2022-04-14 07:23 UTC

README

本项目已不再维护。在我开始开发时,类似的工具尚未存在,或者在 deptrac 的情况下,没有覆盖我的用例。如今,更好的工具已经存在,我很乐意将用户引向那些工具。

  • PHPAt 是 PHPArch 的精神继承者,甚至包含其中的一些代码
  • Deptrac 采用更基于图的方法来解决这个问题

如果之前使用过 PHPArch,两者都可以作为良好的替代品。

PHPArch 构建状态

这是什么?

PHPArch 是一个 PHP 项目的架构测试库,仍在开发中。它受到 archlint (C#)archunit (java) 的启发。

它可用于帮助强制执行应用程序中的架构边界,以防止随着时间的推移,通过在先前定义良好的架构边界之间引入依赖项而导致架构恶化。

安装

您可以使用 composer 安装 PHPArch。如果您不知道 composer 是什么,那么您可能不需要架构测试库。

$ composer require j6s/phparch

简单的命名空间验证

PHPArch 可以帮助您完成的 simplest 检查类型是简单的基于命名空间的检查:为哪些命名空间可以或不可以依赖于哪些其他命名空间设置规则。

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')

针对 monorepos 的简写: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');

示例