8000 feature #59291 [TypeInfo] Add `accepts` method (mtarld) · symfony/symfony@4078cb1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4078cb1

Browse files
committed
feature #59291 [TypeInfo] Add accepts method (mtarld)
This PR was merged into the 7.3 branch. Discussion ---------- [TypeInfo] Add `accepts` method | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | | License | MIT Add `Type::accepts($value)` method, which returns `true` whether a given value matches the current type. Commits ------- 92352f5 [TypeInfo] Add `accepts` method
2 parents b013b62 + 92352f5 commit 4078cb1

16 files changed

+255
-0
lines changed

src/Symfony/Component/TypeInfo/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Add `Type::accepts()` method
8+
49
7.2
510
---
611

src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,12 @@ public function testToString()
2929
{
3030
$this->assertSame(DummyBackedEnum::class, (string) new BackedEnumType(DummyBackedEnum::class, Type::int()));
3131
}
32+
33+
public function testAccepts()
34+
{
35+
$type = new BackedEnumType(DummyBackedEnum::class, Type::int());
36+
37+
$this->assertFalse($type->accepts('string'));
38+
$this->assertTrue($type->accepts(DummyBackedEnum::ONE));
39+
}
3240
}

src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,48 @@ public function testIsNullable()
3939
$this->assertTrue((new BuiltinType(TypeIdentifier::MIXED))->isNullable());
4040
$this->assertFalse((new BuiltinType(TypeIdentifier::INT))->isNullable());
4141
}
42+
43+
public function testAccepts()
44+
{
45+
$this->assertFalse((new BuiltinType(TypeIdentifier::ARRAY))->accepts('string'));
46+
$this->assertTrue((new BuiltinType(TypeIdentifier::ARRAY))->accepts([]));
47+
48+
$this->assertFalse((new BuiltinType(TypeIdentifier::BOOL))->accepts('string'));
49+
$this->assertTrue((new BuiltinType(TypeIdentifier::BOOL))->accepts(true));
50+
51+
$this->assertFalse((new BuiltinType(TypeIdentifier::CALLABLE))->accepts('string'));
52+
$this->assertTrue((new BuiltinType(TypeIdentifier::CALLABLE))->accepts('strtoupper'));
53+
54+
$this->assertFalse((new BuiltinType(TypeIdentifier::FALSE))->accepts('string'));
55+
$this->assertTrue((new BuiltinType(TypeIdentifier::FALSE))->accepts(false));
56+
57+
$this->assertFalse((new BuiltinType(TypeIdentifier::FLOAT))->accepts('string'));
58+
$this->assertTrue((new BuiltinType(TypeIdentifier::FLOAT))->accepts(1.23));
59+
60+
$this->assertFalse((new BuiltinType(TypeIdentifier::INT))->accepts('string'));
61+
$this->assertTrue((new BuiltinType(TypeIdentifier::INT))->accepts(123));
62+
63+
$this->assertFalse((new BuiltinType(TypeIdentifier::ITERABLE))->accepts('string'));
64+
$this->assertTrue((new BuiltinType(TypeIdentifier::ITERABLE))->accepts([]));
65+
66+
$this->assertTrue((new BuiltinType(TypeIdentifier::MIXED))->accepts('string'));
67+
68+
$this->assertFalse((new BuiltinType(TypeIdentifier::NULL))->accepts('string'));
69+
$this->assertTrue((new BuiltinType(TypeIdentifier::NULL))->accepts(null));
70+
71+
$this->assertFalse((new BuiltinType(TypeIdentifier::OBJECT))->accepts('string'));
72+
$this->assertTrue((new BuiltinType(TypeIdentifier::OBJECT))->accepts(new \stdClass()));
73+
74+
$this->assertFalse((new BuiltinType(TypeIdentifier::RESOURCE))->accepts('string'));
75+
$this->assertTrue((new BuiltinType(TypeIdentifier::RESOURCE))->accepts(fopen('php://temp', 'r')));
76+
77+
$this->assertFalse((new BuiltinType(TypeIdentifier::STRING))->accepts(123));
78+
$this->assertTrue((new BuiltinType(TypeIdentifier::STRING))->accepts('string'));
79+
80+
$this->assertFalse((new BuiltinType(TypeIdentifier::TRUE))->accepts('string'));
81+
$this->assertTrue((new BuiltinType(TypeIdentifier::TRUE))->accepts(true));
82+
83+
$this->assertFalse((new BuiltinType(TypeIdentifier::NEVER))->accepts('string'));
84+
$this->assertFalse((new BuiltinType(TypeIdentifier::VOID))->accepts('string'));
85+
}
4286
}

src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,41 @@ public function testToString()
8888
$type = new CollectionType(new GenericType(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool()));
8989
$this->assertEquals('array<string,bool>', (string) $type);
9090
}
91+
92+
public function testAccepts()
93+
{
94+
$type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool()));
95+
96+
$this->assertFalse($type->accepts(new \ArrayObject(['foo' => true, 'bar' => true])));
97+
98+
$this->assertTrue($type->accepts(['foo' => true, 'bar' => true]));
99+
$this->assertFalse($type->accepts(['foo' => true, 'bar' => 123]));
100+
$this->assertFalse($type->accepts([1 => true]));
101+
102+
$type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::int(), Type::bool()));
103+
104+
$this->assertTrue($type->accepts([1 => true]));
105+
$this->assertFalse($type->accepts(['foo' => true]));
106+
107+
$type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::int(), Type::bool()), isList: true);
108+
109+
$this->assertTrue($type->accepts([0 => true, 1 => false]));
110+
$this->assertFalse($type->accepts([0 => true, 2 => false]));
111+
112+
$type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ITERABLE), Type::string(), Type::bool()));
113+
114+
$this->assertTrue($type->accepts(new \ArrayObject(['foo' => true, 'bar' => true])));
115+
$this->assertFalse($type->accepts(new \ArrayObject(['foo' => true, 'bar' => 123])));
116+
$this->assertFalse($type->accepts(new \ArrayObject([1 => true])));
117+
118+
$type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ITERABLE), Type::int(), Type::bool()));
119+
120+
$this->assertTrue($type->accepts(new \ArrayObject([1 => true])));
121+
$this->assertFalse($type->accepts(new \ArrayObject(['foo' => true])));
122+
123+
$type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ITERABLE), Type::int(), Type::bool()));
124+
125+
$this->assertTrue($type->accepts(new \ArrayObject([0 => true, 1 => false])));
126+
$this->assertFalse($type->accepts(new \ArrayObject([0 => true, 1 => 'string'])));
127+
}
91128
}

src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,12 @@ public function testToString()
2121
{
2222
$this->assertSame(DummyEnum::class, (string) new EnumType(DummyEnum::class));
2323
}
24+
25+
public function testAccepts()
26+
{
27+
$type = new EnumType(DummyEnum::class);
28+
29+
$this->assertFalse($type->accepts('string'));
30+
$this->assertTrue($type->accepts(DummyEnum::ONE));
31+
}
2432
}

src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,12 @@ public function testWrappedTypeIsSatisfiedBy()
4545
$type = new GenericType(Type::builtin(TypeIdentifier::ITERABLE), Type::bool());
4646
$this->assertFalse($type->wrappedTypeIsSatisfiedBy(static fn (Type $t): bool => 'array' === (string) $t));
4747
}
48+
49+
public function testAccepts()
50+
{
51+
$type = new GenericType(Type::object(self::class), Type::string());
52+
53+
$this->assertFalse($type->accepts('string'));
54+
$this->assertTrue($type->accepts($this));
55+
}
4856
}

src/Symfony/Component/TypeInfo/Tests/Type/IntersectionTypeTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,22 @@ public function testToString()
7777
$type = new IntersectionType(Type::object(\DateTime::class), Type::object(\Iterator::class), Type::object(\Stringable::class));
7878
$this->assertSame(\sprintf('%s&%s&%s', \DateTime::class, \Iterator::class, \Stringable::class), (string) $type);
7979
}
80+
81+
public function testAccepts()
82+
{
83+
$type = new IntersectionType(Type::object(\Traversable::class), Type::object(\Countable::class));
84+
85+
$traversableAndCountable = new \ArrayObject();
86+
87+
$countable = new class implements \Countable {
88+
public function count(): int
89+
{
90+
return 1;
91+
}
92+
};
93+
94+
$this->assertFalse($type->accepts('string'));
95+
$this->assertFalse($type->accepts($countable));
96+
$this->assertTrue($type->accepts($traversableAndCountable));
97+
}
8098
}

src/Symfony/Component/TypeInfo/Tests/Type/NullableTypeTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,13 @@ public function testWrappedTypeIsSatisfiedBy()
4141
$type = new NullableType(Type::string());
4242
$this->assertFalse($type->wrappedTypeIsSatisfiedBy(static fn (Type $t): bool => 'int' === (string) $t));
4343
}
44+
45+
public function testAccepts()
46+
{
47+
$type = new NullableType(Type::int());
48+
49+
$this->assertFalse($type->accepts('string'));
50+
$this->assertTrue($type->accepts(123));
51+
$this->assertTrue($type->accepts(null));
52+
}
4453
}

src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,12 @@ public function testIsIdentifiedBy()
3535

3636
$this->assertTrue((new ObjectType(self::class))->isIdentifiedBy('array', 'object'));
3737
}
38+
39+
public function testAccepts()
40+
{
41+
$this->assertFalse((new ObjectType(self::class))->accepts('string'));
42+
$this->assertFalse((new ObjectType(self::class))->accepts(new \stdClass()));
43+
$this->assertTrue((new ObjectType(parent::class))->accepts($this));
44+
$this->assertTrue((new ObjectType(self::class))->accepts($this));
45+
}
3846
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\TypeInfo\Tests\Type;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\TypeInfo\Type\BuiltinType;
16+
use Symfony\Component\TypeInfo\Type\TemplateType;
17+
use Symfony\Component\TypeInfo\TypeIdentifier;
18+
19+
class TemplateTypeTest extends TestCase
20+
{
21+
public function testAccepts()
22+
{
23+
$this->assertFalse((new TemplateType('T', new BuiltinType(TypeIdentifier::BOOL)))->accepts('string'));
24+
$this->assertTrue((new TemplateType('T', new BuiltinType(TypeIdentifier::BOOL)))->accepts(true));
25+
}
26+
}

src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,13 @@ public function testToString()
8585
$type = new UnionType(Type::int(), Type::string(), Type::intersection(Type::object(\DateTime::class), Type::object(\Iterator::class)));
8686
$this->assertSame(\sprintf('(%s&%s)|int|string', \DateTime::class, \Iterator::class), (string) $type);
8787
}
88+
89+
public function testAccepts()
90+
{
91+
$type = new UnionType(Type::int(), Type::bool());
92+
93+
$this->assertFalse($type->accepts('string'));
94+
$this->assertTrue($type->accepts(123));
95+
$this->assertTrue($type->accepts(false));
96+
}
8897
}

src/Symfony/Component/TypeInfo/Type.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,24 @@ public function isNullable(): bool
5656
{
5757
return false;
5858
}
59+
60+
/**
61+
* Tells if the type (or one of its wrapped/composed parts) accepts the given $value.
62+
*/
63+
public function accepts(mixed $value): bool
64+
{
65+
$specification = static function (Type $type) use (&$specification, $value): bool {
66+
if ($type instanceof WrappingTypeInterface) {
67+
return $type->wrappedTypeIsSatisfiedBy($specification);
68+
}
69+
70+
if ($type instanceof CompositeTypeInterface) {
71+
return $type->composedTypesAreSatisfiedBy($specification);
72+
}
73+
74+
return $type->accepts($value);
75+
};
76+
77+
return $this->isSatisfiedBy($specification);
78+
}
5979
}

src/Symfony/Component/TypeInfo/Type/BuiltinType.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@ public function isNullable(): bool
6262
return \in_array($this->typeIdentifier, [TypeIdentifier::NULL, TypeIdentifier::MIXED]);
6363
}
6464

65+
public function accepts(mixed $value): bool
66+
{
67+
return match ($this->typeIdentifier) {
68+
TypeIdentifier::ARRAY => \is_array($value),
69+
TypeIdentifier::BOOL => \is_bool($value),
70+
TypeIdentifier::CALLABLE => \is_callable($value),
71+
TypeIdentifier::FALSE => false === $value,
72+
TypeIdentifier::FLOAT => \is_float($value),
73+
TypeIdentifier::INT => \is_int($value),
74+
TypeIdentifier::ITERABLE => is_iterable($value),
75+
TypeIdentifier::MIXED => true,
76+
TypeIdentifier::NULL => null === $value,
77+
TypeIdentifier::OBJECT => \is_object($value),
78+
TypeIdentifier::RESOURCE => \is_resource($value),
79+
TypeIdentifier::STRING => \is_string($value),
80+
TypeIdentifier::TRUE => true === $value,
81+
default => false,
82+
};
83+
}
84+
6585
public function __toString(): string
6686
{
6787
return $this->typeIdentifier->value;

src/Symfony/Component/TypeInfo/Type/CollectionType.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,31 @@ public function wrappedTypeIsSatisfiedBy(callable $specification): bool
9292
return $this->getWrappedType()->isSatisfiedBy($specification);
9393
}
9494

95+
public function accepts(mixed $value): bool
96+
{
97+
if (!parent::accepts($value)) {
98+
return false;
99+
}
100+
101+
if ($this->isList() && (!\is_array($value) || !array_is_list($value))) {
102+
return false;
103+
}
104+
105+
$keyType = $this->getCollectionKeyType();
106+
$valueType = $this->getCollectionValueType();
107+
108+
if (is_iterable($value)) {
109+
foreach ($value as $k => $v) {
110+
// key or value do not match
111+
if (!$keyType->accepts($k) || !$valueType->accepts($v)) {
112+
return false;
113+
}
114+
}
115+
}
116+
117+
return true;
118+
}
119+
95120
public function __toString(): string
96121
{
97122
return (string) $this->type;

src/Symfony/Component/TypeInfo/Type/NullableType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,9 @@ public function isNullable(): bool
5959
{
6060
return true;
6161
}
62+
63+
public function accepts(mixed $value): bool
64+
{
65+
return null === $value || parent::accepts($value);
66+
}
6267
}

src/Symfony/Component/TypeInfo/Type/ObjectType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ public function isIdentifiedBy(TypeIdentifier|string ...$identifiers): bool
6666
return false;
6767
}
6868

69+
public function accepts(mixed $value): bool
70+
{
71+
return $value instanceof $this->className;
72+
}
73+
6974
public function __toString(): string
7075
{
7176
return $this->className;

0 commit comments

Comments
 (0)
0