-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
PHP 8.5 is coming soon. I believe that PHPUnit's constraints is a very good use case for the new pipe operator.
The new pipe operator forwards a value to any kind of callable, including invokable objects. If PHPUnit's constraints were invokable, then they could be used with the new pipe operator to directly test some value against a constraint.
I have created a proof-of-concept here: https://github.com/linepogl/should
Example of an invocable constraint:
final class IsEqual extends Constraint
{
...
/**
* @template A
* @param A $actual
* @return A
*/
#[Override]
public function __invoke(mixed $actual): mixed
{
Assert::assertThat($actual, $this);
return $actual;
}
...
}
Usage:
$actual |> new IsEqual('test')
Benefits
Readability
Invokable constraints give a syntax similar to expect( $actual )->to...
for free.
// (current)
Assert::assertThat(new IsEqual('test'), $actual);
// (proposed)
$actual |> new IsEqual('test');
Static type checking
Since every __invoke
method has its own signature, the acceptable types of the $actual
value can be statically defined and checked:
$actual = 5;
// (current)
// The generic signature of `assertThat` does not help static type checkers
// resulting in a run-time TypeError: expected string, got int
Assert::assertThat(new RegularExpression('/test/'), $actual);
// (proposed)
// A tailored signature for `__invoke` of the constraint `RegularExpression`
// makes it possible to detect the error statically
$actual |> new RegularExpression('/test/');
Static type narrowing
Again the signature of __invoke
can be used to pass type narrowing instructions to the static type checker.
// (current)
// impossible to statically determine what's the type of $actual
Assert::assertThat(new IsString(), $actual);
// (proposed)
// thanks to the signature of `__invoke`, it's possible to narrow the type of $actual to string,
$actual |> new IsString();
Simple chaining
Testing a value against multiple constraints can be easy with the proposed syntax
// (proposed)
$actual
|> new IsString()
|> new RegularExpression('/test/');
Minor expectation parameters placement
The minor parameters of an assertion, like delta in the example below, are conceptually part of the expectation. However, the order of the arguments separates them from the expected value:
// (current)
Assert::assertEqualsWithDelta(5, $actual, 0.05);
With the proposed syntax, the minor expectation parameters are nicely grouped with the expected value:
// (proposed)
$actual |> new IsEqualWithDelta(5, 0.05);