innmind / specification
Interfaces allowing to compose a tree of specifications
4.1.0
2024-07-13 14:45 UTC
Requires
- php: ~8.1
Requires (Dev)
- innmind/coding-standard: ~2.0
- vimeo/psalm: ~4.4
README
Library containing interfaces allowing to compose a tree of specification. As you may want to type hint the object you to test against, the interfaces does not enforce a method isSatisfiedBy
, it will be up to you to create such method.
The goal here is to have a set of interfaces so a specification can be composed easily, and decomposed afterward.
The decomposition part is useful in the case you want to translate your specification into, let's say, a doctrine query (or anything you wish).
Implementation example
use Innmind\Specification\{ Specification, Composite, Operator, Not, Comparator, Sign, }; class User { public $name; public function __construct(string $name) { $this->name = $name; } } class AndSpecification implements Composite { private $left; private $right; public function __construct(Specification $left, Specification $right) { $this->left = $left; $this->right = $right; } public function left(): Specification { return $this->left; } public function operator(): Operator { return Operator::and; } public function right(): Specification { return $this->right; } public function isSatisfiedBy(User $user): bool { return $this->left->isSatisfiedBy($user) && $this->right->isSatisfiedBy($user); } } class OrSpecification implements Composite { private $left; private $right; public function __construct(Specification $left, Specification $right) { $this->left = $left; $this->right = $right; } public function left(): Specification { return $this->left; } public function operator(): Operator { return Operator::or; } public function right(): Specification { return $this->right; } public function isSatisfiedBy(User $user): bool { return $this->left->isSatisfiedBy($user) || $this->right->isSatisfiedBy($user); } } class NotSpecification implements Not { private $specification; public function __construct(Specification $specification) { $this->specification = $specification; } public function specification(): Specification { return $this->specification; } public function isSatisfiedBy(User $user): bool { return !$this->specification->isSatisfiedBy($user); } } class NameSpecification implements Comparator { private $name; public function __construct(string $name) { $this->name = $name; } public function and(Specification $specification): Composite { return new AndSpecification($this, $specification); } public function or(Specification $specification): Composite { return new OrSpecification($this, $specification); } public function not(): Not { return new NotSpecification($this); } public function property(): string { return 'name'; } public function sign(): Sign { return Sign::equality; } public function value() { return $this->name; } public function isSatisfiedBy(User $user): bool { return $user->name === $this->name; } } $spec = (new NameSpecification('John')) ->or(new NameSpecification('Doe')) ->or((new NameSpecification('John Doe'))->not()); $spec->isSatisfiedBy(new User('John')); //true $spec->isSatisfiedBy(new User('Doe')); //true $spec->isSatisfiedBy(new User('John Doe')); //false $spec->isSatisfiedBy(new User('42')); //true
The object $spec
could be easily decomposed to create the given WHERE
statement of a sql query: WHERE name = "John" or name = "Doe" or name != "John Doe"
.