makinacorpus/normalization

用于类名到业务名称映射的工具,部分与Symfony和其他工具集成

2.1.1 2023-12-08 10:57 UTC

This package is auto-updated.

Last update: 2024-09-08 12:28:16 UTC


README

PHP类名到业务域名命名策略和规范化辅助工具。

主要功能是名称映射,提供可逆和可预测的类名到业务名称业务名称到类名转换,其目的是将其置于任何公开PHP类名的组件之前,以便能够将内部类型名称别名到业务域名。

标签

每个名称转换条目都与一个任意标签相关联,其目的是在上下文中消除逻辑名称的歧义,例如考虑您有以下两个类

  • \App\Entity\Order 逻辑上别名为 my_app.order
  • \App\Command\Order 逻辑上别名为 my_app.order

您将有一个名称冲突。为了解决这个问题,可以按以下方式对每个类进行标记

  • \App\Entity\Order 逻辑上别名为 my_app.order 并带有 entity 标签,
  • \App\Command\Order 逻辑上别名为 my_app.order 并带有 command 标签。

在将别名名称转换为PHP类名时,您可以指定当前上下文中的预期标签,从而避免这些歧义。

在通过逻辑名称查询PHP名称类时

  • 如果只有一个条目匹配,无论使用哪个标签,则返回此条目,
  • 如果找到带有default标签的条目,则返回它,
  • 如果存在多个匹配项且没有带有default标签的条目,则引发异常。

PHP类可能用于多个逻辑名称,因此在通过PHP类名查询逻辑名称时适用以下规则

  • 如果只有一个条目匹配,无论使用哪个标签,则返回此条目,
  • 如果找到带有default标签的条目,则返回它,
  • 如果存在多个匹配项且没有带有default标签的条目,则引发异常。

从PHP类名到和从PHP类名的解析算法行为相同。

静态命名策略

使用属性

您可以为单个类提供一个默认名称。

namespace App\Entity;

use MakinaCorpus\Normalization\Alias;

#[Alias(name: "foo")]
class Foo
{
}

或者使用标签针对某些上下文

namespace App\Command;

use MakinaCorpus\Normalization\Alias;

#[Alias(name: "do_foo", tag: "command")]
class Foo
{
}

或者如果您愿意,在不同上下文中提供多个名称

namespace App\Command;

use MakinaCorpus\Normalization\Alias;

#[Alias(name: "foo")]
#[Alias(name: "do_foo", tag: "command")]
#[Alias(name: "foo_done", tag: "event")]
class Foo
{
}

您也可以在同一个标签或默认标签下提供尽可能多的别名,例如当您重命名业务项并希望保持向后兼容时

namespace App\Command;

use MakinaCorpus\Normalization\Alias;

#[Alias(name: "do_foo", tag: "command")]
#[Alias(name: "older_legacy_name", tag: "command", deprecated: true)]
class Foo
{
}

您也可以简单地提供一个优先级(结果将与使用deprecated参数相同),但不会在PHP侧引发任何弃用通知

namespace App\Command;

use MakinaCorpus\Normalization\Alias;

#[Alias(name: "do_foo", tag: "command", priority: 100)]
#[Alias(name: "older_legacy_name", tag: "command", priority: -100)]
class Foo
{
}

之前解释的算法将使用所有这些参数工作。

使用Symfony配置

@todo

动态命名策略

这提供了三种不同的类命名策略

  • 透传名称转换,不进行任何转换。公开的名称是您的PHP类名。

  • 基于前缀的名称转换,将字符串Foo\Shop\Domain\Order\Command\BasketProductAdd转换为FooShop.Order.Command.BasketProductAdd,考虑到Foo\Shop\Domain命名空间前缀将始终静态转换为FooShop,并使用.替换分隔符。

  • 静态映射名称转换,使用用户提供的静态映射。

  • 当然,您可以使用 MakinaCorpus\Normalization\NameMappingStrategy 接口实现自己的策略。

您可以将名称映射配置为包含无限数量的策略,每个策略通过一个 标签 进行标识,这允许使用此 API 的每个服务都有自己的命名策略。

名称映射允许用户自定义别名映射,它可以包含单个 PHP 类名无限多的名称别名,使您的项目能够抵御过时的约定。例如,当您在消息总线中插入时更改命名约定:您的应用程序可以在升级的同时继续消费旧消息。

此外,它还提供了一些其他辅助工具。

  • 一个自定义的 Serializer 接口,具有默认实现,该实现使用 symfony/serializer 组件。这使得使用此 API 的代码能够从可替换的序列化器中受益。

  • symfony/serializer 提供的 ramsey/uuid 正常化和反正常化器。

  • 未来可能还会添加更多。

配置

安装此包

composer req makinacorpus/normalization

如果您使用的是 Symfony,请在 config/bundles.php 中添加该束。

<?php

return [
    // ... Your other bundles.
    MakinaCorpus\Normalization\Bridge\Symfony\NormalizationBundle::class => ['all' => true],
];

或者,您可以独立设置名称映射。

use MakinaCorpus\Normalization\NameMap\DefaultNameMap;
use MakinaCorpus\Normalization\NameMap\PrefixNameMappingStrategy;

$nameMap = new DefaultNameMap(
    new PrefixNameMappingStrategy(
        'MyApp',
        'My\\Namespace\\Prefix',
    )
);

Symfony束配置

以下是一个示例 config/packages/normalization.yaml 文件

#
# Sample configuration
#
normalization:
    default_strategy:
        #
        # Default name mapping strategy configuration.
        #
        # Per default the "PrefixNameMappingStrategy" is used, which means
        # that you need to give an application name prefix string, which will
        # be all normalized names prefix, and a PHP class namespace prefix
        # that will identify which PHP classes belongs to you or not.
        #
        # Per default the app name is "App" and the namespace prefix is
        # "App" as well, to mimic default Symfony skeleton app.
        #
        app_name: MyApp
        class_prefix: MyVendor\\MyApp

    strategy:
        #
        # Keys here are arbitrary user-defined tags.
        #
        # Tags purpose is to allow API user to define different strategies
        # for different contextes.
        # 
        # See \MakinaCorpus\Normalization\NameMap::TAG_* constants which
        # provides a few samples values.
        #
        # Values must be container services identifiers.
        #
        command: \App\Infra\Normalization\CustomCommandNameMappingStrategy
        event: \App\Infra\Normalization\CustomEventNameMappingStrategy

    static:
        #
        # Keys here are arbitrary user-defined tags.
        #
        # Tags purpose is to allow API user to define different strategies
        # for different contextes.
        # 
        # See \MakinaCorpus\Normalization\NameMap::TAG_* constants which
        # provides a few samples values.
        #
        command:
            #
            # Actual business domain name to PHP class name conversion.
            #
            map:
                Php\Native\Type: my_app.normalized_name
                Php\Other\Native\Type: my_app.other_normalized_name

            #
            # Legacy aliases to PHP class name conversion.
            #
            aliases:
                Php\Legacy\Name: Php\Native\Type
                Php\EvenMoreLegacy\Name: Php\Native\Type
                my_app.legacy_normalized_name: Php\Native\Type
                my_app.other_legacy_normalized_name: my_app.normalized_name

用法

为了使用名称映射,只需将其服务注入到需要它的服务中即可

namespace App\Infra\Bus;

use MakinaCorpus\Normalization\NameMap;

class SomeBus
{
    public function __construct(
        private NameMap $nameMap
    {
    }

    /**
     * This is fictional pseudo-code.
     */
    public function getClassBusinessName(object $message): void
    {
        return $this
            ->nameMap
            ->fromPhpType(
                \get_class($message),
                'some_tag'
            )
        ;
    }
}

如果您正在开发一个 Symfony 应用程序,您可以在对象上使用 MakinaCorpus\Normalization\NameMap\NameMapAware 接口,以便容器自动填充它

namespace App\Infra\Bus;

use MakinaCorpus\Normalization\NameMap\NameMapAware;
use MakinaCorpus\Normalization\NameMap\NameMapAwareTrait;

class SomeBus implements NameMapAware
{
    use NameMapAwareTrait;

    /**
     * This is fictional pseudo-code.
     */
    public function send(object $message): void
    {
        return $this
            ->getNameMap()
            ->fromPhpType(
                \get_class($message),
                'some_tag'
            )
        ;
    }
}

测试

sys/ 文件夹中有一个包含各种容器和各种 PHP 版本的 Docker 环境。为了使测试在所有 PHP 版本中都能工作,您需要运行 composer update --prefer-lowest,否则 PHP 7.4 测试将失败。

composer install
composer update --prefer-lowest
cd sys/
./docker-rebuild.sh # Run this only once
./docker-run.sh