From 99e4572ad9ba25b2d35dc8f023003eb69b86d08e Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 23 Nov 2019 15:55:00 +0100 Subject: [PATCH 01/36] add dbal config & extension --- src/DependencyInjection/Configuration.php | 274 ++++++++++++++++++ .../DoctrineDBALExtension.php | 270 +++++++++++++++++ src/Resources/config/dbal.xml | 103 +++++++ 3 files changed, 647 insertions(+) create mode 100644 src/DependencyInjection/Configuration.php create mode 100644 src/DependencyInjection/DoctrineDBALExtension.php create mode 100644 src/Resources/config/dbal.xml diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..e45edfa --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,274 @@ +debug = (bool) $debug; + } + + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder('doctrine_dbal'); + $rootNode = $treeBuilder->getRootNode(); + + $this->addDbalSection($rootNode); + + return $treeBuilder; + } + + /** + * Add DBAL section to configuration tree + */ + private function addDbalSection(ArrayNodeDefinition $node) + { + $node + ->beforeNormalization() + ->ifTrue(static function ($v) { + return is_array($v) && ! array_key_exists('connections', $v) && ! array_key_exists('connection', $v); + }) + ->then(static function ($v) { + // Key that should not be rewritten to the connection config + $excludedKeys = ['default_connection' => true, 'types' => true, 'type' => true]; + $connection = []; + foreach ($v as $key => $value) { + if (isset($excludedKeys[$key])) { + continue; + } + $connection[$key] = $v[$key]; + unset($v[$key]); + } + $v['default_connection'] = isset($v['default_connection']) ? (string) $v['default_connection'] : 'default'; + $v['connections'] = [$v['default_connection'] => $connection]; + + return $v; + }) + ->end() + ->children() + ->scalarNode('default_connection')->end() + ->end() + ->fixXmlConfig('type') + ->children() + ->arrayNode('types') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization() + ->ifString() + ->then(static function ($v) { + return ['class' => $v]; + }) + ->end() + ->children() + ->scalarNode('class')->isRequired()->end() + ->booleanNode('commented')->setDeprecated()->end() + ->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('connection') + ->append($this->getDbalConnectionsNode()); + } + + /** + * Return the dbal connections node + * + * @return ArrayNodeDefinition + */ + private function getDbalConnectionsNode() + { + $treeBuilder = new TreeBuilder('connections'); + $node = $treeBuilder->getRootNode(); + + /** @var ArrayNodeDefinition $connectionNode */ + $connectionNode = $node + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array'); + + $this->configureDbalDriverNode($connectionNode); + + $connectionNode + ->fixXmlConfig('option') + ->fixXmlConfig('mapping_type') + ->fixXmlConfig('slave') + ->fixXmlConfig('shard') + ->fixXmlConfig('default_table_option') + ->children() + ->scalarNode('driver')->defaultValue('pdo_mysql')->end() + ->scalarNode('platform_service')->end() + ->booleanNode('auto_commit')->end() + ->scalarNode('schema_filter')->end() + ->booleanNode('logging')->defaultValue($this->debug)->end() + ->booleanNode('profiling')->defaultValue($this->debug)->end() + ->booleanNode('profiling_collect_backtrace') + ->defaultValue(false) + ->info('Enables collecting backtraces when profiling is enabled') + ->end() + ->scalarNode('server_version')->end() + ->scalarNode('driver_class')->end() + ->scalarNode('wrapper_class')->end() + ->scalarNode('shard_manager_class')->end() + ->scalarNode('shard_choser')->end() + ->scalarNode('shard_choser_service')->end() + ->booleanNode('keep_slave')->end() + ->arrayNode('options') + ->useAttributeAsKey('key') + ->prototype('scalar')->end() + ->end() + ->arrayNode('mapping_types') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('default_table_options') + ->info("This option is used by the schema-tool and affects generated SQL. Possible keys include 'charset','collate', and 'engine'.") + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end(); + + $slaveNode = $connectionNode + ->children() + ->arrayNode('slaves') + ->useAttributeAsKey('name') + ->prototype('array'); + $this->configureDbalDriverNode($slaveNode); + + $shardNode = $connectionNode + ->children() + ->arrayNode('shards') + ->prototype('array') + ->children() + ->integerNode('id') + ->min(1) + ->isRequired() + ->end() + ->end(); + $this->configureDbalDriverNode($shardNode); + + return $node; + } + + /** + * Adds config keys related to params processed by the DBAL drivers + * + * These keys are available for slave configurations too. + */ + private function configureDbalDriverNode(ArrayNodeDefinition $node) + { + $node + ->children() + ->scalarNode('url')->info('A URL with connection information; any parameter value parsed from this string will override explicitly set parameters')->end() + ->scalarNode('dbname')->end() + ->scalarNode('host')->defaultValue('localhost')->end() + ->scalarNode('port')->defaultNull()->end() + ->scalarNode('user')->defaultValue('root')->end() + ->scalarNode('password')->defaultNull()->end() + ->scalarNode('application_name')->end() + ->scalarNode('charset')->end() + ->scalarNode('path')->end() + ->booleanNode('memory')->end() + ->scalarNode('unix_socket')->info('The unix socket to use for MySQL')->end() + ->booleanNode('persistent')->info('True to use as persistent connection for the ibm_db2 driver')->end() + ->scalarNode('protocol')->info('The protocol to use for the ibm_db2 driver (default to TCPIP if ommited)')->end() + ->booleanNode('service') + ->info('True to use SERVICE_NAME as connection parameter instead of SID for Oracle') + ->end() + ->scalarNode('servicename') + ->info( + 'Overrules dbname parameter if given and used as SERVICE_NAME or SID connection parameter ' . + 'for Oracle depending on the service parameter.' + ) + ->end() + ->scalarNode('sessionMode') + ->info('The session mode to use for the oci8 driver') + ->end() + ->scalarNode('server') + ->info('The name of a running database server to connect to for SQL Anywhere.') + ->end() + ->scalarNode('default_dbname') + ->info( + 'Override the default database (postgres) to connect to for PostgreSQL connexion.' + ) + ->end() + ->scalarNode('sslmode') + ->info( + 'Determines whether or with what priority a SSL TCP/IP connection will be negotiated with ' . + 'the server for PostgreSQL.' + ) + ->end() + ->scalarNode('sslrootcert') + ->info( + 'The name of a file containing SSL certificate authority (CA) certificate(s). ' . + 'If the file exists, the server\'s certificate will be verified to be signed by one of these authorities.' + ) + ->end() + ->scalarNode('sslcert') + ->info( + 'The path to the SSL client certificate file for PostgreSQL.' + ) + ->end() + ->scalarNode('sslkey') + ->info( + 'The path to the SSL client key file for PostgreSQL.' + ) + ->end() + ->scalarNode('sslcrl') + ->info( + 'The file name of the SSL certificate revocation list for PostgreSQL.' + ) + ->end() + ->booleanNode('pooled')->info('True to use a pooled server with the oci8/pdo_oracle driver')->end() + ->booleanNode('MultipleActiveResultSets')->info('Configuring MultipleActiveResultSets for the pdo_sqlsrv driver')->end() + ->booleanNode('use_savepoints')->info('Use savepoints for nested transactions')->end() + ->scalarNode('instancename') + ->info( + 'Optional parameter, complete whether to add the INSTANCE_NAME parameter in the connection.' . + ' It is generally used to connect to an Oracle RAC server to select the name' . + ' of a particular instance.' + ) + ->end() + ->scalarNode('connectstring') + ->info( + 'Complete Easy Connect connection descriptor, see https://docs.oracle.com/database/121/NETAG/naming.htm.' . + 'When using this option, you will still need to provide the user and password parameters, but the other ' . + 'parameters will no longer be used. Note that when using this parameter, the getHost and getPort methods' . + ' from Doctrine\DBAL\Connection will no longer function as expected.' + ) + ->end() + ->end() + ->beforeNormalization() + ->ifTrue(static function ($v) { + return ! isset($v['sessionMode']) && isset($v['session_mode']); + }) + ->then(static function ($v) { + $v['sessionMode'] = $v['session_mode']; + unset($v['session_mode']); + + return $v; + }) + ->end() + ->beforeNormalization() + ->ifTrue(static function ($v) { + return ! isset($v['MultipleActiveResultSets']) && isset($v['multiple_active_result_sets']); + }) + ->then(static function ($v) { + $v['MultipleActiveResultSets'] = $v['multiple_active_result_sets']; + unset($v['multiple_active_result_sets']); + + return $v; + }) + ->end(); + } +} diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php new file mode 100644 index 0000000..6b84928 --- /dev/null +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -0,0 +1,270 @@ +getParameter('kernel.debug')); + $config = $this->processConfiguration($configuration, $configs); + + $this->dbalLoad($config, $container); + } + + /** + * Loads the DBAL configuration. + * + * Usage example: + * + * + * + * @param array $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ + private function dbalLoad(array $config, ContainerBuilder $container) + { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('dbal.xml'); + + if (empty($config['default_connection'])) { + $keys = array_keys($config['connections']); + $config['default_connection'] = reset($keys); + } + + $defaultConnection = $config['default_connection']; + + $container->setAlias('database_connection', sprintf('doctrine.dbal.%s_connection', $defaultConnection)); + $container->getAlias('database_connection')->setPublic(true); + $container->setAlias('doctrine.dbal.event_manager', new Alias(sprintf('doctrine.dbal.%s_connection.event_manager', $defaultConnection), false)); + + $container->setParameter('doctrine.dbal.connection_factory.types', $config['types']); + + $connections = []; + + foreach (array_keys($config['connections']) as $name) { + $connections[$name] = sprintf('doctrine.dbal.%s_connection', $name); + } + + $container->setParameter('doctrine.connections', $connections); + $container->setParameter('doctrine.default_connection', $defaultConnection); + + foreach ($config['connections'] as $name => $connection) { + $this->loadDbalConnection($name, $connection, $container); + } + } + + /** + * Loads a configured DBAL connection. + * + * @param string $name The name of the connection + * @param array $connection A dbal connection configuration. + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function loadDbalConnection($name, array $connection, ContainerBuilder $container) + { + $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration')); + $logger = null; + if ($connection['logging']) { + $logger = new Reference('doctrine.dbal.logger'); + } + unset($connection['logging']); + + if ($connection['profiling']) { + $profilingAbstractId = $connection['profiling_collect_backtrace'] ? + 'doctrine.dbal.logger.backtrace' : + 'doctrine.dbal.logger.profiling'; + + $profilingLoggerId = $profilingAbstractId . '.' . $name; + $container->setDefinition($profilingLoggerId, new ChildDefinition($profilingAbstractId)); + $profilingLogger = new Reference($profilingLoggerId); + $container->getDefinition('data_collector.doctrine')->addMethodCall('addLogger', [$name, $profilingLogger]); + + if ($logger !== null) { + $chainLogger = new ChildDefinition('doctrine.dbal.logger.chain'); + $chainLogger->addMethodCall('addLogger', [$profilingLogger]); + + $loggerId = 'doctrine.dbal.logger.chain.' . $name; + $container->setDefinition($loggerId, $chainLogger); + $logger = new Reference($loggerId); + } else { + $logger = $profilingLogger; + } + } + unset($connection['profiling'], $connection['profiling_collect_backtrace']); + + if (isset($connection['auto_commit'])) { + $configuration->addMethodCall('setAutoCommit', [$connection['auto_commit']]); + } + + unset($connection['auto_commit']); + + if (isset($connection['schema_filter']) && $connection['schema_filter']) { + $definition = new Definition(RegexSchemaAssetFilter::class, [$connection['schema_filter']]); + $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]); + $container->setDefinition(sprintf('doctrine.dbal.%s_regex_schema_filter', $name), $definition); + } + + unset($connection['schema_filter']); + + if ($logger) { + $configuration->addMethodCall('setSQLLogger', [$logger]); + } + + // event manager + $container->setDefinition(sprintf('doctrine.dbal.%s_connection.event_manager', $name), new ChildDefinition('doctrine.dbal.connection.event_manager')); + + // connection + $options = $this->getConnectionOptions($connection); + + $def = $container + ->setDefinition(sprintf('doctrine.dbal.%s_connection', $name), new ChildDefinition('doctrine.dbal.connection')) + ->setPublic(true) + ->setArguments([ + $options, + new Reference(sprintf('doctrine.dbal.%s_connection.configuration', $name)), + new Reference(sprintf('doctrine.dbal.%s_connection.event_manager', $name)), + $connection['mapping_types'], + ]); + + // Set class in case "wrapper_class" option was used to assist IDEs + if (isset($options['wrapperClass'])) { + $def->setClass($options['wrapperClass']); + } + + if (! empty($connection['use_savepoints'])) { + $def->addMethodCall('setNestTransactionsWithSavepoints', [$connection['use_savepoints']]); + } + + // Create a shard_manager for this connection + if (! isset($options['shards'])) { + return; + } + + $shardManagerDefinition = new Definition($options['shardManagerClass'], [new Reference(sprintf('doctrine.dbal.%s_connection', $name))]); + $container->setDefinition(sprintf('doctrine.dbal.%s_shard_manager', $name), $shardManagerDefinition); + } + + protected function getConnectionOptions($connection) + { + $options = $connection; + + if (isset($options['platform_service'])) { + $options['platform'] = new Reference($options['platform_service']); + unset($options['platform_service']); + } + unset($options['mapping_types']); + + if (isset($options['shard_choser_service'])) { + $options['shard_choser'] = new Reference($options['shard_choser_service']); + unset($options['shard_choser_service']); + } + + foreach ([ + 'options' => 'driverOptions', + 'driver_class' => 'driverClass', + 'wrapper_class' => 'wrapperClass', + 'keep_slave' => 'keepSlave', + 'shard_choser' => 'shardChoser', + 'shard_manager_class' => 'shardManagerClass', + 'server_version' => 'serverVersion', + 'default_table_options' => 'defaultTableOptions', + ] as $old => $new) { + if (! isset($options[$old])) { + continue; + } + + $options[$new] = $options[$old]; + unset($options[$old]); + } + + if (! empty($options['slaves']) && ! empty($options['shards'])) { + throw new InvalidArgumentException('Sharding and master-slave connection cannot be used together'); + } + + if (! empty($options['slaves'])) { + $nonRewrittenKeys = [ + 'driver' => true, + 'driverOptions' => true, + 'driverClass' => true, + 'wrapperClass' => true, + 'keepSlave' => true, + 'shardChoser' => true, + 'platform' => true, + 'slaves' => true, + 'master' => true, + 'shards' => true, + 'serverVersion' => true, + 'defaultTableOptions' => true, + // included by safety but should have been unset already + 'logging' => true, + 'profiling' => true, + 'mapping_types' => true, + 'platform_service' => true, + ]; + foreach ($options as $key => $value) { + if (isset($nonRewrittenKeys[$key])) { + continue; + } + $options['master'][$key] = $value; + unset($options[$key]); + } + if (empty($options['wrapperClass'])) { + // Change the wrapper class only if the user does not already forced using a custom one. + $options['wrapperClass'] = 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection'; + } + } else { + unset($options['slaves']); + } + + if (! empty($options['shards'])) { + $nonRewrittenKeys = [ + 'driver' => true, + 'driverOptions' => true, + 'driverClass' => true, + 'wrapperClass' => true, + 'keepSlave' => true, + 'shardChoser' => true, + 'platform' => true, + 'slaves' => true, + 'global' => true, + 'shards' => true, + 'serverVersion' => true, + 'defaultTableOptions' => true, + // included by safety but should have been unset already + 'logging' => true, + 'profiling' => true, + 'mapping_types' => true, + 'platform_service' => true, + ]; + foreach ($options as $key => $value) { + if (isset($nonRewrittenKeys[$key])) { + continue; + } + $options['global'][$key] = $value; + unset($options[$key]); + } + if (empty($options['wrapperClass'])) { + // Change the wrapper class only if the user does not already forced using a custom one. + $options['wrapperClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection'; + } + if (empty($options['shardManagerClass'])) { + // Change the shard manager class only if the user does not already forced using a custom one. + $options['shardManagerClass'] = 'Doctrine\\DBAL\\Sharding\\PoolingShardManager'; + } + } else { + unset($options['shards']); + } + + return $options; + } +} diff --git a/src/Resources/config/dbal.xml b/src/Resources/config/dbal.xml new file mode 100644 index 0000000..efd7a92 --- /dev/null +++ b/src/Resources/config/dbal.xml @@ -0,0 +1,103 @@ + + + + + + Doctrine\DBAL\Logging\LoggerChain + Doctrine\DBAL\Logging\DebugStack + Symfony\Bridge\Doctrine\Logger\DbalLogger + Doctrine\DBAL\Configuration + Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector + Symfony\Bridge\Doctrine\ContainerAwareEventManager + Doctrine\Bundle\DoctrineBundle\ConnectionFactory + Doctrine\DBAL\Event\Listeners\MysqlSessionInit + Doctrine\DBAL\Event\Listeners\OracleSessionInit + Doctrine\Bundle\DoctrineBundle\Registry + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %doctrine.dbal.connection_factory.types% + + + + + + + + + + + + + + + %doctrine.connections% + %doctrine.entity_managers% + %doctrine.default_connection% + %doctrine.default_entity_manager% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3a589e938837546bed41433c53d09a37dd4cab20 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 23 Nov 2019 17:34:00 +0100 Subject: [PATCH 02/36] cleanup config: remove orm stuff --- src/Resources/config/dbal.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Resources/config/dbal.xml b/src/Resources/config/dbal.xml index efd7a92..febf42b 100644 --- a/src/Resources/config/dbal.xml +++ b/src/Resources/config/dbal.xml @@ -14,9 +14,6 @@ Doctrine\Bundle\DoctrineBundle\ConnectionFactory Doctrine\DBAL\Event\Listeners\MysqlSessionInit Doctrine\DBAL\Event\Listeners\OracleSessionInit - Doctrine\Bundle\DoctrineBundle\Registry - - @@ -57,15 +54,6 @@ - - - %doctrine.connections% - %doctrine.entity_managers% - %doctrine.default_connection% - %doctrine.default_entity_manager% - - - From dfca364983666230a30e0373954f12909d92631d Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 10 Dec 2019 18:49:50 +0100 Subject: [PATCH 03/36] require doctrine/dbal --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index e421e1f..1210222 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ ], "require": { "php": "^7.1", + "doctrine/dbal": "^2.9", "symfony/http-kernel": "^3.4.30|^4.3.3|^5.0" }, "require-dev": { From 193d4ece8d6f5089fe3e4acf7cd934aac4071fff Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 10 Dec 2019 19:55:45 +0100 Subject: [PATCH 04/36] move more dbal related things --- composer.json | 9 +- src/ConnectionFactory.php | 173 +++++++++ src/DBAL/Logging/BacktraceLogger.php | 23 ++ .../BlacklistSchemaAssetFilter.php | 29 ++ .../SchemaFilter/RegexSchemaAssetFilter.php | 25 ++ .../SchemaAssetsFilterManager.php | 31 ++ .../Compiler/DbalSchemaFilterPass.php | 57 +++ .../Compiler/WellKnownSchemaFilterPass.php | 65 ++++ .../DoctrineDBALExtension.php | 2 + src/DoctrineDBALBundle.php | 39 +- src/Resources/config/dbal.xml | 13 +- src/Twig/DoctrineDBALExtension.php | 349 ++++++++++++++++++ 12 files changed, 795 insertions(+), 20 deletions(-) create mode 100644 src/ConnectionFactory.php create mode 100644 src/DBAL/Logging/BacktraceLogger.php create mode 100644 src/DBAL/SchemaFilter/BlacklistSchemaAssetFilter.php create mode 100644 src/DBAL/SchemaFilter/RegexSchemaAssetFilter.php create mode 100644 src/DBAL/SchemaFilter/SchemaAssetsFilterManager.php create mode 100644 src/DependencyInjection/Compiler/DbalSchemaFilterPass.php create mode 100644 src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php create mode 100644 src/Twig/DoctrineDBALExtension.php diff --git a/composer.json b/composer.json index 1210222..cd3c278 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,19 @@ "require": { "php": "^7.1", "doctrine/dbal": "^2.9", + "symfony/config": "^3.4.30|^4.3.3|^5.0", + "symfony/dependency-injection": "^3.4.30|^4.3.3|^5.0", + "symfony/doctrine-bridge": "^3.4.30|^4.3.3|^5.0", + "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0", "symfony/http-kernel": "^3.4.30|^4.3.3|^5.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", "phpunit/phpunit": "^7.5 || ^8.4", - "symfony/phpunit-bridge": "^5.0" + "symfony/phpunit-bridge": "^5.0", + "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", + "symfony/yaml": "^3.4.30|^4.3.3|^5.0", + "twig/twig": "^1.34|^2.12" }, "config": { "sort-packages": true diff --git a/src/ConnectionFactory.php b/src/ConnectionFactory.php new file mode 100644 index 0000000..65c010c --- /dev/null +++ b/src/ConnectionFactory.php @@ -0,0 +1,173 @@ +typesConfig = $typesConfig; + } + + /** + * Create a connection by name. + * + * @param mixed[] $params + * @param string[]|Type[] $mappingTypes + * + * @return Connection + */ + public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = []) + { + if (! $this->initialized) { + $this->initializeTypes(); + } + + $connection = DriverManager::getConnection($params, $config, $eventManager); + + if (! empty($mappingTypes)) { + $platform = $this->getDatabasePlatform($connection); + foreach ($mappingTypes as $dbType => $doctrineType) { + $platform->registerDoctrineTypeMapping($dbType, $doctrineType); + } + } + + if (! empty($this->typesConfig)) { + $this->markTypesCommented($this->getDatabasePlatform($connection)); + } + + return $connection; + } + + /** + * Try to get the database platform. + * + * This could fail if types should be registered to an predefined/unused connection + * and the platform version is unknown. + * For details have a look at DoctrineBundle issue #673. + * + * @return AbstractPlatform + * + * @throws DBALException + */ + private function getDatabasePlatform(Connection $connection) + { + try { + return $connection->getDatabasePlatform(); + } catch (DriverException $driverException) { + throw new DBALException( + 'An exception occured while establishing a connection to figure out your platform version.' . PHP_EOL . + "You can circumvent this by setting a 'server_version' configuration value" . PHP_EOL . PHP_EOL . + 'For further information have a look at:' . PHP_EOL . + 'https://github.com/doctrine/DoctrineBundle/issues/673', + 0, + $driverException + ); + } + } + + /** + * initialize the types + */ + private function initializeTypes() + { + foreach ($this->typesConfig as $typeName => $typeConfig) { + if (Type::hasType($typeName)) { + Type::overrideType($typeName, $typeConfig['class']); + } else { + Type::addType($typeName, $typeConfig['class']); + } + } + + $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; + } + + // 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 + ); + } + } +} diff --git a/src/DBAL/Logging/BacktraceLogger.php b/src/DBAL/Logging/BacktraceLogger.php new file mode 100644 index 0000000..4015f9d --- /dev/null +++ b/src/DBAL/Logging/BacktraceLogger.php @@ -0,0 +1,23 @@ +queries[$this->currentQuery]['backtrace'] = $backtrace; + } +} diff --git a/src/DBAL/SchemaFilter/BlacklistSchemaAssetFilter.php b/src/DBAL/SchemaFilter/BlacklistSchemaAssetFilter.php new file mode 100644 index 0000000..dcfb1ca --- /dev/null +++ b/src/DBAL/SchemaFilter/BlacklistSchemaAssetFilter.php @@ -0,0 +1,29 @@ +blacklist = $blacklist; + } + + public function __invoke($assetName) : bool + { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return ! in_array($assetName, $this->blacklist, true); + } +} diff --git a/src/DBAL/SchemaFilter/RegexSchemaAssetFilter.php b/src/DBAL/SchemaFilter/RegexSchemaAssetFilter.php new file mode 100644 index 0000000..ff5a106 --- /dev/null +++ b/src/DBAL/SchemaFilter/RegexSchemaAssetFilter.php @@ -0,0 +1,25 @@ +filterExpression = $filterExpression; + } + + public function __invoke($assetName) : bool + { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return preg_match($this->filterExpression, $assetName); + } +} diff --git a/src/DBAL/SchemaFilter/SchemaAssetsFilterManager.php b/src/DBAL/SchemaFilter/SchemaAssetsFilterManager.php new file mode 100644 index 0000000..de3587c --- /dev/null +++ b/src/DBAL/SchemaFilter/SchemaAssetsFilterManager.php @@ -0,0 +1,31 @@ +schemaAssetFilters = $schemaAssetFilters; + } + + public function __invoke($assetName) : bool + { + foreach ($this->schemaAssetFilters as $schemaAssetFilter) { + if ($schemaAssetFilter($assetName) === false) { + return false; + } + } + + return true; + } +} diff --git a/src/DependencyInjection/Compiler/DbalSchemaFilterPass.php b/src/DependencyInjection/Compiler/DbalSchemaFilterPass.php new file mode 100644 index 0000000..41bf77d --- /dev/null +++ b/src/DependencyInjection/Compiler/DbalSchemaFilterPass.php @@ -0,0 +1,57 @@ +findTaggedServiceIds('doctrine.dbal.schema_filter'); + + if (count($filters) > 0 && ! method_exists(Configuration::class, 'setSchemaAssetsFilter')) { + throw new LogicException('The doctrine.dbal.schema_filter tag is only supported when using doctrine/dbal 2.9 or higher.'); + } + + $connectionFilters = []; + foreach ($filters as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + $name = isset($attributes['connection']) ? $attributes['connection'] : $container->getParameter('doctrine.default_connection'); + + if (! isset($connectionFilters[$name])) { + $connectionFilters[$name] = []; + } + + $connectionFilters[$name][] = new Reference($id); + } + } + + foreach ($connectionFilters as $name => $references) { + $configurationId = sprintf('doctrine.dbal.%s_connection.configuration', $name); + + if (! $container->hasDefinition($configurationId)) { + continue; + } + + $definition = new ChildDefinition('doctrine.dbal.schema_asset_filter_manager'); + $definition->setArgument(0, $references); + + $id = sprintf('doctrine.dbal.%s_schema_asset_filter_manager', $name); + $container->setDefinition($id, $definition); + $container->findDefinition($configurationId) + ->addMethodCall('setSchemaAssetsFilter', [new Reference($id)]); + } + } +} diff --git a/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php b/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php new file mode 100644 index 0000000..16364b9 --- /dev/null +++ b/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php @@ -0,0 +1,65 @@ +getDefinitions() as $definition) { + if ($definition->isAbstract() || $definition->isSynthetic()) { + continue; + } + + switch ($definition->getClass()) { + case PdoAdapter::class: + $blacklist[] = $definition->getArguments()[3]['db_table'] ?? 'cache_items'; + break; + + case PdoSessionHandler::class: + $blacklist[] = $definition->getArguments()[1]['db_table'] ?? 'lock_keys'; + break; + + case PdoStore::class: + $blacklist[] = $definition->getArguments()[1]['db_table'] ?? 'sessions'; + break; + + case Connection::class: + $blacklist[] = $definition->getArguments()[0]['table_name'] ?? 'messenger_messages'; + break; + } + } + + if (! $blacklist) { + return; + } + + $definition = $container->getDefinition('doctrine.dbal.well_known_schema_asset_filter'); + $definition->replaceArgument(0, $blacklist); + + foreach (array_keys($container->getParameter('doctrine.connections')) as $name) { + $definition->addTag('doctrine.dbal.schema_filter', ['connection' => $name]); + } + } +} diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index 6b84928..8d98aaa 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -2,11 +2,13 @@ namespace Doctrine\Bundle\DBALBundle\DependencyInjection; +use Doctrine\Bundle\DBALBundle\DBAL\RegexSchemaAssetFilter; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; diff --git a/src/DoctrineDBALBundle.php b/src/DoctrineDBALBundle.php index c62d4ea..c680453 100644 --- a/src/DoctrineDBALBundle.php +++ b/src/DoctrineDBALBundle.php @@ -2,18 +2,8 @@ namespace Doctrine\Bundle\DBALBundle; -use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DbalSchemaFilterPass; -use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\EntityListenerPass; -use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\ServiceRepositoryCompilerPass; -use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass; -use Doctrine\Common\Util\ClassUtils; -use Doctrine\ORM\EntityManager; -use Doctrine\ORM\Proxy\Autoloader; -use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\DoctrineValidationPass; -use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; -use Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider\EntityFactory; -use Symfony\Component\Console\Application; -use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Doctrine\Bundle\DBALBundle\DependencyInjection\Compiler\DbalSchemaFilterPass; +use Doctrine\Bundle\DBALBundle\DependencyInjection\Compiler\WellKnownSchemaFilterPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -22,7 +12,30 @@ class DoctrineDBALBundle extends Bundle /** * {@inheritDoc} */ - public function registerCommands(Application $application) + public function build(ContainerBuilder $container) { + parent::build($container); + $container->addCompilerPass(new WellKnownSchemaFilterPass()); + $container->addCompilerPass(new DbalSchemaFilterPass()); + } + + /** + * {@inheritDoc} + */ + public function shutdown() + { + // Close all connections to avoid reaching too many connections in the process when booting again later (tests) + if (! $this->container->hasParameter('doctrine.connections')) { + return; + } + + // TODO: use ConnectionRegistry? + foreach ($this->container->getParameter('doctrine.connections') as $id) { + if (! $this->container->initialized($id)) { + continue; + } + + $this->container->get($id)->close(); + } } } diff --git a/src/Resources/config/dbal.xml b/src/Resources/config/dbal.xml index febf42b..e346359 100644 --- a/src/Resources/config/dbal.xml +++ b/src/Resources/config/dbal.xml @@ -11,7 +11,7 @@ Doctrine\DBAL\Configuration Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector Symfony\Bridge\Doctrine\ContainerAwareEventManager - Doctrine\Bundle\DoctrineBundle\ConnectionFactory + Doctrine\Bundle\DBALBundle\ConnectionFactory Doctrine\DBAL\Event\Listeners\MysqlSessionInit Doctrine\DBAL\Event\Listeners\OracleSessionInit @@ -27,7 +27,7 @@ - + @@ -54,19 +54,19 @@ - + - + - + - + diff --git a/src/Twig/DoctrineDBALExtension.php b/src/Twig/DoctrineDBALExtension.php new file mode 100644 index 0000000..46fb655 --- /dev/null +++ b/src/Twig/DoctrineDBALExtension.php @@ -0,0 +1,349 @@ + true]), + new TwigFilter('doctrine_pretty_query', [$this, 'formatQuery'], ['is_safe' => ['html']]), + new TwigFilter('doctrine_replace_query_parameters', [$this, 'replaceQueryParameters']), + ]; + } + + /** + * Get the possible combinations of elements from the given array + * + * @param array $elements + * @param int $combinationsLevel + * + * @return array + */ + private function getPossibleCombinations(array $elements, $combinationsLevel) + { + $baseCount = count($elements); + $result = []; + + if ($combinationsLevel === 1) { + foreach ($elements as $element) { + $result[] = [$element]; + } + + return $result; + } + + $nextLevelElements = $this->getPossibleCombinations($elements, $combinationsLevel - 1); + + foreach ($nextLevelElements as $nextLevelElement) { + $lastElement = $nextLevelElement[$combinationsLevel - 2]; + $found = false; + + foreach ($elements as $key => $element) { + if ($element === $lastElement) { + $found = true; + continue; + } + + if ($found !== true || $key >= $baseCount) { + continue; + } + + $tmp = $nextLevelElement; + $newCombination = array_slice($tmp, 0); + $newCombination[] = $element; + $result[] = array_slice($newCombination, 0); + } + } + + return $result; + } + + /** + * Shrink the values of parameters from a combination + * + * @param array $parameters + * @param array $combination + * + * @return string + */ + private function shrinkParameters(array $parameters, array $combination) + { + array_shift($parameters); + $result = ''; + + $maxLength = $this->maxCharWidth; + $maxLength -= count($parameters) * 5; + $maxLength /= count($parameters); + + foreach ($parameters as $key => $value) { + $isLarger = false; + + if (strlen($value) > $maxLength) { + $value = wordwrap($value, $maxLength, "\n", true); + $value = explode("\n", $value); + $value = $value[0]; + + $isLarger = true; + } + $value = self::escapeFunction($value); + + if (! is_numeric($value)) { + $value = substr($value, 1, -1); + } + + if ($isLarger) { + $value .= ' [...]'; + } + + $result .= ' ' . $combination[$key] . ' ' . $value; + } + + return trim($result); + } + + /** + * Attempt to compose the best scenario minified query so that a user could find it without expanding it + * + * @param string $query + * @param array $keywords + * @param int $required + * + * @return string + */ + private function composeMiniQuery($query, array $keywords, $required) + { + // Extract the mandatory keywords and consider the rest as optional keywords + $mandatoryKeywords = array_splice($keywords, 0, $required); + + $combinations = []; + $combinationsCount = count($keywords); + + // Compute all the possible combinations of keywords to match the query for + while ($combinationsCount > 0) { + $combinations = array_merge($combinations, $this->getPossibleCombinations($keywords, $combinationsCount)); + $combinationsCount--; + } + + // Try and match the best case query pattern + foreach ($combinations as $combination) { + $combination = array_merge($mandatoryKeywords, $combination); + + $regexp = implode('(.*) ', $combination) . ' (.*)'; + $regexp = '/^' . $regexp . '/is'; + + if (preg_match($regexp, $query, $matches)) { + return $this->shrinkParameters($matches, $combination); + } + } + + // Try and match the simplest query form that contains only the mandatory keywords + $regexp = implode(' (.*)', $mandatoryKeywords) . ' (.*)'; + $regexp = '/^' . $regexp . '/is'; + + if (preg_match($regexp, $query, $matches)) { + return $this->shrinkParameters($matches, $mandatoryKeywords); + } + + // Fallback in case we didn't managed to find any good match (can we actually have that happen?!) + return substr($query, 0, $this->maxCharWidth); + } + + /** + * Minify the query + * + * @param string $query + * + * @return string + */ + public function minifyQuery($query) + { + $result = ''; + $keywords = []; + $required = 1; + + // Check if we can match the query against any of the major types + switch (true) { + case stripos($query, 'SELECT') !== false: + $keywords = ['SELECT', 'FROM', 'WHERE', 'HAVING', 'ORDER BY', 'LIMIT']; + $required = 2; + break; + + case stripos($query, 'DELETE') !== false: + $keywords = ['DELETE', 'FROM', 'WHERE', 'ORDER BY', 'LIMIT']; + $required = 2; + break; + + case stripos($query, 'UPDATE') !== false: + $keywords = ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT']; + $required = 2; + break; + + case stripos($query, 'INSERT') !== false: + $keywords = ['INSERT', 'INTO', 'VALUE', 'VALUES']; + $required = 2; + break; + + // If there's no match so far just truncate it to the maximum allowed by the interface + default: + $result = substr($query, 0, $this->maxCharWidth); + } + + // If we had a match then we should minify it + if ($result === '') { + $result = $this->composeMiniQuery($query, $keywords, $required); + } + + return $result; + } + + /** + * Escape parameters of a SQL query + * DON'T USE THIS FUNCTION OUTSIDE ITS INTENDED SCOPE + * + * @internal + * + * @param mixed $parameter + * + * @return string + */ + public static function escapeFunction($parameter) + { + $result = $parameter; + + switch (true) { + // Check if result is non-unicode string using PCRE_UTF8 modifier + case is_string($result) && ! preg_match('//u', $result): + $result = '0x' . strtoupper(bin2hex($result)); + break; + + case is_string($result): + $result = "'" . addslashes($result) . "'"; + break; + + case is_array($result): + foreach ($result as &$value) { + $value = static::escapeFunction($value); + } + + $result = implode(', ', $result); + break; + + case is_object($result): + $result = addslashes((string) $result); + break; + + case $result === null: + $result = 'NULL'; + break; + + case is_bool($result): + $result = $result ? '1' : '0'; + break; + } + + return $result; + } + + /** + * Return a query with the parameters replaced + * + * @param string $query + * @param array|Data $parameters + * + * @return string + */ + public function replaceQueryParameters($query, $parameters) + { + if ($parameters instanceof Data) { + $parameters = $parameters->getValue(true); + } + + $i = 0; + + if (! array_key_exists(0, $parameters) && array_key_exists(1, $parameters)) { + $i = 1; + } + + return preg_replace_callback( + '/\?|((?([^"]*+)<\/pre>/Us', '\1', $html); + } else { + $html = SqlFormatter::format($sql); + $html = preg_replace('/
([^"]*+)<\/pre>/Us', '
\2
', $html); + } + + return $html; + } + + /** + * Get the name of the extension + * + * @return string + */ + public function getName() + { + return 'doctrine_extension'; + } +} From afcbb2c50fd01d17d2f6902adba85164fc361ca0 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 10 Dec 2019 20:48:48 +0100 Subject: [PATCH 05/36] add data collector + some fixes --- composer.json | 1 + .../DoctrineDBALDataCollector.php | 55 +++++++++++++++++++ .../DoctrineDBALExtension.php | 2 +- src/Resources/config/dbal.xml | 9 ++- src/Resources/views/Collector/panel.html.twig | 7 +++ src/Twig/DoctrineDBALExtension.php | 4 +- 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 src/DataCollector/DoctrineDBALDataCollector.php create mode 100644 src/Resources/views/Collector/panel.html.twig diff --git a/composer.json b/composer.json index cd3c278..9fc3044 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "require": { "php": "^7.1", "doctrine/dbal": "^2.9", + "jdorn/sql-formatter": "^1.2.16", "symfony/config": "^3.4.30|^4.3.3|^5.0", "symfony/dependency-injection": "^3.4.30|^4.3.3|^5.0", "symfony/doctrine-bridge": "^3.4.30|^4.3.3|^5.0", diff --git a/src/DataCollector/DoctrineDBALDataCollector.php b/src/DataCollector/DoctrineDBALDataCollector.php new file mode 100644 index 0000000..f08d46f --- /dev/null +++ b/src/DataCollector/DoctrineDBALDataCollector.php @@ -0,0 +1,55 @@ +data = []; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'doctrine.dbal'; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->data = []; + + foreach ($this->loggers as $logger) { + $logger->queries = []; + $logger->currentQuery = 0; + } + } + + /** + * Adds the stack logger for a connection. + */ + public function addLogger(string $name, DebugStack $logger) + { + $this->loggers[$name] = $logger; + } +} diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index 8d98aaa..bbadd66 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -89,7 +89,7 @@ protected function loadDbalConnection($name, array $connection, ContainerBuilder $profilingLoggerId = $profilingAbstractId . '.' . $name; $container->setDefinition($profilingLoggerId, new ChildDefinition($profilingAbstractId)); $profilingLogger = new Reference($profilingLoggerId); - $container->getDefinition('data_collector.doctrine')->addMethodCall('addLogger', [$name, $profilingLogger]); + $container->getDefinition('data_collector.doctrine.dbal')->addMethodCall('addLogger', [$name, $profilingLogger]); if ($logger !== null) { $chainLogger = new ChildDefinition('doctrine.dbal.logger.chain'); diff --git a/src/Resources/config/dbal.xml b/src/Resources/config/dbal.xml index e346359..e69f740 100644 --- a/src/Resources/config/dbal.xml +++ b/src/Resources/config/dbal.xml @@ -9,7 +9,7 @@ Doctrine\DBAL\Logging\DebugStack Symfony\Bridge\Doctrine\Logger\DbalLogger Doctrine\DBAL\Configuration - Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector + Doctrine\Bundle\DBALBundle\DataCollector\DoctrineDBALDataCollector Symfony\Bridge\Doctrine\ContainerAwareEventManager Doctrine\Bundle\DBALBundle\ConnectionFactory Doctrine\DBAL\Event\Listeners\MysqlSessionInit @@ -35,9 +35,8 @@ - - - + + @@ -54,7 +53,7 @@ - + diff --git a/src/Resources/views/Collector/panel.html.twig b/src/Resources/views/Collector/panel.html.twig new file mode 100644 index 0000000..c52c480 --- /dev/null +++ b/src/Resources/views/Collector/panel.html.twig @@ -0,0 +1,7 @@ +{% extends request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as helper %} + +{% block toolbar %} + TODO... +{% endblock %} diff --git a/src/Twig/DoctrineDBALExtension.php b/src/Twig/DoctrineDBALExtension.php index 46fb655..b9aadcb 100644 --- a/src/Twig/DoctrineDBALExtension.php +++ b/src/Twig/DoctrineDBALExtension.php @@ -1,6 +1,6 @@ Date: Tue, 10 Dec 2019 21:11:14 +0100 Subject: [PATCH 06/36] fix data collector --- .../DoctrineDBALDataCollector.php | 210 +++++++++- src/Resources/views/Collector/icon.svg | 4 + src/Resources/views/Collector/panel.html.twig | 365 +++++++++++++++++- 3 files changed, 576 insertions(+), 3 deletions(-) create mode 100644 src/Resources/views/Collector/icon.svg diff --git a/src/DataCollector/DoctrineDBALDataCollector.php b/src/DataCollector/DoctrineDBALDataCollector.php index f08d46f..ba5820a 100644 --- a/src/DataCollector/DoctrineDBALDataCollector.php +++ b/src/DataCollector/DoctrineDBALDataCollector.php @@ -3,12 +3,20 @@ namespace Doctrine\Bundle\DBALBundle\DataCollector; use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; +use Symfony\Bridge\Doctrine\DataCollector\ObjectParameter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; class DoctrineDBALDataCollector extends DataCollector { + /** + * @var string[] + */ + private $groupedQueries; + /** * @var DebugStack[] */ @@ -19,9 +27,26 @@ class DoctrineDBALDataCollector extends DataCollector */ public function collect(Request $request, Response $response, \Throwable $exception = null) { - //TODO: re-implement based on collector from DoctrineBundle and Symfony DoctrineBridge + $queries = []; + foreach ($this->loggers as $name => $logger) { + $queries[$name] = $this->sanitizeQueries($name, $logger->queries); + } - $this->data = []; + $this->data = [ + 'queries' => $queries, + ]; + + // Might be good idea to replicate this block in doctrine bridge so we can drop this from here after some time. + // This code is compatible with such change, because cloneVar is supposed to check if input is already cloned. + foreach ($this->data['queries'] as &$queries) { + foreach ($queries as &$query) { + $query['params'] = $this->cloneVar($query['params']); + // To be removed when the required minimum version of symfony/doctrine-bridge is >= 4.4 + $query['runnable'] = $query['runnable'] ?? true; + } + } + + $this->groupedQueries = null; } /** @@ -52,4 +77,185 @@ public function addLogger(string $name, DebugStack $logger) { $this->loggers[$name] = $logger; } + + public function getConnections() + { + return []; + } + + public function getQueryCount() + { + return array_sum(array_map('count', $this->data['queries'])); + } + + public function getQueries() + { + return $this->data['queries']; + } + + public function getTime() + { + $time = 0; + foreach ($this->data['queries'] as $queries) { + foreach ($queries as $query) { + $time += $query['executionMS']; + } + } + + return $time; + } + + public function getGroupedQueryCount() + { + $count = 0; + foreach ($this->getGroupedQueries() as $connectionGroupedQueries) { + $count += count($connectionGroupedQueries); + } + + return $count; + } + + public function getGroupedQueries() + { + if ($this->groupedQueries !== null) { + return $this->groupedQueries; + } + + $this->groupedQueries = []; + $totalExecutionMS = 0; + foreach ($this->data['queries'] as $connection => $queries) { + $connectionGroupedQueries = []; + foreach ($queries as $i => $query) { + $key = $query['sql']; + if (! isset($connectionGroupedQueries[$key])) { + $connectionGroupedQueries[$key] = $query; + $connectionGroupedQueries[$key]['executionMS'] = 0; + $connectionGroupedQueries[$key]['count'] = 0; + $connectionGroupedQueries[$key]['index'] = $i; // "Explain query" relies on query index in 'queries'. + } + $connectionGroupedQueries[$key]['executionMS'] += $query['executionMS']; + $connectionGroupedQueries[$key]['count']++; + $totalExecutionMS += $query['executionMS']; + } + usort($connectionGroupedQueries, static function ($a, $b) { + if ($a['executionMS'] === $b['executionMS']) { + return 0; + } + + return $a['executionMS'] < $b['executionMS'] ? 1 : -1; + }); + $this->groupedQueries[$connection] = $connectionGroupedQueries; + } + + foreach ($this->groupedQueries as $connection => $queries) { + foreach ($queries as $i => $query) { + $this->groupedQueries[$connection][$i]['executionPercent'] = + $this->executionTimePercentage($query['executionMS'], $totalExecutionMS); + } + } + + return $this->groupedQueries; + } + + private function executionTimePercentage($executionTimeMS, $totalExecutionTimeMS) + { + if ($totalExecutionTimeMS === 0.0 || $totalExecutionTimeMS === 0) { + return 0; + } + + return $executionTimeMS / $totalExecutionTimeMS * 100; + } + + private function sanitizeQueries(string $connectionName, array $queries): array + { + foreach ($queries as $i => $query) { + $queries[$i] = $this->sanitizeQuery($connectionName, $query); + } + + return $queries; + } + + private function sanitizeQuery(string $connectionName, array $query): array + { + $query['explainable'] = true; + $query['runnable'] = true; + if (null === $query['params']) { + $query['params'] = []; + } + if (!\is_array($query['params'])) { + $query['params'] = [$query['params']]; + } + if (!\is_array($query['types'])) { + $query['types'] = []; + } + foreach ($query['params'] as $j => $param) { + $e = null; + if (isset($query['types'][$j])) { + // Transform the param according to the type + $type = $query['types'][$j]; + if (\is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $query['types'][$j] = $type->getBindingType(); + try { + //TODO: this needs a ConnectionRegistry + //$param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + } catch (\TypeError $e) { + } catch (ConversionException $e) { + } + } + } + + list($query['params'][$j], $explainable, $runnable) = $this->sanitizeParam($param, $e); + if (!$explainable) { + $query['explainable'] = false; + } + + if (!$runnable) { + $query['runnable'] = false; + } + } + + $query['params'] = $this->cloneVar($query['params']); + + return $query; + } + + /** + * Sanitizes a param. + * + * The return value is an array with the sanitized value and a boolean + * indicating if the original value was kept (allowing to use the sanitized + * value to explain the query). + */ + private function sanitizeParam($var, ?\Throwable $error): array + { + if (\is_object($var)) { + return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error]; + } + + if ($error) { + return ['âš  '.$error->getMessage(), false, false]; + } + + if (\is_array($var)) { + $a = []; + $explainable = $runnable = true; + foreach ($var as $k => $v) { + list($value, $e, $r) = $this->sanitizeParam($v, null); + $explainable = $explainable && $e; + $runnable = $runnable && $r; + $a[$k] = $value; + } + + return [$a, $explainable, $runnable]; + } + + if (\is_resource($var)) { + return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false]; + } + + return [$var, true, true]; + } } diff --git a/src/Resources/views/Collector/icon.svg b/src/Resources/views/Collector/icon.svg new file mode 100644 index 0000000..49fb528 --- /dev/null +++ b/src/Resources/views/Collector/icon.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/Resources/views/Collector/panel.html.twig b/src/Resources/views/Collector/panel.html.twig index c52c480..3b26461 100644 --- a/src/Resources/views/Collector/panel.html.twig +++ b/src/Resources/views/Collector/panel.html.twig @@ -3,5 +3,368 @@ {% import _self as helper %} {% block toolbar %} - TODO... + {% if collector.querycount > 0 %} + + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% set icon %} + {% if profiler_markup_version == 1 %} + + Doctrine DBAL + {{ collector.querycount }} + {% if collector.querycount > 0 %} + in {{ '%0.2f'|format(collector.time * 1000) }} ms + {% endif %} + + {% else %} + + {% set status = collector.querycount > 50 ? 'yellow' %} + + {{ include('@DoctrineDBAL/Collector/icon.svg') }} + + {{ collector.querycount }} + + in + {{ '%0.2f'|format(collector.time * 1000) }} + ms + + + {% endif %} + {% endset %} + + {% set text %} +
+ Database Queries + {{ collector.querycount }} +
+
+ Query time + {{ '%0.2f'|format(collector.time * 1000) }} ms +
+ {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }} + + {% endif %} +{% endblock %} + +{% block menu %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% if profiler_markup_version == 1 %} + + + + Doctrine DBAL + + {{ collector.querycount }} + {{ '%0.0f'|format(collector.time * 1000) }} ms + + + + {% else %} + + + {{ include('@DoctrineDBAL/Collector/icon.svg') }} + Doctrine DBAL + + + {% endif %} +{% endblock %} + +{% block panel %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% if 'explain' == page %} + {{ render(controller('DoctrineBundle:Profiler:explain', { + token: token, + panel: 'db', + connectionName: request.query.get('connection'), + query: request.query.get('query') + })) }} + {% else %} + {{ block('queries') }} + {% endif %} {% endblock %} + +{% block queries %} + + + {% if profiler_markup_version > 1 %} +

Query Metrics

+ +
+
+ {{ collector.querycount }} + Database Queries +
+ +
+ {{ collector.groupedQueryCount }} + Different statements +
+ +
+ {{ '%0.2f'|format(collector.time * 1000) }} ms + Query time +
+
+ {% endif %} + + {% set group_queries = request.query.getBoolean('group') %} + {% if group_queries %} +

Grouped Statements

+

Show all queries

+ {% else %} +

Queries

+

Group similar statements

+ {% endif %} + + {% for connection, queries in collector.queries %} + {% if collector.connections|length > 1 %} +

{{ connection }} connection

+ {% endif %} + + {% if queries is empty %} +
+

No database queries were performed.

+
+ {% else %} + {% if group_queries %} + {% set queries = collector.groupedQueries[connection] %} + {% endif %} + + + + {% if group_queries %} + + + {% else %} + + + {% endif %} + + + + + {% for i, query in queries %} + {% set i = group_queries ? query.index : i %} + + {% if group_queries %} + + + {% else %} + + + {% endif %} + + + {% endfor %} + +
TimeCount#TimeInfo
+ + {{ '%0.2f'|format(query.executionMS * 1000) }} ms
({{ '%0.2f'|format(query.executionPercent) }}%)
+
{{ query.count }}{{ loop.index }}{{ '%0.2f'|format(query.executionMS * 1000) }} ms + {{ query.sql|doctrine_pretty_query(highlight_only = true) }} + +
+ Parameters: {{ profiler_dump(query.params, 2) }} +
+ +
+ View formatted query + + {% if query.runnable %} +    + View runnable query + {% endif %} + + {% if query.explainable %} +    + Explain query + {% endif %} + + {% if query.backtrace is defined %} +    + View query backtrace + {% endif %} +
+ + + + {% if query.runnable %} + + {% endif %} + + {% if query.explainable %} +
+ {% endif %} + + {% if query.backtrace is defined %} + + {% endif %} +
+ {% endif %} + {% endfor %} + +

Database Connections

+ + {% if not collector.connections %} +
+

There are no configured database connections.

+
+ {% else %} + {{ helper.render_simple_table('Name', 'Service', collector.connections) }} + {% endif %} + + +{% endblock %} + +{% macro render_simple_table(label1, label2, data) %} + + + + + + + + + {% for key, value in data %} + + + + + {% endfor %} + +
{{ label1 }}{{ label2 }}
{{ key }}{{ value }}
+{% endmacro %} From 5f7fec195eca33df28912a5671005b0955607340 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 10 Dec 2019 21:21:20 +0100 Subject: [PATCH 07/36] add ProfilerController --- src/Controller/ProfilerController.php | 124 ++++++++++++++++++ .../DoctrineDBALDataCollector.php | 1 + .../views/Collector/explain.html.twig | 28 ++++ src/Resources/views/Collector/panel.html.twig | 10 +- 4 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 src/Controller/ProfilerController.php create mode 100644 src/Resources/views/Collector/explain.html.twig diff --git a/src/Controller/ProfilerController.php b/src/Controller/ProfilerController.php new file mode 100644 index 0000000..3b11027 --- /dev/null +++ b/src/Controller/ProfilerController.php @@ -0,0 +1,124 @@ +container = $container; + } + + /** + * Renders the profiler panel for the given token. + * + * @param string $token The profiler token + * @param string $connectionName + * @param int $query + * + * @return Response A Response instance + */ + public function explainAction($token, $connectionName, $query) + { + /** @var Profiler $profiler */ + $profiler = $this->container->get('profiler'); + $profiler->disable(); + + $profile = $profiler->loadProfile($token); + $queries = $profile->getCollector('doctrine.dbal')->getQueries(); + + if (! isset($queries[$connectionName][$query])) { + return new Response('This query does not exist.'); + } + + $query = $queries[$connectionName][$query]; + if (! $query['explainable']) { + return new Response('This query cannot be explained.'); + } + + /** @var Connection $connection */ + //TODO: this needs ConnectionRegistry + $connection = $this->container->get('doctrine')->getConnection($connectionName); + try { + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof SqlitePlatform) { + $results = $this->explainSQLitePlatform($connection, $query); + } elseif ($platform instanceof SQLServerPlatform) { + $results = $this->explainSQLServerPlatform($connection, $query); + } else { + $results = $this->explainOtherPlatform($connection, $query); + } + } catch (Exception $e) { + return new Response('This query cannot be explained.'); + } + + return new Response($this->container->get('twig')->render('@DoctrineDBAL/Collector/explain.html.twig', [ + 'data' => $results, + 'query' => $query, + ])); + } + + private function explainSQLitePlatform(Connection $connection, $query) + { + $params = $query['params']; + + if ($params instanceof Data) { + $params = $params->getValue(true); + } + + return $connection->executeQuery('EXPLAIN QUERY PLAN ' . $query['sql'], $params, $query['types']) + ->fetchAll(PDO::FETCH_ASSOC); + } + + private function explainSQLServerPlatform(Connection $connection, $query) + { + if (stripos($query['sql'], 'SELECT') === 0) { + $sql = 'SET STATISTICS PROFILE ON; ' . $query['sql'] . '; SET STATISTICS PROFILE OFF;'; + } else { + $sql = 'SET SHOWPLAN_TEXT ON; GO; SET NOEXEC ON; ' . $query['sql'] . '; SET NOEXEC OFF; GO; SET SHOWPLAN_TEXT OFF;'; + } + + $params = $query['params']; + + if ($params instanceof Data) { + $params = $params->getValue(true); + } + + $stmt = $connection->executeQuery($sql, $params, $query['types']); + $stmt->nextRowset(); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + private function explainOtherPlatform(Connection $connection, $query) + { + $params = $query['params']; + + if ($params instanceof Data) { + $params = $params->getValue(true); + } + + return $connection->executeQuery('EXPLAIN ' . $query['sql'], $params, $query['types']) + ->fetchAll(PDO::FETCH_ASSOC); + } +} diff --git a/src/DataCollector/DoctrineDBALDataCollector.php b/src/DataCollector/DoctrineDBALDataCollector.php index ba5820a..4df0793 100644 --- a/src/DataCollector/DoctrineDBALDataCollector.php +++ b/src/DataCollector/DoctrineDBALDataCollector.php @@ -80,6 +80,7 @@ public function addLogger(string $name, DebugStack $logger) public function getConnections() { + //TODO: this needs a ConnectionRegistry return []; } diff --git a/src/Resources/views/Collector/explain.html.twig b/src/Resources/views/Collector/explain.html.twig new file mode 100644 index 0000000..3b2dd7f --- /dev/null +++ b/src/Resources/views/Collector/explain.html.twig @@ -0,0 +1,28 @@ +{% if data[0]|length > 1 %} + {# The platform returns a table for the explanation (e.g. MySQL), display all columns #} + + + + {% for label in data[0]|keys %} + + {% endfor %} + + + + {% for row in data %} + + {% for key, item in row %} + + {% endfor %} + + {% endfor %} + +
{{ label }}
{{ item|replace({',': ', '}) }}
+{% else %} + {# The Platform returns a single column for a textual explanation (e.g. PostgreSQL), display all lines #} +
+        {%- for row in data -%}
+            {{ row|first }}{{ "\n" }}
+        {%- endfor -%}
+    
+{% endif %} diff --git a/src/Resources/views/Collector/panel.html.twig b/src/Resources/views/Collector/panel.html.twig index 3b26461..cf430d1 100644 --- a/src/Resources/views/Collector/panel.html.twig +++ b/src/Resources/views/Collector/panel.html.twig @@ -76,9 +76,9 @@ {% set profiler_markup_version = profiler_markup_version|default(1) %} {% if 'explain' == page %} - {{ render(controller('DoctrineBundle:Profiler:explain', { + {{ render(controller('Doctrine\\Bundle\\DBALBundle\\Controller\\ProfilerController::explainAction', { token: token, - panel: 'db', + panel: 'doctrine.dbal', connectionName: request.query.get('connection'), query: request.query.get('query') })) }} @@ -134,10 +134,10 @@ {% set group_queries = request.query.getBoolean('group') %} {% if group_queries %}

Grouped Statements

-

Show all queries

+

Show all queries

{% else %}

Queries

-

Group similar statements

+

Group similar statements

{% endif %} {% for connection, queries in collector.queries %} @@ -197,7 +197,7 @@ {% if query.explainable %}    - Explain query + Explain query {% endif %} {% if query.backtrace is defined %} From 6d754880e789010e5cdd29fe053fef925cbb790f Mon Sep 17 00:00:00 2001 From: David Maicher Date: Tue, 10 Dec 2019 21:47:41 +0100 Subject: [PATCH 08/36] add ConnectionRegistry --- composer.json | 1 + src/ConnectionRegistry.php | 36 +++++++++++++++++++++ src/Psr11ConnectionRegistry.php | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/ConnectionRegistry.php create mode 100644 src/Psr11ConnectionRegistry.php diff --git a/composer.json b/composer.json index 9fc3044..79cafa1 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "php": "^7.1", "doctrine/dbal": "^2.9", "jdorn/sql-formatter": "^1.2.16", + "psr/container": "^1.0", "symfony/config": "^3.4.30|^4.3.3|^5.0", "symfony/dependency-injection": "^3.4.30|^4.3.3|^5.0", "symfony/doctrine-bridge": "^3.4.30|^4.3.3|^5.0", diff --git a/src/ConnectionRegistry.php b/src/ConnectionRegistry.php new file mode 100644 index 0000000..8501dc5 --- /dev/null +++ b/src/ConnectionRegistry.php @@ -0,0 +1,36 @@ + An array of Connection instances. + */ + public function getConnections() : array; + + /** + * Gets all connection names. + * + * @return array An array of connection names. + */ + public function getConnectionNames() : array; +} diff --git a/src/Psr11ConnectionRegistry.php b/src/Psr11ConnectionRegistry.php new file mode 100644 index 0000000..d72a1c9 --- /dev/null +++ b/src/Psr11ConnectionRegistry.php @@ -0,0 +1,57 @@ +container = $container; + $this->defaultConnectionName = $defaultConnectionName; + $this->connectionNames = $connectionNames; + } + + public function getDefaultConnectionName(): string + { + return $this->defaultConnectionName; + } + + public function getConnection(?string $name = null): Connection + { + return $this->container->get($name !== null ? $name : $this->defaultConnectionName); + } + + public function getConnections(): array + { + $connections = []; + + foreach ($this->connectionNames as $connectionName) { + $connections[$connectionName] = $this->container->get($connectionName); + } + + return $connections; + } + + public function getConnectionNames(): array + { + return $this->connectionNames; + } +} From d7d6ce777203a72e49b2c13beb8586acaabed82e Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 29 Feb 2020 13:56:12 +0100 Subject: [PATCH 09/36] use registry --- src/Controller/ProfilerController.php | 5 ++-- .../DoctrineDBALDataCollector.php | 28 +++++++++++++++---- .../DoctrineDBALExtension.php | 10 +++++++ src/Resources/config/dbal.xml | 5 ++++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/Controller/ProfilerController.php b/src/Controller/ProfilerController.php index 3b11027..6e146f5 100644 --- a/src/Controller/ProfilerController.php +++ b/src/Controller/ProfilerController.php @@ -57,8 +57,9 @@ public function explainAction($token, $connectionName, $query) } /** @var Connection $connection */ - //TODO: this needs ConnectionRegistry - $connection = $this->container->get('doctrine')->getConnection($connectionName); + + //TODO: make service private and use DI + $connection = $this->container->get('doctrine.dbal.connection_registry')->getConnection($connectionName); try { $platform = $connection->getDatabasePlatform(); if ($platform instanceof SqlitePlatform) { diff --git a/src/DataCollector/DoctrineDBALDataCollector.php b/src/DataCollector/DoctrineDBALDataCollector.php index 4df0793..287e6be 100644 --- a/src/DataCollector/DoctrineDBALDataCollector.php +++ b/src/DataCollector/DoctrineDBALDataCollector.php @@ -2,6 +2,7 @@ namespace Doctrine\Bundle\DBALBundle\DataCollector; +use Doctrine\Bundle\DBALBundle\ConnectionRegistry; use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; @@ -22,6 +23,22 @@ class DoctrineDBALDataCollector extends DataCollector */ private $loggers = []; + /** + * @var ConnectionRegistry + */ + private $connectionRegistry; + + /** + * @var array + */ + private $connectionServiceIds; + + public function __construct(ConnectionRegistry $connectionRegistry, array $connectionServiceIds) + { + $this->connectionRegistry = $connectionRegistry; + $this->connectionServiceIds = $connectionServiceIds; + } + /** * {@inheritdoc} */ @@ -34,6 +51,7 @@ public function collect(Request $request, Response $response, \Throwable $except $this->data = [ 'queries' => $queries, + 'connections' => $this->connectionServiceIds, ]; // Might be good idea to replicate this block in doctrine bridge so we can drop this from here after some time. @@ -80,8 +98,7 @@ public function addLogger(string $name, DebugStack $logger) public function getConnections() { - //TODO: this needs a ConnectionRegistry - return []; + return $this->data['connections']; } public function getQueryCount() @@ -200,15 +217,14 @@ private function sanitizeQuery(string $connectionName, array $query): array if ($type instanceof Type) { $query['types'][$j] = $type->getBindingType(); try { - //TODO: this needs a ConnectionRegistry - //$param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + $param = $type->convertToDatabaseValue($param, $this->connectionRegistry->getConnection($connectionName)->getDatabasePlatform()); } catch (\TypeError $e) { } catch (ConversionException $e) { } } } - list($query['params'][$j], $explainable, $runnable) = $this->sanitizeParam($param, $e); + [$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e); if (!$explainable) { $query['explainable'] = false; } @@ -244,7 +260,7 @@ private function sanitizeParam($var, ?\Throwable $error): array $a = []; $explainable = $runnable = true; foreach ($var as $k => $v) { - list($value, $e, $r) = $this->sanitizeParam($v, null); + [$value, $e, $r] = $this->sanitizeParam($v, null); $explainable = $explainable && $e; $runnable = $runnable && $r; $a[$k] = $value; diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index bbadd66..6cb65b1 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -6,6 +6,7 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -63,6 +64,15 @@ private function dbalLoad(array $config, ContainerBuilder $container) foreach ($config['connections'] as $name => $connection) { $this->loadDbalConnection($name, $connection, $container); } + + $registry = $container->getDefinition('doctrine.dbal.connection_registry'); + $registry->setArguments([ + ServiceLocatorTagPass::register($container, array_map(function($id) { + return new Reference($id); + }, $connections)), + $defaultConnection, + array_keys($connections), + ]); } /** diff --git a/src/Resources/config/dbal.xml b/src/Resources/config/dbal.xml index e69f740..5e15d67 100644 --- a/src/Resources/config/dbal.xml +++ b/src/Resources/config/dbal.xml @@ -37,6 +37,11 @@ + + %doctrine.connections% + + + From 20b3cb2a57cd44fae9f002a55f06e3cd3748f19f Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 29 Feb 2020 18:52:35 +0100 Subject: [PATCH 10/36] start adding tests --- composer.json | 5 +- phpunit.xml.dist | 24 +++ tests/ConnectionFactoryTest.php | 211 +++++++++++++++++++++++++++ tests/Fixtures/TestCommentedType.php | 24 +++ tests/Fixtures/TestType.php | 19 +++ 5 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 phpunit.xml.dist create mode 100644 tests/ConnectionFactoryTest.php create mode 100644 tests/Fixtures/TestCommentedType.php create mode 100644 tests/Fixtures/TestType.php diff --git a/composer.json b/composer.json index 79cafa1..a88a352 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.5 || ^8.4", + "phpunit/phpunit": "^7.5 || ^8.5", "symfony/phpunit-bridge": "^5.0", "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", "symfony/yaml": "^3.4.30|^4.3.3|^5.0", @@ -41,6 +41,9 @@ "autoload": { "psr-4": { "Doctrine\\Bundle\\DBALBundle\\": "src" } }, + "autoload-dev": { + "psr-4": { "Doctrine\\Bundle\\DBALBundle\\Tests\\": "tests" } + }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..4d79064 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + + tests/ + tests/Fixtures + + + + + + ./src/ + + + diff --git a/tests/ConnectionFactoryTest.php b/tests/ConnectionFactoryTest.php new file mode 100644 index 0000000..6da0576 --- /dev/null +++ b/tests/ConnectionFactoryTest.php @@ -0,0 +1,211 @@ + FakeDriver::class]; + $config = null; + $eventManager = null; + $mappingTypes = [0]; + $exception = new DriverException('', $this->createMock(Driver\AbstractDriverException::class)); + + // put the mock into the fake driver + FakeDriver::$exception = $exception; + + try { + $factory->createConnection($params, $config, $eventManager, $mappingTypes); + } 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 + ); + } +} + +/** + * FakeDriver class to simulate a problem discussed in DoctrineBundle issue #673 + * In order to not use a real database driver we have to create our own fake/mock implementation. + * + * @link https://github.com/doctrine/DoctrineBundle/issues/673 + */ +class FakeDriver implements Driver +{ + /** + * Exception Mock + * + * @var DriverException + */ + 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. + * + * @link https://github.com/doctrine/DoctrineBundle/issues/673 + */ + public function getDatabasePlatform() + { + if (self::$exception !== null) { + throw self::$exception; + } + + return static::$platform ?? new MySqlPlatform(); + } + + // ----- below this line follow only dummy methods to satisfy the interface requirements ---- + + /** + * @param mixed[] $params + * @param string|null $username + * @param string|null $password + * @param mixed[] $driverOptions + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + { + throw new Exception('not implemented'); + } + + public function getSchemaManager(Connection $conn) + { + throw new Exception('not implemented'); + } + + public function getName() + { + return 'FakeDriver'; + } + + public function getDatabase(Connection $conn) + { + return 'fake_db'; + } +} diff --git a/tests/Fixtures/TestCommentedType.php b/tests/Fixtures/TestCommentedType.php new file mode 100644 index 0000000..c7fe856 --- /dev/null +++ b/tests/Fixtures/TestCommentedType.php @@ -0,0 +1,24 @@ + Date: Sat, 29 Feb 2020 19:08:27 +0100 Subject: [PATCH 11/36] add some more tests --- phpunit.xml.dist | 1 - src/Twig/DoctrineDBALExtension.php | 2 +- tests/BundleTest.php | 32 ++++++ .../DoctrineDBALDataCollectorTest.php | 65 +++++++++++ tests/Twig/DoctrineDBALExtensionTest.php | 106 ++++++++++++++++++ 5 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 tests/BundleTest.php create mode 100644 tests/DataCollector/DoctrineDBALDataCollectorTest.php create mode 100644 tests/Twig/DoctrineDBALExtensionTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4d79064..f5a6bab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,6 @@ colors="true" bootstrap="vendor/autoload.php" failOnRisky="true" - failOnWarning="true" > diff --git a/src/Twig/DoctrineDBALExtension.php b/src/Twig/DoctrineDBALExtension.php index b9aadcb..9d1dfd9 100644 --- a/src/Twig/DoctrineDBALExtension.php +++ b/src/Twig/DoctrineDBALExtension.php @@ -296,7 +296,7 @@ static function ($matches) use ($parameters, &$i) { } $value = array_key_exists($i, $parameters) ? $parameters[$i] : $parameters[$key]; - $result = DoctrineExtension::escapeFunction($value); + $result = self::escapeFunction($value); $i++; return $result; diff --git a/tests/BundleTest.php b/tests/BundleTest.php new file mode 100644 index 0000000..0c6571b --- /dev/null +++ b/tests/BundleTest.php @@ -0,0 +1,32 @@ +build($container); + + $config = $container->getCompilerPassConfig(); + $passes = $config->getBeforeOptimizationPasses(); + + $foundSchemaFilter = false; + + foreach ($passes as $pass) { + if ($pass instanceof DbalSchemaFilterPass) { + $foundSchemaFilter = true; + break; + } + } + + $this->assertTrue($foundSchemaFilter, 'DbalSchemaFilterPass was not found'); + } +} diff --git a/tests/DataCollector/DoctrineDBALDataCollectorTest.php b/tests/DataCollector/DoctrineDBALDataCollectorTest.php new file mode 100644 index 0000000..6ea38c6 --- /dev/null +++ b/tests/DataCollector/DoctrineDBALDataCollectorTest.php @@ -0,0 +1,65 @@ +getMockBuilder(DebugStack::class)->getMock(); + $logger->queries = []; + $logger->queries[] = [ + 'sql' => 'SELECT * FROM foo WHERE bar = :bar', + 'params' => [':bar' => 1], + 'types' => null, + 'executionMS' => 32, + ]; + $logger->queries[] = [ + 'sql' => 'SELECT * FROM foo WHERE bar = :bar', + 'params' => [':bar' => 2], + 'types' => null, + 'executionMS' => 25, + ]; + $collector = $this->createCollector(); + $collector->addLogger('default', $logger); + $collector->collect(new Request(), new Response()); + $groupedQueries = $collector->getGroupedQueries(); + $this->assertCount(1, $groupedQueries['default']); + $this->assertSame('SELECT * FROM foo WHERE bar = :bar', $groupedQueries['default'][0]['sql']); + $this->assertSame(2, $groupedQueries['default'][0]['count']); + + $logger->queries[] = [ + 'sql' => 'SELECT * FROM bar', + 'params' => [], + 'types' => null, + 'executionMS' => 25, + ]; + $collector->collect(new Request(), new Response()); + $groupedQueries = $collector->getGroupedQueries(); + $this->assertCount(2, $groupedQueries['default']); + $this->assertSame('SELECT * FROM bar', $groupedQueries['default'][1]['sql']); + $this->assertSame(1, $groupedQueries['default'][1]['count']); + } + + private function createCollector(): DoctrineDBALDataCollector + { + $registry = $this->createMock(ConnectionRegistry::class); + $registry + ->expects($this->any()) + ->method('getConnection') + ->with('default') + ->will($this->returnValue($this->createMock(Connection::class))); + + return new DoctrineDBALDataCollector($registry, [ + 'default' => 'doctrine.dbal.default_connection' + ]); + } +} diff --git a/tests/Twig/DoctrineDBALExtensionTest.php b/tests/Twig/DoctrineDBALExtensionTest.php new file mode 100644 index 0000000..0ed0dc2 --- /dev/null +++ b/tests/Twig/DoctrineDBALExtensionTest.php @@ -0,0 +1,106 @@ +replaceQueryParameters($query, $parameters); + $this->assertEquals('a=1 OR (1)::string OR b=2', $result); + } + + public function testReplaceQueryParametersWithStartingIndexAtOne() + { + $extension = new DoctrineDBALExtension(); + $query = 'a=? OR b=?'; + $parameters = [ + 1 => 1, + 2 => 2, + ]; + + $result = $extension->replaceQueryParameters($query, $parameters); + $this->assertEquals('a=1 OR b=2', $result); + } + + public function testReplaceQueryParameters() + { + $extension = new DoctrineDBALExtension(); + $query = 'a=? OR b=?'; + $parameters = [ + 1, + 2, + ]; + + $result = $extension->replaceQueryParameters($query, $parameters); + $this->assertEquals('a=1 OR b=2', $result); + } + + public function testReplaceQueryParametersWithNamedIndex() + { + $extension = new DoctrineDBALExtension(); + $query = 'a=:a OR b=:b'; + $parameters = [ + 'a' => 1, + 'b' => 2, + ]; + + $result = $extension->replaceQueryParameters($query, $parameters); + $this->assertEquals('a=1 OR b=2', $result); + } + + public function testEscapeBinaryParameter() + { + $binaryString = pack('H*', '9d40b8c1417f42d099af4782ec4b20b6'); + $this->assertEquals('0x9D40B8C1417F42D099AF4782EC4B20B6', DoctrineDBALExtension::escapeFunction($binaryString)); + } + + public function testEscapeStringParameter() + { + $this->assertEquals("'test string'", DoctrineDBALExtension::escapeFunction('test string')); + } + + public function testEscapeArrayParameter() + { + $this->assertEquals("1, NULL, 'test', foo", DoctrineDBALExtension::escapeFunction([1, null, 'test', new DummyClass('foo')])); + } + + public function testEscapeObjectParameter() + { + $object = new DummyClass('bar'); + $this->assertEquals('bar', DoctrineDBALExtension::escapeFunction($object)); + } + + public function testEscapeNullParameter() + { + $this->assertEquals('NULL', DoctrineDBALExtension::escapeFunction(null)); + } + + public function testEscapeBooleanParameter() + { + $this->assertEquals('1', DoctrineDBALExtension::escapeFunction(true)); + } +} + +class DummyClass +{ + /** @var string */ + protected $str; + + public function __construct($str) + { + $this->str = $str; + } + + public function __toString() + { + return $this->str; + } +} From 7356d863b0486ca3aff9f66e39713c4da599e533 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 29 Feb 2020 19:30:37 +0100 Subject: [PATCH 12/36] add some more tests --- composer.json | 1 + tests/BundleTest.php | 2 +- tests/ContainerTest.php | 35 +++++++ tests/ContainerTestCase.php | 83 +++++++++++++++++ .../DoctrineDBALDataCollectorTest.php | 2 +- tests/ProfilerTest.php | 92 +++++++++++++++++++ tests/Twig/DoctrineDBALExtensionTest.php | 2 +- 7 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 tests/ContainerTest.php create mode 100644 tests/ContainerTestCase.php create mode 100644 tests/ProfilerTest.php diff --git a/composer.json b/composer.json index a88a352..96a6db0 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "phpunit/phpunit": "^7.5 || ^8.5", "symfony/phpunit-bridge": "^5.0", "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", + "symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0", "symfony/yaml": "^3.4.30|^4.3.3|^5.0", "twig/twig": "^1.34|^2.12" }, diff --git a/tests/BundleTest.php b/tests/BundleTest.php index 0c6571b..cef49ae 100644 --- a/tests/BundleTest.php +++ b/tests/BundleTest.php @@ -1,6 +1,6 @@ createXmlBundleTestContainer(); + + $this->assertInstanceOf(DbalLogger::class, $container->get('doctrine.dbal.logger')); + $this->assertInstanceOf(DoctrineDBALDataCollector::class, $container->get('data_collector.doctrine.dbal')); + $this->assertInstanceOf(DBALConfiguration::class, $container->get('doctrine.dbal.default_connection.configuration')); + $this->assertInstanceOf(EventManager::class, $container->get('doctrine.dbal.default_connection.event_manager')); + $this->assertInstanceOf(Connection::class, $container->get('doctrine.dbal.default_connection')); + $this->assertInstanceOf(Connection::class, $container->get('database_connection')); + $this->assertInstanceOf(EventManager::class, $container->get('doctrine.dbal.event_manager')); + + $this->assertSame($container->get('my.platform'), $container->get('doctrine.dbal.default_connection')->getDatabasePlatform()); + + $this->assertTrue(Type::hasType('test')); + + $this->assertFalse($container->has('doctrine.dbal.default_connection.events.mysqlsessioninit')); + } +} diff --git a/tests/ContainerTestCase.php b/tests/ContainerTestCase.php new file mode 100644 index 0000000..2c10963 --- /dev/null +++ b/tests/ContainerTestCase.php @@ -0,0 +1,83 @@ + 'app', + 'kernel.debug' => false, + 'kernel.bundles' => ['XmlBundle' => 'Fixtures\Bundles\XmlBundle\XmlBundle'], + 'kernel.cache_dir' => sys_get_temp_dir(), + 'kernel.environment' => 'test', + 'kernel.root_dir' => __DIR__ . '/../../../../', // src dir + 'kernel.project_dir' => __DIR__ . '/../../../../', // src dir + 'kernel.bundles_metadata' => [], + 'container.build_id' => uniqid(), + ])); + $container->set('annotation_reader', new AnnotationReader()); + + $extension = new DoctrineDBALExtension(); + $container->registerExtension($extension); + $extension->load([[ + 'connections' => [ + 'default' => [ + 'driver' => 'pdo_mysql', + 'charset' => 'UTF8', + 'platform-service' => 'my.platform', + ], + ], + 'default_connection' => 'default', + 'types' => [ + 'test' => [ + 'class' => TestType::class, + 'commented' => false, + ], + ], + ]], $container); + + $container->setDefinition('my.platform', new Definition('Doctrine\DBAL\Platforms\MySqlPlatform'))->setPublic(true); + + $container->getCompilerPassConfig()->setOptimizationPasses([new ResolveChildDefinitionsPass()]); + $container->getCompilerPassConfig()->setRemovingPasses([]); + // make all Doctrine services public, so we can fetch them in the test + $container->getCompilerPassConfig()->addPass(new TestCaseAllPublicCompilerPass()); + $container->compile(); + + return $container; + } +} + +class TestCaseAllPublicCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + foreach ($container->getDefinitions() as $id => $definition) { + if (strpos($id, 'doctrine') === false) { + continue; + } + + $definition->setPublic(true); + } + + foreach ($container->getAliases() as $id => $alias) { + if (strpos($id, 'doctrine') === false) { + continue; + } + + $alias->setPublic(true); + } + } +} diff --git a/tests/DataCollector/DoctrineDBALDataCollectorTest.php b/tests/DataCollector/DoctrineDBALDataCollectorTest.php index 6ea38c6..5dcb51e 100644 --- a/tests/DataCollector/DoctrineDBALDataCollectorTest.php +++ b/tests/DataCollector/DoctrineDBALDataCollectorTest.php @@ -1,6 +1,6 @@ logger = new DebugStack(); + $registry = $this->createMock(ConnectionRegistry::class); + $this->collector = new DoctrineDBALDataCollector($registry, []); + $this->collector->addLogger('foo', $this->logger); + + $twigLoaderFilesystem = new FilesystemLoader(__DIR__ . '/../src/Resources/views/Collector'); + $twigLoaderFilesystem->addPath(__DIR__ . '/../vendor/symfony/web-profiler-bundle/Resources/views', 'WebProfiler'); + $this->twig = new Environment($twigLoaderFilesystem, ['debug' => true, 'strict_variables' => true]); + + $fragmentHandler = $this->createMock(FragmentHandler::class); + $fragmentHandler->method('render')->willReturn(''); + + $kernelRuntime = new HttpKernelRuntime($fragmentHandler); + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator->method('generate')->willReturn(''); + + $this->twig->addExtension(new CodeExtension('', '', '')); + $this->twig->addExtension(new RoutingExtension($urlGenerator)); + $this->twig->addExtension(new HttpKernelExtension($fragmentHandler)); + $this->twig->addExtension(new WebProfilerExtension()); + $this->twig->addExtension(new DoctrineDBALExtension()); + + $loader = $this->createMock(RuntimeLoaderInterface::class); + $loader->method('load')->willReturn($kernelRuntime); + $this->twig->addRuntimeLoader($loader); + } + + public function testRender() + { + $this->logger->queries = [ + [ + 'sql' => 'SELECT * FROM foo WHERE bar IN (?, ?)', + 'params' => ['foo', 'bar'], + 'types' => null, + 'executionMS' => 1, + ], + ]; + + $this->collector->collect($request = new Request(['group' => '0']), $response = new Response()); + + $profile = new Profile('foo'); + + $output = $this->twig->render('panel.html.twig', [ + 'request' => $request, + 'token' => 'foo', + 'page' => 'foo', + 'profile' => $profile, + 'collector' => $this->collector, + 'queries' => $this->logger->queries, + ]); + + $output = str_replace(["\e[37m", "\e[0m", "\e[32;1m", "\e[34;1m"], '', $output); + $this->assertContains("SELECT * FROM foo WHERE bar IN ('foo', 'bar');", $output); + } +} diff --git a/tests/Twig/DoctrineDBALExtensionTest.php b/tests/Twig/DoctrineDBALExtensionTest.php index 0ed0dc2..b6e9176 100644 --- a/tests/Twig/DoctrineDBALExtensionTest.php +++ b/tests/Twig/DoctrineDBALExtensionTest.php @@ -1,6 +1,6 @@ Date: Sat, 7 Mar 2020 10:49:15 +0100 Subject: [PATCH 13/36] adjust composer requirements --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 96a6db0..e1490b7 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,9 @@ "doctrine/dbal": "^2.9", "jdorn/sql-formatter": "^1.2.16", "psr/container": "^1.0", - "symfony/config": "^3.4.30|^4.3.3|^5.0", - "symfony/dependency-injection": "^3.4.30|^4.3.3|^5.0", - "symfony/doctrine-bridge": "^3.4.30|^4.3.3|^5.0", + "symfony/config": "^4.3.3|^5.0", + "symfony/dependency-injection": "^4.3.3|^5.0", + "symfony/doctrine-bridge": "4.3.7|^5.0", "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0", "symfony/http-kernel": "^3.4.30|^4.3.3|^5.0" }, From af74eec7a40466028306c6970eda4d7231c0839a Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 7 Mar 2020 10:52:27 +0100 Subject: [PATCH 14/36] adjust composer requirements --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e1490b7..95f41f3 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "psr/container": "^1.0", "symfony/config": "^4.3.3|^5.0", "symfony/dependency-injection": "^4.3.3|^5.0", - "symfony/doctrine-bridge": "4.3.7|^5.0", + "symfony/doctrine-bridge": "^4.3.7|^5.0", "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0", "symfony/http-kernel": "^3.4.30|^4.3.3|^5.0" }, From 76309310f68297ce2158cb845c94fc5689cbdffa Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 7 Mar 2020 11:21:13 +0100 Subject: [PATCH 15/36] CS fixes --- makefile | 0 phpcs.xml.dist | 53 +++++++++++ src/ConnectionFactory.php | 2 +- src/Controller/ProfilerController.php | 4 +- .../DoctrineDBALDataCollector.php | 89 +++++++++++-------- .../DoctrineDBALExtension.php | 16 ++-- src/Psr11ConnectionRegistry.php | 39 ++++---- tests/BundleTest.php | 2 +- tests/ContainerTestCase.php | 3 +- .../DoctrineDBALDataCollectorTest.php | 6 +- tests/ProfilerTest.php | 6 +- 11 files changed, 147 insertions(+), 73 deletions(-) create mode 100644 makefile create mode 100644 phpcs.xml.dist diff --git a/makefile b/makefile new file mode 100644 index 0000000..e69de29 diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..6037ec0 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,53 @@ + + + + + + + + + + + + . + vendor/* + + + + + + + + + + + + + + + + + tests/* + + + tests/* + + + tests/Fixtures/* + + + src/DependencyInjection/* + src/Twig/DoctrineDBALExtension.php + tests/* + + + src/DependencyInjection/* + src/Twig/DoctrineDBALExtension.php + tests/* + + + src/DependencyInjection/* + src/Twig/DoctrineDBALExtension.php + tests/* + + diff --git a/src/ConnectionFactory.php b/src/ConnectionFactory.php index 65c010c..69e8331 100644 --- a/src/ConnectionFactory.php +++ b/src/ConnectionFactory.php @@ -10,9 +10,9 @@ 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; -use const E_USER_DEPRECATED; class ConnectionFactory { diff --git a/src/Controller/ProfilerController.php b/src/Controller/ProfilerController.php index 6e146f5..dfdbd6e 100644 --- a/src/Controller/ProfilerController.php +++ b/src/Controller/ProfilerController.php @@ -16,9 +16,7 @@ //TODO: refactor and make this controller a service? class ProfilerController implements ContainerAwareInterface { - /** - * @var ContainerInterface - */ + /** @var ContainerInterface */ private $container; /** diff --git a/src/DataCollector/DoctrineDBALDataCollector.php b/src/DataCollector/DoctrineDBALDataCollector.php index 287e6be..32f29f9 100644 --- a/src/DataCollector/DoctrineDBALDataCollector.php +++ b/src/DataCollector/DoctrineDBALDataCollector.php @@ -10,39 +10,40 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Throwable; +use TypeError; +use function is_array; +use function is_object; +use function is_resource; +use function is_string; class DoctrineDBALDataCollector extends DataCollector { - /** - * @var string[] - */ + /** @var string[] */ private $groupedQueries; - /** - * @var DebugStack[] - */ + /** @var DebugStack[] */ private $loggers = []; - /** - * @var ConnectionRegistry - */ + /** @var ConnectionRegistry */ private $connectionRegistry; - /** - * @var array - */ + /** @var array */ private $connectionServiceIds; + /** + * @param array $connectionServiceIds + */ public function __construct(ConnectionRegistry $connectionRegistry, array $connectionServiceIds) { - $this->connectionRegistry = $connectionRegistry; + $this->connectionRegistry = $connectionRegistry; $this->connectionServiceIds = $connectionServiceIds; } /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Throwable $exception = null) + public function collect(Request $request, Response $response, Throwable $exception = null) { $queries = []; foreach ($this->loggers as $name => $logger) { @@ -64,7 +65,7 @@ public function collect(Request $request, Response $response, \Throwable $except } } - $this->groupedQueries = null; + $this->groupedQueries = null; } /** @@ -83,7 +84,7 @@ public function reset() $this->data = []; foreach ($this->loggers as $logger) { - $logger->queries = []; + $logger->queries = []; $logger->currentQuery = 0; } } @@ -184,7 +185,12 @@ private function executionTimePercentage($executionTimeMS, $totalExecutionTimeMS return $executionTimeMS / $totalExecutionTimeMS * 100; } - private function sanitizeQueries(string $connectionName, array $queries): array + /** + * @param mixed[] $queries + * + * @return mixed[] + */ + private function sanitizeQueries(string $connectionName, array $queries) : array { foreach ($queries as $i => $query) { $queries[$i] = $this->sanitizeQuery($connectionName, $query); @@ -193,17 +199,22 @@ private function sanitizeQueries(string $connectionName, array $queries): array return $queries; } - private function sanitizeQuery(string $connectionName, array $query): array + /** + * @param mixed[] $query + * + * @return mixed[] + */ + private function sanitizeQuery(string $connectionName, array $query) : array { $query['explainable'] = true; - $query['runnable'] = true; - if (null === $query['params']) { + $query['runnable'] = true; + if ($query['params'] === null) { $query['params'] = []; } - if (!\is_array($query['params'])) { + if (! is_array($query['params'])) { $query['params'] = [$query['params']]; } - if (!\is_array($query['types'])) { + if (! is_array($query['types'])) { $query['types'] = []; } foreach ($query['params'] as $j => $param) { @@ -211,27 +222,29 @@ private function sanitizeQuery(string $connectionName, array $query): array if (isset($query['types'][$j])) { // Transform the param according to the type $type = $query['types'][$j]; - if (\is_string($type)) { + if (is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $query['types'][$j] = $type->getBindingType(); try { $param = $type->convertToDatabaseValue($param, $this->connectionRegistry->getConnection($connectionName)->getDatabasePlatform()); - } catch (\TypeError $e) { + } catch (TypeError $e) { } catch (ConversionException $e) { } } } [$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e); - if (!$explainable) { + if (! $explainable) { $query['explainable'] = false; } - if (!$runnable) { - $query['runnable'] = false; + if ($runnable) { + continue; } + + $query['runnable'] = false; } $query['params'] = $this->cloneVar($query['params']); @@ -245,31 +258,33 @@ private function sanitizeQuery(string $connectionName, array $query): array * The return value is an array with the sanitized value and a boolean * indicating if the original value was kept (allowing to use the sanitized * value to explain the query). + * + * @return mixed[] */ - private function sanitizeParam($var, ?\Throwable $error): array + private function sanitizeParam($var, ?Throwable $error) : array { - if (\is_object($var)) { - return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error]; + if (is_object($var)) { + return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && ! $error]; } if ($error) { - return ['âš  '.$error->getMessage(), false, false]; + return ['âš  ' . $error->getMessage(), false, false]; } - if (\is_array($var)) { - $a = []; + if (is_array($var)) { + $a = []; $explainable = $runnable = true; foreach ($var as $k => $v) { [$value, $e, $r] = $this->sanitizeParam($v, null); - $explainable = $explainable && $e; - $runnable = $runnable && $r; - $a[$k] = $value; + $explainable = $explainable && $e; + $runnable = $runnable && $r; + $a[$k] = $value; } return [$a, $explainable, $runnable]; } - if (\is_resource($var)) { + if (is_resource($var)) { return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false]; } diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index 6cb65b1..4b647d3 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -19,7 +19,7 @@ class DoctrineDBALExtension extends Extension public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration($container->getParameter('kernel.debug')); - $config = $this->processConfiguration($configuration, $configs); + $config = $this->processConfiguration($configuration, $configs); $this->dbalLoad($config, $container); } @@ -67,7 +67,7 @@ private function dbalLoad(array $config, ContainerBuilder $container) $registry = $container->getDefinition('doctrine.dbal.connection_registry'); $registry->setArguments([ - ServiceLocatorTagPass::register($container, array_map(function($id) { + ServiceLocatorTagPass::register($container, array_map(static function ($id) { return new Reference($id); }, $connections)), $defaultConnection, @@ -76,12 +76,12 @@ private function dbalLoad(array $config, ContainerBuilder $container) } /** - * Loads a configured DBAL connection. - * - * @param string $name The name of the connection - * @param array $connection A dbal connection configuration. - * @param ContainerBuilder $container A ContainerBuilder instance - */ + * Loads a configured DBAL connection. + * + * @param string $name The name of the connection + * @param array $connection A dbal connection configuration. + * @param ContainerBuilder $container A ContainerBuilder instance + */ protected function loadDbalConnection($name, array $connection, ContainerBuilder $container) { $configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), new ChildDefinition('doctrine.dbal.connection.configuration')); diff --git a/src/Psr11ConnectionRegistry.php b/src/Psr11ConnectionRegistry.php index d72a1c9..c3670c4 100644 --- a/src/Psr11ConnectionRegistry.php +++ b/src/Psr11ConnectionRegistry.php @@ -7,39 +7,45 @@ class Psr11ConnectionRegistry implements ConnectionRegistry { - /** - * @var ContainerInterface - */ + /** @var ContainerInterface */ private $container; - /** - * @var string - */ + /** @var string */ private $defaultConnectionName; - /** - * @var string[] - */ + /** @var string[] */ private $connectionNames; + /** + * @param string[] $connectionNames + */ public function __construct(ContainerInterface $container, string $defaultConnectionName, array $connectionNames) { - $this->container = $container; + $this->container = $container; $this->defaultConnectionName = $defaultConnectionName; - $this->connectionNames = $connectionNames; + $this->connectionNames = $connectionNames; } - public function getDefaultConnectionName(): string + /** + * @inheritDoc + */ + public function getDefaultConnectionName() : string { return $this->defaultConnectionName; } - public function getConnection(?string $name = null): Connection + /** + * @inheritDoc + */ + public function getConnection(?string $name = null) : Connection { return $this->container->get($name !== null ? $name : $this->defaultConnectionName); } - public function getConnections(): array + /** + * @inheritDoc + */ + public function getConnections() : array { $connections = []; @@ -50,7 +56,10 @@ public function getConnections(): array return $connections; } - public function getConnectionNames(): array + /** + * @inheritDoc + */ + public function getConnectionNames() : array { return $this->connectionNames; } diff --git a/tests/BundleTest.php b/tests/BundleTest.php index cef49ae..c825114 100644 --- a/tests/BundleTest.php +++ b/tests/BundleTest.php @@ -18,7 +18,7 @@ public function testBuildCompilerPasses() $config = $container->getCompilerPassConfig(); $passes = $config->getBeforeOptimizationPasses(); - $foundSchemaFilter = false; + $foundSchemaFilter = false; foreach ($passes as $pass) { if ($pass instanceof DbalSchemaFilterPass) { diff --git a/tests/ContainerTestCase.php b/tests/ContainerTestCase.php index 2c10963..5b1283e 100644 --- a/tests/ContainerTestCase.php +++ b/tests/ContainerTestCase.php @@ -46,7 +46,8 @@ public function createXmlBundleTestContainer() 'commented' => false, ], ], - ]], $container); + ], + ], $container); $container->setDefinition('my.platform', new Definition('Doctrine\DBAL\Platforms\MySqlPlatform'))->setPublic(true); diff --git a/tests/DataCollector/DoctrineDBALDataCollectorTest.php b/tests/DataCollector/DoctrineDBALDataCollectorTest.php index 5dcb51e..c652196 100644 --- a/tests/DataCollector/DoctrineDBALDataCollectorTest.php +++ b/tests/DataCollector/DoctrineDBALDataCollectorTest.php @@ -49,7 +49,7 @@ public function testGetGroupedQueries() $this->assertSame(1, $groupedQueries['default'][1]['count']); } - private function createCollector(): DoctrineDBALDataCollector + private function createCollector() : DoctrineDBALDataCollector { $registry = $this->createMock(ConnectionRegistry::class); $registry @@ -58,8 +58,6 @@ private function createCollector(): DoctrineDBALDataCollector ->with('default') ->will($this->returnValue($this->createMock(Connection::class))); - return new DoctrineDBALDataCollector($registry, [ - 'default' => 'doctrine.dbal.default_connection' - ]); + return new DoctrineDBALDataCollector($registry, ['default' => 'doctrine.dbal.default_connection']); } } diff --git a/tests/ProfilerTest.php b/tests/ProfilerTest.php index 3f94d77..8e6d801 100644 --- a/tests/ProfilerTest.php +++ b/tests/ProfilerTest.php @@ -32,10 +32,10 @@ class ProfilerTest extends BaseTestCase /** @var DoctrineDBALDataCollector */ private $collector; - public function setUp(): void + public function setUp() : void { - $this->logger = new DebugStack(); - $registry = $this->createMock(ConnectionRegistry::class); + $this->logger = new DebugStack(); + $registry = $this->createMock(ConnectionRegistry::class); $this->collector = new DoctrineDBALDataCollector($registry, []); $this->collector->addLogger('foo', $this->logger); From 4f5ec77789fa547caefcb944415a929f21549931 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sat, 7 Mar 2020 11:48:45 +0100 Subject: [PATCH 16/36] add more tests --- makefile | 0 tests/DBAL/Logging/BacktraceLoggerTest.php | 20 +++++++++++++++++ tests/DBAL/RegexSchemaAssetFilterTest.php | 17 +++++++++++++++ tests/DBAL/SchemaAssetsFilterManagerTest.php | 23 ++++++++++++++++++++ 4 files changed, 60 insertions(+) delete mode 100644 makefile create mode 100644 tests/DBAL/Logging/BacktraceLoggerTest.php create mode 100644 tests/DBAL/RegexSchemaAssetFilterTest.php create mode 100644 tests/DBAL/SchemaAssetsFilterManagerTest.php diff --git a/makefile b/makefile deleted file mode 100644 index e69de29..0000000 diff --git a/tests/DBAL/Logging/BacktraceLoggerTest.php b/tests/DBAL/Logging/BacktraceLoggerTest.php new file mode 100644 index 0000000..9fdb36d --- /dev/null +++ b/tests/DBAL/Logging/BacktraceLoggerTest.php @@ -0,0 +1,20 @@ +startQuery('SELECT column FROM table'); + $currentQuery = current($logger->queries); + self::assertSame('SELECT column FROM table', $currentQuery['sql']); + self::assertNull($currentQuery['params']); + self::assertNull($currentQuery['types']); + self::assertGreaterThan(0, $currentQuery['backtrace']); + } +} diff --git a/tests/DBAL/RegexSchemaAssetFilterTest.php b/tests/DBAL/RegexSchemaAssetFilterTest.php new file mode 100644 index 0000000..b7fd509 --- /dev/null +++ b/tests/DBAL/RegexSchemaAssetFilterTest.php @@ -0,0 +1,17 @@ +assertTrue($filter('do_not_t_ignore_me')); + $this->assertFalse($filter('t_ignore_me')); + } +} diff --git a/tests/DBAL/SchemaAssetsFilterManagerTest.php b/tests/DBAL/SchemaAssetsFilterManagerTest.php new file mode 100644 index 0000000..1166547 --- /dev/null +++ b/tests/DBAL/SchemaAssetsFilterManagerTest.php @@ -0,0 +1,23 @@ +assertSame( + ['do_not_filter'], + array_values(array_filter($tables, $manager)) + ); + } +} From 2c6f5bd6ae1a8f59a766fcaee09a243ab737f7da Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sun, 8 Mar 2020 10:02:16 +0100 Subject: [PATCH 17/36] add schema + extension tests --- src/DependencyInjection/Configuration.php | 5 +- .../DoctrineDBALExtension.php | 18 +- .../config/schema/doctrine_dbal-1.0.xsd | 112 +++++ .../AbstractDoctrineExtensionTest.php | 476 ++++++++++++++++++ .../AnnotationsBundle/AnnotationsBundle.php | 9 + .../Bundles/AnnotationsBundle/Entity/Test.php | 7 + .../Entity/TestCustomClassRepoEntity.php | 20 + .../Entity/TestCustomServiceRepoEntity.php | 20 + .../Entity/TestDefaultRepoEntity.php | 20 + .../TestCustomClassRepoRepository.php | 14 + .../TestCustomServiceRepoRepository.php | 15 + .../RepositoryServiceBundle.php | 9 + .../AnnotationsBundle/AnnotationsBundle.php | 9 + .../Vendor/AnnotationsBundle/Entity/Test.php | 7 + .../Bundles/XmlBundle/Entity/Test.php | 7 + .../Resources/config/doctrine/Test.orm.xml | 0 .../Fixtures/Bundles/XmlBundle/XmlBundle.php | 9 + .../Bundles/YamlBundle/Entity/Test.php | 9 + .../Resources/config/doctrine/Test.orm.yml | 5 + .../Bundles/YamlBundle/YamlBundle.php | 9 + .../Fixtures/TestKernel.php | 57 +++ .../Fixtures/config/xml/dbal_auto_commit.xml | 11 + .../Fixtures/config/xml/dbal_logging.xml | 37 ++ .../config/xml/dbal_oracle_connectstring.xml | 11 + .../config/xml/dbal_oracle_instancename.xml | 11 + .../Fixtures/config/xml/dbal_savepoints.xml | 20 + .../config/xml/dbal_schema_filter.xml | 14 + .../xml/dbal_service_multiple_connections.xml | 66 +++ .../dbal_service_pool_sharding_connection.xml | 23 + .../xml/dbal_service_single_connection.xml | 17 + ...service_single_master_slave_connection.xml | 13 + .../Fixtures/config/xml/dbal_types.xml | 13 + ...ell_known_schema_filter_default_tables.xml | 22 + ..._known_schema_filter_overridden_tables.xml | 43 ++ .../Fixtures/config/yml/dbal_auto_commit.yml | 2 + .../Fixtures/config/yml/dbal_logging.yml | 20 + .../config/yml/dbal_oracle_connectstring.yml | 2 + .../config/yml/dbal_oracle_instancename.yml | 2 + .../Fixtures/config/yml/dbal_savepoints.yml | 9 + .../config/yml/dbal_schema_filter.yml | 7 + .../yml/dbal_service_multiple_connections.yml | 52 ++ .../dbal_service_pool_sharding_connection.yml | 19 + .../yml/dbal_service_single_connection.yml | 6 + ...service_single_master_slave_connection.yml | 14 + .../Fixtures/config/yml/dbal_types.yml | 6 + ...ell_known_schema_filter_default_tables.yml | 24 + ..._known_schema_filter_overridden_tables.yml | 37 ++ .../XmlDoctrineExtensionTest.php | 16 + .../YamlDoctrineExtensionTest.php | 16 + 49 files changed, 1365 insertions(+), 5 deletions(-) create mode 100644 src/Resources/config/schema/doctrine_dbal-1.0.xsd create mode 100644 tests/DependencyInjection/AbstractDoctrineExtensionTest.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/Entity/Test.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Entity/TestCustomClassRepoEntity.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Entity/TestCustomServiceRepoEntity.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Entity/TestDefaultRepoEntity.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Repository/TestCustomClassRepoRepository.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Repository/TestCustomServiceRepoRepository.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/RepositoryServiceBundle.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/Entity/Test.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/XmlBundle/Entity/Test.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/XmlBundle/Resources/config/doctrine/Test.orm.xml create mode 100644 tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/YamlBundle/Entity/Test.php create mode 100644 tests/DependencyInjection/Fixtures/Bundles/YamlBundle/Resources/config/doctrine/Test.orm.yml create mode 100644 tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php create mode 100644 tests/DependencyInjection/Fixtures/TestKernel.php create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_auto_commit.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_logging.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_connectstring.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_instancename.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_savepoints.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_schema_filter.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_service_multiple_connections.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_service_pool_sharding_connection.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_connection.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_master_slave_connection.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/dbal_types.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_default_tables.xml create mode 100644 tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_overridden_tables.xml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_auto_commit.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_logging.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_connectstring.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_instancename.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_savepoints.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_schema_filter.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_service_multiple_connections.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_service_pool_sharding_connection.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_connection.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_master_slave_connection.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/dbal_types.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_default_tables.yml create mode 100644 tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_overridden_tables.yml create mode 100644 tests/DependencyInjection/XmlDoctrineExtensionTest.php create mode 100644 tests/DependencyInjection/YamlDoctrineExtensionTest.php diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index e45edfa..d26c136 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -28,10 +28,7 @@ public function getConfigTreeBuilder() return $treeBuilder; } - - /** - * Add DBAL section to configuration tree - */ + private function addDbalSection(ArrayNodeDefinition $node) { $node diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index 4b647d3..6a2fa81 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -2,7 +2,7 @@ namespace Doctrine\Bundle\DBALBundle\DependencyInjection; -use Doctrine\Bundle\DBALBundle\DBAL\RegexSchemaAssetFilter; +use Doctrine\Bundle\DBALBundle\DBAL\SchemaFilter\RegexSchemaAssetFilter; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -279,4 +279,20 @@ protected function getConnectionOptions($connection) return $options; } + + /** + * {@inheritDoc} + */ + public function getXsdValidationBasePath() : string + { + return __DIR__ . '/../Resources/config/schema'; + } + + /** + * {@inheritDoc} + */ + public function getNamespace() : string + { + return 'http://symfony.com/schema/dic/doctrine_dbal'; + } } diff --git a/src/Resources/config/schema/doctrine_dbal-1.0.xsd b/src/Resources/config/schema/doctrine_dbal-1.0.xsd new file mode 100644 index 0000000..aa09204 --- /dev/null +++ b/src/Resources/config/schema/doctrine_dbal-1.0.xsd @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/tests/DependencyInjection/AbstractDoctrineExtensionTest.php new file mode 100644 index 0000000..fdbb553 --- /dev/null +++ b/tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -0,0 +1,476 @@ +loadContainer('dbal_service_multiple_connections'); + + // doctrine.dbal.mysql_connection + $config = $container->getDefinition('doctrine.dbal.mysql_connection')->getArgument(0); + + $this->assertEquals('mysql_s3cr3t', $config['password']); + $this->assertEquals('mysql_user', $config['user']); + $this->assertEquals('mysql_db', $config['dbname']); + $this->assertEquals('/path/to/mysqld.sock', $config['unix_socket']); + + // doctrine.dbal.sqlite_connection + $config = $container->getDefinition('doctrine.dbal.sqlite_connection')->getArgument(0); + $this->assertSame('pdo_sqlite', $config['driver']); + $this->assertSame('sqlite_db', $config['dbname']); + $this->assertSame('sqlite_user', $config['user']); + $this->assertSame('sqlite_s3cr3t', $config['password']); + $this->assertSame('/tmp/db.sqlite', $config['path']); + $this->assertTrue($config['memory']); + + // doctrine.dbal.oci8_connection + $config = $container->getDefinition('doctrine.dbal.oci_connection')->getArgument(0); + $this->assertSame('oci8', $config['driver']); + $this->assertSame('oracle_db', $config['dbname']); + $this->assertSame('oracle_user', $config['user']); + $this->assertSame('oracle_s3cr3t', $config['password']); + $this->assertSame('oracle_service', $config['servicename']); + $this->assertTrue($config['service']); + $this->assertTrue($config['pooled']); + $this->assertSame('utf8', $config['charset']); + + // doctrine.dbal.ibmdb2_connection + $config = $container->getDefinition('doctrine.dbal.ibmdb2_connection')->getArgument(0); + $this->assertSame('ibm_db2', $config['driver']); + $this->assertSame('ibmdb2_db', $config['dbname']); + $this->assertSame('ibmdb2_user', $config['user']); + $this->assertSame('ibmdb2_s3cr3t', $config['password']); + $this->assertSame('TCPIP', $config['protocol']); + + // doctrine.dbal.pgsql_connection + $config = $container->getDefinition('doctrine.dbal.pgsql_connection')->getArgument(0); + $this->assertSame('pdo_pgsql', $config['driver']); + $this->assertSame('pgsql_schema', $config['dbname']); + $this->assertSame('pgsql_user', $config['user']); + $this->assertSame('pgsql_s3cr3t', $config['password']); + $this->assertSame('pgsql_db', $config['default_dbname']); + $this->assertSame('require', $config['sslmode']); + $this->assertSame('postgresql-ca.pem', $config['sslrootcert']); + $this->assertSame('postgresql-cert.pem', $config['sslcert']); + $this->assertSame('postgresql-key.pem', $config['sslkey']); + $this->assertSame('postgresql.crl', $config['sslcrl']); + $this->assertSame('utf8', $config['charset']); + + // doctrine.dbal.sqlanywhere_connection + $config = $container->getDefinition('doctrine.dbal.sqlanywhere_connection')->getArgument(0); + $this->assertSame('sqlanywhere', $config['driver']); + $this->assertSame('localhost', $config['host']); + $this->assertSame(2683, $config['port']); + $this->assertSame('sqlanywhere_server', $config['server']); + $this->assertSame('sqlanywhere_db', $config['dbname']); + $this->assertSame('sqlanywhere_user', $config['user']); + $this->assertSame('sqlanywhere_s3cr3t', $config['password']); + $this->assertTrue($config['persistent']); + $this->assertSame('utf8', $config['charset']); + } + + public function testDbalLoadFromXmlSingleConnections() + { + $container = $this->loadContainer('dbal_service_single_connection'); + + // doctrine.dbal.mysql_connection + $config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0); + + $this->assertEquals('mysql_s3cr3t', $config['password']); + $this->assertEquals('mysql_user', $config['user']); + $this->assertEquals('mysql_db', $config['dbname']); + $this->assertEquals('/path/to/mysqld.sock', $config['unix_socket']); + $this->assertEquals('5.6.20', $config['serverVersion']); + } + + public function testDbalLoadSingleMasterSlaveConnection() + { + $container = $this->loadContainer('dbal_service_single_master_slave_connection'); + + // doctrine.dbal.mysql_connection + $param = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0); + + $this->assertEquals('Doctrine\\DBAL\\Connections\\MasterSlaveConnection', $param['wrapperClass']); + $this->assertTrue($param['keepSlave']); + $this->assertEquals( + [ + 'user' => 'mysql_user', + 'password' => 'mysql_s3cr3t', + 'port' => null, + 'dbname' => 'mysql_db', + 'host' => 'localhost', + 'unix_socket' => '/path/to/mysqld.sock', + ], + $param['master'] + ); + $this->assertEquals( + [ + 'user' => 'slave_user', + 'password' => 'slave_s3cr3t', + 'port' => null, + 'dbname' => 'slave_db', + 'host' => 'localhost', + 'unix_socket' => '/path/to/mysqld_slave.sock', + ], + $param['slaves']['slave1'] + ); + $this->assertEquals(['engine' => 'InnoDB'], $param['defaultTableOptions']); + } + + public function testDbalLoadPoolShardingConnection() + { + $container = $this->loadContainer('dbal_service_pool_sharding_connection'); + + // doctrine.dbal.mysql_connection + $param = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0); + + $this->assertEquals('Doctrine\\DBAL\\Sharding\\PoolingShardConnection', $param['wrapperClass']); + $this->assertEquals(new Reference('foo.shard_choser'), $param['shardChoser']); + $this->assertEquals( + [ + 'user' => 'mysql_user', + 'password' => 'mysql_s3cr3t', + 'port' => null, + 'dbname' => 'mysql_db', + 'host' => 'localhost', + 'unix_socket' => '/path/to/mysqld.sock', + ], + $param['global'] + ); + $this->assertEquals( + [ + 'user' => 'shard_user', + 'password' => 'shard_s3cr3t', + 'port' => null, + 'dbname' => 'shard_db', + 'host' => 'localhost', + 'unix_socket' => '/path/to/mysqld_shard.sock', + 'id' => 1, + ], + $param['shards'][0] + ); + $this->assertEquals(['engine' => 'InnoDB'], $param['defaultTableOptions']); + } + + public function testDbalLoadSavepointsForNestedTransactions() + { + $container = $this->loadContainer('dbal_savepoints'); + + $calls = $container->getDefinition('doctrine.dbal.savepoints_connection')->getMethodCalls(); + $this->assertCount(1, $calls); + $this->assertEquals('setNestTransactionsWithSavepoints', $calls[0][0]); + $this->assertTrue($calls[0][1][0]); + + $calls = $container->getDefinition('doctrine.dbal.nosavepoints_connection')->getMethodCalls(); + $this->assertCount(0, $calls); + + $calls = $container->getDefinition('doctrine.dbal.notset_connection')->getMethodCalls(); + $this->assertCount(0, $calls); + } + + public function testLoadLogging() + { + $container = $this->loadContainer('dbal_logging'); + + $definition = $container->getDefinition('doctrine.dbal.log_connection.configuration'); + $this->assertDICDefinitionMethodCallOnce($definition, 'setSQLLogger', [new Reference('doctrine.dbal.logger')]); + + $definition = $container->getDefinition('doctrine.dbal.profile_connection.configuration'); + $this->assertDICDefinitionMethodCallOnce($definition, 'setSQLLogger', [new Reference('doctrine.dbal.logger.profiling.profile')]); + + $definition = $container->getDefinition('doctrine.dbal.profile_with_backtrace_connection.configuration'); + $this->assertDICDefinitionMethodCallOnce($definition, 'setSQLLogger', [new Reference('doctrine.dbal.logger.backtrace.profile_with_backtrace')]); + + $definition = $container->getDefinition('doctrine.dbal.backtrace_without_profile_connection.configuration'); + $this->assertDICDefinitionNoMethodCall($definition, 'setSQLLogger'); + + $definition = $container->getDefinition('doctrine.dbal.both_connection.configuration'); + $this->assertDICDefinitionMethodCallOnce($definition, 'setSQLLogger', [new Reference('doctrine.dbal.logger.chain.both')]); + } + + public function testSetTypes() + { + $container = $this->loadContainer('dbal_types'); + + $this->assertEquals( + ['test' => ['class' => TestType::class]], + $container->getParameter('doctrine.dbal.connection_factory.types') + ); + $this->assertEquals('%doctrine.dbal.connection_factory.types%', $container->getDefinition('doctrine.dbal.connection_factory')->getArgument(0)); + } + + public function testDbalAutoCommit() + { + $container = $this->loadContainer('dbal_auto_commit'); + + $definition = $container->getDefinition('doctrine.dbal.default_connection.configuration'); + $this->assertDICDefinitionMethodCallOnce($definition, 'setAutoCommit', [false]); + } + + public function testDbalOracleConnectstring() + { + $container = $this->loadContainer('dbal_oracle_connectstring'); + + $config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0); + $this->assertSame('scott@sales-server:1521/sales.us.example.com', $config['connectstring']); + } + + public function testDbalOracleInstancename() + { + $container = $this->loadContainer('dbal_oracle_instancename'); + + $config = $container->getDefinition('doctrine.dbal.default_connection')->getArgument(0); + $this->assertSame('mySuperInstance', $config['instancename']); + } + + public function testDbalSchemaFilterNewConfig() + { + $container = $this->getContainer([]); + $loader = new DoctrineDBALExtension(); + $container->registerExtension($loader); + $container->addCompilerPass(new WellKnownSchemaFilterPass()); + $container->addCompilerPass(new DbalSchemaFilterPass()); + + // ignore table1 table on "default" connection + $container->register('dummy_filter1', DummySchemaAssetsFilter::class) + ->setArguments(['table1']) + ->addTag('doctrine.dbal.schema_filter'); + + // ignore table2 table on "connection2" connection + $container->register('dummy_filter2', DummySchemaAssetsFilter::class) + ->setArguments(['table2']) + ->addTag('doctrine.dbal.schema_filter', ['connection' => 'connection2']); + + $this->loadFromFile($container, 'dbal_schema_filter'); + + $assetNames = ['table1', 'table2', 'table3', 't_ignored']; + $expectedConnectionAssets = [ + // ignores table1 + schema_filter applies + 'connection1' => ['table2', 'table3'], + // ignores table2, no schema_filter applies + 'connection2' => ['table1', 'table3', 't_ignored'], + // connection3 has no ignores, handled separately + ]; + + $this->compileContainer($container); + + $getConfiguration = static function (string $connectionName) use ($container) : Configuration { + return $container->get(sprintf('doctrine.dbal.%s_connection', $connectionName))->getConfiguration(); + }; + + foreach ($expectedConnectionAssets as $connectionName => $expectedTables) { + $connConfig = $getConfiguration($connectionName); + $this->assertSame($expectedTables, array_values(array_filter($assetNames, $connConfig->getSchemaAssetsFilter())), sprintf('Filtering for connection "%s"', $connectionName)); + } + + $this->assertNull($connConfig = $getConfiguration('connection3')->getSchemaAssetsFilter()); + } + + public function testWellKnownSchemaFilterDefaultTables() + { + $container = $this->getContainer([]); + $loader = new DoctrineDBALExtension(); + $container->registerExtension($loader); + $container->addCompilerPass(new WellKnownSchemaFilterPass()); + $container->addCompilerPass(new DbalSchemaFilterPass()); + + $this->loadFromFile($container, 'well_known_schema_filter_default_tables'); + + $this->compileContainer($container); + + $definition = $container->getDefinition('doctrine.dbal.well_known_schema_asset_filter'); + + $this->assertSame([['cache_items', 'lock_keys', 'sessions', 'messenger_messages']], $definition->getArguments()); + $this->assertSame([['connection' => 'connection1'], ['connection' => 'connection2'], ['connection' => 'connection3']], $definition->getTag('doctrine.dbal.schema_filter')); + + $definition = $container->getDefinition('doctrine.dbal.connection1_schema_asset_filter_manager'); + + $this->assertEquals([new Reference('doctrine.dbal.well_known_schema_asset_filter'), new Reference('doctrine.dbal.connection1_regex_schema_filter')], $definition->getArgument(0)); + + $filter = $container->get('well_known_filter'); + + $this->assertFalse($filter('sessions')); + $this->assertFalse($filter('cache_items')); + $this->assertFalse($filter('lock_keys')); + $this->assertFalse($filter('messenger_messages')); + $this->assertTrue($filter('anything_else')); + } + + public function testWellKnownSchemaFilterOverriddenTables() + { + $container = $this->getContainer([]); + $loader = new DoctrineDBALExtension(); + $container->registerExtension($loader); + $container->addCompilerPass(new WellKnownSchemaFilterPass()); + $container->addCompilerPass(new DbalSchemaFilterPass()); + + $this->loadFromFile($container, 'well_known_schema_filter_overridden_tables'); + + $this->compileContainer($container); + + $filter = $container->get('well_known_filter'); + + $this->assertFalse($filter('app_session')); + $this->assertFalse($filter('app_cache')); + $this->assertFalse($filter('app_locks')); + $this->assertFalse($filter('app_messages')); + $this->assertTrue($filter('sessions')); + $this->assertTrue($filter('cache_items')); + $this->assertTrue($filter('lock_keys')); + $this->assertTrue($filter('messenger_messages')); + } + + private function loadContainer($fixture, array $bundles = ['YamlBundle'], CompilerPassInterface $compilerPass = null) + { + $container = $this->getContainer($bundles); + $container->registerExtension(new DoctrineDBALExtension()); + + $this->loadFromFile($container, $fixture); + + if ($compilerPass !== null) { + $container->addCompilerPass($compilerPass); + } + + $this->compileContainer($container); + + return $container; + } + + private function getContainer(array $bundles) + { + $map = []; + + foreach ($bundles as $bundle) { + require_once __DIR__ . '/Fixtures/Bundles/' . $bundle . '/' . $bundle . '.php'; + + $map[$bundle] = 'Fixtures\\Bundles\\' . $bundle . '\\' . $bundle; + } + + $container = new ContainerBuilder(new ParameterBag([ + 'kernel.name' => 'app', + 'kernel.debug' => false, + 'kernel.bundles' => $map, + 'kernel.cache_dir' => sys_get_temp_dir(), + 'kernel.environment' => 'test', + 'kernel.root_dir' => __DIR__ . '/../../', // src dir + 'kernel.project_dir' => __DIR__ . '/../../', // src dir + 'kernel.bundles_metadata' => [], + 'container.build_id' => uniqid(), + ])); + + $container->registerExtension(new DoctrineDBALExtension()); + + // Register dummy cache services so we don't have to load the FrameworkExtension + $container->setDefinition('cache.system', (new Definition(ArrayAdapter::class))->setPublic(true)); + $container->setDefinition('cache.app', (new Definition(ArrayAdapter::class))->setPublic(true)); + + return $container; + } + + private function assertDICConstructorArguments(Definition $definition, $args) + { + $this->assertEquals($args, $definition->getArguments(), "Expected and actual DIC Service constructor arguments of definition '" . $definition->getClass() . "' don't match."); + } + + /** + * Assertion for the DI Container, check if the given definition contains a method call with the given parameters. + * + * @param string $methodName + * @param array $params + */ + private function assertDICDefinitionMethodCallOnce(Definition $definition, $methodName, array $params = null) + { + $calls = $definition->getMethodCalls(); + $called = false; + foreach ($calls as $call) { + if ($call[0] !== $methodName) { + continue; + } + + if ($called) { + $this->fail("Method '" . $methodName . "' is expected to be called only once, a second call was registered though."); + } else { + $called = true; + if ($params !== null) { + $this->assertEquals($params, $call[1], "Expected parameters to methods '" . $methodName . "' do not match the actual parameters."); + } + } + } + if ($called) { + return; + } + + $this->fail("Method '" . $methodName . "' is expected to be called once, definition does not contain a call though."); + } + + /** + * Assertion for the DI Container, check if the given definition does not contain a method call with the given parameters. + * + * @param string $methodName + * @param array $params + */ + private function assertDICDefinitionNoMethodCall(Definition $definition, $methodName, array $params = null) + { + $calls = $definition->getMethodCalls(); + foreach ($calls as $call) { + if ($call[0] !== $methodName) { + continue; + } + + if ($params !== null) { + $this->assertNotEquals($params, $call[1], "Method '" . $methodName . "' is not expected to be called with the given parameters."); + } else { + $this->fail("Method '" . $methodName . "' is not expected to be called"); + } + } + } + + private function compileContainer(ContainerBuilder $container) + { + $container->getCompilerPassConfig()->setOptimizationPasses([new ResolveChildDefinitionsPass()]); + $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->compile(); + } +} + +class DummySchemaAssetsFilter +{ + /** @var string */ + private $tableToIgnore; + + public function __construct(string $tableToIgnore) + { + $this->tableToIgnore = $tableToIgnore; + } + + public function __invoke($assetName) : bool + { + if ($assetName instanceof AbstractAsset) { + $assetName = $assetName->getName(); + } + + return $assetName !== $this->tableToIgnore; + } +} diff --git a/tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php b/tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php new file mode 100644 index 0000000..899d3e1 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php @@ -0,0 +1,9 @@ +load(static function (ContainerBuilder $container) { + $container->loadFromExtension('framework', ['secret' => 'F00']); + + $container->loadFromExtension('doctrine_dbal', [ + ['driver' => 'pdo_sqlite'], + ]); + + // Register a NullLogger to avoid getting the stderr default logger of FrameworkBundle + $container->register('logger', NullLogger::class); + }); + } + + public function getProjectDir() : string + { + if ($this->projectDir === null) { + $this->projectDir = sys_get_temp_dir() . '/sf_kernel_' . md5(mt_rand()); + } + + return $this->projectDir; + } + + public function getRootDir() : string + { + return $this->getProjectDir(); + } +} diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_auto_commit.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_auto_commit.xml new file mode 100644 index 0000000..d20d4b1 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_auto_commit.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_logging.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_logging.xml new file mode 100644 index 0000000..2dc481d --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_logging.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_connectstring.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_connectstring.xml new file mode 100644 index 0000000..d6bef4b --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_connectstring.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_instancename.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_instancename.xml new file mode 100644 index 0000000..cef7b5c --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_oracle_instancename.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_savepoints.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_savepoints.xml new file mode 100644 index 0000000..317dea8 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_savepoints.xml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_schema_filter.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_schema_filter.xml new file mode 100644 index 0000000..67d9675 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_schema_filter.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_service_multiple_connections.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_multiple_connections.xml new file mode 100644 index 0000000..59a4ad2 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_multiple_connections.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_service_pool_sharding_connection.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_pool_sharding_connection.xml new file mode 100644 index 0000000..b5afd13 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_pool_sharding_connection.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + InnoDB + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_connection.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_connection.xml new file mode 100644 index 0000000..a4d0f85 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_connection.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_master_slave_connection.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_master_slave_connection.xml new file mode 100644 index 0000000..031ccb3 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_service_single_master_slave_connection.xml @@ -0,0 +1,13 @@ + + + + + + + InnoDB + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/dbal_types.xml b/tests/DependencyInjection/Fixtures/config/xml/dbal_types.xml new file mode 100644 index 0000000..a8aaed8 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/dbal_types.xml @@ -0,0 +1,13 @@ + + + + + + Doctrine\Bundle\DBALBundle\Tests\Fixtures\TestType + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_default_tables.xml b/tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_default_tables.xml new file mode 100644 index 0000000..83b1654 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_default_tables.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_overridden_tables.xml b/tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_overridden_tables.xml new file mode 100644 index 0000000..a8a4dc6 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/xml/well_known_schema_filter_overridden_tables.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + app_cache + + + + + + app_session + + + + + + app_locks + + + + + app_messages + + + + diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_auto_commit.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_auto_commit.yml new file mode 100644 index 0000000..a692a41 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_auto_commit.yml @@ -0,0 +1,2 @@ +doctrine_dbal: + auto_commit: false diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_logging.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_logging.yml new file mode 100644 index 0000000..b78ab04 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_logging.yml @@ -0,0 +1,20 @@ +doctrine_dbal: + default_connection: mysql + connections: + log: + logging: true + profiling: false + profile: + logging: false + profiling: true + profile_with_backtrace: + logging: false + profiling: true + profiling_collect_backtrace: true + backtrace_without_profile: + logging: false + profiling: false + profiling_collect_backtrace: true + both: + logging: true + profiling: true diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_connectstring.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_connectstring.yml new file mode 100644 index 0000000..edad0b7 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_connectstring.yml @@ -0,0 +1,2 @@ +doctrine_dbal: + connectstring: scott@sales-server:1521/sales.us.example.com diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_instancename.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_instancename.yml new file mode 100644 index 0000000..14e7e3f --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_oracle_instancename.yml @@ -0,0 +1,2 @@ +doctrine_dbal: + instancename: mySuperInstance diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_savepoints.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_savepoints.yml new file mode 100644 index 0000000..973e05d --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_savepoints.yml @@ -0,0 +1,9 @@ +doctrine_dbal: + default_connection: savepoints + connections: + savepoints: + use_savepoints: true + nosavepoints: + use_savepoints: false + notset: + user: root diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_schema_filter.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_schema_filter.yml new file mode 100644 index 0000000..b49ec1b --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_schema_filter.yml @@ -0,0 +1,7 @@ +doctrine_dbal: + default_connection: connection1 + connections: + connection1: + schema_filter: ~^(?!t_)~ + connection2: [] + connection3: [] diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_service_multiple_connections.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_multiple_connections.yml new file mode 100644 index 0000000..a8af0f3 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_multiple_connections.yml @@ -0,0 +1,52 @@ +doctrine_dbal: + default_connection: mysql + connections: + mysql: + dbname: mysql_db + user: mysql_user + password: mysql_s3cr3t + unix_socket: /path/to/mysqld.sock + sqlite: + driver: pdo_sqlite + dbname: sqlite_db + user: sqlite_user + password: sqlite_s3cr3t + path: /tmp/db.sqlite + memory: true + oci: + driver: oci8 + dbname: oracle_db + user: oracle_user + password: oracle_s3cr3t + servicename: oracle_service + service: true + pooled: true + charset: utf8 + ibmdb2: + driver: ibm_db2 + dbname: ibmdb2_db + user: ibmdb2_user + password: ibmdb2_s3cr3t + protocol: TCPIP + pgsql: + driver: pdo_pgsql + dbname: pgsql_schema + user: pgsql_user + password: pgsql_s3cr3t + default_dbname: pgsql_db + sslmode: require + sslrootcert: postgresql-ca.pem + sslcert: postgresql-cert.pem + sslkey: postgresql-key.pem + sslcrl: postgresql.crl + charset: utf8 + sqlanywhere: + driver: sqlanywhere + host: localhost + port: 2683 + server: sqlanywhere_server + dbname: sqlanywhere_db + user: sqlanywhere_user + password: sqlanywhere_s3cr3t + persistent: true + charset: utf8 diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_service_pool_sharding_connection.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_pool_sharding_connection.yml new file mode 100644 index 0000000..b89a214 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_pool_sharding_connection.yml @@ -0,0 +1,19 @@ +doctrine_dbal: + dbname: mysql_db + user: mysql_user + password: mysql_s3cr3t + unix_socket: /path/to/mysqld.sock + shard_choser_service: foo.shard_choser + default_table_options: + engine: InnoDB + shards: + - + id: 1 + user: shard_user + dbname: shard_db + password: shard_s3cr3t + unix_socket: /path/to/mysqld_shard.sock + +services: + foo.shard_choser: + class: stdClass diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_connection.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_connection.yml new file mode 100644 index 0000000..b176bfc --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_connection.yml @@ -0,0 +1,6 @@ +doctrine_dbal: + dbname: mysql_db + user: mysql_user + password: mysql_s3cr3t + unix_socket: /path/to/mysqld.sock + server_version: 5.6.20 diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_master_slave_connection.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_master_slave_connection.yml new file mode 100644 index 0000000..b031bc8 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_service_single_master_slave_connection.yml @@ -0,0 +1,14 @@ +doctrine_dbal: + dbname: mysql_db + user: mysql_user + password: mysql_s3cr3t + unix_socket: /path/to/mysqld.sock + keep_slave: true + default_table_options: + engine: InnoDB + slaves: + slave1: + user: slave_user + dbname: slave_db + password: slave_s3cr3t + unix_socket: /path/to/mysqld_slave.sock diff --git a/tests/DependencyInjection/Fixtures/config/yml/dbal_types.yml b/tests/DependencyInjection/Fixtures/config/yml/dbal_types.yml new file mode 100644 index 0000000..fc84299 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/dbal_types.yml @@ -0,0 +1,6 @@ +doctrine_dbal: + default_connection: default + types: + test: Doctrine\Bundle\DBALBundle\Tests\Fixtures\TestType + connections: + default: ~ diff --git a/tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_default_tables.yml b/tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_default_tables.yml new file mode 100644 index 0000000..d2ad3d8 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_default_tables.yml @@ -0,0 +1,24 @@ +doctrine_dbal: + default_connection: connection1 + connections: + connection1: + schema_filter: ~^(?!t_)~ + connection2: [] + connection3: [] + +services: + well_known_filter: + alias: 'doctrine.dbal.well_known_schema_asset_filter' + public: true + + symfony.cache: + class: 'Symfony\Component\Cache\Adapter\PdoAdapter' + + symfony.session: + class: 'Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler' + + symfony.lock: + class: 'Symfony\Component\Lock\Store\PdoStore' + + symfony.messenger: + class: 'Symfony\Component\Messenger\Transport\Doctrine\Connection' diff --git a/tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_overridden_tables.yml b/tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_overridden_tables.yml new file mode 100644 index 0000000..9c9f975 --- /dev/null +++ b/tests/DependencyInjection/Fixtures/config/yml/well_known_schema_filter_overridden_tables.yml @@ -0,0 +1,37 @@ +doctrine_dbal: + default_connection: connection1 + connections: + connection1: + schema_filter: ~^(?!t_)~ + connection2: [] + connection3: [] + +services: + well_known_filter: + alias: 'doctrine.dbal.well_known_schema_asset_filter' + public: true + + symfony.cache: + class: 'Symfony\Component\Cache\Adapter\PdoAdapter' + arguments: + - ~ + - ~ + - ~ + - db_table: app_cache + + symfony.session: + class: 'Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler' + arguments: + - ~ + - db_table: app_session + + symfony.lock: + class: 'Symfony\Component\Lock\Store\PdoStore' + arguments: + - ~ + - db_table: app_locks + + symfony.messenger: + class: 'Symfony\Component\Messenger\Transport\Doctrine\Connection' + arguments: + - table_name: app_messages diff --git a/tests/DependencyInjection/XmlDoctrineExtensionTest.php b/tests/DependencyInjection/XmlDoctrineExtensionTest.php new file mode 100644 index 0000000..c8b10f2 --- /dev/null +++ b/tests/DependencyInjection/XmlDoctrineExtensionTest.php @@ -0,0 +1,16 @@ +load($file . '.xml'); + } +} diff --git a/tests/DependencyInjection/YamlDoctrineExtensionTest.php b/tests/DependencyInjection/YamlDoctrineExtensionTest.php new file mode 100644 index 0000000..0884f25 --- /dev/null +++ b/tests/DependencyInjection/YamlDoctrineExtensionTest.php @@ -0,0 +1,16 @@ +load($file . '.yml'); + } +} From 98fb67e3c7b97c27df7d16d2e75e519f0d2d5732 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sun, 8 Mar 2020 10:06:12 +0100 Subject: [PATCH 18/36] cleanup --- src/DependencyInjection/Configuration.php | 2 +- .../AbstractDoctrineExtensionTest.php | 30 +++---------------- .../AnnotationsBundle/AnnotationsBundle.php | 9 ------ .../Bundles/AnnotationsBundle/Entity/Test.php | 7 ----- .../Entity/TestCustomClassRepoEntity.php | 20 ------------- .../Entity/TestCustomServiceRepoEntity.php | 20 ------------- .../Entity/TestDefaultRepoEntity.php | 20 ------------- .../TestCustomClassRepoRepository.php | 14 --------- .../TestCustomServiceRepoRepository.php | 15 ---------- .../RepositoryServiceBundle.php | 9 ------ .../AnnotationsBundle/AnnotationsBundle.php | 9 ------ .../Vendor/AnnotationsBundle/Entity/Test.php | 7 ----- .../Bundles/XmlBundle/Entity/Test.php | 7 ----- .../Resources/config/doctrine/Test.orm.xml | 0 .../Fixtures/Bundles/XmlBundle/XmlBundle.php | 9 ------ .../Bundles/YamlBundle/Entity/Test.php | 9 ------ .../Resources/config/doctrine/Test.orm.yml | 5 ---- .../Bundles/YamlBundle/YamlBundle.php | 9 ------ 18 files changed, 5 insertions(+), 196 deletions(-) delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/Entity/Test.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Entity/TestCustomClassRepoEntity.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Entity/TestCustomServiceRepoEntity.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Entity/TestDefaultRepoEntity.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Repository/TestCustomClassRepoRepository.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/Repository/TestCustomServiceRepoRepository.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/RepositoryServiceBundle/RepositoryServiceBundle.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/Entity/Test.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/XmlBundle/Entity/Test.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/XmlBundle/Resources/config/doctrine/Test.orm.xml delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/YamlBundle/Entity/Test.php delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/YamlBundle/Resources/config/doctrine/Test.orm.yml delete mode 100644 tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index d26c136..34753c8 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -28,7 +28,7 @@ public function getConfigTreeBuilder() return $treeBuilder; } - + private function addDbalSection(ArrayNodeDefinition $node) { $node diff --git a/tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/tests/DependencyInjection/AbstractDoctrineExtensionTest.php index fdbb553..164499f 100644 --- a/tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -9,16 +9,12 @@ use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Schema\AbstractAsset; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\DoctrineProvider; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; abstract class AbstractDoctrineExtensionTest extends TestCase { @@ -342,36 +338,23 @@ public function testWellKnownSchemaFilterOverriddenTables() $this->assertTrue($filter('messenger_messages')); } - private function loadContainer($fixture, array $bundles = ['YamlBundle'], CompilerPassInterface $compilerPass = null) + private function loadContainer($fixture) { - $container = $this->getContainer($bundles); + $container = $this->getContainer([]); $container->registerExtension(new DoctrineDBALExtension()); $this->loadFromFile($container, $fixture); - - if ($compilerPass !== null) { - $container->addCompilerPass($compilerPass); - } - $this->compileContainer($container); return $container; } - private function getContainer(array $bundles) + private function getContainer() { - $map = []; - - foreach ($bundles as $bundle) { - require_once __DIR__ . '/Fixtures/Bundles/' . $bundle . '/' . $bundle . '.php'; - - $map[$bundle] = 'Fixtures\\Bundles\\' . $bundle . '\\' . $bundle; - } - $container = new ContainerBuilder(new ParameterBag([ 'kernel.name' => 'app', 'kernel.debug' => false, - 'kernel.bundles' => $map, + 'kernel.bundles' => [], 'kernel.cache_dir' => sys_get_temp_dir(), 'kernel.environment' => 'test', 'kernel.root_dir' => __DIR__ . '/../../', // src dir @@ -389,11 +372,6 @@ private function getContainer(array $bundles) return $container; } - private function assertDICConstructorArguments(Definition $definition, $args) - { - $this->assertEquals($args, $definition->getArguments(), "Expected and actual DIC Service constructor arguments of definition '" . $definition->getClass() . "' don't match."); - } - /** * Assertion for the DI Container, check if the given definition contains a method call with the given parameters. * diff --git a/tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php b/tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php deleted file mode 100644 index 899d3e1..0000000 --- a/tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Sun, 8 Mar 2020 10:45:44 +0100 Subject: [PATCH 19/36] more cleanup --- .../Fixtures/TestKernel.php | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 tests/DependencyInjection/Fixtures/TestKernel.php diff --git a/tests/DependencyInjection/Fixtures/TestKernel.php b/tests/DependencyInjection/Fixtures/TestKernel.php deleted file mode 100644 index aef3040..0000000 --- a/tests/DependencyInjection/Fixtures/TestKernel.php +++ /dev/null @@ -1,57 +0,0 @@ -load(static function (ContainerBuilder $container) { - $container->loadFromExtension('framework', ['secret' => 'F00']); - - $container->loadFromExtension('doctrine_dbal', [ - ['driver' => 'pdo_sqlite'], - ]); - - // Register a NullLogger to avoid getting the stderr default logger of FrameworkBundle - $container->register('logger', NullLogger::class); - }); - } - - public function getProjectDir() : string - { - if ($this->projectDir === null) { - $this->projectDir = sys_get_temp_dir() . '/sf_kernel_' . md5(mt_rand()); - } - - return $this->projectDir; - } - - public function getRootDir() : string - { - return $this->getProjectDir(); - } -} From 25dc5ad2577d2a09351bc627185517537165efcc Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sun, 8 Mar 2020 10:55:00 +0100 Subject: [PATCH 20/36] address first review comments --- .../Compiler/WellKnownSchemaFilterPass.php | 6 ------ src/Psr11ConnectionRegistry.php | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php b/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php index 16364b9..ff1f205 100644 --- a/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php +++ b/src/DependencyInjection/Compiler/WellKnownSchemaFilterPass.php @@ -2,7 +2,6 @@ namespace Doctrine\Bundle\DBALBundle\DependencyInjection\Compiler; -use Doctrine\DBAL\Configuration; use Symfony\Component\Cache\Adapter\PdoAdapter; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -20,11 +19,6 @@ class WellKnownSchemaFilterPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - if (! method_exists(Configuration::class, 'setSchemaAssetsFilter')) { - // only supported when using doctrine/dbal 2.9 or higher - return; - } - $blacklist = []; foreach ($container->getDefinitions() as $definition) { diff --git a/src/Psr11ConnectionRegistry.php b/src/Psr11ConnectionRegistry.php index c3670c4..2837634 100644 --- a/src/Psr11ConnectionRegistry.php +++ b/src/Psr11ConnectionRegistry.php @@ -39,7 +39,7 @@ public function getDefaultConnectionName() : string */ public function getConnection(?string $name = null) : Connection { - return $this->container->get($name !== null ? $name : $this->defaultConnectionName); + return $this->container->get($name ?? $this->defaultConnectionName); } /** From ded7eb80eefdeb930fc2dd154a80c34e1ede5abc Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sun, 8 Mar 2020 15:31:00 +0100 Subject: [PATCH 21/36] add RunSqlDoctrineCommand proxy --- composer.json | 1 + src/Command/Proxy/ConnectionHelperTrait.php | 16 ++++++++ src/Command/Proxy/RunSqlDoctrineCommand.php | 45 +++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/Command/Proxy/ConnectionHelperTrait.php create mode 100644 src/Command/Proxy/RunSqlDoctrineCommand.php diff --git a/composer.json b/composer.json index 95f41f3..0881104 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "jdorn/sql-formatter": "^1.2.16", "psr/container": "^1.0", "symfony/config": "^4.3.3|^5.0", + "symfony/console": "^3.4.30|^4.3.3|^5.0", "symfony/dependency-injection": "^4.3.3|^5.0", "symfony/doctrine-bridge": "^4.3.7|^5.0", "symfony/framework-bundle": "^3.4.30|^4.3.3|^5.0", diff --git a/src/Command/Proxy/ConnectionHelperTrait.php b/src/Command/Proxy/ConnectionHelperTrait.php new file mode 100644 index 0000000..c090f12 --- /dev/null +++ b/src/Command/Proxy/ConnectionHelperTrait.php @@ -0,0 +1,16 @@ +getKernel()->getContainer()->get('doctrine.dbal.connection_registry')->getConnection($connectionName); + $helperSet = $application->getHelperSet(); + $helperSet->set(new ConnectionHelper($connection), 'db'); + } +} diff --git a/src/Command/Proxy/RunSqlDoctrineCommand.php b/src/Command/Proxy/RunSqlDoctrineCommand.php new file mode 100644 index 0000000..e08d997 --- /dev/null +++ b/src/Command/Proxy/RunSqlDoctrineCommand.php @@ -0,0 +1,45 @@ +setName('doctrine:query:sql') + ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command') + ->setHelp(<<%command.name% command executes the given SQL query and +outputs the results: + +php %command.full_name% "SELECT * FROM users" +EOT + ); + } + + /** + * {@inheritDoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->setConnectionHelper($this->getApplication(), $input->getOption('connection')); + + return parent::execute($input, $output); + } +} From bd278bf12a05ff36a69b9ef6fa27302019d4c2eb Mon Sep 17 00:00:00 2001 From: David Maicher Date: Sun, 8 Mar 2020 15:32:31 +0100 Subject: [PATCH 22/36] cs fix --- ...nectionHelperTrait.php => ApplicationConnectionHelper.php} | 4 ++-- src/Command/Proxy/RunSqlDoctrineCommand.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Command/Proxy/{ConnectionHelperTrait.php => ApplicationConnectionHelper.php} (84%) diff --git a/src/Command/Proxy/ConnectionHelperTrait.php b/src/Command/Proxy/ApplicationConnectionHelper.php similarity index 84% rename from src/Command/Proxy/ConnectionHelperTrait.php rename to src/Command/Proxy/ApplicationConnectionHelper.php index c090f12..23b4a49 100644 --- a/src/Command/Proxy/ConnectionHelperTrait.php +++ b/src/Command/Proxy/ApplicationConnectionHelper.php @@ -5,12 +5,12 @@ use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; use Symfony\Bundle\FrameworkBundle\Console\Application; -trait ConnectionHelperTrait +trait ApplicationConnectionHelper { private function setConnectionHelper(Application $application, ?string $connectionName) { $connection = $application->getKernel()->getContainer()->get('doctrine.dbal.connection_registry')->getConnection($connectionName); - $helperSet = $application->getHelperSet(); + $helperSet = $application->getHelperSet(); $helperSet->set(new ConnectionHelper($connection), 'db'); } } diff --git a/src/Command/Proxy/RunSqlDoctrineCommand.php b/src/Command/Proxy/RunSqlDoctrineCommand.php index e08d997..673caa8 100644 --- a/src/Command/Proxy/RunSqlDoctrineCommand.php +++ b/src/Command/Proxy/RunSqlDoctrineCommand.php @@ -12,7 +12,7 @@ */ class RunSqlDoctrineCommand extends RunSqlCommand { - use ConnectionHelperTrait; + use ApplicationConnectionHelper; /** * {@inheritDoc} From cdc3fd4d12918427705a43fc4b90398861c86849 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 19:45:59 +0200 Subject: [PATCH 23/36] add 2 more commands + tests --- src/Command/CreateDatabaseCommand.php | 120 ++++++++++++++++++ src/Command/DropDatabaseCommand.php | 133 ++++++++++++++++++++ tests/Command/CreateDatabaseCommandTest.php | 121 ++++++++++++++++++ tests/Command/DropDatabaseCommandTest.php | 110 ++++++++++++++++ tests/ContainerTestCase.php | 27 ++-- 5 files changed, 498 insertions(+), 13 deletions(-) create mode 100644 src/Command/CreateDatabaseCommand.php create mode 100644 src/Command/DropDatabaseCommand.php create mode 100644 tests/Command/CreateDatabaseCommandTest.php create mode 100644 tests/Command/DropDatabaseCommandTest.php diff --git a/src/Command/CreateDatabaseCommand.php b/src/Command/CreateDatabaseCommand.php new file mode 100644 index 0000000..2cddd1f --- /dev/null +++ b/src/Command/CreateDatabaseCommand.php @@ -0,0 +1,120 @@ +registry = $registry; + } + + /** + * {@inheritDoc} + */ + protected function configure() + { + $this + ->setName('doctrine:database:create') + ->setDescription('Creates the configured database') + ->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command') + ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command') + ->addOption('if-not-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database already exists') + ->setHelp(<<%command.name% command creates the default connections database: + + php %command.full_name% + +You can also optionally specify the name of a connection to create the database for: + + php %command.full_name% --connection=default +EOT + ); + } + + /** + * {@inheritDoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $connectionName = $input->getOption('connection'); + if (empty($connectionName)) { + $connectionName = $this->registry->getDefaultConnectionName(); + } + $connection = $this->registry->getConnection($connectionName); + + $ifNotExists = $input->getOption('if-not-exists'); + + $params = $connection->getParams(); + if (isset($params['master'])) { + $params = $params['master']; + } + + // Cannot inject `shard` option in parent::getDoctrineConnection + // cause it will try to connect to a non-existing database + if (isset($params['shards'])) { + $shards = $params['shards']; + // Default select global + $params = array_merge($params, $params['global']); + unset($params['global']['dbname'], $params['global']['path'], $params['global']['url']); + if ($input->getOption('shard')) { + foreach ($shards as $i => $shard) { + if ($shard['id'] === (int) $input->getOption('shard')) { + // Select sharded database + $params = array_merge($params, $shard); + unset($params['shards'][$i]['dbname'], $params['shards'][$i]['path'], $params['shards'][$i]['url'], $params['id']); + break; + } + } + } + } + + $hasPath = isset($params['path']); + $name = $hasPath ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false); + if (! $name) { + throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be created."); + } + // Need to get rid of _every_ occurrence of dbname from connection configuration and we have already extracted all relevant info from url + unset($params['dbname'], $params['path'], $params['url']); + + $tmpConnection = DriverManager::getConnection($params); + $tmpConnection->connect($input->getOption('shard')); + $shouldNotCreateDatabase = $ifNotExists && in_array($name, $tmpConnection->getSchemaManager()->listDatabases()); + + // Only quote if we don't have a path + if (! $hasPath) { + $name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name); + } + + $error = false; + try { + if ($shouldNotCreateDatabase) { + $output->writeln(sprintf('Database %s for connection named %s already exists. Skipped.', $name, $connectionName)); + } else { + $tmpConnection->getSchemaManager()->createDatabase($name); + $output->writeln(sprintf('Created database %s for connection named %s', $name, $connectionName)); + } + } catch (Exception $e) { + $output->writeln(sprintf('Could not create database %s for connection named %s', $name, $connectionName)); + $output->writeln(sprintf('%s', $e->getMessage())); + $error = true; + } + + $tmpConnection->close(); + + return $error ? 1 : 0; + } +} diff --git a/src/Command/DropDatabaseCommand.php b/src/Command/DropDatabaseCommand.php new file mode 100644 index 0000000..931ef93 --- /dev/null +++ b/src/Command/DropDatabaseCommand.php @@ -0,0 +1,133 @@ +registry = $registry; + } + + /** + * {@inheritDoc} + */ + protected function configure() + { + $this + ->setName('doctrine:database:drop') + ->setDescription('Drops the configured database') + ->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command') + ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command') + ->addOption('if-exists', null, InputOption::VALUE_NONE, 'Don\'t trigger an error, when the database doesn\'t exist') + ->addOption('force', null, InputOption::VALUE_NONE, 'Set this parameter to execute this action') + ->setHelp(<<%command.name% command drops the default connections database: + + php %command.full_name% + +The --force parameter has to be used to actually drop the database. + +You can also optionally specify the name of a connection to drop the database for: + + php %command.full_name% --connection=default + +Be careful: All data in a given database will be lost when executing this command. +EOT + ); + } + + /** + * {@inheritDoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $connectionName = $input->getOption('connection'); + if (empty($connectionName)) { + $connectionName = $this->registry->getDefaultConnectionName(); + } + $connection = $this->registry->getConnection($connectionName); + + $ifExists = $input->getOption('if-exists'); + + $params = $connection->getParams(); + if (isset($params['master'])) { + $params = $params['master']; + } + + if (isset($params['shards'])) { + $shards = $params['shards']; + // Default select global + $params = array_merge($params, $params['global']); + if ($input->getOption('shard')) { + foreach ($shards as $shard) { + if ($shard['id'] === (int) $input->getOption('shard')) { + // Select sharded database + $params = $shard; + unset($params['id']); + break; + } + } + } + } + + $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false); + if (! $name) { + throw new InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped."); + } + unset($params['dbname'], $params['url']); + + if (! $input->getOption('force')) { + $output->writeln('ATTENTION: This operation should not be executed in a production environment.'); + $output->writeln(''); + $output->writeln(sprintf('Would drop the database %s for connection named %s.', $name, $connectionName)); + $output->writeln('Please run the operation with --force to execute'); + $output->writeln('All data will be lost!'); + + return self::RETURN_CODE_NO_FORCE; + } + + // Reopen connection without database name set + // as some vendors do not allow dropping the database connected to. + $connection->close(); + $connection = DriverManager::getConnection($params); + $shouldDropDatabase = ! $ifExists || in_array($name, $connection->getSchemaManager()->listDatabases()); + + // Only quote if we don't have a path + if (! isset($params['path'])) { + $name = $connection->getDatabasePlatform()->quoteSingleIdentifier($name); + } + + try { + if ($shouldDropDatabase) { + $connection->getSchemaManager()->dropDatabase($name); + $output->writeln(sprintf('Dropped database %s for connection named %s', $name, $connectionName)); + } else { + $output->writeln(sprintf('Database %s for connection named %s doesn\'t exist. Skipped.', $name, $connectionName)); + } + + return 0; + } catch (Exception $e) { + $output->writeln(sprintf('Could not drop database %s for connection named %s', $name, $connectionName)); + $output->writeln(sprintf('%s', $e->getMessage())); + + return self::RETURN_CODE_NOT_DROP; + } + } +} diff --git a/tests/Command/CreateDatabaseCommandTest.php b/tests/Command/CreateDatabaseCommandTest.php new file mode 100644 index 0000000..7a5a17c --- /dev/null +++ b/tests/Command/CreateDatabaseCommandTest.php @@ -0,0 +1,121 @@ + sys_get_temp_dir() . '/' . $dbName, + 'driver' => 'pdo_sqlite', + ]; + + $registry = $this->createRegistry($connectionName, $params); + + $application = new Application(); + $application->add(new CreateDatabaseCommand($registry)); + + $command = $application->find('doctrine:database:create'); + + $commandTester = new CommandTester($command); + $commandTester->execute( + array_merge(['command' => $command->getName()]) + ); + + $this->assertStringContainsString('Created database ' . sys_get_temp_dir() . '/' . $dbName . ' for connection named ' . $connectionName, $commandTester->getDisplay()); + } + + public function testExecuteWithShardOption() + { + $connectionName = 'foo'; + $params = [ + 'dbname' => 'test', + 'memory' => true, + 'driver' => 'pdo_sqlite', + 'global' => [ + 'driver' => 'pdo_sqlite', + 'dbname' => 'test', + 'path' => sys_get_temp_dir() . '/global', + ], + 'shards' => [ + 'foo' => [ + 'id' => 1, + 'path' => sys_get_temp_dir() . '/shard_1', + 'driver' => 'pdo_sqlite', + ], + 'bar' => [ + 'id' => 2, + 'path' => sys_get_temp_dir() . '/shard_2', + 'driver' => 'pdo_sqlite', + ], + ], + ]; + + $registry = $this->createRegistry($connectionName, $params); + + $application = new Application(); + $application->add(new CreateDatabaseCommand($registry)); + + $command = $application->find('doctrine:database:create'); + + $commandTester = new CommandTester($command); + $commandTester->execute(['command' => $command->getName(), '--shard' => 1]); + + $this->assertStringContainsString('Created database ' . sys_get_temp_dir() . '/shard_1 for connection named ' . $connectionName, $commandTester->getDisplay()); + + $commandTester = new CommandTester($command); + $commandTester->execute(['command' => $command->getName(), '--shard' => 2]); + + $this->assertStringContainsString('Created database ' . sys_get_temp_dir() . '/shard_2 for connection named ' . $connectionName, $commandTester->getDisplay()); + } + + /** + * @param string $connectionName Connection name + * @param mixed[]|null $params Connection parameters + * + * @return MockObject&ConnectionRegistry + */ + private function createRegistry($connectionName, $params = null) + { + $registry = $this->createMock(ConnectionRegistry::class); + + $registry->expects($this->any()) + ->method('getDefaultConnectionName') + ->willReturn($connectionName); + + $mockConnection = $this->getMockBuilder(Connection::class) + ->disableOriginalConstructor() + ->setMethods(['getParams']) + ->getMockForAbstractClass(); + + $mockConnection->expects($this->any()) + ->method('getParams') + ->withAnyParameters() + ->willReturn($params); + + $registry->expects($this->any()) + ->method('getConnection') + ->withAnyParameters() + ->willReturn($mockConnection); + + return $registry; + } +} diff --git a/tests/Command/DropDatabaseCommandTest.php b/tests/Command/DropDatabaseCommandTest.php new file mode 100644 index 0000000..43633bf --- /dev/null +++ b/tests/Command/DropDatabaseCommandTest.php @@ -0,0 +1,110 @@ + 'sqlite:///' . sys_get_temp_dir() . '/test.db', + 'path' => sys_get_temp_dir() . '/' . $dbName, + 'driver' => 'pdo_sqlite', + ]; + + $registry = $this->createRegistry($connectionName, $params); + + $application = new Application(); + $application->add(new DropDatabaseCommand($registry)); + + $command = $application->find('doctrine:database:drop'); + + $commandTester = new CommandTester($command); + $commandTester->execute( + array_merge(['command' => $command->getName(), '--force' => true]) + ); + + $this->assertStringContainsString( + sprintf( + 'Dropped database %s for connection named %s', + sys_get_temp_dir() . '/' . $dbName, + $connectionName + ), + $commandTester->getDisplay() + ); + } + + public function testExecuteWithoutOptionForceWillFailWithAttentionMessage() + { + $connectionName = 'default'; + $dbName = 'test'; + $params = [ + 'path' => sys_get_temp_dir() . '/' . $dbName, + 'driver' => 'pdo_sqlite', + ]; + + $registry = $this->createRegistry($connectionName, $params); + + $application = new Application(); + $application->add(new DropDatabaseCommand($registry)); + + $command = $application->find('doctrine:database:drop'); + + $commandTester = new CommandTester($command); + $commandTester->execute( + array_merge(['command' => $command->getName()]) + ); + + $this->assertStringContainsString( + sprintf( + 'Would drop the database %s for connection named %s.', + sys_get_temp_dir() . '/' . $dbName, + $connectionName + ), + $commandTester->getDisplay() + ); + $this->assertStringContainsString('Please run the operation with --force to execute', $commandTester->getDisplay()); + } + + /** + * @param string $connectionName Connection name + * @param mixed[]|null $params Connection parameters + * + * @return MockObject&ConnectionRegistry + */ + private function createRegistry($connectionName, $params = null) + { + $registry = $this->createMock(ConnectionRegistry::class); + + $registry->expects($this->any()) + ->method('getDefaultConnectionName') + ->willReturn($connectionName); + + $mockConnection = $this->getMockBuilder(Connection::class) + ->disableOriginalConstructor() + ->setMethods(['getParams']) + ->getMockForAbstractClass(); + + $mockConnection->expects($this->any()) + ->method('getParams') + ->withAnyParameters() + ->willReturn($params); + + $registry->expects($this->any()) + ->method('getConnection') + ->withAnyParameters() + ->willReturn($mockConnection); + + return $registry; + } +} diff --git a/tests/ContainerTestCase.php b/tests/ContainerTestCase.php index 5b1283e..3bb7cb9 100644 --- a/tests/ContainerTestCase.php +++ b/tests/ContainerTestCase.php @@ -31,22 +31,23 @@ public function createXmlBundleTestContainer() $extension = new DoctrineDBALExtension(); $container->registerExtension($extension); - $extension->load([[ - 'connections' => [ - 'default' => [ - 'driver' => 'pdo_mysql', - 'charset' => 'UTF8', - 'platform-service' => 'my.platform', + $extension->load([ + [ + 'connections' => [ + 'default' => [ + 'driver' => 'pdo_mysql', + 'charset' => 'UTF8', + 'platform-service' => 'my.platform', + ], ], - ], - 'default_connection' => 'default', - 'types' => [ - 'test' => [ - 'class' => TestType::class, - 'commented' => false, + 'default_connection' => 'default', + 'types' => [ + 'test' => [ + 'class' => TestType::class, + 'commented' => false, + ], ], ], - ], ], $container); $container->setDefinition('my.platform', new Definition('Doctrine\DBAL\Platforms\MySqlPlatform'))->setPublic(true); From ea8b8be46e6d603ba14eb38ccef4810ed35e5023 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 19:52:53 +0200 Subject: [PATCH 24/36] define command services + cleanup --- .../Proxy/ApplicationConnectionHelper.php | 16 ---------------- ...SqlDoctrineCommand.php => RunSqlCommand.php} | 15 +++++++++++---- src/Resources/config/dbal.xml | 17 ++++++----------- 3 files changed, 17 insertions(+), 31 deletions(-) delete mode 100644 src/Command/Proxy/ApplicationConnectionHelper.php rename src/Command/Proxy/{RunSqlDoctrineCommand.php => RunSqlCommand.php} (63%) diff --git a/src/Command/Proxy/ApplicationConnectionHelper.php b/src/Command/Proxy/ApplicationConnectionHelper.php deleted file mode 100644 index 23b4a49..0000000 --- a/src/Command/Proxy/ApplicationConnectionHelper.php +++ /dev/null @@ -1,16 +0,0 @@ -getKernel()->getContainer()->get('doctrine.dbal.connection_registry')->getConnection($connectionName); - $helperSet = $application->getHelperSet(); - $helperSet->set(new ConnectionHelper($connection), 'db'); - } -} diff --git a/src/Command/Proxy/RunSqlDoctrineCommand.php b/src/Command/Proxy/RunSqlCommand.php similarity index 63% rename from src/Command/Proxy/RunSqlDoctrineCommand.php rename to src/Command/Proxy/RunSqlCommand.php index 673caa8..030b51b 100644 --- a/src/Command/Proxy/RunSqlDoctrineCommand.php +++ b/src/Command/Proxy/RunSqlCommand.php @@ -2,7 +2,9 @@ namespace Doctrine\Bundle\DBALBundle\Command\Proxy; -use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand; +use Doctrine\DBAL\Tools\Console\Command\RunSqlCommand as DoctrineRunSqlCommand; +use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper; +use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -10,10 +12,8 @@ /** * Execute a SQL query and output the results. */ -class RunSqlDoctrineCommand extends RunSqlCommand +class RunSqlCommand extends DoctrineRunSqlCommand { - use ApplicationConnectionHelper; - /** * {@inheritDoc} */ @@ -42,4 +42,11 @@ protected function execute(InputInterface $input, OutputInterface $output) return parent::execute($input, $output); } + + private function setConnectionHelper(Application $application, ?string $connectionName) + { + $connection = $application->getKernel()->getContainer()->get('doctrine.dbal.connection_registry')->getConnection($connectionName); + $helperSet = $application->getHelperSet(); + $helperSet->set(new ConnectionHelper($connection), 'db'); + } } diff --git a/src/Resources/config/dbal.xml b/src/Resources/config/dbal.xml index 5e15d67..dab9fa9 100644 --- a/src/Resources/config/dbal.xml +++ b/src/Resources/config/dbal.xml @@ -70,27 +70,22 @@ - + + - - + + - - - - - + - --> From ed66ca2e21c5884c081aea91714920bf77d94f89 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 19:59:51 +0200 Subject: [PATCH 25/36] cleanup --- .travis.yml | 21 ++++++++++----------- composer.json | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index ab28d45..4ae7b92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ php: - 7.1 - 7.2 - 7.3 - - 7.4snapshot + - 7.4 env: global: @@ -21,7 +21,7 @@ before_install: - composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master install: - - travis_retry composer update -n --prefer-dist --prefer-stable + - travis_retry composer update -n --prefer-stable script: - ./vendor/bin/phpunit -v @@ -32,20 +32,19 @@ jobs: - php: 7.1 env: LOWEST SYMFONY_DEPRECATIONS_HELPER=weak install: - - travis_retry composer update -n --prefer-lowest --prefer-stable --prefer-dist + - travis_retry composer update -n --prefer-lowest --prefer-stable - # Test against latest Symfony 4.4 dev + # Symfony 4.4 - php: 7.3 env: SYMFONY_REQUIRE="4.4.*" install: - - travis_retry composer update -n --prefer-dist + - travis_retry composer update -n - # Test dev versions - - php: 7.3 - if: type = cron - env: DEV + # Symfony 5.0 + - php: 7.4 + env: SYMFONY_REQUIRE="5.0.*" install: - - travis_retry composer update -n --prefer-dist + - travis_retry composer update -n - stage: Code Quality env: CODING_STANDARDS @@ -56,7 +55,7 @@ jobs: - stage: Coverage php: 7.3 install: - - travis_retry composer update -n --prefer-dist + - travis_retry composer update -n before_script: - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi diff --git a/composer.json b/composer.json index 0881104..50f31fe 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "keywords": ["DBAL", "Database"], "homepage": "http://www.doctrine-project.org", "license": "MIT", - "minimum-stability": "dev", + "minimum-stability": "stable", "authors": [ { "name": "Doctrine Project", @@ -34,10 +34,10 @@ "twig/twig": "^1.34|^2.12" }, "config": { - "sort-packages": true + "sort-packages": true, + "preferred-install": "dist" }, "conflict": { - "doctrine/orm": "<2.6", "twig/twig": "<1.34|>=2.0,<2.4" }, "autoload": { From 1a3d3e6add7a7e25757405c0609ffc456f2235ff Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:15:10 +0200 Subject: [PATCH 26/36] use correct version of factory from 2.0.x --- src/ConnectionFactory.php | 133 ++++++++++---------------- tests/ConnectionFactoryTest.php | 159 ++++++++++---------------------- 2 files changed, 96 insertions(+), 196 deletions(-) diff --git a/src/ConnectionFactory.php b/src/ConnectionFactory.php index 69e8331..0e73150 100644 --- a/src/ConnectionFactory.php +++ b/src/ConnectionFactory.php @@ -6,13 +6,12 @@ use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\DriverManager; 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; +use function is_subclass_of; class ConnectionFactory { @@ -33,30 +32,64 @@ public function __construct(array $typesConfig) /** * Create a connection by name. * - * @param mixed[] $params + * @param mixed[] $params * @param string[]|Type[] $mappingTypes * * @return Connection */ - public function createConnection(array $params, Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = []) - { - if (! $this->initialized) { + public function createConnection( + array $params, + Configuration $config = null, + EventManager $eventManager = null, + array $mappingTypes = [] + ) { + if (!$this->initialized) { $this->initializeTypes(); } - $connection = DriverManager::getConnection($params, $config, $eventManager); + if (!isset($params['pdo']) && !isset($params['charset'])) { + $wrapperClass = null; + if (isset($params['wrapperClass'])) { + if (!is_subclass_of($params['wrapperClass'], Connection::class)) { + throw DBALException::invalidWrapperClass($params['wrapperClass']); + } + + $wrapperClass = $params['wrapperClass']; + $params['wrapperClass'] = null; + } + + $connection = DriverManager::getConnection($params, $config, $eventManager); + $params = $connection->getParams(); + $driver = $connection->getDriver(); + + if ($driver instanceof AbstractMySQLDriver) { + $params['charset'] = 'utf8mb4'; + + if (!isset($params['defaultTableOptions']['collate'])) { + $params['defaultTableOptions']['collate'] = 'utf8mb4_unicode_ci'; + } + } else { + $params['charset'] = 'utf8'; + } + + if ($wrapperClass !== null) { + $params['wrapperClass'] = $wrapperClass; + } else { + $wrapperClass = Connection::class; + } - if (! empty($mappingTypes)) { + $connection = new $wrapperClass($params, $driver, $config, $eventManager); + } else { + $connection = DriverManager::getConnection($params, $config, $eventManager); + } + + if (!empty($mappingTypes)) { $platform = $this->getDatabasePlatform($connection); foreach ($mappingTypes as $dbType => $doctrineType) { $platform->registerDoctrineTypeMapping($dbType, $doctrineType); } } - if (! empty($this->typesConfig)) { - $this->markTypesCommented($this->getDatabasePlatform($connection)); - } - return $connection; } @@ -67,11 +100,9 @@ public function createConnection(array $params, Configuration $config = null, Ev * and the platform version is unknown. * For details have a look at DoctrineBundle issue #673. * - * @return AbstractPlatform - * * @throws DBALException */ - private function getDatabasePlatform(Connection $connection) + private function getDatabasePlatform(Connection $connection): AbstractPlatform { try { return $connection->getDatabasePlatform(); @@ -90,7 +121,7 @@ private function getDatabasePlatform(Connection $connection) /** * initialize the types */ - private function initializeTypes() + private function initializeTypes(): void { foreach ($this->typesConfig as $typeName => $typeConfig) { if (Type::hasType($typeName)) { @@ -102,72 +133,4 @@ private function initializeTypes() $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; - } - - // 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 - ); - } - } } diff --git a/tests/ConnectionFactoryTest.php b/tests/ConnectionFactoryTest.php index 6da0576..943bad7 100644 --- a/tests/ConnectionFactoryTest.php +++ b/tests/ConnectionFactoryTest.php @@ -3,8 +3,8 @@ namespace Doctrine\Bundle\DBALBundle\Tests; use Doctrine\Bundle\DBALBundle\ConnectionFactory; -use Doctrine\Bundle\DBALBundle\Tests\Fixtures\TestCommentedType; -use Doctrine\Bundle\DBALBundle\Tests\Fixtures\TestType; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception\DriverException; @@ -18,15 +18,15 @@ class ConnectionFactoryTest extends TestCase /** * @expectedException \Doctrine\DBAL\DBALException */ - public function testContainer() + public function testContainer(): void { - $typesConfig = []; - $factory = new ConnectionFactory($typesConfig); - $params = ['driverClass' => FakeDriver::class]; - $config = null; + $typesConfig = []; + $factory = new ConnectionFactory($typesConfig); + $params = ['driverClass' => FakeDriver::class]; + $config = null; $eventManager = null; $mappingTypes = [0]; - $exception = new DriverException('', $this->createMock(Driver\AbstractDriverException::class)); + $exception = new DriverException('', $this->createMock(Driver\AbstractDriverException::class)); // put the mock into the fake driver FakeDriver::$exception = $exception; @@ -41,110 +41,30 @@ public function testContainer() } } - /** - * @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 + public function testDefaultCharset(): void { - return [ - 'uncommentedTypeMarkedNotCommented' => [ - 'type' => [ - 'class' => TestType::class, - 'commented' => false, - ], - 'expectedCalls' => 0, - ], - 'commentedTypeNotMarked' => [ - 'type' => [ - 'class' => TestCommentedType::class, - 'commented' => null, - ], - 'expectedCalls' => 0, - ], + $factory = new ConnectionFactory([]); + $params = [ + 'driverClass' => FakeDriver::class, + 'wrapperClass' => FakeConnection::class, ]; - } - /** - * @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 - ); - } + $creationCount = FakeConnection::$creationCount; + $connection = $factory->createConnection($params); - /** - * @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 - ); + $this->assertInstanceof(FakeConnection::class, $connection); + $this->assertSame('utf8', $connection->getParams()['charset']); + $this->assertSame(1 + $creationCount, FakeConnection::$creationCount); } - /** - * @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 + public function testDefaultCharsetMySql(): void { - $this->testRegisterTypes( - [ - 'class' => TestCommentedType::class, - 'commented' => true, - ], - 0 - ); - } + $factory = new ConnectionFactory([]); + $params = ['driver' => 'pdo_mysql']; - /** - * @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 - ); + $connection = $factory->createConnection($params); + + $this->assertSame('utf8mb4', $connection->getParams()['charset']); } } @@ -172,7 +92,7 @@ class FakeDriver implements Driver * * @link https://github.com/doctrine/DoctrineBundle/issues/673 */ - public function getDatabasePlatform() + public function getDatabasePlatform(): AbstractPlatform { if (self::$exception !== null) { throw self::$exception; @@ -184,28 +104,45 @@ public function getDatabasePlatform() // ----- below this line follow only dummy methods to satisfy the interface requirements ---- /** - * @param mixed[] $params + * @param mixed[] $params * @param string|null $username * @param string|null $password - * @param mixed[] $driverOptions + * @param mixed[] $driverOptions */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params, $username = null, $password = null, array $driverOptions = []): void { throw new Exception('not implemented'); } - public function getSchemaManager(Connection $conn) + public function getSchemaManager(Connection $conn): void { throw new Exception('not implemented'); } - public function getName() + public function getName(): string { return 'FakeDriver'; } - public function getDatabase(Connection $conn) + public function getDatabase(Connection $conn): string { return 'fake_db'; } } + +class FakeConnection extends Connection +{ + /** @var int */ + public static $creationCount = 0; + + public function __construct( + array $params, + FakeDriver $driver, + ?Configuration $config = null, + ?EventManager $eventManager = null + ) { + ++self::$creationCount; + + parent::__construct($params, $driver, $config, $eventManager); + } +} From d05284d99102c1726c68efc444e2dd6fabf976a0 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:16:28 +0200 Subject: [PATCH 27/36] fix CS --- src/ConnectionFactory.php | 22 ++++++++++---------- tests/ConnectionFactoryTest.php | 36 ++++++++++++++++----------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/ConnectionFactory.php b/src/ConnectionFactory.php index 0e73150..6dace69 100644 --- a/src/ConnectionFactory.php +++ b/src/ConnectionFactory.php @@ -32,7 +32,7 @@ public function __construct(array $typesConfig) /** * Create a connection by name. * - * @param mixed[] $params + * @param mixed[] $params * @param string[]|Type[] $mappingTypes * * @return Connection @@ -43,29 +43,29 @@ public function createConnection( EventManager $eventManager = null, array $mappingTypes = [] ) { - if (!$this->initialized) { + if (! $this->initialized) { $this->initializeTypes(); } - if (!isset($params['pdo']) && !isset($params['charset'])) { + if (! isset($params['pdo']) && ! isset($params['charset'])) { $wrapperClass = null; if (isset($params['wrapperClass'])) { - if (!is_subclass_of($params['wrapperClass'], Connection::class)) { + if (! is_subclass_of($params['wrapperClass'], Connection::class)) { throw DBALException::invalidWrapperClass($params['wrapperClass']); } - $wrapperClass = $params['wrapperClass']; + $wrapperClass = $params['wrapperClass']; $params['wrapperClass'] = null; } $connection = DriverManager::getConnection($params, $config, $eventManager); - $params = $connection->getParams(); - $driver = $connection->getDriver(); + $params = $connection->getParams(); + $driver = $connection->getDriver(); if ($driver instanceof AbstractMySQLDriver) { $params['charset'] = 'utf8mb4'; - if (!isset($params['defaultTableOptions']['collate'])) { + if (! isset($params['defaultTableOptions']['collate'])) { $params['defaultTableOptions']['collate'] = 'utf8mb4_unicode_ci'; } } else { @@ -83,7 +83,7 @@ public function createConnection( $connection = DriverManager::getConnection($params, $config, $eventManager); } - if (!empty($mappingTypes)) { + if (! empty($mappingTypes)) { $platform = $this->getDatabasePlatform($connection); foreach ($mappingTypes as $dbType => $doctrineType) { $platform->registerDoctrineTypeMapping($dbType, $doctrineType); @@ -102,7 +102,7 @@ public function createConnection( * * @throws DBALException */ - private function getDatabasePlatform(Connection $connection): AbstractPlatform + private function getDatabasePlatform(Connection $connection) : AbstractPlatform { try { return $connection->getDatabasePlatform(); @@ -121,7 +121,7 @@ private function getDatabasePlatform(Connection $connection): AbstractPlatform /** * initialize the types */ - private function initializeTypes(): void + private function initializeTypes() : void { foreach ($this->typesConfig as $typeName => $typeConfig) { if (Type::hasType($typeName)) { diff --git a/tests/ConnectionFactoryTest.php b/tests/ConnectionFactoryTest.php index 943bad7..f314a9c 100644 --- a/tests/ConnectionFactoryTest.php +++ b/tests/ConnectionFactoryTest.php @@ -18,15 +18,15 @@ class ConnectionFactoryTest extends TestCase /** * @expectedException \Doctrine\DBAL\DBALException */ - public function testContainer(): void + public function testContainer() : void { - $typesConfig = []; - $factory = new ConnectionFactory($typesConfig); - $params = ['driverClass' => FakeDriver::class]; - $config = null; + $typesConfig = []; + $factory = new ConnectionFactory($typesConfig); + $params = ['driverClass' => FakeDriver::class]; + $config = null; $eventManager = null; $mappingTypes = [0]; - $exception = new DriverException('', $this->createMock(Driver\AbstractDriverException::class)); + $exception = new DriverException('', $this->createMock(Driver\AbstractDriverException::class)); // put the mock into the fake driver FakeDriver::$exception = $exception; @@ -41,26 +41,26 @@ public function testContainer(): void } } - public function testDefaultCharset(): void + public function testDefaultCharset() : void { $factory = new ConnectionFactory([]); - $params = [ + $params = [ 'driverClass' => FakeDriver::class, 'wrapperClass' => FakeConnection::class, ]; $creationCount = FakeConnection::$creationCount; - $connection = $factory->createConnection($params); + $connection = $factory->createConnection($params); $this->assertInstanceof(FakeConnection::class, $connection); $this->assertSame('utf8', $connection->getParams()['charset']); $this->assertSame(1 + $creationCount, FakeConnection::$creationCount); } - public function testDefaultCharsetMySql(): void + public function testDefaultCharsetMySql() : void { $factory = new ConnectionFactory([]); - $params = ['driver' => 'pdo_mysql']; + $params = ['driver' => 'pdo_mysql']; $connection = $factory->createConnection($params); @@ -92,7 +92,7 @@ class FakeDriver implements Driver * * @link https://github.com/doctrine/DoctrineBundle/issues/673 */ - public function getDatabasePlatform(): AbstractPlatform + public function getDatabasePlatform() : AbstractPlatform { if (self::$exception !== null) { throw self::$exception; @@ -104,27 +104,27 @@ public function getDatabasePlatform(): AbstractPlatform // ----- below this line follow only dummy methods to satisfy the interface requirements ---- /** - * @param mixed[] $params + * @param mixed[] $params * @param string|null $username * @param string|null $password - * @param mixed[] $driverOptions + * @param mixed[] $driverOptions */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []): void + public function connect(array $params, $username = null, $password = null, array $driverOptions = []) : void { throw new Exception('not implemented'); } - public function getSchemaManager(Connection $conn): void + public function getSchemaManager(Connection $conn) : void { throw new Exception('not implemented'); } - public function getName(): string + public function getName() : string { return 'FakeDriver'; } - public function getDatabase(Connection $conn): string + public function getDatabase(Connection $conn) : string { return 'fake_db'; } From 479e43f080a655e9dfc3659e92e2f4d8ad2b1b72 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:20:09 +0200 Subject: [PATCH 28/36] remove unused class --- tests/Fixtures/TestCommentedType.php | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 tests/Fixtures/TestCommentedType.php diff --git a/tests/Fixtures/TestCommentedType.php b/tests/Fixtures/TestCommentedType.php deleted file mode 100644 index c7fe856..0000000 --- a/tests/Fixtures/TestCommentedType.php +++ /dev/null @@ -1,24 +0,0 @@ - Date: Mon, 11 May 2020 20:29:50 +0200 Subject: [PATCH 29/36] fix registry + add test --- src/Psr11ConnectionRegistry.php | 9 +++- tests/Psr11ConnectionRegistryTest.php | 72 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/Psr11ConnectionRegistryTest.php diff --git a/src/Psr11ConnectionRegistry.php b/src/Psr11ConnectionRegistry.php index 2837634..487ac69 100644 --- a/src/Psr11ConnectionRegistry.php +++ b/src/Psr11ConnectionRegistry.php @@ -3,6 +3,7 @@ namespace Doctrine\Bundle\DBALBundle; use Doctrine\DBAL\Connection; +use InvalidArgumentException; use Psr\Container\ContainerInterface; class Psr11ConnectionRegistry implements ConnectionRegistry @@ -39,7 +40,13 @@ public function getDefaultConnectionName() : string */ public function getConnection(?string $name = null) : Connection { - return $this->container->get($name ?? $this->defaultConnectionName); + $name = $name ?? $this->defaultConnectionName; + + if (! $this->container->has($name)) { + throw new InvalidArgumentException(sprintf('Connection with name "%s" does not exist.', $name)); + } + + return $this->container->get($name); } /** diff --git a/tests/Psr11ConnectionRegistryTest.php b/tests/Psr11ConnectionRegistryTest.php new file mode 100644 index 0000000..a6a5220 --- /dev/null +++ b/tests/Psr11ConnectionRegistryTest.php @@ -0,0 +1,72 @@ + */ + private $connections; + + protected function setUp() : void + { + /** @var Connection&Stub $fooConnection */ + $fooConnection = $this->createStub(Connection::class); + /** @var Connection&Stub $barConnection */ + $barConnection = $this->createStub(Connection::class); + + $this->connections = [ + 'foo' => $fooConnection, + 'bar' => $barConnection, + ]; + + $container = new Container(); + $container->set('foo', $fooConnection); + $container->set('bar', $barConnection); + + $this->registry = new Psr11ConnectionRegistry($container, 'bar', array_keys($this->connections)); + } + + public function testGetDefaultConnection() : void + { + $this->assertSame($this->connections['bar'], $this->registry->getConnection()); + } + + public function testGetConnectionByName() : void + { + $this->assertSame($this->connections['foo'], $this->registry->getConnection('foo')); + $this->assertSame($this->connections['bar'], $this->registry->getConnection('bar')); + } + + public function testGetNotExistentConnection() : void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Connection with name "something" does not exist.'); + $this->registry->getConnection('something'); + } + + public function testGetDefaultConnectionName() : void + { + $this->assertSame('bar', $this->registry->getDefaultConnectionName()); + } + + public function getGetConnections() : void + { + $this->assertSame($this->connections, $this->registry->getConnections()); + } + + public function testGetConnectionNames() : void + { + $this->assertSame(array_keys($this->connections), $this->registry->getConnectionNames()); + } +} From 4ccb2265908ffd93bbede782e244962cde028e7f Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:33:00 +0200 Subject: [PATCH 30/36] drop support for phpunit 7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 50f31fe..54f8150 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^7.5 || ^8.5", + "phpunit/phpunit": "^8.5", "symfony/phpunit-bridge": "^5.0", "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", "symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0", From 1c46aa54eccd12f23149598c984694ca8521bd50 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:46:22 +0200 Subject: [PATCH 31/36] fix phpunit 7 compat --- composer.json | 2 +- tests/Psr11ConnectionRegistryTest.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 54f8150..50f31fe 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^7.5 || ^8.5", "symfony/phpunit-bridge": "^5.0", "symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0", "symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0", diff --git a/tests/Psr11ConnectionRegistryTest.php b/tests/Psr11ConnectionRegistryTest.php index a6a5220..2d8b039 100644 --- a/tests/Psr11ConnectionRegistryTest.php +++ b/tests/Psr11ConnectionRegistryTest.php @@ -6,7 +6,7 @@ use Doctrine\Bundle\DBALBundle\Psr11ConnectionRegistry; use Doctrine\DBAL\Connection; use InvalidArgumentException; -use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Container; @@ -15,15 +15,15 @@ class Psr11ConnectionRegistryTest extends TestCase /** @var ConnectionRegistry */ private $registry; - /** @var array */ + /** @var array */ private $connections; protected function setUp() : void { - /** @var Connection&Stub $fooConnection */ - $fooConnection = $this->createStub(Connection::class); - /** @var Connection&Stub $barConnection */ - $barConnection = $this->createStub(Connection::class); + /** @var Connection&MockObject $fooConnection */ + $fooConnection = $this->createMock(Connection::class); + /** @var Connection&MockObject $barConnection */ + $barConnection = $this->createMock(Connection::class); $this->connections = [ 'foo' => $fooConnection, From 4d595a3490c3e4f54574bfad8ca4306f32cf5106 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:50:35 +0200 Subject: [PATCH 32/36] fix phpunit warnings --- tests/ConnectionFactoryTest.php | 6 +++--- tests/ProfilerTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ConnectionFactoryTest.php b/tests/ConnectionFactoryTest.php index f314a9c..25f4b80 100644 --- a/tests/ConnectionFactoryTest.php +++ b/tests/ConnectionFactoryTest.php @@ -6,6 +6,7 @@ use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\AbstractPlatform; @@ -15,11 +16,10 @@ class ConnectionFactoryTest extends TestCase { - /** - * @expectedException \Doctrine\DBAL\DBALException - */ public function testContainer() : void { + $this->expectException(DBALException::class); + $typesConfig = []; $factory = new ConnectionFactory($typesConfig); $params = ['driverClass' => FakeDriver::class]; diff --git a/tests/ProfilerTest.php b/tests/ProfilerTest.php index 8e6d801..a180afa 100644 --- a/tests/ProfilerTest.php +++ b/tests/ProfilerTest.php @@ -87,6 +87,6 @@ public function testRender() ]); $output = str_replace(["\e[37m", "\e[0m", "\e[32;1m", "\e[34;1m"], '', $output); - $this->assertContains("SELECT * FROM foo WHERE bar IN ('foo', 'bar');", $output); + $this->assertStringContainsString("SELECT * FROM foo WHERE bar IN ('foo', 'bar');", $output); } } From 5436c29ca0f8a6a46220143bd5647d4a4e854540 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Mon, 11 May 2020 20:54:48 +0200 Subject: [PATCH 33/36] fix CS --- tests/ConnectionFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ConnectionFactoryTest.php b/tests/ConnectionFactoryTest.php index 25f4b80..99a0bbb 100644 --- a/tests/ConnectionFactoryTest.php +++ b/tests/ConnectionFactoryTest.php @@ -19,7 +19,7 @@ class ConnectionFactoryTest extends TestCase public function testContainer() : void { $this->expectException(DBALException::class); - + $typesConfig = []; $factory = new ConnectionFactory($typesConfig); $params = ['driverClass' => FakeDriver::class]; From 4df8eea91ec0b22af1b8dd70e134ed407a18ba84 Mon Sep 17 00:00:00 2001 From: David Maicher Date: Wed, 13 May 2020 13:08:44 +0200 Subject: [PATCH 34/36] fix deprecations for doctrine/dbal >= 2.11 --- .../Proxy/ConnectionProviderAdapter.php | 30 +++++++++++++++++++ src/Command/Proxy/RunSqlCommand.php | 17 +++++++++-- .../DoctrineDBALExtension.php | 11 +++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/Command/Proxy/ConnectionProviderAdapter.php diff --git a/src/Command/Proxy/ConnectionProviderAdapter.php b/src/Command/Proxy/ConnectionProviderAdapter.php new file mode 100644 index 0000000..8d04d3a --- /dev/null +++ b/src/Command/Proxy/ConnectionProviderAdapter.php @@ -0,0 +1,30 @@ +registry = $registry; + } + + public function getDefaultConnection(): Connection + { + return $this->registry->getConnection(); + } + + public function getConnection(string $name): Connection + { + return $this->registry->getConnection($name); + } +} diff --git a/src/Command/Proxy/RunSqlCommand.php b/src/Command/Proxy/RunSqlCommand.php index 030b51b..a84c5eb 100644 --- a/src/Command/Proxy/RunSqlCommand.php +++ b/src/Command/Proxy/RunSqlCommand.php @@ -12,8 +12,14 @@ /** * Execute a SQL query and output the results. */ -class RunSqlCommand extends DoctrineRunSqlCommand +final class RunSqlCommand extends DoctrineRunSqlCommand { + // no type-hint for BC compatibility with dbal < 2.11 + public function __construct($connectionProvider = null) + { + parent::__construct($connectionProvider); + } + /** * {@inheritDoc} */ @@ -23,14 +29,18 @@ protected function configure() $this ->setName('doctrine:query:sql') - ->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command') ->setHelp(<<%command.name% command executes the given SQL query and outputs the results: php %command.full_name% "SELECT * FROM users" EOT - ); + ); + + if (!$this->getDefinition()->hasOption('connection')) { + // BC compatibility with dbal < 2.11 + $this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command'); + } } /** @@ -38,6 +48,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { + // BC compatibility with dbal < 2.11 $this->setConnectionHelper($this->getApplication(), $input->getOption('connection')); return parent::execute($input, $output); diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index 6a2fa81..ede51b9 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -2,7 +2,9 @@ namespace Doctrine\Bundle\DBALBundle\DependencyInjection; +use Doctrine\Bundle\DBALBundle\Command\Proxy\ConnectionProviderAdapter; use Doctrine\Bundle\DBALBundle\DBAL\SchemaFilter\RegexSchemaAssetFilter; +use Doctrine\DBAL\Tools\Console\ConnectionProvider; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -73,6 +75,15 @@ private function dbalLoad(array $config, ContainerBuilder $container) $defaultConnection, array_keys($connections), ]); + + if (class_exists(ConnectionProvider::class)) { + // dbal >= 2.11 + $container->register('doctrine.dbal.cli.connection_provider', ConnectionProviderAdapter::class) + ->setArguments([new Reference('doctrine.dbal.connection_registry')]); + $container->findDefinition('doctrine.query_sql_command')->setArguments([ + new Reference('doctrine.dbal.cli.connection_provider') + ]); + } } /** From 8d511943595e6ea13b70e33f5992be0b39a8375f Mon Sep 17 00:00:00 2001 From: David Maicher Date: Wed, 13 May 2020 13:14:09 +0200 Subject: [PATCH 35/36] fix CS --- src/Command/Proxy/ConnectionProviderAdapter.php | 8 +++----- src/Command/Proxy/RunSqlCommand.php | 12 ++++++++---- src/DependencyInjection/DoctrineDBALExtension.php | 14 +++++++------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Command/Proxy/ConnectionProviderAdapter.php b/src/Command/Proxy/ConnectionProviderAdapter.php index 8d04d3a..41fdb0d 100644 --- a/src/Command/Proxy/ConnectionProviderAdapter.php +++ b/src/Command/Proxy/ConnectionProviderAdapter.php @@ -8,9 +8,7 @@ final class ConnectionProviderAdapter implements ConnectionProvider { - /** - * @var ConnectionRegistry - */ + /** @var ConnectionRegistry */ private $registry; public function __construct(ConnectionRegistry $registry) @@ -18,12 +16,12 @@ public function __construct(ConnectionRegistry $registry) $this->registry = $registry; } - public function getDefaultConnection(): Connection + public function getDefaultConnection() : Connection { return $this->registry->getConnection(); } - public function getConnection(string $name): Connection + public function getConnection(string $name) : Connection { return $this->registry->getConnection($name); } diff --git a/src/Command/Proxy/RunSqlCommand.php b/src/Command/Proxy/RunSqlCommand.php index a84c5eb..0649a02 100644 --- a/src/Command/Proxy/RunSqlCommand.php +++ b/src/Command/Proxy/RunSqlCommand.php @@ -14,7 +14,9 @@ */ final class RunSqlCommand extends DoctrineRunSqlCommand { - // no type-hint for BC compatibility with dbal < 2.11 + /** + * no type-hint for BC compatibility with dbal < 2.11 + */ public function __construct($connectionProvider = null) { parent::__construct($connectionProvider); @@ -37,10 +39,12 @@ protected function configure() EOT ); - if (!$this->getDefinition()->hasOption('connection')) { - // BC compatibility with dbal < 2.11 - $this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command'); + if ($this->getDefinition()->hasOption('connection')) { + return; } + + // BC compatibility with dbal < 2.11 + $this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command'); } /** diff --git a/src/DependencyInjection/DoctrineDBALExtension.php b/src/DependencyInjection/DoctrineDBALExtension.php index ede51b9..a943807 100644 --- a/src/DependencyInjection/DoctrineDBALExtension.php +++ b/src/DependencyInjection/DoctrineDBALExtension.php @@ -76,14 +76,14 @@ private function dbalLoad(array $config, ContainerBuilder $container) array_keys($connections), ]); - if (class_exists(ConnectionProvider::class)) { - // dbal >= 2.11 - $container->register('doctrine.dbal.cli.connection_provider', ConnectionProviderAdapter::class) - ->setArguments([new Reference('doctrine.dbal.connection_registry')]); - $container->findDefinition('doctrine.query_sql_command')->setArguments([ - new Reference('doctrine.dbal.cli.connection_provider') - ]); + if (! class_exists(ConnectionProvider::class)) { + return; } + + // dbal >= 2.11 + $container->register('doctrine.dbal.cli.connection_provider', ConnectionProviderAdapter::class) + ->setArguments([new Reference('doctrine.dbal.connection_registry')]); + $container->findDefinition('doctrine.query_sql_command')->setArguments([new Reference('doctrine.dbal.cli.connection_provider')]); } /** From a7361d8626b28cfd35d75cda4af63dc9005a0acc Mon Sep 17 00:00:00 2001 From: David Maicher Date: Wed, 13 May 2020 13:15:04 +0200 Subject: [PATCH 36/36] fix comments --- src/Command/Proxy/RunSqlCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Command/Proxy/RunSqlCommand.php b/src/Command/Proxy/RunSqlCommand.php index 0649a02..7c3c7cd 100644 --- a/src/Command/Proxy/RunSqlCommand.php +++ b/src/Command/Proxy/RunSqlCommand.php @@ -15,7 +15,7 @@ final class RunSqlCommand extends DoctrineRunSqlCommand { /** - * no type-hint for BC compatibility with dbal < 2.11 + * no type-hint for BC with dbal < 2.11 */ public function __construct($connectionProvider = null) { @@ -43,7 +43,7 @@ protected function configure() return; } - // BC compatibility with dbal < 2.11 + // BC with dbal < 2.11 $this->addOption('connection', null, InputOption::VALUE_OPTIONAL, 'The connection to use for this command'); } @@ -52,7 +52,7 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - // BC compatibility with dbal < 2.11 + // BC with dbal < 2.11 $this->setConnectionHelper($this->getApplication(), $input->getOption('connection')); return parent::execute($input, $output);