From 578dd8c45f11c86ac6cd35bc046bad515bf61957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jos=C3=A9=20Garc=C3=ADa=20Navarro?= Date: Tue, 5 Jul 2022 15:17:51 +0200 Subject: [PATCH 1/2] refactor: sql factory for mysql and pgsql --- adapters/Doctrine/DBAL/ConnectionFactory.php | 74 ------------ .../DBAL/Factory/ConnectionFactory.php | 41 +++++++ .../DBAL/Factory/SqlConnectionFactory.php | 111 ++++++++++++++++++ composer.json | 4 +- 4 files changed, 155 insertions(+), 75 deletions(-) delete mode 100644 adapters/Doctrine/DBAL/ConnectionFactory.php create mode 100644 adapters/Doctrine/DBAL/Factory/ConnectionFactory.php create mode 100644 adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php diff --git a/adapters/Doctrine/DBAL/ConnectionFactory.php b/adapters/Doctrine/DBAL/ConnectionFactory.php deleted file mode 100644 index 314d93a..0000000 --- a/adapters/Doctrine/DBAL/ConnectionFactory.php +++ /dev/null @@ -1,74 +0,0 @@ -getDbNameFromEnv($params['dbname']); - } else { - $dbName = $this->getDbNameFromEnv($params['master']['dbname']); - } - - if ('pdo_sqlite' === $params['driver']) { - if (isset($params['path'])) { - $params['path'] = str_replace('__DBNAME__', $dbName, $params['path']); - } - - if (isset($params['master']['path'])) { - $params['master']['path'] = str_replace('__DBNAME__', $dbName, $params['master']['path']); - } - - if (!empty($params['slaves'])) { - foreach ($params['slaves'] as &$slave) { - $slave['path'] = str_replace('__DBNAME__', $dbName, $slave['path']); - } - } - } else { - $params['dbname'] = $dbName; - } - - return parent::createConnection($params, $config, $eventManager, $mappingTypes); - } - - protected function getDbNameFromEnv($dbName) - { - if ($this->issetDbNameEnvValue()) { - return $dbName.'_'.$this->getDbNameEnvValue(); - } - - return $dbName; - } - - protected function issetDbNameEnvValue() - { - $dbName = $this->getDbNameEnvValue(); - - return !empty($dbName); - } - - protected function getDbNameEnvValue() - { - return getenv(EnvCommandCreator::ENV_TEST_CHANNEL_READABLE); - } -} diff --git a/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php b/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php new file mode 100644 index 0000000..7714789 --- /dev/null +++ b/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php @@ -0,0 +1,41 @@ + '/(?P^(mysql|postgresql))\:\/\/(?P.+)?\:(?P.+)' . + '?\@(?P.+)\:(?P\d{4})\/(?P.+)\?(?P[a-zA-Z].+=.+&?)/', + 'sqlite' => '/(?P^(sqlite))\:\/\/(\%kernel\..+\%|.+)(?P.*\.db$)/' + ]; + + protected function getDbNameFromEnv($dbName): string + { + if ($this->issetDbNameEnvValue()) { + return $dbName.'_'.$this->getDbNameEnvValue(); + } + + return $dbName; + } + + protected function issetDbNameEnvValue(): bool + { + $dbName = $this->getDbNameEnvValue(); + + return !empty($dbName); + } + + protected function getDbNameEnvValue(): ?string + { + $dbName = getenv(EnvCommandCreator::ENV_TEST_CHANNEL_READABLE); + + return $dbName ?? null; + } +} diff --git a/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php b/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php new file mode 100644 index 0000000..12a86d2 --- /dev/null +++ b/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php @@ -0,0 +1,111 @@ + 3306, + 'postgresql' => 5432 + ]; + + private const PARAMS_LIST = [ + 'default' => [ + 'driver', + 'host', + 'port', + 'dbname', + 'user', + 'password', + 'charset', + ], + 'url' => ['url'] + ]; + + /** + * @throws Exception + */ + public function createConnection( + array $params, + Configuration $config = null, + EventManager $eventManager = null, + array $mappingTypes = [] + ): Connection + { + if ($this->isDsn($params)) { + $params['url'] = $this->getCompiledDsn($params); + $params = $this->prepareParamsByConnectionType($params, 'url'); + return parent::createConnection($params, $config, $eventManager, $mappingTypes); + } + + if($this->isMasterSlaveOrUnique($params)) { + $originalDatabaseName = $params['master']['dbname'] ?? $params['dbname'] ?? null; + $params['dbname'] = $originalDatabaseName ? $this->getDbNameFromEnv($originalDatabaseName) : null; + $params = $this->prepareParamsByConnectionType($params); + return parent::createConnection($params, $config, $eventManager, $mappingTypes); + } + + return parent::createConnection($params, $config, $eventManager, $mappingTypes); + } + + private function isDsn(array $params): bool + { + return !empty($params['url']) && preg_match_all(self::CONNECTION_STRING_PATTERNS['sql'], $params['url']); + } + + private function isMasterSlaveOrUnique(array $params): bool + { + return isset($params['master']['dbname']) || isset($params['dbname']); + } + + private function getCompiledDsn(array $params = []): string + { + $compiledDsn = preg_replace( + self::CONNECTION_STRING_PATTERNS['sql'], + self::DSN_REPLACE, + $params['url'] + ); + + if(!is_string($compiledDsn)) { + throw new InvalidArgumentException( + sprintf('The provided DSN isn`t valid <%s>', $params['url']) + ); + } + + [$protocol, $databaseName, $port] = $this->getInfoFromDsn($params); + + return str_replace( + [self::DATABASE_NAME_PLACEHOLDER, self::DATABASE_PORT_PLACEHOLDER], + [$this->getDbNameFromEnv($databaseName), $port ?: self::DEFAULT_PORTS[$protocol]], + $compiledDsn + ); + } + + private function getInfoFromDsn(array $params): array + { + $dsn = preg_match_all(self::CONNECTION_STRING_PATTERNS['sql'], $params['url'] ?? '', $dsnPieces); + + if($dsn && !empty($dsnPieces['database']) && !empty($dsnPieces['protocol'])) { + return [$dsnPieces['protocol'], $dsnPieces['database'], $dsnPieces['port'] ?? null]; + } + + throw new InvalidArgumentException( + sprintf('Unable to get database name from DSN <%s>', $params['url'] ?? '') + ); + } + + private function prepareParamsByConnectionType(array $params, string $connectionType = 'default'): array + { + $paramsList = $connectionType === 'default' ? self::PARAMS_LIST['url'] : self::PARAMS_LIST[$connectionType]; + + return array_diff_key($params,array_flip($paramsList)); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 69d1144..13d5e50 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,9 @@ "require-dev": { "behat/behat": "^3.6", "phpstan/phpstan": "^0.12.99", - "phpstan/phpstan-phpunit": "^0.12.22" + "phpstan/phpstan-phpunit": "^0.12.22", + "phpunit/phpunit": "^9.5", + "doctrine/doctrine-bundle": "^2.7" }, "config": { "bin-dir": "bin/" From 4ef3008fffff47cc9ed9d241d633d0de979d266b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jos=C3=A9=20Garcia=20Navarro?= Date: Tue, 5 Jul 2022 23:36:01 +0200 Subject: [PATCH 2/2] refactor: change approach for DSN build on database test scenario --- .../DBAL/Factory/ConnectionFactory.php | 15 ++- .../DBAL/Factory/SqlConnectionFactory.php | 99 +++++++++++-------- 2 files changed, 64 insertions(+), 50 deletions(-) diff --git a/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php b/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php index 7714789..3b8ff40 100644 --- a/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php +++ b/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php @@ -7,19 +7,18 @@ class ConnectionFactory extends BaseConnectionFactory { - protected const DATABASE_NAME_PLACEHOLDER = '__DBNAME__'; - protected const DATABASE_PORT_PLACEHOLDER = '__DBPORT__'; - protected const CONNECTION_STRING_PATTERNS = [ - 'sql' => '/(?P^(mysql|postgresql))\:\/\/(?P.+)?\:(?P.+)' . + 'sql' => '/(?P^(mysql|postgresql))\:\/\/(?P.+)?\:(?P.+)'. '?\@(?P.+)\:(?P\d{4})\/(?P.+)\?(?P[a-zA-Z].+=.+&?)/', - 'sqlite' => '/(?P^(sqlite))\:\/\/(\%kernel\..+\%|.+)(?P.*\.db$)/' + 'sqlite' => '/(?P^(sqlite))\:\/\/(\%kernel\..+\%|.+)(?P.*\.db$)/', ]; - protected function getDbNameFromEnv($dbName): string + protected function getDbNameFromEnv(string $dbName): string { if ($this->issetDbNameEnvValue()) { - return $dbName.'_'.$this->getDbNameEnvValue(); + return preg_match('/\d$/m', $dbName) + ? $dbName.'_test' + : $dbName.'_'.$this->getDbNameEnvValue(); } return $dbName; @@ -36,6 +35,6 @@ protected function getDbNameEnvValue(): ?string { $dbName = getenv(EnvCommandCreator::ENV_TEST_CHANNEL_READABLE); - return $dbName ?? null; + return is_string($dbName) ? $dbName : null; } } diff --git a/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php b/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php index 12a86d2..abec768 100644 --- a/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php +++ b/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php @@ -10,11 +10,11 @@ class SqlConnectionFactory extends ConnectionFactory { - private const DSN_REPLACE = '$protocol://$user:$password@$host:__DBPORT__/$database__DBNAME__?$parameters'; + private const DSN_TEMPLATE = '{{PROTOCOL}}://{{USER}}:{{PASSWORD}}@{{HOST}}:{{PORT}}/{{DB}}?{{PARAMS}}'; private const DEFAULT_PORTS = [ - 'mysql' => 3306, - 'postgresql' => 5432 + 'mysql' => 3306, + 'postgresql' => 5432, ]; private const PARAMS_LIST = [ @@ -22,12 +22,11 @@ class SqlConnectionFactory extends ConnectionFactory 'driver', 'host', 'port', - 'dbname', 'user', 'password', 'charset', ], - 'url' => ['url'] + 'dsn' => ['url', 'dbname'], ]; /** @@ -38,18 +37,23 @@ public function createConnection( Configuration $config = null, EventManager $eventManager = null, array $mappingTypes = [] - ): Connection - { - if ($this->isDsn($params)) { - $params['url'] = $this->getCompiledDsn($params); - $params = $this->prepareParamsByConnectionType($params, 'url'); + ): Connection { + + if ($this->isMasterSlaveOrUnique($params)) { + $originalDatabaseName = $params['master']['dbname'] ?? $params['dbname'] ?? null; + $params['dbname'] = $originalDatabaseName ? $this->getDbNameFromEnv($originalDatabaseName) : null; + $params = $this->prepareParamsByConnectionType($params); + return parent::createConnection($params, $config, $eventManager, $mappingTypes); } - if($this->isMasterSlaveOrUnique($params)) { - $originalDatabaseName = $params['master']['dbname'] ?? $params['dbname'] ?? null; - $params['dbname'] = $originalDatabaseName ? $this->getDbNameFromEnv($originalDatabaseName) : null; - $params = $this->prepareParamsByConnectionType($params); + if ($this->isDsn($params)) { + [$dsn, $databaseName] = $this->getCompiledDsn($params); + + $params['url'] = $dsn; + $params['dbname'] = $this->getDbNameFromEnv($databaseName); + $params = $this->prepareParamsByConnectionType($params); + return parent::createConnection($params, $config, $eventManager, $mappingTypes); } @@ -66,46 +70,57 @@ private function isMasterSlaveOrUnique(array $params): bool return isset($params['master']['dbname']) || isset($params['dbname']); } - private function getCompiledDsn(array $params = []): string + private function getCompiledDsn(array $params = []): array { - $compiledDsn = preg_replace( - self::CONNECTION_STRING_PATTERNS['sql'], - self::DSN_REPLACE, - $params['url'] + [$protocol, $user, $password, $databaseName, $host, $port, $parameters] = $this->getInfoFromDsn($params); + + $dsn = str_replace( + ['{{PROTOCOL}}', '{{USER}}', '{{PASSWORD}}', '{{HOST}}', ':{{PORT}}', '{{DB}}', '?{{PARAMS}}'], + [ + $protocol, + $user, + $password, + $host, + $port ? ":$port" : ':'.self::DEFAULT_PORTS[$protocol], + $databaseName, + $parameters ? "?$parameters" : '', + ], + self::DSN_TEMPLATE ); - if(!is_string($compiledDsn)) { - throw new InvalidArgumentException( - sprintf('The provided DSN isn`t valid <%s>', $params['url']) - ); - } - - [$protocol, $databaseName, $port] = $this->getInfoFromDsn($params); - - return str_replace( - [self::DATABASE_NAME_PLACEHOLDER, self::DATABASE_PORT_PLACEHOLDER], - [$this->getDbNameFromEnv($databaseName), $port ?: self::DEFAULT_PORTS[$protocol]], - $compiledDsn - ); + return [$dsn, $databaseName]; } private function getInfoFromDsn(array $params): array { - $dsn = preg_match_all(self::CONNECTION_STRING_PATTERNS['sql'], $params['url'] ?? '', $dsnPieces); - - if($dsn && !empty($dsnPieces['database']) && !empty($dsnPieces['protocol'])) { - return [$dsnPieces['protocol'], $dsnPieces['database'], $dsnPieces['port'] ?? null]; + $dsn = preg_match(self::CONNECTION_STRING_PATTERNS['sql'], $params['url'] ?? '', $dsnPieces); + + $isValidDsn = $dsn + && !empty($dsnPieces['protocol']) + && !empty($dsnPieces['user']) + && !empty($dsnPieces['password']) + && !empty($dsnPieces['database']) + && !empty($dsnPieces['host']); + + if ($isValidDsn) { + return [ + $dsnPieces['protocol'], + $dsnPieces['user'], + $dsnPieces['password'], + $dsnPieces['database'], + $dsnPieces['host'], + $dsnPieces['port'] ?? null, + $dsnPieces['parameters'] ?? null, + ]; } - throw new InvalidArgumentException( - sprintf('Unable to get database name from DSN <%s>', $params['url'] ?? '') - ); + throw new InvalidArgumentException(sprintf('Unable to get database name from DSN <%s>', $params['url'] ?? '')); } private function prepareParamsByConnectionType(array $params, string $connectionType = 'default'): array { - $paramsList = $connectionType === 'default' ? self::PARAMS_LIST['url'] : self::PARAMS_LIST[$connectionType]; + $paramsList = self::PARAMS_LIST[$connectionType]; - return array_diff_key($params,array_flip($paramsList)); + return array_diff_key($params, array_flip($paramsList)); } -} \ No newline at end of file +}