Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract type factory and registry from Type into TypeRegistry #3354

Merged
merged 1 commit into from
Apr 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/Doctrine/DBAL/DBALException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand All @@ -18,6 +19,7 @@
use function is_string;
use function json_encode;
use function preg_replace;
use function spl_object_hash;
use function sprintf;

class DBALException extends Exception
Expand Down Expand Up @@ -277,4 +279,16 @@ public static function typeNotFound($name)
{
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))
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On one hand, not a big fan of making a big exception class that grows with time, on the other hand, if these exceptions are not meant to be caught but to help the user figure out their issue, maybe this is fine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only for 2.x.

}
151 changes: 78 additions & 73 deletions lib/Doctrine/DBAL/Types/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
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;
Expand All @@ -16,76 +18,70 @@
*/
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()
morozov marked this conversation as resolved.
Show resolved Hide resolved
{
}

Expand Down Expand Up @@ -148,6 +144,29 @@ abstract public function getSQLDeclaration(array $fieldDeclaration, AbstractPlat
*/
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;
}
Majkl578 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Factory method to create type instances.
* Type instances are implemented as flyweights.
Expand All @@ -160,14 +179,7 @@ abstract public function getName();
*/
public static function getType($name)
morozov marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}

/**
Expand All @@ -182,11 +194,7 @@ public static function getType($name)
*/
public static function addType($name, $className)
Majkl578 marked this conversation as resolved.
Show resolved Hide resolved
{
if (isset(self::$_typesMap[$name])) {
throw DBALException::typeExists($name);
}

self::$_typesMap[$name] = $className;
self::getTypeRegistry()->register($name, new $className());
}

/**
Expand All @@ -198,7 +206,7 @@ public static function addType($name, $className)
*/
public static function hasType($name)
{
return isset(self::$_typesMap[$name]);
return self::getTypeRegistry()->has($name);
}

/**
Expand All @@ -213,15 +221,7 @@ public static function hasType($name)
*/
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());
}

/**
Expand All @@ -245,7 +245,12 @@ public function getBindingType()
*/
public static function getTypesMap()
{
return self::$_typesMap;
return array_map(
static function (Type $type) : string {
return get_class($type);
},
greg0ire marked this conversation as resolved.
Show resolved Hide resolved
self::getTypeRegistry()->getMap()
);
}

/**
Expand Down
118 changes: 118 additions & 0 deletions lib/Doctrine/DBAL/Types/TypeRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?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)) {
Majkl578 marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}
Loading