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..3b8ff40 --- /dev/null +++ b/adapters/Doctrine/DBAL/Factory/ConnectionFactory.php @@ -0,0 +1,40 @@ + '/(?P^(mysql|postgresql))\:\/\/(?P.+)?\:(?P.+)'. + '?\@(?P.+)\:(?P\d{4})\/(?P.+)\?(?P[a-zA-Z].+=.+&?)/', + 'sqlite' => '/(?P^(sqlite))\:\/\/(\%kernel\..+\%|.+)(?P.*\.db$)/', + ]; + + protected function getDbNameFromEnv(string $dbName): string + { + if ($this->issetDbNameEnvValue()) { + return preg_match('/\d$/m', $dbName) + ? $dbName.'_test' + : $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 is_string($dbName) ? $dbName : null; + } +} diff --git a/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php b/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php new file mode 100644 index 0000000..abec768 --- /dev/null +++ b/adapters/Doctrine/DBAL/Factory/SqlConnectionFactory.php @@ -0,0 +1,126 @@ + 3306, + 'postgresql' => 5432, + ]; + + private const PARAMS_LIST = [ + 'default' => [ + 'driver', + 'host', + 'port', + 'user', + 'password', + 'charset', + ], + 'dsn' => ['url', 'dbname'], + ]; + + /** + * @throws Exception + */ + public function createConnection( + array $params, + Configuration $config = null, + EventManager $eventManager = null, + array $mappingTypes = [] + ): 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->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); + } + + 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 = []): array + { + [$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 + ); + + return [$dsn, $databaseName]; + } + + private function getInfoFromDsn(array $params): array + { + $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'] ?? '')); + } + + private function prepareParamsByConnectionType(array $params, string $connectionType = 'default'): array + { + $paramsList = self::PARAMS_LIST[$connectionType]; + + return array_diff_key($params, array_flip($paramsList)); + } +} 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/"