diff --git a/CHANGELOG.md b/CHANGELOG.md index f3a9fda..364e3d5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,23 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip - Nothing --> +## [1.3.0] - 2020-10-17 +### Added +- Lot of improvements for `DisjunctionType` and `ConjunctionType`: + - General code moved to `AbstractAggregatedType` class; + - They can be iterated over now; + - They can be merged with other aggregated types using late static binding; + - Duplicates auto removed from given subtypes; + - At least 2 subtypes required. +- `DisjunctionTypeFactory` and `ConjunctionTypeFactory` also improved: + - General code moved to `AbstractAggregatedTypeFactory` class; + - They can now parse complex type strings, such as: `int|string|array|string|null`. +- `GroupTypeFactory` allows parsing grouped with `()` type strings like: `(string|int)[]` +- `MemoizedTypeFactory` allows cache results of calls to `supports()` and `make()`. + Recommend wrap type factory with memoized decorator in production. +### Fixed +- Fixed string representation of collection type with complex value type. + ## [1.2.0] - 2020-10-13 ### Removed - `Type` interface doesn't declare static methods `support()` and `create()` no more. diff --git a/README.md b/README.md index addbde6..5713649 100755 --- a/README.md +++ b/README.md @@ -35,8 +35,9 @@ You can also use factory to create type object from string ```php use spaceonfire\Type\Factory\CompositeTypeFactory; +use spaceonfire\Type\Factory\MemoizedTypeFactory; -$factory = CompositeTypeFactory::makeWithDefaultFactories(); +$factory = new MemoizedTypeFactory(CompositeTypeFactory::makeWithDefaultFactories()); $factory->make('int'); $factory->make('string[]'); $factory->make('array'); diff --git a/composer.json b/composer.json index 3bf1cb8..b33c72e 100755 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "config": { diff --git a/src/AbstractAggregatedType.php b/src/AbstractAggregatedType.php new file mode 100644 index 0000000..2684a02 --- /dev/null +++ b/src/AbstractAggregatedType.php @@ -0,0 +1,61 @@ +prepareTypes($types)); + + Assert::allIsInstanceOf($types, Type::class); + Assert::minCount($types, 2); + + $this->types = $types; + $this->delimiter = $delimiter; + } + + private function prepareTypes(iterable $types): array + { + $output = []; + + foreach ($types as $type) { + if ($type instanceof static) { + $output += $this->prepareTypes($type); + continue; + } + + $output[(string)$type] = $type; + } + + return $output; + } + + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->types); + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + return implode($this->delimiter, $this->types); + } +} diff --git a/src/CollectionType.php b/src/CollectionType.php index 48bdc8f..46f03cb 100755 --- a/src/CollectionType.php +++ b/src/CollectionType.php @@ -62,7 +62,11 @@ public function check($value): bool */ public function __toString(): string { - if ($this->iterableType instanceof InstanceOfType || $this->keyType !== null) { + if ( + $this->iterableType instanceof InstanceOfType || + $this->valueType instanceof AbstractAggregatedType || + $this->keyType !== null + ) { return $this->iterableType . '<' . implode(',', array_filter([$this->keyType, $this->valueType])) . '>'; } diff --git a/src/ConjunctionType.php b/src/ConjunctionType.php index 4f81e44..bdaf63f 100755 --- a/src/ConjunctionType.php +++ b/src/ConjunctionType.php @@ -6,25 +6,18 @@ use spaceonfire\Type\Factory\CompositeTypeFactory; use spaceonfire\Type\Factory\ConjunctionTypeFactory; -use Webmozart\Assert\Assert; -final class ConjunctionType implements Type +final class ConjunctionType extends AbstractAggregatedType { public const DELIMITER = '&'; - /** - * @var Type[] - */ - private $conjuncts; - /** * ConjunctionType constructor. * @param Type[] $conjuncts */ - public function __construct(array $conjuncts) + public function __construct(iterable $conjuncts) { - Assert::allIsInstanceOf($conjuncts, Type::class); - $this->conjuncts = $conjuncts; + parent::__construct($conjuncts, self::DELIMITER); } /** @@ -32,7 +25,7 @@ public function __construct(array $conjuncts) */ public function check($value): bool { - foreach ($this->conjuncts as $type) { + foreach ($this->types as $type) { if (!$type->check($value)) { return false; } @@ -41,16 +34,6 @@ public function check($value): bool return true; } - /** - * @inheritDoc - */ - public function __toString(): string - { - return implode(self::DELIMITER, array_map(static function (Type $type): string { - return (string)$type; - }, $this->conjuncts)); - } - /** * @param string $type * @return bool diff --git a/src/DisjunctionType.php b/src/DisjunctionType.php index b7ab00c..e483b4d 100755 --- a/src/DisjunctionType.php +++ b/src/DisjunctionType.php @@ -6,25 +6,18 @@ use spaceonfire\Type\Factory\CompositeTypeFactory; use spaceonfire\Type\Factory\DisjunctionTypeFactory; -use Webmozart\Assert\Assert; -final class DisjunctionType implements Type +final class DisjunctionType extends AbstractAggregatedType { public const DELIMITER = '|'; - /** - * @var Type[] - */ - private $disjuncts; - /** * DisjunctionType constructor. * @param Type[] $disjuncts */ - public function __construct(array $disjuncts) + public function __construct(iterable $disjuncts) { - Assert::allIsInstanceOf($disjuncts, Type::class); - $this->disjuncts = $disjuncts; + parent::__construct($disjuncts, self::DELIMITER); } /** @@ -32,7 +25,7 @@ public function __construct(array $disjuncts) */ public function check($value): bool { - foreach ($this->disjuncts as $type) { + foreach ($this->types as $type) { if ($type->check($value)) { return true; } @@ -41,16 +34,6 @@ public function check($value): bool return false; } - /** - * @inheritDoc - */ - public function __toString(): string - { - return implode(self::DELIMITER, array_map(static function (Type $type): string { - return (string)$type; - }, $this->disjuncts)); - } - /** * @param string $type * @return bool diff --git a/src/Factory/AbstractAggregatedTypeFactory.php b/src/Factory/AbstractAggregatedTypeFactory.php new file mode 100644 index 0000000..e1eedca --- /dev/null +++ b/src/Factory/AbstractAggregatedTypeFactory.php @@ -0,0 +1,66 @@ +delimiter = $delimiter; + } + + private function split(string $string): array + { + return (explode($this->delimiter, $string, 2) ?: []) + ['', '']; + } + + final protected function parse(string $type): ?array + { + if ($this->parent === null) { + return null; + } + + $type = $this->removeWhitespaces($type); + + [$left, $right] = $this->split($type); + + if ($left === '' || $right === '') { + return null; + } + + while (!$this->parent->supports($left)) { + [$appendLeft, $right] = $this->split($right); + + if ($appendLeft !== '') { + $left .= $this->delimiter . $appendLeft; + } + + if ($right === '') { + break; + } + } + + if (!$this->parent->supports($right) || !$this->parent->supports($left)) { + return null; + } + + return [$left, $right]; + } + + /** + * @inheritDoc + */ + final public function supports(string $type): bool + { + return $this->parse($type) !== null; + } +} diff --git a/src/Factory/BuiltinTypeFactory.php b/src/Factory/BuiltinTypeFactory.php index e367b08..aa41444 100644 --- a/src/Factory/BuiltinTypeFactory.php +++ b/src/Factory/BuiltinTypeFactory.php @@ -31,7 +31,7 @@ public function __construct(bool $strictByDefault = true) */ public function supports(string $type): bool { - $type = self::prepareType($type); + $type = $this->prepareType($type); return in_array($type, BuiltinType::ALL, true); } @@ -41,7 +41,7 @@ public function supports(string $type): bool */ public function make(string $type): Type { - $type = self::prepareType($type); + $type = $this->prepareType($type); if (!$this->supports($type)) { throw new TypeNotSupportedException($type, BuiltinType::class); @@ -50,9 +50,9 @@ public function make(string $type): Type return new BuiltinType($type, $this->prepareStrictArgument($type)); } - private static function prepareType(string $type): string + private function prepareType(string $type): string { - $type = strtolower($type); + $type = strtolower($this->removeWhitespaces($type)); if (strpos($type, 'resource') === 0) { $type = BuiltinType::RESOURCE; diff --git a/src/Factory/CollectionTypeFactory.php b/src/Factory/CollectionTypeFactory.php index 78e2e7c..4f34c6d 100644 --- a/src/Factory/CollectionTypeFactory.php +++ b/src/Factory/CollectionTypeFactory.php @@ -46,7 +46,7 @@ public function supports(string $type): bool $this->iterableTypeFactory->setParent($this->parent); - $typeParts = self::parseType($type); + $typeParts = $this->parseType($type); if ($typeParts === null) { return false; @@ -84,7 +84,7 @@ public function make(string $type): Type } /** @var array $parsed */ - $parsed = self::parseType($type); + $parsed = $this->parseType($type); $parsed['value'] = $this->parent->make($parsed['value']); $parsed['key'] = $parsed['key'] ? $this->parent->make($parsed['key']) : null; @@ -97,8 +97,10 @@ public function make(string $type): Type * @param string $type * @return array|null */ - private static function parseType(string $type): ?array + private function parseType(string $type): ?array { + $type = $this->removeWhitespaces($type); + $result = [ 'iterable' => null, 'key' => null, @@ -115,7 +117,7 @@ private static function parseType(string $type): ?array (strpos($type, '>') === strlen($type) - 1) ) { $result['iterable'] = substr($type, 0, $openPos); - [$key, $value] = array_map('trim', explode(',', substr($type, $openPos + 1, -1))) + [null, null]; + [$key, $value] = explode(',', substr($type, $openPos + 1, -1)) + [null, null]; if (!$value && !$key) { return null; diff --git a/src/Factory/CompositeTypeFactory.php b/src/Factory/CompositeTypeFactory.php index 1fb2b85..256397d 100644 --- a/src/Factory/CompositeTypeFactory.php +++ b/src/Factory/CompositeTypeFactory.php @@ -33,6 +33,7 @@ public static function makeWithDefaultFactories(): self public static function makeDefaultFactories(): iterable { yield new CollectionTypeFactory(); + yield new GroupTypeFactory(); yield new ConjunctionTypeFactory(); yield new DisjunctionTypeFactory(); yield new InstanceOfTypeFactory(); diff --git a/src/Factory/ConjunctionTypeFactory.php b/src/Factory/ConjunctionTypeFactory.php index a6ab588..41b0cf2 100644 --- a/src/Factory/ConjunctionTypeFactory.php +++ b/src/Factory/ConjunctionTypeFactory.php @@ -8,32 +8,11 @@ use spaceonfire\Type\Exception\TypeNotSupportedException; use spaceonfire\Type\Type; -final class ConjunctionTypeFactory implements TypeFactoryInterface +final class ConjunctionTypeFactory extends AbstractAggregatedTypeFactory { - use TypeFactoryTrait; - - /** - * @inheritDoc - */ - public function supports(string $type): bool + public function __construct() { - if ($this->parent === null) { - return false; - } - - $parts = explode(ConjunctionType::DELIMITER, $type); - - if (count($parts) < 2) { - return false; - } - - foreach ($parts as $part) { - if (!$this->parent->supports(trim($part))) { - return false; - } - } - - return true; + parent::__construct(ConjunctionType::DELIMITER); } /** @@ -41,14 +20,12 @@ public function supports(string $type): bool */ public function make(string $type): Type { - if (!$this->supports($type)) { + $parsed = $this->parse($type); + + if ($parsed === null || $this->parent === null) { throw new TypeNotSupportedException($type, ConjunctionType::class); } - $conjuncts = array_map(function (string $subType): Type { - return $this->parent->make(trim($subType)); - }, explode(ConjunctionType::DELIMITER, $type)); - - return new ConjunctionType($conjuncts); + return new ConjunctionType(array_map([$this->parent, 'make'], $parsed)); } } diff --git a/src/Factory/DisjunctionTypeFactory.php b/src/Factory/DisjunctionTypeFactory.php index 9ddb622..f1cd8da 100644 --- a/src/Factory/DisjunctionTypeFactory.php +++ b/src/Factory/DisjunctionTypeFactory.php @@ -8,32 +8,11 @@ use spaceonfire\Type\Exception\TypeNotSupportedException; use spaceonfire\Type\Type; -final class DisjunctionTypeFactory implements TypeFactoryInterface +final class DisjunctionTypeFactory extends AbstractAggregatedTypeFactory { - use TypeFactoryTrait; - - /** - * @inheritDoc - */ - public function supports(string $type): bool + public function __construct() { - if ($this->parent === null) { - return false; - } - - $parts = explode(DisjunctionType::DELIMITER, $type); - - if (count($parts) < 2) { - return false; - } - - foreach ($parts as $part) { - if (!$this->parent->supports(trim($part))) { - return false; - } - } - - return true; + parent::__construct(DisjunctionType::DELIMITER); } /** @@ -41,14 +20,12 @@ public function supports(string $type): bool */ public function make(string $type): Type { - if (!$this->supports($type)) { + $parsed = $this->parse($type); + + if ($parsed === null || $this->parent === null) { throw new TypeNotSupportedException($type, DisjunctionType::class); } - $conjuncts = array_map(function (string $subType): Type { - return $this->parent->make(trim($subType)); - }, explode(DisjunctionType::DELIMITER, $type)); - - return new DisjunctionType($conjuncts); + return new DisjunctionType(array_map([$this->parent, 'make'], $parsed)); } } diff --git a/src/Factory/GroupTypeFactory.php b/src/Factory/GroupTypeFactory.php new file mode 100644 index 0000000..c2de835 --- /dev/null +++ b/src/Factory/GroupTypeFactory.php @@ -0,0 +1,45 @@ +parent === null) { + return false; + } + + $type = $this->removeWhitespaces($type); + + return + strlen($type) > 2 && + $type[0] === '(' && + strrev($type)[0] === ')' && + $this->parent->supports(substr($type, 1, -1)); + } + + /** + * @inheritDoc + */ + public function make(string $type): Type + { + $type = $this->removeWhitespaces($type); + + if (!$this->supports($type)) { + throw new TypeNotSupportedException($type); + } + + return $this->parent->make(substr($type, 1, -1)); + } +} diff --git a/src/Factory/InstanceOfTypeFactory.php b/src/Factory/InstanceOfTypeFactory.php index 852a48a..f379864 100644 --- a/src/Factory/InstanceOfTypeFactory.php +++ b/src/Factory/InstanceOfTypeFactory.php @@ -31,6 +31,7 @@ public function __construct(bool $autoload = true) */ public function supports(string $type): bool { + $type = $this->removeWhitespaces($type); return class_exists($type, $this->autoload) || interface_exists($type, $this->autoload); } @@ -39,6 +40,8 @@ public function supports(string $type): bool */ public function make(string $type): Type { + $type = $this->removeWhitespaces($type); + if (!$this->supports($type)) { throw new TypeNotSupportedException($type, InstanceOfType::class); } diff --git a/src/Factory/MemoizedTypeFactory.php b/src/Factory/MemoizedTypeFactory.php new file mode 100644 index 0000000..7509bc0 --- /dev/null +++ b/src/Factory/MemoizedTypeFactory.php @@ -0,0 +1,65 @@ + + */ + private $cacheSupports = []; + /** + * @var array + */ + private $cacheMake = []; + + /** + * MemoizedTypeFactory constructor. + * @param TypeFactoryInterface $underlyingFactory + */ + public function __construct(TypeFactoryInterface $underlyingFactory) + { + $this->underlyingFactory = $underlyingFactory; + $this->underlyingFactory->setParent($this); + } + + /** + * @inheritDoc + */ + public function supports(string $type): bool + { + if (!isset($this->cacheSupports[$type])) { + $this->cacheSupports[$type] = $this->underlyingFactory->supports($type); + } + + return $this->cacheSupports[$type]; + } + + /** + * @inheritDoc + */ + public function make(string $type): Type + { + if (!isset($this->cacheMake[$type])) { + $this->cacheMake[$type] = $this->underlyingFactory->make($type); + } + + return $this->cacheMake[$type]; + } + + /** + * @inheritDoc + */ + public function setParent(TypeFactoryInterface $parent): void + { + $this->underlyingFactory->setParent($parent); + } +} diff --git a/src/Factory/MixedTypeFactory.php b/src/Factory/MixedTypeFactory.php index 8dfeafb..5da9112 100644 --- a/src/Factory/MixedTypeFactory.php +++ b/src/Factory/MixedTypeFactory.php @@ -17,6 +17,7 @@ final class MixedTypeFactory implements TypeFactoryInterface */ public function supports(string $type): bool { + $type = $this->removeWhitespaces($type); return $type === MixedType::NAME; } @@ -25,6 +26,7 @@ public function supports(string $type): bool */ public function make(string $type): Type { + $type = $this->removeWhitespaces($type); if (!$this->supports($type)) { throw new TypeNotSupportedException($type, MixedType::class); } diff --git a/src/Factory/PartialSupportTypeFactory.php b/src/Factory/PartialSupportTypeFactory.php index 18fa46d..998faaf 100644 --- a/src/Factory/PartialSupportTypeFactory.php +++ b/src/Factory/PartialSupportTypeFactory.php @@ -36,6 +36,7 @@ public function __construct(TypeFactoryInterface $factory, callable $supportedPr */ public function supports(string $type): bool { + $type = $this->removeWhitespaces($type); return $this->factory->supports($type) && ($this->supportedPredicate)($type); } @@ -44,6 +45,8 @@ public function supports(string $type): bool */ public function make(string $type): Type { + $type = $this->removeWhitespaces($type); + if (!$this->supports($type)) { throw new TypeNotSupportedException($type); } diff --git a/src/Factory/TypeFactoryTrait.php b/src/Factory/TypeFactoryTrait.php index 00a5a05..f7b4181 100644 --- a/src/Factory/TypeFactoryTrait.php +++ b/src/Factory/TypeFactoryTrait.php @@ -19,4 +19,9 @@ public function setParent(TypeFactoryInterface $parent): void { $this->parent = $parent; } + + protected function removeWhitespaces(string $string): string + { + return preg_replace('/\s+/', '', $string) ?? $string; + } } diff --git a/src/Factory/VoidTypeFactory.php b/src/Factory/VoidTypeFactory.php index 0d10eeb..6ba781f 100644 --- a/src/Factory/VoidTypeFactory.php +++ b/src/Factory/VoidTypeFactory.php @@ -17,6 +17,7 @@ final class VoidTypeFactory implements TypeFactoryInterface */ public function supports(string $type): bool { + $type = $this->removeWhitespaces($type); return $type === VoidType::NAME; } @@ -25,6 +26,8 @@ public function supports(string $type): bool */ public function make(string $type): Type { + $type = $this->removeWhitespaces($type); + if (!$this->supports($type)) { throw new TypeNotSupportedException($type, VoidType::class); } diff --git a/tests/CollectionTypeTest.php b/tests/CollectionTypeTest.php index 39cf1be..c5419b9 100755 --- a/tests/CollectionTypeTest.php +++ b/tests/CollectionTypeTest.php @@ -35,20 +35,30 @@ public function testCheck(): void public function testStringify(): void { $integerCollection = new CollectionType(new BuiltinType(BuiltinType::INT)); - self::assertEquals('int[]', (string)$integerCollection); + self::assertSame('int[]', (string)$integerCollection); $arrayStringInt = new CollectionType( new BuiltinType(BuiltinType::INT), new BuiltinType(BuiltinType::STRING), new BuiltinType(BuiltinType::ARRAY) ); - self::assertEquals('array', (string)$arrayStringInt); + self::assertSame('array', (string)$arrayStringInt); $arrayIteratorInteger = new CollectionType( new BuiltinType(BuiltinType::INT), null, new InstanceOfType(ArrayIterator::class) ); - self::assertEquals('ArrayIterator', (string)$arrayIteratorInteger); + self::assertSame('ArrayIterator', (string)$arrayIteratorInteger); + + $arrayOfBoolOrInt = new CollectionType( + new DisjunctionType([ + new BuiltinType(BuiltinType::BOOL), + new BuiltinType(BuiltinType::INT), + ]), + null, + new BuiltinType(BuiltinType::ARRAY) + ); + self::assertSame('array', (string)$arrayOfBoolOrInt); } } diff --git a/tests/ConjunctionTypeTest.php b/tests/ConjunctionTypeTest.php index 1661119..f8f8a0a 100755 --- a/tests/ConjunctionTypeTest.php +++ b/tests/ConjunctionTypeTest.php @@ -32,6 +32,6 @@ public function testStringify(): void new InstanceOfType(Traversable::class), ]); - self::assertEquals(JsonSerializable::class . '&' . Traversable::class, (string)$type); + self::assertSame(JsonSerializable::class . '&' . Traversable::class, (string)$type); } } diff --git a/tests/DisjunctionTypeTest.php b/tests/DisjunctionTypeTest.php index 7ca1db3..8dc78ff 100755 --- a/tests/DisjunctionTypeTest.php +++ b/tests/DisjunctionTypeTest.php @@ -25,6 +25,6 @@ public function testStringify(): void new BuiltinType(BuiltinType::INT), new BuiltinType(BuiltinType::NULL), ]); - self::assertEquals('int|null', (string)$type); + self::assertSame('int|null', (string)$type); } } diff --git a/tests/Factory/CollectionTypeFactoryTest.php b/tests/Factory/CollectionTypeFactoryTest.php index b3dc80f..9a472e8 100644 --- a/tests/Factory/CollectionTypeFactoryTest.php +++ b/tests/Factory/CollectionTypeFactoryTest.php @@ -31,6 +31,7 @@ public function testSupports(): void self::assertTrue($factory->supports('int[]')); self::assertTrue($factory->supports('array')); self::assertTrue($factory->supports('iterable')); + self::assertTrue($factory->supports('(string|int)[]')); self::assertTrue($factory->supports('ArrayIterator')); self::assertTrue($factory->supports('Traversable')); self::assertFalse($factory->supports('[]')); @@ -40,6 +41,7 @@ public function testSupports(): void self::assertFalse($factory->supports('string')); self::assertFalse($factory->supports('array')); self::assertFalse($factory->supports('array')); + self::assertFalse($factory->supports('arraysupports('void')); self::assertFalse($factory->supports('[]')); self::assertFalse($factory->supports('unknown')); + self::assertTrue($factory->supports('int|string|array|string|null')); + self::assertTrue($factory->supports('int | string|array < bool | int >|string |null')); } public function testMake(): void @@ -44,6 +46,7 @@ public function testMake(): void self::assertInstanceOf(BuiltinType::class, $factory->make('int')); self::assertInstanceOf(MixedType::class, $factory->make('mixed')); self::assertInstanceOf(VoidType::class, $factory->make('void')); + self::assertInstanceOf(DisjunctionType::class, $factory->make('int|string|array|string|null')); } public function testMakeException(): void diff --git a/tests/Factory/ConjunctionTypeFactoryTest.php b/tests/Factory/ConjunctionTypeFactoryTest.php index 0195249..251b285 100644 --- a/tests/Factory/ConjunctionTypeFactoryTest.php +++ b/tests/Factory/ConjunctionTypeFactoryTest.php @@ -25,6 +25,7 @@ public function testSupports(): void self::assertTrue($factory->supports(JsonSerializable::class . '&' . Traversable::class)); self::assertFalse($factory->supports(JsonSerializable::class . '&unknown')); self::assertFalse($factory->supports(JsonSerializable::class)); + self::assertFalse($factory->supports('arraysupports(JsonSerializable::class . '|' . Traversable::class)); self::assertFalse($factory->supports(JsonSerializable::class . '|unknown')); self::assertFalse($factory->supports(JsonSerializable::class)); + self::assertFalse($factory->supports('arraysetParent(CompositeTypeFactory::makeWithDefaultFactories()); + return $factory; + } + + public function testSupports(): void + { + $factory = $this->makeFactory(); + + self::assertTrue($factory->supports('(int)')); + self::assertTrue($factory->supports('(int|string)')); + self::assertTrue($factory->supports('(int&string)')); + self::assertFalse($factory->supports('(int&string')); + self::assertFalse($factory->supports('int&string)')); + self::assertFalse($factory->supports('(unknown)')); + self::assertFalse($factory->supports('()')); + } + + public function testNoSupportsWithoutParent(): void + { + $factory = new GroupTypeFactory(); + self::assertFalse($factory->supports('(int)')); + } + + public function testMake(): void + { + $factory = $this->makeFactory(); + + self::assertInstanceOf(BuiltinType::class, $factory->make('(int)')); + self::assertInstanceOf(DisjunctionType::class, $factory->make('(int|string)')); + self::assertInstanceOf(ConjunctionType::class, $factory->make('(int&string)')); + } + + public function testMakeException(): void + { + $factory = $this->makeFactory(); + + $this->expectException(TypeNotSupportedException::class); + $factory->make('(unknown)'); + } +} diff --git a/tests/Factory/MemoizedTypeFactoryTest.php b/tests/Factory/MemoizedTypeFactoryTest.php new file mode 100644 index 0000000..3b775d9 --- /dev/null +++ b/tests/Factory/MemoizedTypeFactoryTest.php @@ -0,0 +1,48 @@ +prophesize(TypeFactoryInterface::class); + $underlyingFactory->setParent(Argument::any()); + $underlyingFactory->supports('supported-type')->willReturn(true)->shouldBeCalledOnce(); + $underlyingFactory->supports('unsupported-type')->willReturn(false)->shouldBeCalledOnce(); + + $factory = new MemoizedTypeFactory($underlyingFactory->reveal()); + + self::assertTrue($factory->supports('supported-type')); + self::assertTrue($factory->supports('supported-type')); + self::assertFalse($factory->supports('unsupported-type')); + self::assertFalse($factory->supports('unsupported-type')); + } + + public function testMake(): void + { + $expectedReturn = $this->prophesize(Type::class)->reveal(); + $underlyingFactory = $this->prophesize(TypeFactoryInterface::class); + $underlyingFactory->setParent(Argument::any()); + $underlyingFactory->make('supported-type')->willReturn($expectedReturn)->shouldBeCalledOnce(); + + $factory = new MemoizedTypeFactory($underlyingFactory->reveal()); + + self::assertSame($expectedReturn, $factory->make('supported-type')); + self::assertSame($expectedReturn, $factory->make('supported-type')); + } + + public function testSetParent(): void + { + $underlyingFactory = $this->prophesize(TypeFactoryInterface::class); + $underlyingFactory->setParent(Argument::any())->shouldBeCalled(); + $factory = new MemoizedTypeFactory($underlyingFactory->reveal()); + $factory->setParent($factory); + } +} diff --git a/tests/InstanceOfTypeTest.php b/tests/InstanceOfTypeTest.php index 34c7912..e2ddcea 100755 --- a/tests/InstanceOfTypeTest.php +++ b/tests/InstanceOfTypeTest.php @@ -26,6 +26,6 @@ public function testCheck(): void public function testStringify(): void { $type = new InstanceOfType(stdClass::class); - self::assertEquals(stdClass::class, (string)$type); + self::assertSame(stdClass::class, (string)$type); } }