Unverified Commit 11b67bbd authored by Sergei Morozov's avatar Sergei Morozov Committed by GitHub

Merge pull request #3354 from Majkl578/type-registry

Extract type factory and registry from Type into TypeRegistry
parents 773275d1 089f32e6
......@@ -6,6 +6,7 @@ use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface;
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Exception;
use Throwable;
use function array_map;
......@@ -18,6 +19,7 @@ use function is_resource;
use function is_string;
use function json_encode;
use function preg_replace;
use function spl_object_hash;
use function sprintf;
class DBALException extends Exception
......@@ -277,4 +279,16 @@ class DBALException extends Exception
{
return new self('Type to be overwritten ' . $name . ' does not exist.');
}
public static function typeNotRegistered(Type $type) : self
{
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))
);
}
}
......@@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Types;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use function array_map;
use function get_class;
use function str_replace;
use function strrpos;
use function substr;
......@@ -16,76 +18,70 @@ use function substr;
*/
abstract class Type
{
public const TARRAY = 'array';
public const SIMPLE_ARRAY = 'simple_array';
public const JSON_ARRAY = 'json_array';
public const JSON = 'json';
public const BIGINT = 'bigint';
public const BINARY = 'binary';
public const BLOB = 'blob';
public const BOOLEAN = 'boolean';
public const DATE = 'date';
public const DATE_IMMUTABLE = 'date_immutable';
public const DATEINTERVAL = 'dateinterval';
public const DATETIME = 'datetime';
public const DATETIME_IMMUTABLE = 'datetime_immutable';
public const DATETIMETZ = 'datetimetz';
public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable';
public const DATE = 'date';
public const DATE_IMMUTABLE = 'date_immutable';
public const TIME = 'time';
public const TIME_IMMUTABLE = 'time_immutable';
public const DECIMAL = 'decimal';
public const FLOAT = 'float';
public const GUID = 'guid';
public const INTEGER = 'integer';
public const JSON = 'json';
public const JSON_ARRAY = 'json_array';
public const OBJECT = 'object';
public const SIMPLE_ARRAY = 'simple_array';
public const SMALLINT = 'smallint';
public const STRING = 'string';
public const TARRAY = 'array';
public const TEXT = 'text';
public const BINARY = 'binary';
public const BLOB = 'blob';
public const FLOAT = 'float';
public const GUID = 'guid';
public const DATEINTERVAL = 'dateinterval';
/**
* Map of already instantiated type objects. One instance per type (flyweight).
*
* @var self[]
*/
private static $_typeObjects = [];
public const TIME = 'time';
public const TIME_IMMUTABLE = 'time_immutable';
/**
* The map of supported doctrine mapping types.
*
* @var string[]
*/
private static $_typesMap = [
self::TARRAY => ArrayType::class,
self::SIMPLE_ARRAY => SimpleArrayType::class,
self::JSON_ARRAY => JsonArrayType::class,
self::JSON => JsonType::class,
self::OBJECT => ObjectType::class,
self::BOOLEAN => BooleanType::class,
self::INTEGER => IntegerType::class,
self::SMALLINT => SmallIntType::class,
self::BIGINT => BigIntType::class,
self::STRING => StringType::class,
self::TEXT => TextType::class,
self::DATETIME => DateTimeType::class,
self::DATETIME_IMMUTABLE => DateTimeImmutableType::class,
self::DATETIMETZ => DateTimeTzType::class,
private const BUILTIN_TYPES_MAP = [
self::BIGINT => BigIntType::class,
self::BINARY => BinaryType::class,
self::BLOB => BlobType::class,
self::BOOLEAN => BooleanType::class,
self::DATE => DateType::class,
self::DATE_IMMUTABLE => DateImmutableType::class,
self::DATEINTERVAL => DateIntervalType::class,
self::DATETIME => DateTimeType::class,
self::DATETIME_IMMUTABLE => DateTimeImmutableType::class,
self::DATETIMETZ => DateTimeTzType::class,
self::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class,
self::DATE => DateType::class,
self::DATE_IMMUTABLE => DateImmutableType::class,
self::TIME => TimeType::class,
self::TIME_IMMUTABLE => TimeImmutableType::class,
self::DECIMAL => DecimalType::class,
self::FLOAT => FloatType::class,
self::BINARY => BinaryType::class,
self::BLOB => BlobType::class,
self::GUID => GuidType::class,
self::DATEINTERVAL => DateIntervalType::class,
self::DECIMAL => DecimalType::class,
self::FLOAT => FloatType::class,
self::GUID => GuidType::class,
self::INTEGER => IntegerType::class,
self::JSON => JsonType::class,
self::JSON_ARRAY => JsonArrayType::class,
self::OBJECT => ObjectType::class,
self::SIMPLE_ARRAY => SimpleArrayType::class,
self::SMALLINT => SmallIntType::class,
self::STRING => StringType::class,
self::TARRAY => ArrayType::class,
self::TEXT => TextType::class,
self::TIME => TimeType::class,
self::TIME_IMMUTABLE => TimeImmutableType::class,
];
/** @var TypeRegistry|null */
private static $typeRegistry;
/**
* Prevents instantiation and forces use of the factory method.
* @internal Do not instantiate directly - use {@see Type::addType()} method instead.
*/
final private function __construct()
final public function __construct()
{
}
......@@ -148,6 +144,29 @@ abstract class Type
*/
abstract public function getName();
/**
* @internal This method is only to be used within DBAL for forward compatibility purposes. Do not use directly.
*/
final public static function getTypeRegistry() : TypeRegistry
{
if (self::$typeRegistry === null) {
self::$typeRegistry = self::createTypeRegistry();
}
return self::$typeRegistry;
}
private static function createTypeRegistry() : TypeRegistry
{
$registry = new TypeRegistry();
foreach (self::BUILTIN_TYPES_MAP as $name => $class) {
$registry->register($name, new $class());
}
return $registry;
}
/**
* Factory method to create type instances.
* Type instances are implemented as flyweights.
......@@ -160,14 +179,7 @@ abstract class Type
*/
public static function getType($name)
{
if (! isset(self::$_typeObjects[$name])) {
if (! isset(self::$_typesMap[$name])) {
throw DBALException::unknownColumnType($name);
}
self::$_typeObjects[$name] = new self::$_typesMap[$name]();
}
return self::$_typeObjects[$name];
return self::getTypeRegistry()->get($name);
}
/**
......@@ -182,11 +194,7 @@ abstract class Type
*/
public static function addType($name, $className)
{
if (isset(self::$_typesMap[$name])) {
throw DBALException::typeExists($name);
}
self::$_typesMap[$name] = $className;
self::getTypeRegistry()->register($name, new $className());
}
/**
......@@ -198,7 +206,7 @@ abstract class Type
*/
public static function hasType($name)
{
return isset(self::$_typesMap[$name]);
return self::getTypeRegistry()->has($name);
}
/**
......@@ -213,15 +221,7 @@ abstract class Type
*/
public static function overrideType($name, $className)
{
if (! isset(self::$_typesMap[$name])) {
throw DBALException::typeNotFound($name);
}
if (isset(self::$_typeObjects[$name])) {
unset(self::$_typeObjects[$name]);
}
self::$_typesMap[$name] = $className;
self::getTypeRegistry()->override($name, new $className());
}
/**
......@@ -245,7 +245,12 @@ abstract class Type
*/
public static function getTypesMap()
{
return self::$_typesMap;
return array_map(
static function (Type $type) : string {
return get_class($type);
},
self::getTypeRegistry()->getMap()
);
}
/**
......
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Types;
use Doctrine\DBAL\DBALException;
use function array_search;
use function in_array;
/**
* The type registry is responsible for holding a map of all known DBAL types.
* The types are stored using the flyweight pattern so that one type only exists as exactly one instance.
*
* @internal TypeRegistry exists for forward compatibility, its API should not be considered stable.
*/
final class TypeRegistry
{
/** @var array<string, Type> Map of type names and their corresponding flyweight objects. */
private $instances = [];
/**
* Finds a type by the given name.
*
* @throws DBALException
*/
public function get(string $name) : Type
{
if (! isset($this->instances[$name])) {
throw DBALException::unknownColumnType($name);
}
return $this->instances[$name];
}
/**
* Finds a name for the given type.
*
* @throws DBALException
*/
public function lookupName(Type $type) : string
{
$name = $this->findTypeName($type);
if ($name === null) {
throw DBALException::typeNotRegistered($type);
}
return $name;
}
/**
* Checks if there is a type of the given name.
*/
public function has(string $name) : bool
{
return isset($this->instances[$name]);
}
/**
* Registers a custom type to the type map.
*
* @throws DBALException
*/
public function register(string $name, Type $type) : void
{
if (isset($this->instances[$name])) {
throw DBALException::typeExists($name);
}
if ($this->findTypeName($type) !== null) {
throw DBALException::typeAlreadyRegistered($type);
}
$this->instances[$name] = $type;
}
/**
* Overrides an already defined type to use a different implementation.
*
* @throws DBALException
*/
public function override(string $name, Type $type) : void
{
if (! isset($this->instances[$name])) {
throw DBALException::typeNotFound($name);
}
if (! in_array($this->findTypeName($type), [$name, null], true)) {
throw DBALException::typeAlreadyRegistered($type);
}
$this->instances[$name] = $type;
}
/**
* Gets the map of all registered types and their corresponding type instances.
*
* @internal
*
* @return array<string, Type>
*/
public function getMap() : array
{
return $this->instances;
}
private function findTypeName(Type $type) : ?string
{
$name = array_search($type, $this->instances, true);
if ($name === false) {
return null;
}
return $name;
}
}
<?php
declare(strict_types=1);
namespace Doctrine\Tests\DBAL\Types;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Types\BinaryType;
use Doctrine\DBAL\Types\BlobType;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\TextType;
use Doctrine\DBAL\Types\TypeRegistry;
use PHPUnit\Framework\TestCase;
class TypeRegistryTest extends TestCase
{
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::TEST_TYPE_NAME, $this->testType);
$this->registry->register(self::OTHER_TEST_TYPE_NAME, $this->otherTestType);
}
public function testGet() : void
{
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');
}
public function testGetReturnsSameInstances() : void
{
self::assertSame(
$this->registry->get(self::TEST_TYPE_NAME),
$this->registry->get(self::TEST_TYPE_NAME)
);
}
public function testLookupName() : void
{
self::assertSame(
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);
$this->registry->lookupName(new TextType());
}
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
{
$newType = new TextType();
$this->registry->register('some', $newType);
self::assertTrue($this->registry->has('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', new TextType());
}
public function testRegisterWithAlreadyRegisteredInstance() : void
{
$newType = new TextType();
$this->registry->register('some', $newType);
$this->expectException(DBALException::class);
$this->registry->register('other', $newType);
}
public function testOverride() : void
{
$baseType = new TextType();
$overrideType = new StringType();
$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('second', $newType);
}
public function testOverrideWithUnknownType() : void
{
$this->expectException(DBALException::class);
$this->registry->override('unknown', new TextType());
}
public function testGetMap() : void
{
$registeredTypes = $this->registry->getMap();
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]);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\Tests\DBAL\Types;
use Doctrine\DBAL\Types\Type;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
class TypeTest extends TestCase
{
/**
* @dataProvider defaultTypesProvider()
*/
public function testDefaultTypesAreRegistered(string $name) : void
{
self::assertTrue(Type::hasType($name));
}
/**
* @return string[][]
*/
public function defaultTypesProvider() : iterable
{
foreach ((new ReflectionClass(Type::class))->getReflectionConstants() as $constant) {
if (! $constant->isPublic()) {
continue;
}
yield [$constant->getValue()];
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment