diff --git a/src/Utils/Type.php b/src/Utils/Type.php index 2c481d622..2debc7117 100644 --- a/src/Utils/Type.php +++ b/src/Utils/Type.php @@ -59,6 +59,22 @@ function ($t) use ($reflection) { return self::resolve($t->getName(), $reflectio } + /** + * Creates the Type object according to the text notation. + */ + public static function fromString(string $type): self + { + if (!preg_match('#(?:\?([\w\\\\]+)|[\w\\\\]+(?:\|[\w\\\\]+)*)$#AD', $type, $m)) { + throw new Nette\InvalidArgumentException("Invalid type '$type'."); + } + if (isset($m[1])) { + return new self([$m[1], 'null']); + } else { + return new self(explode('|', $type)); + } + } + + /** * Resolves 'self', 'static' and 'parent' to the actual class name. * @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection @@ -104,4 +120,61 @@ public function getNames(): array { return $this->types; } + + + /** + * Returns the array of subtypes that make up the compound type as Type objects: + * @return self[] + */ + public function getTypes(): array + { + return array_map(function ($name) { return self::fromString($name); }, $this->types); + } + + + /** + * Returns the type name for single types, otherwise null. + */ + public function getSingleName(): ?string + { + return $this->single + ? $this->types[0] + : null; + } + + + /** + * Returns true whether it is a union type. + */ + public function isUnion(): bool + { + return count($this->types) > 1; + } + + + /** + * Returns true whether it is a single type. Simple nullable types are also considered to be single types. + */ + public function isSingle(): bool + { + return $this->single; + } + + + /** + * Returns true whether the type is both a single and a PHP built-in type. + */ + public function isBuiltin(): bool + { + return $this->single && Reflection::isBuiltinType($this->types[0]); + } + + + /** + * Returns true whether the type is both a single and a class name. + */ + public function isClass(): bool + { + return $this->single && !Reflection::isBuiltinType($this->types[0]); + } } diff --git a/tests/Utils/Type.fromString.phpt b/tests/Utils/Type.fromString.phpt new file mode 100644 index 000000000..cbae02253 --- /dev/null +++ b/tests/Utils/Type.fromString.phpt @@ -0,0 +1,108 @@ +getNames()); +Assert::equal([Type::fromString('string')], $type->getTypes()); +Assert::same('string', (string) $type); +Assert::same('string', $type->getSingleName()); +Assert::false($type->isClass()); +Assert::false($type->isUnion()); +Assert::true($type->isSingle()); +Assert::true($type->isBuiltin()); + + +$type = Type::fromString('string|null'); + +Assert::same(['string', 'null'], $type->getNames()); +Assert::equal([Type::fromString('string'), Type::fromString('null')], $type->getTypes()); +Assert::same('?string', (string) $type); +Assert::same('string', $type->getSingleName()); +Assert::false($type->isClass()); +Assert::true($type->isUnion()); +Assert::true($type->isSingle()); +Assert::true($type->isBuiltin()); + + +$type = Type::fromString('null|string'); + +Assert::same(['string', 'null'], $type->getNames()); +Assert::equal([Type::fromString('string'), Type::fromString('null')], $type->getTypes()); +Assert::same('?string', (string) $type); +Assert::same('string', $type->getSingleName()); +Assert::false($type->isClass()); +Assert::true($type->isUnion()); +Assert::true($type->isSingle()); +Assert::true($type->isBuiltin()); + + +$type = Type::fromString('?string'); + +Assert::same(['string', 'null'], $type->getNames()); +Assert::equal([Type::fromString('string'), Type::fromString('null')], $type->getTypes()); +Assert::same('?string', (string) $type); +Assert::same('string', $type->getSingleName()); +Assert::false($type->isClass()); +Assert::true($type->isUnion()); +Assert::true($type->isSingle()); +Assert::true($type->isBuiltin()); + + +$type = Type::fromString('NS\Foo'); + +Assert::same(['NS\Foo'], $type->getNames()); +Assert::equal([Type::fromString('NS\Foo')], $type->getTypes()); +Assert::same('NS\Foo', (string) $type); +Assert::same('NS\Foo', $type->getSingleName()); +Assert::true($type->isClass()); +Assert::false($type->isUnion()); +Assert::true($type->isSingle()); +Assert::false($type->isBuiltin()); + + +$type = Type::fromString('string|Foo'); + +Assert::same(['string', 'Foo'], $type->getNames()); +Assert::equal([Type::fromString('string'), Type::fromString('Foo')], $type->getTypes()); +Assert::same('string|Foo', (string) $type); +Assert::null($type->getSingleName()); +Assert::false($type->isClass()); +Assert::true($type->isUnion()); +Assert::false($type->isSingle()); +Assert::false($type->isBuiltin()); + + +$type = Type::fromString('mixed'); + +Assert::same(['mixed'], $type->getNames()); +Assert::equal([Type::fromString('mixed')], $type->getTypes()); +Assert::same('mixed', (string) $type); +Assert::same('mixed', $type->getSingleName()); +Assert::false($type->isClass()); +Assert::false($type->isUnion()); +Assert::true($type->isSingle()); +Assert::true($type->isBuiltin()); + + +$type = Type::fromString('null'); // invalid type + +Assert::same(['null'], $type->getNames()); +Assert::equal([Type::fromString('null')], $type->getTypes()); +Assert::same('null', (string) $type); +Assert::same('null', $type->getSingleName()); +Assert::false($type->isClass()); +Assert::false($type->isUnion()); +Assert::true($type->isSingle()); +Assert::true($type->isBuiltin());