mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
implements decorators pipeline
This commit is contained in:
@ -17,6 +17,9 @@ trait Extendable
|
||||
*/
|
||||
private static $extends = [];
|
||||
|
||||
/** @var array<string, array<Closure>> */
|
||||
private static $decorators = [];
|
||||
|
||||
/**
|
||||
* Register a custom extend.
|
||||
*/
|
||||
@ -25,6 +28,11 @@ trait Extendable
|
||||
static::$extends[$name] = $extend;
|
||||
}
|
||||
|
||||
public static function decorate(string $name, Closure $decorator): void
|
||||
{
|
||||
static::$decorators[$name][] = $decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if extend is registered.
|
||||
*/
|
||||
@ -33,6 +41,32 @@ trait Extendable
|
||||
return array_key_exists($name, static::$extends);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if decorator are registered.
|
||||
*/
|
||||
public static function hasDecorators(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, static::$decorators);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, Closure>
|
||||
*/
|
||||
public function decorators(string $name, object $context, string $scope): array
|
||||
{
|
||||
if (!self::hasDecorators($name)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decorators = [];
|
||||
foreach (self::$decorators[$name] as $decorator) {
|
||||
//@phpstan-ignore-next-line
|
||||
$decorators[] = $decorator->bindTo($context, $scope);
|
||||
}
|
||||
|
||||
return $decorators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically handle calls to the class.
|
||||
*
|
||||
|
||||
@ -7,6 +7,7 @@ namespace Pest;
|
||||
use BadMethodCallException;
|
||||
use Pest\Concerns\Extendable;
|
||||
use Pest\Concerns\RetrievesValues;
|
||||
use Pest\Support\Pipeline;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
|
||||
@ -250,23 +251,50 @@ final class Expectation
|
||||
*
|
||||
* @param array<int, mixed> $parameters
|
||||
*
|
||||
* @return HigherOrderExpectation|mixed
|
||||
* @return HigherOrderExpectation|Expectation
|
||||
*/
|
||||
public function __call(string $method, array $parameters)
|
||||
{
|
||||
if (method_exists($this->coreExpectation, $method)) {
|
||||
//@phpstan-ignore-next-line
|
||||
$this->coreExpectation = $this->coreExpectation->{$method}(...$parameters);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!self::hasExtend($method)) {
|
||||
if (!$this->hasExpectation($method)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
return new HigherOrderExpectation($this, $this->value->$method(...$parameters));
|
||||
}
|
||||
|
||||
return $this->__extendsCall($method, $parameters);
|
||||
Pipeline::send(...$parameters)
|
||||
->through($this->decorators($method, $this, Expectation::class))
|
||||
->finally(function ($parameters) use ($method): void {
|
||||
$this->callExpectation($method, $parameters);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $parameters
|
||||
*/
|
||||
private function callExpectation(string $name, array $parameters): void
|
||||
{
|
||||
if (method_exists($this->coreExpectation, $name)) {
|
||||
//@phpstan-ignore-next-line
|
||||
$this->coreExpectation->{$name}(...$parameters);
|
||||
} else {
|
||||
if (self::hasExtend($name)) {
|
||||
$this->__extendsCall($name, $parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function hasExpectation(string $name): bool
|
||||
{
|
||||
if (method_exists($this->coreExpectation, $name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (self::hasExtend($name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -30,4 +30,9 @@ final class Extendable
|
||||
{
|
||||
$this->extendableClass::extend($name, $extend);
|
||||
}
|
||||
|
||||
public function decorate(string $name, Closure $extend): void
|
||||
{
|
||||
$this->extendableClass::decorate($name, $extend);
|
||||
}
|
||||
}
|
||||
|
||||
69
src/Support/Pipeline.php
Normal file
69
src/Support/Pipeline.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Support;
|
||||
|
||||
use Closure;
|
||||
|
||||
final class Pipeline
|
||||
{
|
||||
/** @var array<Closure> */
|
||||
private $pipes = [];
|
||||
|
||||
/** @var array<mixed> */
|
||||
private $passable;
|
||||
|
||||
/**
|
||||
* @param array<mixed> $passable
|
||||
*/
|
||||
public function __construct(...$passable)
|
||||
{
|
||||
$this->passable = $passable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $passable
|
||||
*/
|
||||
public static function send(...$passable): self
|
||||
{
|
||||
return new self(...$passable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Closure> $pipes
|
||||
*/
|
||||
public function through(array $pipes): self
|
||||
{
|
||||
$this->pipes = $pipes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function finally(Closure $finalClosure): void
|
||||
{
|
||||
$pipeline = array_reduce(
|
||||
array_reverse($this->pipes),
|
||||
$this->carry(),
|
||||
$this->prepareFinalClosure($finalClosure)
|
||||
);
|
||||
|
||||
$pipeline(...$this->passable);
|
||||
}
|
||||
|
||||
public function carry(): Closure
|
||||
{
|
||||
return function ($stack, $pipe): Closure {
|
||||
return function (...$passable) use ($stack, $pipe) {
|
||||
return $pipe($stack, ...$passable);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private function prepareFinalClosure(Closure $finalClosure): Closure
|
||||
{
|
||||
return function (...$passable) use ($finalClosure) {
|
||||
return $finalClosure($passable);
|
||||
};
|
||||
}
|
||||
}
|
||||
56
tests/Features/Expect/HigherOrder/decorate.php
Normal file
56
tests/Features/Expect/HigherOrder/decorate.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use function PHPUnit\Framework\assertEquals;
|
||||
use function PHPUnit\Framework\assertInstanceOf;
|
||||
|
||||
class Number
|
||||
{
|
||||
public $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
|
||||
class Character
|
||||
{
|
||||
public $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
}
|
||||
|
||||
expect()->decorate('toBe', function ($next, $expected) {
|
||||
if ($this->value instanceof Character) {
|
||||
assertInstanceOf(Character::class, $expected);
|
||||
assertEquals($this->value->value, $expected->value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$next($expected);
|
||||
});
|
||||
|
||||
expect()->decorate('toBe', function ($next, $expected) {
|
||||
if ($this->value instanceof Number) {
|
||||
assertInstanceOf(Number::class, $expected);
|
||||
assertEquals($this->value->value, $expected->value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$next($expected);
|
||||
});
|
||||
|
||||
test('pass', function () {
|
||||
$number = new Number(1);
|
||||
|
||||
$letter = new Character('A');
|
||||
|
||||
expect($number)->toBe(new Number(1));
|
||||
expect($letter)->toBe(new Character('A'));
|
||||
expect(3)->toBe(3);
|
||||
});
|
||||
Reference in New Issue
Block a user