diff --git a/lib/Doctrine/DBAL/DBALException.php b/lib/Doctrine/DBAL/DBALException.php index 9516d685d27..cb281b9ef7d 100644 --- a/lib/Doctrine/DBAL/DBALException.php +++ b/lib/Doctrine/DBAL/DBALException.php @@ -18,7 +18,7 @@ use function is_resource; use function is_string; use function json_encode; -use function spl_object_id; +use function spl_object_hash; use function sprintf; use function str_split; @@ -287,6 +287,13 @@ public static function typeNotFound($name) public static function typeNotRegistered(Type $type) : self { - return new self(sprintf('Type of the class %s@%d is not registered.', get_class($type), spl_object_id($type))); + return new self(sprintf('Type of the class %s@%s is not registered.', get_class($type), spl_object_hash($type))); + } + + public static function typeAlreadyRegistered(Type $type) : self + { + return new self( + sprintf('Type of the class %s@%s is already registered.', get_class($type), spl_object_hash($type)) + ); } } diff --git a/lib/Doctrine/DBAL/Types/Type.php b/lib/Doctrine/DBAL/Types/Type.php index c6faaece916..3e9c968eefa 100644 --- a/lib/Doctrine/DBAL/Types/Type.php +++ b/lib/Doctrine/DBAL/Types/Type.php @@ -5,8 +5,10 @@ use Doctrine\DBAL\DBALException; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Platforms\AbstractPlatform; +use function array_map; use function end; use function explode; +use function get_class; use function str_replace; /** @@ -77,7 +79,7 @@ abstract class Type private static $typeRegistry; /** - * @internal Do not instantiate directly - use the factory method instead. + * @internal Do not instantiate directly - use {@see Type::addType()} method instead. */ final public function __construct() { @@ -156,7 +158,7 @@ private static function createTypeRegistry() : TypeRegistry $registry = new TypeRegistry(); foreach (self::BUILTIN_TYPES_MAP as $name => $class) { - $registry->register($name, $class); + $registry->register($name, new $class()); } return $registry; @@ -189,7 +191,7 @@ public static function getType($name) */ public static function addType($name, $className) { - self::getTypeRegistry()->register($name, $className); + self::getTypeRegistry()->register($name, new $className()); } /** @@ -216,7 +218,7 @@ public static function hasType($name) */ public static function overrideType($name, $className) { - self::getTypeRegistry()->override($name, $className); + self::getTypeRegistry()->override($name, new $className()); } /** @@ -240,7 +242,12 @@ public function getBindingType() */ public static function getTypesMap() { - return self::getTypeRegistry()->getTypeClasses(); + return array_map( + static function (Type $type) : string { + return get_class($type); + }, + self::getTypeRegistry()->all() + ); } /** diff --git a/lib/Doctrine/DBAL/Types/TypeRegistry.php b/lib/Doctrine/DBAL/Types/TypeRegistry.php index 1c32bb1d9c5..1062200a992 100644 --- a/lib/Doctrine/DBAL/Types/TypeRegistry.php +++ b/lib/Doctrine/DBAL/Types/TypeRegistry.php @@ -6,17 +6,15 @@ use Doctrine\DBAL\DBALException; use function array_search; +use function in_array; /** * @internal */ final class TypeRegistry { - /** @var Type[] Map of already instantiated type objects. One instance per type (flyweight). */ - private $typeInstances = []; - - /** @var string[] Map of defined types and their classes. */ - private $typeClasses = []; + /** @var Type[] Map of already instantiated type objects. */ + private $instances = []; /** * Factory method to create type instances. @@ -26,15 +24,11 @@ final class TypeRegistry */ public function get(string $name) : Type { - if (isset($this->typeInstances[$name])) { - return $this->typeInstances[$name]; - } - - if (isset($this->typeClasses[$name])) { - return $this->typeInstances[$name] = new $this->typeClasses[$name](); + if (! isset($this->instances[$name])) { + throw DBALException::unknownColumnType($name); } - throw DBALException::unknownColumnType($name); + return $this->instances[$name]; } /** @@ -44,9 +38,9 @@ public function get(string $name) : Type */ public function lookupName(Type $type) : string { - $name = array_search($type, $this->typeInstances, true); + $name = $this->findTypeName($type); - if ($name === false) { + if ($name === null) { throw DBALException::typeNotRegistered($type); } @@ -58,24 +52,25 @@ public function lookupName(Type $type) : string */ public function has(string $name) : bool { - return isset($this->typeClasses[$name]); + return isset($this->instances[$name]); } /** * Adds a custom type to the type map. * - * @param string $name The name of the type. This should correspond to what getName() returns. - * @param string $class The class name of the custom type. - * * @throws DBALException */ - public function register(string $name, string $class) : void + public function register(string $name, Type $type) : void { - if (isset($this->typeClasses[$name])) { + if (isset($this->instances[$name])) { throw DBALException::typeExists($name); } - $this->typeClasses[$name] = $class; + if ($this->findTypeName($type) !== null) { + throw DBALException::typeAlreadyRegistered($type); + } + + $this->instances[$name] = $type; } /** @@ -83,25 +78,37 @@ public function register(string $name, string $class) : void * * @throws DBALException */ - public function override(string $name, string $class) : void + public function override(string $name, Type $type) : void { - if (! isset($this->typeClasses[$name])) { + if (! isset($this->instances[$name])) { throw DBALException::typeNotFound($name); } - unset($this->typeInstances[$name]); + if (! in_array($this->findTypeName($type), [$name, null], true)) { + throw DBALException::typeAlreadyRegistered($type); + } - $this->typeClasses[$name] = $class; + $this->instances[$name] = $type; } /** - * Gets the types array map which holds all registered types and the corresponding - * type class + * Gets the map of all registered types and their corresponding type instances. * - * @return string[] + * @return Type[] */ - public function getTypeClasses() : array + public function all() : array { - return $this->typeClasses; + return $this->instances; + } + + private function findTypeName(Type $type) : ?string + { + $name = array_search($type, $this->instances, true); + + if ($name === false) { + return null; + } + + return $name; } } diff --git a/tests/Doctrine/Tests/DBAL/Types/TypeRegistryTest.php b/tests/Doctrine/Tests/DBAL/Types/TypeRegistryTest.php index 11fb0e2d44a..4118725ddec 100644 --- a/tests/Doctrine/Tests/DBAL/Types/TypeRegistryTest.php +++ b/tests/Doctrine/Tests/DBAL/Types/TypeRegistryTest.php @@ -14,24 +14,32 @@ class TypeRegistryTest extends TestCase { - private const TEST_TYPE_NAME = 'test'; - private const TEST_TYPE_CLASS = BlobType::class; - private const OTHER_TYPE_NAME = 'other'; - private const OTHER_TYPE_CLASS = BinaryType::class; + private const TEST_TYPE_NAME = 'test'; + private const OTHER_TEST_TYPE_NAME = 'other'; /** @var TypeRegistry */ private $registry; + /** @var BlobType */ + private $testType; + + /** @var BinaryType */ + private $otherTestType; + protected function setUp() : void { + $this->testType = new BlobType(); + $this->otherTestType = new BinaryType(); + $this->registry = new TypeRegistry(); - $this->registry->register(self::OTHER_TYPE_NAME, self::OTHER_TYPE_CLASS); - $this->registry->register(self::TEST_TYPE_NAME, self::TEST_TYPE_CLASS); + $this->registry->register(self::TEST_TYPE_NAME, $this->testType); + $this->registry->register(self::OTHER_TEST_TYPE_NAME, $this->otherTestType); } public function testGet() : void { - self::assertInstanceOf(self::TEST_TYPE_CLASS, $this->registry->get(self::TEST_TYPE_NAME)); + self::assertSame($this->testType, $this->registry->get(self::TEST_TYPE_NAME)); + self::assertSame($this->otherTestType, $this->registry->get(self::OTHER_TEST_TYPE_NAME)); $this->expectException(DBALException::class); $this->registry->get('unknown'); @@ -49,7 +57,11 @@ public function testLookupName() : void { self::assertSame( self::TEST_TYPE_NAME, - $this->registry->lookupName($this->registry->get(self::TEST_TYPE_NAME)) + $this->registry->lookupName($this->testType) + ); + self::assertSame( + self::OTHER_TEST_TYPE_NAME, + $this->registry->lookupName($this->otherTestType) ); $this->expectException(DBALException::class); @@ -59,40 +71,84 @@ public function testLookupName() : void public function testHas() : void { self::assertTrue($this->registry->has(self::TEST_TYPE_NAME)); + self::assertTrue($this->registry->has(self::OTHER_TEST_TYPE_NAME)); self::assertFalse($this->registry->has('unknown')); } public function testRegister() : void { - $this->registry->register('some', TextType::class); + $newType = new TextType(); + + $this->registry->register('some', $newType); self::assertTrue($this->registry->has('some')); - self::assertInstanceOf(TextType::class, $this->registry->get('some')); + self::assertSame($newType, $this->registry->get('some')); + } + + public function testRegisterWithAlradyRegisteredName() : void + { + $this->registry->register('some', new TextType()); $this->expectException(DBALException::class); - $this->registry->register('some', TextType::class); + $this->registry->register('some', new TextType()); + } + + public function testRegisterWithAlreadyRegisteredInstance() : void + { + $newType = new TextType(); + + $this->registry->register('some', $newType); + + $this->expectException(DBALException::class); + $this->registry->register('some', $newType); } public function testOverride() : void { - $this->registry->register('some', TextType::class); - $this->registry->override('some', StringType::class); + $baseType = new TextType(); + $overrideType = new StringType(); - self::assertTrue($this->registry->has('some')); - self::assertInstanceOf(StringType::class, $this->registry->get('some')); + $this->registry->register('some', $baseType); + $this->registry->override('some', $overrideType); + + self::assertSame($overrideType, $this->registry->get('some')); + } + + public function testOverrideAllowsExistingInstance() : void + { + $type = new TextType(); + + $this->registry->register('some', $type); + $this->registry->override('some', $type); + + self::assertSame($type, $this->registry->get('some')); + } + + public function testOverrideWithAlreadyRegisteredInstance() : void + { + $newType = new TextType(); + + $this->registry->register('first', $newType); + $this->registry->register('second', new StringType()); $this->expectException(DBALException::class); - $this->registry->override('unknown', StringType::class); + $this->registry->override('second', $newType); } - public function testGetTypeClasses() : void + public function testOverrideWithUnknownType() : void { - self::assertSame( - [ - self::OTHER_TYPE_NAME => self::OTHER_TYPE_CLASS, - self::TEST_TYPE_NAME => self::TEST_TYPE_CLASS, - ], - $this->registry->getTypeClasses() - ); + $this->expectException(DBALException::class); + $this->registry->override('unknown', new TextType()); + } + + public function testAll() : void + { + $registeredTypes = $this->registry->all(); + + self::assertCount(2, $registeredTypes); + self::assertArrayHasKey(self::TEST_TYPE_NAME, $registeredTypes); + self::assertArrayHasKey(self::OTHER_TEST_TYPE_NAME, $registeredTypes); + self::assertSame($this->testType, $registeredTypes[self::TEST_TYPE_NAME]); + self::assertSame($this->otherTestType, $registeredTypes[self::OTHER_TEST_TYPE_NAME]); } }