Skip to content

Commit

Permalink
[feature] improve Argument::supports()
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Jul 19, 2021
1 parent d5d19c1 commit 9fd65e3
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![CI Status](https://github.com/zenstruck/callback/workflows/CI/badge.svg)](https://github.com/zenstruck/callback/actions?query=workflow%3ACI)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/zenstruck/callback/badges/quality-score.png?b=1.x)](https://scrutinizer-ci.com/g/zenstruck/callback/?branch=1.x)
[![Code Coverage](https://codecov.io/gh/zenstruck/callback/branch/1.x/graph/badge.svg?token=R7OHYYGPKM)](https://codecov.io/gh/zenstruck/callback)
[![Latest Version](https://img.shields.io/packagist/v/zenstruck/callback.svg)](https://packagist.org/packages/zenstruck/callback)

Callable wrapper to validate and inject arguments.

Expand Down
25 changes: 22 additions & 3 deletions src/Callback/Argument.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
*/
final class Argument
{
public const EXACT = 2;
public const COVARIANCE = 4;
public const CONTRAVARIANCE = 8;

/** @var \ReflectionNamedType[] */
private $types = [];

Expand Down Expand Up @@ -49,15 +53,30 @@ public function isUnionType(): bool
return \count($this->types) > 1;
}

public function supports(string $type): bool
/**
* @param string $type The type to check if this argument supports
* @param int|null $options {@see EXACT} to only check if exact match
* {@see COVARIANCE} to check if exact or, if class, is instanceof argument type
* {@see CONTRAVARIANCE} to check if exact or, if class, argument type is instance of class
* Bitwise disjunction of above is allowed
*/
public function supports(string $type, int $options = self::EXACT|self::COVARIANCE): bool
{
if (!$this->hasType()) {
// no type-hint so any type is supported
return true;
}

foreach ($this->types() as $t) {
if ($t === $type || \is_a($t, $type, true)) {
foreach ($this->types() as $supportedType) {
if ($options & self::EXACT && $supportedType === $type) {
return true;
}

if ($options & self::COVARIANCE && \is_a($type, $supportedType, true)) {
return true;
}

if ($options & self::CONTRAVARIANCE && \is_a($supportedType, $type, true)) {
return true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Callback/Parameter/TypedParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected function valueFor(Argument $argument)
throw new UnresolveableArgument('Argument has no type.');
}

if ($argument->supports($this->type)) {
if ($argument->supports($this->type, Argument::EXACT|Argument::COVARIANCE|Argument::CONTRAVARIANCE)) {
return $this->value;
}

Expand Down
41 changes: 41 additions & 0 deletions tests/CallbackTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,47 @@ public function value_factory_cannot_accept_union_argument(): void
->invoke(Parameter::typed('string', Parameter::factory(function(string $type) { return $type; })))
;
}

/**
* @test
*/
public function argument_supports(): void
{
$callback1 = Callback::createFor(function(Object1 $object, string $string, $noType) {});
$callback2 = Callback::createFor(function(Object2 $object, string $string, $noType) {});

$this->assertTrue($callback1->argument(0)->supports(Object1::class));
$this->assertTrue($callback1->argument(0)->supports(Object2::class));
$this->assertFalse($callback1->argument(0)->supports('string'));
$this->assertFalse($callback1->argument(0)->supports(Object3::class));
$this->assertFalse($callback1->argument(0)->supports(Object2::class, Argument::EXACT));
$this->assertTrue($callback1->argument(1)->supports('string'));
$this->assertFalse($callback1->argument(1)->supports('int'));
$this->assertTrue($callback1->argument(2)->supports(Object1::class));
$this->assertTrue($callback1->argument(2)->supports(Object2::class));
$this->assertTrue($callback1->argument(2)->supports('string'));
$this->assertTrue($callback1->argument(2)->supports('string'));
$this->assertTrue($callback1->argument(2)->supports('int'));

$this->assertTrue($callback2->argument(0)->supports(Object1::class, Argument::EXACT|Argument::COVARIANCE|Argument::CONTRAVARIANCE));
$this->assertFalse($callback2->argument(0)->supports(Object3::class, Argument::EXACT|Argument::COVARIANCE|Argument::CONTRAVARIANCE));
}

/**
* @test
*/
public function argument_supports_value(): void
{
$this->markTestIncomplete();
}

/**
* @test
*/
public function resolved_contravariant_value_must_be_supported_by_argument(): void
{
$this->markTestIncomplete();
}
}

class Object1
Expand Down

0 comments on commit 9fd65e3

Please sign in to comment.