mirror of
https://github.com/pestphp/pest.git
synced 2026-03-12 10:47:25 +01:00
implements decorators pipeline
This commit is contained in:
@ -17,6 +17,9 @@ trait Extendable
|
|||||||
*/
|
*/
|
||||||
private static $extends = [];
|
private static $extends = [];
|
||||||
|
|
||||||
|
/** @var array<string, array<Closure>> */
|
||||||
|
private static $decorators = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a custom extend.
|
* Register a custom extend.
|
||||||
*/
|
*/
|
||||||
@ -25,6 +28,11 @@ trait Extendable
|
|||||||
static::$extends[$name] = $extend;
|
static::$extends[$name] = $extend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function decorate(string $name, Closure $decorator): void
|
||||||
|
{
|
||||||
|
static::$decorators[$name][] = $decorator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if extend is registered.
|
* Checks if extend is registered.
|
||||||
*/
|
*/
|
||||||
@ -33,6 +41,32 @@ trait Extendable
|
|||||||
return array_key_exists($name, static::$extends);
|
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.
|
* Dynamically handle calls to the class.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace Pest;
|
|||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
use Pest\Concerns\Extendable;
|
use Pest\Concerns\Extendable;
|
||||||
use Pest\Concerns\RetrievesValues;
|
use Pest\Concerns\RetrievesValues;
|
||||||
|
use Pest\Support\Pipeline;
|
||||||
use PHPUnit\Framework\Assert;
|
use PHPUnit\Framework\Assert;
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
@ -250,23 +251,50 @@ final class Expectation
|
|||||||
*
|
*
|
||||||
* @param array<int, mixed> $parameters
|
* @param array<int, mixed> $parameters
|
||||||
*
|
*
|
||||||
* @return HigherOrderExpectation|mixed
|
* @return HigherOrderExpectation|Expectation
|
||||||
*/
|
*/
|
||||||
public function __call(string $method, array $parameters)
|
public function __call(string $method, array $parameters)
|
||||||
{
|
{
|
||||||
if (method_exists($this->coreExpectation, $method)) {
|
if (!$this->hasExpectation($method)) {
|
||||||
//@phpstan-ignore-next-line
|
|
||||||
$this->coreExpectation = $this->coreExpectation->{$method}(...$parameters);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self::hasExtend($method)) {
|
|
||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
return new HigherOrderExpectation($this, $this->value->$method(...$parameters));
|
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);
|
$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