diff --git a/ConnectionFactory.php b/ConnectionFactory.php index faac74b82..a75e1159e 100644 --- a/ConnectionFactory.php +++ b/ConnectionFactory.php @@ -10,15 +10,15 @@ use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; +use const E_USER_DEPRECATED; +use function get_class; +use function trigger_error; class ConnectionFactory { /** @var mixed[][] */ private $typesConfig = []; - /** @var string[] */ - private $commentedTypes = []; - /** @var bool */ private $initialized = false; @@ -52,11 +52,9 @@ public function createConnection(array $params, Configuration $config = null, Ev $platform->registerDoctrineTypeMapping($dbType, $doctrineType); } } - if (! empty($this->commentedTypes)) { - $platform = $this->getDatabasePlatform($connection); - foreach ($this->commentedTypes as $type) { - $platform->markDoctrineTypeCommented(Type::getType($type)); - } + + if (! empty($this->typesConfig)) { + $this->markTypesCommented($this->getDatabasePlatform($connection)); } return $connection; @@ -94,18 +92,82 @@ private function getDatabasePlatform(Connection $connection) */ private function initializeTypes() { - foreach ($this->typesConfig as $type => $typeConfig) { - if (Type::hasType($type)) { - Type::overrideType($type, $typeConfig['class']); + foreach ($this->typesConfig as $typeName => $typeConfig) { + if (Type::hasType($typeName)) { + Type::overrideType($typeName, $typeConfig['class']); } else { - Type::addType($type, $typeConfig['class']); + Type::addType($typeName, $typeConfig['class']); } - if (! $typeConfig['commented']) { + } + + $this->initialized = true; + } + + private function markTypesCommented(AbstractPlatform $platform) : void + { + foreach ($this->typesConfig as $typeName => $typeConfig) { + $type = Type::getType($typeName); + $requiresSQLCommentHint = $type->requiresSQLCommentHint($platform); + + // Attribute is missing, make sure a type that doesn't require a comment is marked as commented + // This is deprecated behaviour that will be dropped in 2.0. + if ($typeConfig['commented'] === null) { + if (! $requiresSQLCommentHint) { + @trigger_error( + sprintf( + 'The type "%s" was implicitly marked as commented due to the configuration. This is deprecated and will be removed in DoctrineBundle 2.0. Either set the "commented" attribute in the configuration to "false" or mark the type as commented in "%s::requiresSQLCommentHint()."', + $typeName, + get_class($type) + ), + E_USER_DEPRECATED + ); + + $platform->markDoctrineTypeCommented($type); + } + continue; } - $this->commentedTypes[] = $type; + // The following logic generates appropriate deprecation notices telling the user how to update their type configuration. + if ($typeConfig['commented']) { + if (! $requiresSQLCommentHint) { + @trigger_error( + sprintf( + 'The type "%s" was marked as commented in its configuration but not in the type itself. This is deprecated and will be removed in DoctrineBundle 2.0. Please update the return value of "%s::requiresSQLCommentHint()."', + $typeName, + get_class($type) + ), + E_USER_DEPRECATED + ); + + $platform->markDoctrineTypeCommented($type); + + continue; + } + + @trigger_error( + sprintf( + 'The type "%s" was explicitly marked as commented in its configuration. This is no longer necessary and will be removed in DoctrineBundle 2.0. Please remove the "commented" attribute from the type configuration.', + $typeName + ), + E_USER_DEPRECATED + ); + + continue; + } + + if (! $requiresSQLCommentHint) { + continue; + } + + @trigger_error( + sprintf( + 'The type "%s" was marked as uncommented in its configuration but commented in the type itself. This is deprecated and will be removed in DoctrineBundle 2.0. Please update the return value of "%s::requiresSQLCommentHint()" or remove the "commented" attribute from the type configuration.', + $typeName, + get_class($type) + ), + E_USER_DEPRECATED + ); } - $this->initialized = true; } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 0d016ee0b..b0842b774 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -94,7 +94,7 @@ private function addDbalSection(ArrayNodeDefinition $node) ->end() ->children() ->scalarNode('class')->isRequired()->end() - ->booleanNode('commented')->defaultTrue()->end() + ->booleanNode('commented')->defaultNull()->end() ->end() ->end() ->end() diff --git a/Tests/ConnectionFactoryTest.php b/Tests/ConnectionFactoryTest.php index ea0873911..5e708bc36 100644 --- a/Tests/ConnectionFactoryTest.php +++ b/Tests/ConnectionFactoryTest.php @@ -3,9 +3,13 @@ namespace Doctrine\Bundle\DoctrineBundle\Tests; use Doctrine\Bundle\DoctrineBundle\ConnectionFactory; +use Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\TestCommentedType; +use Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\TestType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\ORM\Version; use Exception; @@ -29,11 +33,11 @@ public function testContainer() { $typesConfig = []; $factory = new ConnectionFactory($typesConfig); - $params = ['driverClass' => '\\Doctrine\\Bundle\\DoctrineBundle\\Tests\\FakeDriver']; + $params = ['driverClass' => FakeDriver::class]; $config = null; $eventManager = null; $mappingTypes = [0]; - $exception = new DriverException('', $this->getMockBuilder(Driver\AbstractDriverException::class)->disableOriginalConstructor()->getMock()); + $exception = new DriverException('', $this->createMock(Driver\AbstractDriverException::class)); // put the mock into the fake driver FakeDriver::$exception = $exception; @@ -43,8 +47,116 @@ public function testContainer() } catch (Exception $e) { $this->assertTrue(strpos($e->getMessage(), 'can circumvent this by setting') > 0); throw $e; + } finally { + FakeDriver::$exception = null; } } + + /** + * @dataProvider getValidTypeConfigurations + */ + public function testRegisterTypes(array $type, int $expectedCalls) : void + { + $factory = new ConnectionFactory(['test' => $type]); + $params = ['driverClass' => FakeDriver::class]; + $config = null; + $eventManager = null; + $mappingTypes = []; + + $platform = $this->createMock(AbstractPlatform::class); + $platform + ->expects($this->exactly($expectedCalls)) + ->method('markDoctrineTypeCommented') + ->with($this->isInstanceOf($type['class'])); + + FakeDriver::$platform = $platform; + + try { + $factory->createConnection($params, $config, $eventManager, $mappingTypes); + } finally { + FakeDriver::$platform = null; + } + } + + public static function getValidTypeConfigurations() : array + { + return [ + 'uncommentedTypeMarkedNotCommented' => [ + 'type' => [ + 'class' => TestType::class, + 'commented' => false, + ], + 'expectedCalls' => 0, + ], + 'commentedTypeNotMarked' => [ + 'type' => [ + 'class' => TestCommentedType::class, + 'commented' => null, + ], + 'expectedCalls' => 0, + ], + ]; + } + + /** + * @group legacy + * @expectedDeprecation The type "test" was implicitly marked as commented due to the configuration. This is deprecated and will be removed in DoctrineBundle 2.0. Either set the "commented" attribute in the configuration to "false" or mark the type as commented in "Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\TestType::requiresSQLCommentHint()." + */ + public function testRegisterUncommentedTypeNotMarked() : void + { + $this->testRegisterTypes( + [ + 'class' => TestType::class, + 'commented' => null, + ], + 1 + ); + } + + /** + * @group legacy + * @expectedDeprecation The type "test" was marked as commented in its configuration but not in the type itself. This is deprecated and will be removed in DoctrineBundle 2.0. Please update the return value of "Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\TestType::requiresSQLCommentHint()." + */ + public function testRegisterUncommentedTypeMarkedCommented() : void + { + $this->testRegisterTypes( + [ + 'class' => TestType::class, + 'commented' => true, + ], + 1 + ); + } + + /** + * @group legacy + * @expectedDeprecation The type "test" was explicitly marked as commented in its configuration. This is no longer necessary and will be removed in DoctrineBundle 2.0. Please remove the "commented" attribute from the type configuration. + */ + public function testRegisterCommentedTypeMarkedCommented() : void + { + $this->testRegisterTypes( + [ + 'class' => TestCommentedType::class, + 'commented' => true, + ], + 0 + ); + } + + /** + * @group legacy + * @expectedDeprecation The type "test" was marked as uncommented in its configuration but commented in the type itself. This is deprecated and will be removed in DoctrineBundle 2.0. Please update the return value of "Doctrine\Bundle\DoctrineBundle\Tests\DependencyInjection\TestCommentedType::requiresSQLCommentHint()" or remove the "commented" attribute from the type configuration. + */ + public function testRegisterCommentedTypeMarkedNotCommented() : void + { + $this->testRegisterTypes( + [ + 'class' => TestCommentedType::class, + 'commented' => false, + ], + 0 + ); + } } /** @@ -62,6 +174,9 @@ class FakeDriver implements Driver */ public static $exception; + /** @var AbstractPlatform|null */ + public static $platform; + /** * This method gets called to determine the database version which in our case leeds to the problem. * So we have to fake the exception a driver would normally throw. @@ -70,7 +185,11 @@ class FakeDriver implements Driver */ public function getDatabasePlatform() { - throw self::$exception; + if (self::$exception !== null) { + throw self::$exception; + } + + return static::$platform ?? new MySqlPlatform(); } // ----- below this line follow only dummy methods to satisfy the interface requirements ---- diff --git a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php index dae83e586..e29200036 100644 --- a/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -522,7 +522,7 @@ public function testSetTypes() $container = $this->loadContainer('dbal_types'); $this->assertEquals( - ['test' => ['class' => TestType::class, 'commented' => true]], + ['test' => ['class' => TestType::class, 'commented' => null]], $container->getParameter('doctrine.dbal.connection_factory.types') ); $this->assertEquals('%doctrine.dbal.connection_factory.types%', $container->getDefinition('doctrine.dbal.connection_factory')->getArgument(0)); diff --git a/Tests/DependencyInjection/TestCommentedType.php b/Tests/DependencyInjection/TestCommentedType.php new file mode 100644 index 000000000..a453c964f --- /dev/null +++ b/Tests/DependencyInjection/TestCommentedType.php @@ -0,0 +1,24 @@ + 'default', 'types' => [ - 'test' => TestType::class, + 'test' => [ + 'class' => TestType::class, + 'commented' => false, + ], ], ], 'orm' => [ 'default_entity_manager' => 'default', diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 963871524..f33cab4ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -17,4 +17,8 @@ + + + +