diff --git a/src/Symfony/Component/TypeInfo/CHANGELOG.md b/src/Symfony/Component/TypeInfo/CHANGELOG.md index b1656a7a13694..f8bb3abef81d7 100644 --- a/src/Symfony/Component/TypeInfo/CHANGELOG.md +++ b/src/Symfony/Component/TypeInfo/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.3 +--- + + * Add `Type::accepts()` method + 7.2 --- diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php index a794835ff965e..c513af2f66541 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/BackedEnumTypeTest.php @@ -29,4 +29,12 @@ public function testToString() { $this->assertSame(DummyBackedEnum::class, (string) new BackedEnumType(DummyBackedEnum::class, Type::int())); } + + public function testAccepts() + { + $type = new BackedEnumType(DummyBackedEnum::class, Type::int()); + + $this->assertFalse($type->accepts('string')); + $this->assertTrue($type->accepts(DummyBackedEnum::ONE)); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php index e27d44ad6539f..78317eae630cb 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/BuiltinTypeTest.php @@ -39,4 +39,48 @@ public function testIsNullable() $this->assertTrue((new BuiltinType(TypeIdentifier::MIXED))->isNullable()); $this->assertFalse((new BuiltinType(TypeIdentifier::INT))->isNullable()); } + + public function testAccepts() + { + $this->assertFalse((new BuiltinType(TypeIdentifier::ARRAY))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::ARRAY))->accepts([])); + + $this->assertFalse((new BuiltinType(TypeIdentifier::BOOL))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::BOOL))->accepts(true)); + + $this->assertFalse((new BuiltinType(TypeIdentifier::CALLABLE))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::CALLABLE))->accepts('strtoupper')); + + $this->assertFalse((new BuiltinType(TypeIdentifier::FALSE))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::FALSE))->accepts(false)); + + $this->assertFalse((new BuiltinType(TypeIdentifier::FLOAT))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::FLOAT))->accepts(1.23)); + + $this->assertFalse((new BuiltinType(TypeIdentifier::INT))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::INT))->accepts(123)); + + $this->assertFalse((new BuiltinType(TypeIdentifier::ITERABLE))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::ITERABLE))->accepts([])); + + $this->assertTrue((new BuiltinType(TypeIdentifier::MIXED))->accepts('string')); + + $this->assertFalse((new BuiltinType(TypeIdentifier::NULL))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::NULL))->accepts(null)); + + $this->assertFalse((new BuiltinType(TypeIdentifier::OBJECT))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::OBJECT))->accepts(new \stdClass())); + + $this->assertFalse((new BuiltinType(TypeIdentifier::RESOURCE))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::RESOURCE))->accepts(fopen('php://temp', 'r'))); + + $this->assertFalse((new BuiltinType(TypeIdentifier::STRING))->accepts(123)); + $this->assertTrue((new BuiltinType(TypeIdentifier::STRING))->accepts('string')); + + $this->assertFalse((new BuiltinType(TypeIdentifier::TRUE))->accepts('string')); + $this->assertTrue((new BuiltinType(TypeIdentifier::TRUE))->accepts(true)); + + $this->assertFalse((new BuiltinType(TypeIdentifier::NEVER))->accepts('string')); + $this->assertFalse((new BuiltinType(TypeIdentifier::VOID))->accepts('string')); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php index e488457988224..297e5bc6d13cf 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/CollectionTypeTest.php @@ -88,4 +88,41 @@ public function testToString() $type = new CollectionType(new GenericType(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool())); $this->assertEquals('array', (string) $type); } + + public function testAccepts() + { + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::bool())); + + $this->assertFalse($type->accepts(new \ArrayObject(['foo' => true, 'bar' => true]))); + + $this->assertTrue($type->accepts(['foo' => true, 'bar' => true])); + $this->assertFalse($type->accepts(['foo' => true, 'bar' => 123])); + $this->assertFalse($type->accepts([1 => true])); + + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::int(), Type::bool())); + + $this->assertTrue($type->accepts([1 => true])); + $this->assertFalse($type->accepts(['foo' => true])); + + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ARRAY), Type::int(), Type::bool()), isList: true); + + $this->assertTrue($type->accepts([0 => true, 1 => false])); + $this->assertFalse($type->accepts([0 => true, 2 => false])); + + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ITERABLE), Type::string(), Type::bool())); + + $this->assertTrue($type->accepts(new \ArrayObject(['foo' => true, 'bar' => true]))); + $this->assertFalse($type->accepts(new \ArrayObject(['foo' => true, 'bar' => 123]))); + $this->assertFalse($type->accepts(new \ArrayObject([1 => true]))); + + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ITERABLE), Type::int(), Type::bool())); + + $this->assertTrue($type->accepts(new \ArrayObject([1 => true]))); + $this->assertFalse($type->accepts(new \ArrayObject(['foo' => true]))); + + $type = new CollectionType(Type::generic(Type::builtin(TypeIdentifier::ITERABLE), Type::int(), Type::bool())); + + $this->assertTrue($type->accepts(new \ArrayObject([0 => true, 1 => false]))); + $this->assertFalse($type->accepts(new \ArrayObject([0 => true, 1 => 'string']))); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php index 33a14ea2f21e1..59084b24777ab 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/EnumTypeTest.php @@ -21,4 +21,12 @@ public function testToString() { $this->assertSame(DummyEnum::class, (string) new EnumType(DummyEnum::class)); } + + public function testAccepts() + { + $type = new EnumType(DummyEnum::class); + + $this->assertFalse($type->accepts('string')); + $this->assertTrue($type->accepts(DummyEnum::ONE)); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php index 08e00bb729699..a57bfb846181c 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/GenericTypeTest.php @@ -45,4 +45,12 @@ public function testWrappedTypeIsSatisfiedBy() $type = new GenericType(Type::builtin(TypeIdentifier::ITERABLE), Type::bool()); $this->assertFalse($type->wrappedTypeIsSatisfiedBy(static fn (Type $t): bool => 'array' === (string) $t)); } + + public function testAccepts() + { + $type = new GenericType(Type::object(self::class), Type::string()); + + $this->assertFalse($type->accepts('string')); + $this->assertTrue($type->accepts($this)); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/IntersectionTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/IntersectionTypeTest.php index c77d850158044..5ae38eaf9ad58 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/IntersectionTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/IntersectionTypeTest.php @@ -77,4 +77,22 @@ public function testToString() $type = new IntersectionType(Type::object(\DateTime::class), Type::object(\Iterator::class), Type::object(\Stringable::class)); $this->assertSame(\sprintf('%s&%s&%s', \DateTime::class, \Iterator::class, \Stringable::class), (string) $type); } + + public function testAccepts() + { + $type = new IntersectionType(Type::object(\Traversable::class), Type::object(\Countable::class)); + + $traversableAndCountable = new \ArrayObject(); + + $countable = new class implements \Countable { + public function count(): int + { + return 1; + } + }; + + $this->assertFalse($type->accepts('string')); + $this->assertFalse($type->accepts($countable)); + $this->assertTrue($type->accepts($traversableAndCountable)); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/NullableTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/NullableTypeTest.php index ad56707761e5c..dcc7562ce6e17 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/NullableTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/NullableTypeTest.php @@ -41,4 +41,13 @@ public function testWrappedTypeIsSatisfiedBy() $type = new NullableType(Type::string()); $this->assertFalse($type->wrappedTypeIsSatisfiedBy(static fn (Type $t): bool => 'int' === (string) $t)); } + + public function testAccepts() + { + $type = new NullableType(Type::int()); + + $this->assertFalse($type->accepts('string')); + $this->assertTrue($type->accepts(123)); + $this->assertTrue($type->accepts(null)); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php index be38c033b0a88..f7d97f004e925 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/ObjectTypeTest.php @@ -35,4 +35,12 @@ public function testIsIdentifiedBy() $this->assertTrue((new ObjectType(self::class))->isIdentifiedBy('array', 'object')); } + + public function testAccepts() + { + $this->assertFalse((new ObjectType(self::class))->accepts('string')); + $this->assertFalse((new ObjectType(self::class))->accepts(new \stdClass())); + $this->assertTrue((new ObjectType(parent::class))->accepts($this)); + $this->assertTrue((new ObjectType(self::class))->accepts($this)); + } } diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/TemplateTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/TemplateTypeTest.php new file mode 100644 index 0000000000000..1b1cc065044b2 --- /dev/null +++ b/src/Symfony/Component/TypeInfo/Tests/Type/TemplateTypeTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\TypeInfo\Tests\Type; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\TypeInfo\Type\BuiltinType; +use Symfony\Component\TypeInfo\Type\TemplateType; +use Symfony\Component\TypeInfo\TypeIdentifier; + +class TemplateTypeTest extends TestCase +{ + public function testAccepts() + { + $this->assertFalse((new TemplateType('T', new BuiltinType(TypeIdentifier::BOOL)))->accepts('string')); + $this->assertTrue((new TemplateType('T', new BuiltinType(TypeIdentifier::BOOL)))->accepts(true)); + } +} diff --git a/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php b/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php index f5763f93f41f4..d673ce6169119 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/Type/UnionTypeTest.php @@ -85,4 +85,13 @@ public function testToString() $type = new UnionType(Type::int(), Type::string(), Type::intersection(Type::object(\DateTime::class), Type::object(\Iterator::class))); $this->assertSame(\sprintf('(%s&%s)|int|string', \DateTime::class, \Iterator::class), (string) $type); } + + public function testAccepts() + { + $type = new UnionType(Type::int(), Type::bool()); + + $this->assertFalse($type->accepts('string')); + $this->assertTrue($type->accepts(123)); + $this->assertTrue($type->accepts(false)); + } } diff --git a/src/Symfony/Component/TypeInfo/Type.php b/src/Symfony/Component/TypeInfo/Type.php index 2a39f14e7b5bf..f20a16984fdb5 100644 --- a/src/Symfony/Component/TypeInfo/Type.php +++ b/src/Symfony/Component/TypeInfo/Type.php @@ -56,4 +56,24 @@ public function isNullable(): bool { return false; } + + /** + * Tells if the type (or one of its wrapped/composed parts) accepts the given $value. + */ + public function accepts(mixed $value): bool + { + $specification = static function (Type $type) use (&$specification, $value): bool { + if ($type instanceof WrappingTypeInterface) { + return $type->wrappedTypeIsSatisfiedBy($specification); + } + + if ($type instanceof CompositeTypeInterface) { + return $type->composedTypesAreSatisfiedBy($specification); + } + + return $type->accepts($value); + }; + + return $this->isSatisfiedBy($specification); + } } diff --git a/src/Symfony/Component/TypeInfo/Type/BuiltinType.php b/src/Symfony/Component/TypeInfo/Type/BuiltinType.php index 19050c7cfcae8..71ff78b3d94ab 100644 --- a/src/Symfony/Component/TypeInfo/Type/BuiltinType.php +++ b/src/Symfony/Component/TypeInfo/Type/BuiltinType.php @@ -62,6 +62,26 @@ public function isNullable(): bool return \in_array($this->typeIdentifier, [TypeIdentifier::NULL, TypeIdentifier::MIXED]); } + public function accepts(mixed $value): bool + { + return match ($this->typeIdentifier) { + TypeIdentifier::ARRAY => \is_array($value), + TypeIdentifier::BOOL => \is_bool($value), + TypeIdentifier::CALLABLE => \is_callable($value), + TypeIdentifier::FALSE => false === $value, + TypeIdentifier::FLOAT => \is_float($value), + TypeIdentifier::INT => \is_int($value), + TypeIdentifier::ITERABLE => is_iterable($value), + TypeIdentifier::MIXED => true, + TypeIdentifier::NULL => null === $value, + TypeIdentifier::OBJECT => \is_object($value), + TypeIdentifier::RESOURCE => \is_resource($value), + TypeIdentifier::STRING => \is_string($value), + TypeIdentifier::TRUE => true === $value, + default => false, + }; + } + public function __toString(): string { return $this->typeIdentifier->value; diff --git a/src/Symfony/Component/TypeInfo/Type/CollectionType.php b/src/Symfony/Component/TypeInfo/Type/CollectionType.php index 24cc176cc919e..90f5a4a398fe2 100644 --- a/src/Symfony/Component/TypeInfo/Type/CollectionType.php +++ b/src/Symfony/Component/TypeInfo/Type/CollectionType.php @@ -92,6 +92,31 @@ public function wrappedTypeIsSatisfiedBy(callable $specification): bool return $this->getWrappedType()->isSatisfiedBy($specification); } + public function accepts(mixed $value): bool + { + if (!parent::accepts($value)) { + return false; + } + + if ($this->isList() && (!\is_array($value) || !array_is_list($value))) { + return false; + } + + $keyType = $this->getCollectionKeyType(); + $valueType = $this->getCollectionValueType(); + + if (is_iterable($value)) { + foreach ($value as $k => $v) { + // key or value do not match + if (!$keyType->accepts($k) || !$valueType->accepts($v)) { + return false; + } + } + } + + return true; + } + public function __toString(): string { return (string) $this->type; diff --git a/src/Symfony/Component/TypeInfo/Type/NullableType.php b/src/Symfony/Component/TypeInfo/Type/NullableType.php index 6f8d872fab3ef..7ba197693a303 100644 --- a/src/Symfony/Component/TypeInfo/Type/NullableType.php +++ b/src/Symfony/Component/TypeInfo/Type/NullableType.php @@ -59,4 +59,9 @@ public function isNullable(): bool { return true; } + + public function accepts(mixed $value): bool + { + return null === $value || parent::accepts($value); + } } diff --git a/src/Symfony/Component/TypeInfo/Type/ObjectType.php b/src/Symfony/Component/TypeInfo/Type/ObjectType.php index a99c9b4444eb1..4d2c5c3b5515a 100644 --- a/src/Symfony/Component/TypeInfo/Type/ObjectType.php +++ b/src/Symfony/Component/TypeInfo/Type/ObjectType.php @@ -66,6 +66,11 @@ public function isIdentifiedBy(TypeIdentifier|string ...$identifiers): bool return false; } + public function accepts(mixed $value): bool + { + return $value instanceof $this->className; + } + public function __toString(): string { return $this->className; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy