slava-basko / specification-php

封装您的业务决策,使其易于阅读、清晰、易于维护。

1.3.0 2023-12-16 18:43 UTC

This package is auto-updated.

Last update: 2024-09-16 20:26:02 UTC


README

封装您的业务决策,使其易于阅读、清晰、易于维护。换句话说:封装您业务的IF和ELSE,并使用与客户相同的语言进行交流。

如果您不熟悉规范模式,请阅读[https://martinfowler.com.cn/apsupp/spec.pdf]。

这个库不依赖于任何外部库,并在PHP 5.5+上运行。为什么?因为遗留项目仍然存在,它们也需要一些结构。

安装

composer require slava-basko/specification-php

用法

让我们假设我们有一个成年人的规范。

class AdultUserSpecification extends AbstractSpecification
{
    /**
     * @param User $candidate
     * @return bool
     */
    public function isSatisfiedBy($candidate)
    {
        return $candidate->getAge() >= 18;
    }
}

现在让我们检查用户是否真的是成年人。

$adultUserSpecification = new AdultUserSpecification();
$adultUserSpecification->isSatisfiedBy(new User(14)); // false
$adultUserSpecification->isSatisfiedBy(new User(20)); // true

如果您想使用类型化规范,请使用TypedSpecification装饰器/包装器。

$adultUserSpecification = new TypedSpecification(new AdultUserSpecification(), User::class);
$adultUserSpecification->isSatisfiedBy(new User(20)); // true
$adultUserSpecification->isSatisfiedBy('blah'); // InvalidArgumentException will be thrown

TypedSpecification VS public function isSatisfiedBy(User $candidate)

当然,您可以在isSatisfiedBy中使用类型提示来创建自己的规范接口,但迟早您会看到很多99%相似的接口。

interface UserSpecification
{
    public function isSatisfiedBy(User $user);
}

interface ProductSpecification
{
    public function isSatisfiedBy(Product $product);
}

interface CartSpecification
{
    public function isSatisfiedBy(Cart $cart);
}

interface ParcelSpecification
{
    public function isSatisfiedBy(Parcel $parcel);
}
// etc.

或者您可以使用TypedSpecification装饰器来实现相同的目标。

new TypedSpecification(new SomeUserSpecification(), User::class);
new TypedSpecification(new SomeProductSpecification(), Product::class);
new TypedSpecification(new SomeCartSpecification(), Cart::class);
new TypedSpecification(new SomeParcelSpecification(), Parcel::class);

自动完成

在您的最终规范中使用文档块类型提示进行自动完成,例如@param User $candidate

/**
 * @param User $candidate
 * @return bool
 */
public function isSatisfiedBy($candidate)
{
    return $candidate->someMethodThatWillBeAutocompletedInYourIDE();
}

TypedSpecification保证$candidate将是一个User类的实例,文档块@param User $candidate帮助您的IDE自动完成$candidate方法。

组合

此库提供了一些有用的内置规范,如NotSpecificationAndSpecificationOrSpecification,这些可以帮助您组合规范并创建一个新的规范。

$adultPersonSpecification = new AndSpecification([
    new AdultSpecification(),
    new OrSpecification([
        new MaleSpecification(),
        new FemaleSpecification(),
    ])
]);

$adultPersonSpecification->isSatisfiedBy($adultAlien); // false
// because only AdultSpecification was satisfied; assume we know age, and we don't know alien sex.

这里有一个例子,展示了高度可组合的规范可以是什么样子。

// Card of spades and not (two or three of spades), or (card of hearts and not (two or three of hearts))
$spec = new OrSpecification([
    new AndSpecification([
        new SpadesSpecification(),
        new NotSpecification(new OrSpecification([
            new PlayingCardSpecification(PlayingCard::SUIT_SPADES, PlayingCard::RANK_2),
            new PlayingCardSpecification(PlayingCard::SUIT_SPADES, PlayingCard::RANK_3)
        ]))
    ]),
    new AndSpecification([
        new HeartsSpecification(),
        new NotSpecification(new OrSpecification([
            new PlayingCardSpecification(PlayingCard::SUIT_HEARTS, PlayingCard::RANK_2),
            new PlayingCardSpecification(PlayingCard::SUIT_HEARTS, PlayingCard::RANK_3)
        ]))
    ]),
]);

$spec->isSatisfiedBy(new PlayingCard(PlayingCard::SUIT_SPADES, PlayingCard::RANK_4)); // true
$spec->isSatisfiedBy(new PlayingCard(PlayingCard::SUIT_SPADES, PlayingCard::RANK_2)); // false

余数

方法isSatisfiedBy返回bool,有时在false的情况下,您想了解具体出了什么问题。请使用remainderUnsatisfiedBy方法。它返回未满足的规范余数。

$parcel = [
    'value' => 20,
    'destination' => 'CA'
];

$trackableParcelSpecification = new AndSpecification([
    new HighValueParcelSpecification(),
    new OrSpecification([
        new DestinationCASpecification(),
        new DestinationUSSpecification(),
    ])
]);

if ($trackableParcelSpecification->isSatisfiedBy($parcel)) {
    // do something
} else {
    $remainderSpecification = $trackableParcelSpecification->remainderUnsatisfiedBy($parcel);
    // do something with $remainderSpecification
}

// $remainderSpecification is equal to
//
// AndSpecification([
//      HighValueParcelSpecification
// ]);
//
// because only DestinationXX satisfied

您可以使用Utils将其转换为有用的数组。

$remainder = Utils::flatten(Utils::toSnakeCase($trackableParcel->remainderUnsatisfiedBy($parcel)));
// $remainder is equal to ['high_value_parcel']

例如,您可以在$remainder中使用字符串,如high_value_parcel作为翻译键,以显示有意义的错误消息。

许可证

随意使用。我不承担任何责任或保证。可以被认为是MIT。