slava-basko / specification-php
封装您的业务决策,使其易于阅读、清晰、易于维护。
Requires
- php: ~5.5|~7|~8.2
Requires (Dev)
- phpunit/phpunit: 4 - 9
- squizlabs/php_codesniffer: >=3
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方法。
组合
此库提供了一些有用的内置规范,如NotSpecification、AndSpecification和OrSpecification,这些可以帮助您组合规范并创建一个新的规范。
$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。