From 0ce34eeadb9f68af6a91d35f7230454af1f9939c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zigmas=20Satkevi=C4=8Dius?= Date: Thu, 2 Mar 2017 15:01:08 +0200 Subject: [PATCH 1/3] Added master/slave connection support. --- .../Connections/MasterSlaveConnection.php | 115 ++++++++ src/EntityManagerFactory.php | 45 +++ .../Connections/MasterSlaveConnectionTest.php | 259 ++++++++++++++++++ tests/EntityManagerFactoryTest.php | 173 +++++++++++- 4 files changed, 580 insertions(+), 12 deletions(-) create mode 100644 src/Configuration/Connections/MasterSlaveConnection.php create mode 100644 tests/Configuration/Connections/MasterSlaveConnectionTest.php diff --git a/src/Configuration/Connections/MasterSlaveConnection.php b/src/Configuration/Connections/MasterSlaveConnection.php new file mode 100644 index 00000000..88aa823b --- /dev/null +++ b/src/Configuration/Connections/MasterSlaveConnection.php @@ -0,0 +1,115 @@ +resolvedBaseSettings = $resolvedBaseSettings; + } + + /** + * {@inheritdoc} + */ + public function resolve(array $settings = []) + { + $driver = $this->resolvedBaseSettings['driver']; + + return [ + 'wrapperClass' => MasterSlaveDoctrineWrapper::class, + 'driver' => $driver, + 'master' => $this->getConnectionData(isset($settings['write']) ? $settings['write'] : [], $driver), + 'slaves' => $this->getSlavesConfig($settings['read'], $driver), + ]; + } + + /** + * Returns config for slave connections. + * + * @param array $slaves + * @param string $driver + * + * @return array + */ + public function getSlavesConfig(array $slaves, $driver) + { + $handledSlaves = []; + foreach ($slaves as $slave) { + $handledSlaves[] = $this->getConnectionData($slave, $driver); + } + + return $handledSlaves; + } + + /** + * Returns single connection (slave or master) config. + * + * @param array $connection + * @param string $driver + * + * @return array + */ + private function getConnectionData(array $connection, $driver) + { + $connection = $this->replaceKeyIfExists($connection, 'database', $driver === 'pdo_sqlite' ? 'path' : 'dbname'); + $connection = $this->replaceKeyIfExists($connection, 'username', 'user'); + + return array_merge($this->getFilteredConfig(), $connection); + } + + /** + * Returns filtered configuration to use in slaves/masters. + * + * @return array + */ + private function getFilteredConfig() + { + return array_diff_key($this->resolvedBaseSettings, array_flip($this->masterSlaveConfigIgnored)); + } + + /** + * Replaces key in array if it exists. + * + * @param array $array + * @param string $oldKey + * @param string $newKey + * + * @return array + */ + private function replaceKeyIfExists(array $array, $oldKey, $newKey) + { + if (!isset($array[$oldKey])) { + return $array; + } + + $array[$newKey] = $array[$oldKey]; + unset($array[$oldKey]); + + return $array; + } +} diff --git a/src/EntityManagerFactory.php b/src/EntityManagerFactory.php index 1f069ce2..f45cc407 100644 --- a/src/EntityManagerFactory.php +++ b/src/EntityManagerFactory.php @@ -14,6 +14,7 @@ use InvalidArgumentException; use LaravelDoctrine\ORM\Configuration\Cache\CacheManager; use LaravelDoctrine\ORM\Configuration\Connections\ConnectionManager; +use LaravelDoctrine\ORM\Configuration\Connections\MasterSlaveConnection; use LaravelDoctrine\ORM\Configuration\LaravelNamingStrategy; use LaravelDoctrine\ORM\Configuration\MetaData\MetaData; use LaravelDoctrine\ORM\Configuration\MetaData\MetaDataManager; @@ -109,6 +110,10 @@ public function create(array $settings = []) $driver ); + if ($this->isMasterSlaveConfigured($driver) && $this->hasValidMasterSlaveConfig($driver)) { + $connection = (new MasterSlaveConnection($this->config, $connection))->resolve($driver); + } + $this->setNamingStrategy($settings, $configuration); $this->setCustomFunctions($configuration); $this->setCacheSettings($configuration); @@ -440,4 +445,44 @@ protected function registerMappingTypes(array $settings = [], EntityManagerInter $manager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping($dbType, $doctrineType); } } + + /** + * Check if master slave connection was being configured. + * + * @param array $driverConfig + * + * @return bool + */ + private function isMasterSlaveConfigured(array $driverConfig) + { + // Setting read is mandatory for master/slave configuration. Setting write is optional. + // But if write was set and read wasn't, it means configuration is incorrect and we must inform the user. + return isset($driverConfig['read']) || isset($driverConfig['write']); + } + + /** + * Check if slave configuration is valid. + * + * @param array $driverConfig + * + * @return bool + */ + private function hasValidMasterSlaveConfig(array $driverConfig) + { + if (!isset($driverConfig['read'])) { + throw new \InvalidArgumentException("Parameter 'read' must be set for read/write config."); + } + + $slaves = $driverConfig['read']; + + if (!is_array($slaves) || in_array(false, array_map('is_array', $slaves))) { + throw new \InvalidArgumentException("Parameter 'read' must be an array containing multiple arrays."); + } + + if (($key = array_search(0, array_map('count', $slaves))) && $key !== false) { + throw new \InvalidArgumentException("Parameter 'read' config no. {$key} is empty."); + } + + return true; + } } diff --git a/tests/Configuration/Connections/MasterSlaveConnectionTest.php b/tests/Configuration/Connections/MasterSlaveConnectionTest.php new file mode 100644 index 00000000..c4de0df3 --- /dev/null +++ b/tests/Configuration/Connections/MasterSlaveConnectionTest.php @@ -0,0 +1,259 @@ + 'mysql', + 'host' => 'localhost', + 'port' => '3306', + 'database' => 'test', + 'username' => 'homestead', + 'password' => 'secret', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + 'engine' => null, + 'write' => [ + 'port' => 3307, + 'user' => 'homestead1', + 'password' => 'secret1', + ], + 'read' => [ + [ + 'port' => 3308, + 'database' => 'test2', + ], + [ + 'host' => 'localhost2', + 'port' => 3309 + ], + ], + ]; + + $dummyExpectedConfig = [ + 'wrapperClass' => MasterSlaveDoctrineWrapper::class, + 'driver' => 'pdo_mysql', + 'slaves' => [ + [ + 'host' => 'localhost', + 'user' => 'homestead', + 'password' => 'secret', + 'dbname' => 'test2', + 'port' => '3308', + 'charset' => 'charset', + 'unix_socket' => 'unix_socket', + 'prefix' => 'prefix' + ], + [ + 'host' => 'localhost2', + 'user' => 'homestead', + 'password' => 'secret', + 'dbname' => 'test', + 'port' => '3309', + 'charset' => 'charset', + 'unix_socket' => 'unix_socket', + 'prefix' => 'prefix' + ] + ], + 'master' => [ + 'host' => 'localhost', + 'user' => 'homestead1', + 'password' => 'secret1', + 'dbname' => 'test', + 'port' => '3307', + 'charset' => 'charset', + 'unix_socket' => 'unix_socket', + 'prefix' => 'prefix' + ], + ]; + + // Case #0. Simple valid configuration with mysql base settings. + $resolvedBaseSettings = [ + 'driver' => 'pdo_mysql', + 'host' => 'localhost', + 'dbname' => 'test', + 'user' => 'homestead', + 'password' => 'secret', + 'charset' => 'charset', + 'port' => 'port', + 'unix_socket' => 'unix_socket', + 'prefix' => 'prefix' + ]; + $out[] = [$resolvedBaseSettings, $dummyInputConfig, $dummyExpectedConfig]; + + // Case #1. Configuration is only set in the read/wriet nodes. + $resolvedBaseSettings = [ + 'driver' => 'pdo_mysql', + ]; + + $expectedConfig = [ + 'wrapperClass' => MasterSlaveDoctrineWrapper::class, + 'driver' => 'pdo_mysql', + 'slaves' => [ + [ + 'host' => 'localhost', + 'user' => 'homestead', + 'password' => 'secret', + 'dbname' => 'test2', + 'port' => '3308', + ], + [ + 'host' => 'localhost2', + 'user' => 'homestead', + 'password' => 'secret', + 'dbname' => 'test', + 'port' => '3309', + ] + ], + 'master' => [ + 'host' => 'localhost', + 'user' => 'homestead', + 'password' => 'secret1', + 'dbname' => 'test', + 'port' => '3307', + ], + ]; + + $inputConfig = [ + 'write' => [ + 'port' => 3307, + 'password' => 'secret1', + 'host' => 'localhost', + 'database' => 'test', + 'username' => 'homestead' + ], + 'read' => [ + [ + 'port' => 3308, + 'database' => 'test2', + 'host' => 'localhost', + 'username' => 'homestead', + 'password' => 'secret' + ], + [ + 'host' => 'localhost2', + 'port' => 3309, + 'database' => 'test', + 'username' => 'homestead', + 'password' => 'secret' + ], + ], + ]; + $out[] = [$resolvedBaseSettings, $inputConfig, $expectedConfig]; + + // Case #2. Simple valid configuration with oracle base settings. + $expectedConfigOracle = $expectedConfig; + $expectedConfigOracle['driver'] = 'oci8'; + $expectedConfigOracle['master']['user'] = 'homestead1'; + + $resolvedBaseSettings = [ + 'driver' => 'oci8', + 'host' => 'localhost', + 'dbname' => 'test', + 'user' => 'homestead', + 'password' => 'secret', + 'port' => 'port', + ]; + $out[] = [$resolvedBaseSettings, $dummyInputConfig, $expectedConfigOracle]; + + // Case #3. Simple valid configuration with pgqsql base settings. + $expectedConfigPgsql = $expectedConfig; + $expectedConfigPgsql['driver'] = 'pgsql'; + $expectedConfigPgsql['master']['user'] = 'homestead1'; + $expectedConfigPgsql['master']['sslmode'] = 'sslmode'; + $expectedConfigPgsql['slaves'][0]['sslmode'] = 'sslmode'; + $expectedConfigPgsql['slaves'][1]['sslmode'] = 'sslmode'; + + $resolvedBaseSettings = [ + 'driver' => 'pgsql', + 'host' => 'localhost', + 'dbname' => 'test', + 'user' => 'homestead', + 'password' => 'secret', + 'port' => 'port', + 'sslmode' => 'sslmode', + ]; + $out[] = [$resolvedBaseSettings, $dummyInputConfig, $expectedConfigPgsql]; + + // Case #4. Simple valid configuration with sqlite base settings. + $inputConfigSqlite = $dummyInputConfig; + unset($inputConfigSqlite['read'][0]['database']); + unset($inputConfigSqlite['read'][1]['database']); + unset($inputConfigSqlite['write']['database']); + + $expectedConfigSqlite = [ + 'wrapperClass' => MasterSlaveDoctrineWrapper::class, + 'driver' => 'pdo_sqlite', + 'slaves' => [ + [ + 'user' => 'homestead', + 'password' => 'secret', + 'port' => 3308, + 'path' => ':memory', + 'memory' => true, + ], + [ + 'host' => 'localhost2', + 'user' => 'homestead', + 'password' => 'secret', + 'port' => 3309, + 'path' => ':memory', + 'memory' => true, + ] + ], + 'master' => [ + 'user' => 'homestead1', + 'password' => 'secret1', + 'port' => 3307, + 'memory' => true, + 'path' => ':memory', + ], + ]; + + $resolvedBaseSettings = [ + 'driver' => 'pdo_sqlite', + 'path' => ':memory', + 'user' => 'homestead', + 'password' => 'secret', + 'memory' => true + ]; + $out[] = [$resolvedBaseSettings, $inputConfigSqlite, $expectedConfigSqlite]; + + return $out; + } + + /** + * Check if master slave connection manages configuration well. + * + * @param array $resolvedBaseSettings + * @param array $settings + * @param $expectedOutput + * + * @dataProvider getMasterSlaveConnectionData + */ + public function testMasterSlaveConnection(array $resolvedBaseSettings, array $settings, array $expectedOutput) + { + $this->assertEquals( + $expectedOutput, + (new MasterSlaveConnection(m::mock(Repository::class), $resolvedBaseSettings))->resolve($settings) + ); + } +} diff --git a/tests/EntityManagerFactoryTest.php b/tests/EntityManagerFactoryTest.php index ce7920b3..b011a0cc 100644 --- a/tests/EntityManagerFactoryTest.php +++ b/tests/EntityManagerFactoryTest.php @@ -22,6 +22,7 @@ use LaravelDoctrine\ORM\EntityManagerFactory; use LaravelDoctrine\ORM\Loggers\Logger; use LaravelDoctrine\ORM\Resolvers\EntityListenerResolver as LaravelDoctrineEntityListenerResolver; + use Mockery as m; use Mockery\Mock; @@ -484,8 +485,11 @@ public function test_can_set_repository_factory() /** * MOCKS + * + * @param array $driverConfig + * @param bool $strictCallCountChecking */ - protected function mockConfig() + protected function mockConfig($driverConfig = ['driver' => 'mysql'], $strictCallCountChecking = true) { $this->config = m::mock(Repository::class); @@ -495,10 +499,11 @@ protected function mockConfig() ->andReturn('array'); foreach ($this->caches as $cache) { - $this->config->shouldReceive('get') + $expectation = $this->config->shouldReceive('get') ->with('doctrine.cache.' . $cache . '.driver', 'array') - ->atLeast()->once() ->andReturn('array'); + + $strictCallCountChecking ? $expectation->once() : $expectation->never(); } $this->config->shouldReceive('has') @@ -509,21 +514,25 @@ protected function mockConfig() $this->config->shouldReceive('get') ->with('database.connections.mysql') ->once() - ->andReturn([ - 'driver' => 'mysql' - ]); + ->andReturn($driverConfig); - $this->config->shouldReceive('get') + $expectation = $this->config->shouldReceive('get') ->with('doctrine.custom_datetime_functions') - ->once()->andReturn(['datetime']); + ->andReturn(['datetime']); - $this->config->shouldReceive('get') + $strictCallCountChecking ? $expectation->once() : $expectation->never(); + + $expectation = $this->config->shouldReceive('get') ->with('doctrine.custom_numeric_functions') - ->once()->andReturn(['numeric']); + ->andReturn(['numeric']); - $this->config->shouldReceive('get') + $strictCallCountChecking ? $expectation->once() : $expectation->never(); + + $expectation = $this->config->shouldReceive('get') ->with('doctrine.custom_string_functions') - ->once()->andReturn(['string']); + ->andReturn(['string']); + + $strictCallCountChecking ? $expectation->once() : $expectation->never(); } protected function mockCache() @@ -693,6 +702,146 @@ protected function enableLaravelNamingStrategy() $this->configuration->shouldReceive('setNamingStrategy')->once()->with($strategy); } + /** + * Data provider for testMasterSlaveConnection. + * + * @return array + */ + public function getTestMasterSlaveConnectionData() + { + $out = []; + + $dummyInputConfig = [ + 'driver' => 'mysql', + 'host' => 'localhost', + 'port' => '3306', + 'database' => 'test', + 'username' => 'homestead', + 'password' => 'secret', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + 'engine' => null, + 'write' => [ + 'port' => 3307, + ], + 'read' => [ + [ + 'port' => 3308, + 'database' => 'test2', + ], + [ + 'host' => 'localhost2', + 'port' => 3309 + ], + ], + ]; + + // Case #0. Simple valid configuration, everything should go well. + $out[] = [$dummyInputConfig]; + + //Case #1. No read DBs set. + $inputConfig = $dummyInputConfig; + unset($inputConfig['read']); + + $out[] = [ + $inputConfig, + \InvalidArgumentException::class, + "Parameter 'read' must be set for read/write config." + ]; + + //Case #2. 'read' isn't an array + $inputConfig = $dummyInputConfig; + $inputConfig['read'] = 'test'; + + $out[] = [ + $inputConfig, + \InvalidArgumentException::class, + "Parameter 'read' must be an array containing multiple arrays." + ]; + + //Case #3. 'read' has non array entries. + $inputConfig = $dummyInputConfig; + $inputConfig['read'][] = 'test'; + + $out[] = [ + $inputConfig, + \InvalidArgumentException::class, + "Parameter 'read' must be an array containing multiple arrays." + ]; + + //Case #4. 'read' has empty entries + $inputConfig = $dummyInputConfig; + $inputConfig['read'][] = []; + + $out[] = [ + $inputConfig, + \InvalidArgumentException::class, + "Parameter 'read' config no. 2 is empty." + ]; + + return $out; + } + + /** + * Check if config is handled correctly. + * + * @param array $inputConfig + * @param string $expectedException + * @param string $msg + * + * @dataProvider getTestMasterSlaveConnectionData + */ + public function testMasterSlaveConnection( + array $inputConfig, + $expectedException = '', + $msg = '' + ) { + m::resetContainer(); + + $this->mockApp(); + $this->mockResolver(); + $this->mockConfig($inputConfig, empty($expectedException)); + + $this->cache = m::mock(CacheManager::class); + $this->cache->shouldReceive('driver') + ->times(empty($expectedException) ? 4 : 1) + ->andReturn(new ArrayCache()); + + $this->setup = m::mock(Setup::class); + $this->setup->shouldReceive('createConfiguration')->once()->andReturn($this->configuration); + + $this->connection = m::mock(ConnectionManager::class); + $this->connection->shouldReceive('driver') + ->once() + ->with('mysql', $inputConfig) + ->andReturn(['driver' => 'pdo_mysql']); + + $factory = new EntityManagerFactory( + $this->container, + $this->setup, + $this->meta, + $this->connection, + $this->cache, + $this->config, + $this->listenerResolver + ); + + if (!empty($expectedException)) { + $this->setExpectedException($expectedException, $msg); + } else { + $this->disableDebugbar(); + $this->disableCustomCacheNamespace(); + $this->disableSecondLevelCaching(); + $this->disableCustomFunctions(); + $this->enableLaravelNamingStrategy(); + } + + $this->settings['connection'] = 'mysql'; + $factory->create($this->settings); + } + protected function tearDown() { m::close(); From d824138af29df434d42c6f56fccd5d45c579fbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zigmas=20Satkevi=C4=8Dius?= Date: Tue, 7 Mar 2017 15:27:54 +0200 Subject: [PATCH 2/3] Fixed master/slave validation not checking first slave configuration. --- src/EntityManagerFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EntityManagerFactory.php b/src/EntityManagerFactory.php index f45cc407..a1648c01 100644 --- a/src/EntityManagerFactory.php +++ b/src/EntityManagerFactory.php @@ -479,7 +479,7 @@ private function hasValidMasterSlaveConfig(array $driverConfig) throw new \InvalidArgumentException("Parameter 'read' must be an array containing multiple arrays."); } - if (($key = array_search(0, array_map('count', $slaves))) && $key !== false) { + if (($key = array_search(0, array_map('count', $slaves))) !== false) { throw new \InvalidArgumentException("Parameter 'read' config no. {$key} is empty."); } From cddf5b3e1f7c966e30d85a59d166c64775b856bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zigmas=20Satkevi=C4=8Dius?= Date: Tue, 7 Mar 2017 15:28:10 +0200 Subject: [PATCH 3/3] Refactored master/slave connection tests. --- src/EntityManagerFactory.php | 7 +- .../Connections/MasterSlaveConnectionTest.php | 287 ++++++++++++------ tests/EntityManagerFactoryTest.php | 85 +++--- 3 files changed, 248 insertions(+), 131 deletions(-) diff --git a/src/EntityManagerFactory.php b/src/EntityManagerFactory.php index a1648c01..51b31d8d 100644 --- a/src/EntityManagerFactory.php +++ b/src/EntityManagerFactory.php @@ -110,7 +110,8 @@ public function create(array $settings = []) $driver ); - if ($this->isMasterSlaveConfigured($driver) && $this->hasValidMasterSlaveConfig($driver)) { + if ($this->isMasterSlaveConfigured($driver)) { + $this->hasValidMasterSlaveConfig($driver); $connection = (new MasterSlaveConnection($this->config, $connection))->resolve($driver); } @@ -464,8 +465,6 @@ private function isMasterSlaveConfigured(array $driverConfig) * Check if slave configuration is valid. * * @param array $driverConfig - * - * @return bool */ private function hasValidMasterSlaveConfig(array $driverConfig) { @@ -482,7 +481,5 @@ private function hasValidMasterSlaveConfig(array $driverConfig) if (($key = array_search(0, array_map('count', $slaves))) !== false) { throw new \InvalidArgumentException("Parameter 'read' config no. {$key} is empty."); } - - return true; } } diff --git a/tests/Configuration/Connections/MasterSlaveConnectionTest.php b/tests/Configuration/Connections/MasterSlaveConnectionTest.php index c4de0df3..f4446af2 100644 --- a/tests/Configuration/Connections/MasterSlaveConnectionTest.php +++ b/tests/Configuration/Connections/MasterSlaveConnectionTest.php @@ -19,7 +19,49 @@ public function getMasterSlaveConnectionData() { $out = []; - $dummyInputConfig = [ + // Case #0. Simple valid configuration with mysql base settings. + $out[] = [$this->getResolvedMysqlConfig(), $this->getInputConfig(), $this->getExpectedConfig()]; + + // Case #1. Configuration is only set in the read/write nodes. + $out[] = [['driver' => 'pdo_mysql'], $this->getNodesInputConfig(), $this->getNodesExpectedConfig()]; + + // Case #2. Simple valid configuration with oracle base settings. + $out[] = [$this->getResolvedOracleConfig(), $this->getInputConfig(), $this->getOracleExpectedConfig()]; + + // Case #3. Simple valid configuration with pgqsql base settings. + $out[] = [$this->getResolvedPgqsqlConfig(), $this->getInputConfig(), $this->getPgsqlExpectedConfig()]; + + // Case #4. Simple valid configuration with sqlite base settings. + $out[] = [$this->getResolvedSqliteConfig(), $this->getSqliteInputConfig(), $this->getSqliteExpectedConfig()]; + + return $out; + } + + /** + * Check if master slave connection manages configuration well. + * + * @param array $resolvedBaseSettings + * @param array $settings + * @param $expectedOutput + * + * @dataProvider getMasterSlaveConnectionData + */ + public function testMasterSlaveConnection(array $resolvedBaseSettings, array $settings, array $expectedOutput) + { + $this->assertEquals( + $expectedOutput, + (new MasterSlaveConnection(m::mock(Repository::class), $resolvedBaseSettings))->resolve($settings) + ); + } + + /** + * Returns dummy input configuration for testing. + * + * @return array + */ + private function getInputConfig() + { + return [ 'driver' => 'mysql', 'host' => 'localhost', 'port' => '3306', @@ -47,8 +89,16 @@ public function getMasterSlaveConnectionData() ], ], ]; + } - $dummyExpectedConfig = [ + /** + * Returns dummy expected result configuration for testing. + * + * @return array + */ + private function getExpectedConfig() + { + return [ 'wrapperClass' => MasterSlaveDoctrineWrapper::class, 'driver' => 'pdo_mysql', 'slaves' => [ @@ -84,27 +134,50 @@ public function getMasterSlaveConnectionData() 'prefix' => 'prefix' ], ]; + } - // Case #0. Simple valid configuration with mysql base settings. - $resolvedBaseSettings = [ - 'driver' => 'pdo_mysql', - 'host' => 'localhost', - 'dbname' => 'test', - 'user' => 'homestead', - 'password' => 'secret', - 'charset' => 'charset', - 'port' => 'port', - 'unix_socket' => 'unix_socket', - 'prefix' => 'prefix' - ]; - $out[] = [$resolvedBaseSettings, $dummyInputConfig, $dummyExpectedConfig]; - - // Case #1. Configuration is only set in the read/wriet nodes. - $resolvedBaseSettings = [ - 'driver' => 'pdo_mysql', + /** + * Returns dummy input configuration where configuration is only set in read and write nodes. + * + * @return array + */ + private function getNodesInputConfig() + { + return [ + 'write' => [ + 'port' => 3307, + 'password' => 'secret1', + 'host' => 'localhost', + 'database' => 'test', + 'username' => 'homestead' + ], + 'read' => [ + [ + 'port' => 3308, + 'database' => 'test2', + 'host' => 'localhost', + 'username' => 'homestead', + 'password' => 'secret' + ], + [ + 'host' => 'localhost2', + 'port' => 3309, + 'database' => 'test', + 'username' => 'homestead', + 'password' => 'secret' + ], + ], ]; + } - $expectedConfig = [ + /** + * Returns dummy expected output configuration where configuration is only set in read and write nodes. + * + * @return array + */ + private function getNodesExpectedConfig() + { + return [ 'wrapperClass' => MasterSlaveDoctrineWrapper::class, 'driver' => 'pdo_mysql', 'slaves' => [ @@ -131,75 +204,47 @@ public function getMasterSlaveConnectionData() 'port' => '3307', ], ]; + } - $inputConfig = [ - 'write' => [ - 'port' => 3307, - 'password' => 'secret1', - 'host' => 'localhost', - 'database' => 'test', - 'username' => 'homestead' - ], - 'read' => [ - [ - 'port' => 3308, - 'database' => 'test2', - 'host' => 'localhost', - 'username' => 'homestead', - 'password' => 'secret' - ], - [ - 'host' => 'localhost2', - 'port' => 3309, - 'database' => 'test', - 'username' => 'homestead', - 'password' => 'secret' - ], - ], - ]; - $out[] = [$resolvedBaseSettings, $inputConfig, $expectedConfig]; - - // Case #2. Simple valid configuration with oracle base settings. - $expectedConfigOracle = $expectedConfig; + /** + * Returns dummy expected result configuration for testing oracle connections. + * + * @return array + */ + private function getOracleExpectedConfig() + { + $expectedConfigOracle = $this->getNodesExpectedConfig(); $expectedConfigOracle['driver'] = 'oci8'; $expectedConfigOracle['master']['user'] = 'homestead1'; - $resolvedBaseSettings = [ - 'driver' => 'oci8', - 'host' => 'localhost', - 'dbname' => 'test', - 'user' => 'homestead', - 'password' => 'secret', - 'port' => 'port', - ]; - $out[] = [$resolvedBaseSettings, $dummyInputConfig, $expectedConfigOracle]; + return $expectedConfigOracle; + } - // Case #3. Simple valid configuration with pgqsql base settings. - $expectedConfigPgsql = $expectedConfig; + /** + * Returns dummy expected result configuration for testing pgsql connections. + * + * @return array + */ + private function getPgsqlExpectedConfig() + { + $expectedConfigPgsql = $this->getNodesExpectedConfig(); $expectedConfigPgsql['driver'] = 'pgsql'; $expectedConfigPgsql['master']['user'] = 'homestead1'; $expectedConfigPgsql['master']['sslmode'] = 'sslmode'; $expectedConfigPgsql['slaves'][0]['sslmode'] = 'sslmode'; $expectedConfigPgsql['slaves'][1]['sslmode'] = 'sslmode'; - $resolvedBaseSettings = [ - 'driver' => 'pgsql', - 'host' => 'localhost', - 'dbname' => 'test', - 'user' => 'homestead', - 'password' => 'secret', - 'port' => 'port', - 'sslmode' => 'sslmode', - ]; - $out[] = [$resolvedBaseSettings, $dummyInputConfig, $expectedConfigPgsql]; - - // Case #4. Simple valid configuration with sqlite base settings. - $inputConfigSqlite = $dummyInputConfig; - unset($inputConfigSqlite['read'][0]['database']); - unset($inputConfigSqlite['read'][1]['database']); - unset($inputConfigSqlite['write']['database']); + return $expectedConfigPgsql; + } - $expectedConfigSqlite = [ + /** + * Returns dummy expected result configuration for testing Sqlite connections. + * + * @return array + */ + private function getSqliteExpectedConfig() + { + return [ 'wrapperClass' => MasterSlaveDoctrineWrapper::class, 'driver' => 'pdo_sqlite', 'slaves' => [ @@ -227,33 +272,91 @@ public function getMasterSlaveConnectionData() 'path' => ':memory', ], ]; + } - $resolvedBaseSettings = [ + /** + * Returns dummy input configuration for testing Sqlite connections. + * + * @return array + */ + private function getSqliteInputConfig() + { + $inputConfigSqlite = $this->getInputConfig(); + unset($inputConfigSqlite['read'][0]['database']); + unset($inputConfigSqlite['read'][1]['database']); + unset($inputConfigSqlite['write']['database']); + + return $inputConfigSqlite; + } + + /** + * Returns already resolved mysql configuration. + * + * @return array + */ + private function getResolvedMysqlConfig() + { + return [ + 'driver' => 'pdo_mysql', + 'host' => 'localhost', + 'dbname' => 'test', + 'user' => 'homestead', + 'password' => 'secret', + 'charset' => 'charset', + 'port' => 'port', + 'unix_socket' => 'unix_socket', + 'prefix' => 'prefix' + ]; + } + + /** + * Returns already resolved oci configuration. + * + * @return array + */ + private function getResolvedOracleConfig() + { + return [ + 'driver' => 'oci8', + 'host' => 'localhost', + 'dbname' => 'test', + 'user' => 'homestead', + 'password' => 'secret', + 'port' => 'port', + ]; + } + + /** + * Returns already resolved sqlite configuration. + * + * @return array + */ + private function getResolvedSqliteConfig() + { + return [ 'driver' => 'pdo_sqlite', 'path' => ':memory', 'user' => 'homestead', 'password' => 'secret', 'memory' => true ]; - $out[] = [$resolvedBaseSettings, $inputConfigSqlite, $expectedConfigSqlite]; - - return $out; } /** - * Check if master slave connection manages configuration well. + * Returns already resolved pgsql configuration. * - * @param array $resolvedBaseSettings - * @param array $settings - * @param $expectedOutput - * - * @dataProvider getMasterSlaveConnectionData + * @return array */ - public function testMasterSlaveConnection(array $resolvedBaseSettings, array $settings, array $expectedOutput) + private function getResolvedPgqsqlConfig() { - $this->assertEquals( - $expectedOutput, - (new MasterSlaveConnection(m::mock(Repository::class), $resolvedBaseSettings))->resolve($settings) - ); + return [ + 'driver' => 'pgsql', + 'host' => 'localhost', + 'dbname' => 'test', + 'user' => 'homestead', + 'password' => 'secret', + 'port' => 'port', + 'sslmode' => 'sslmode', + ]; } } diff --git a/tests/EntityManagerFactoryTest.php b/tests/EntityManagerFactoryTest.php index b011a0cc..d04a924c 100644 --- a/tests/EntityManagerFactoryTest.php +++ b/tests/EntityManagerFactoryTest.php @@ -22,7 +22,6 @@ use LaravelDoctrine\ORM\EntityManagerFactory; use LaravelDoctrine\ORM\Loggers\Logger; use LaravelDoctrine\ORM\Resolvers\EntityListenerResolver as LaravelDoctrineEntityListenerResolver; - use Mockery as m; use Mockery\Mock; @@ -711,38 +710,11 @@ public function getTestMasterSlaveConnectionData() { $out = []; - $dummyInputConfig = [ - 'driver' => 'mysql', - 'host' => 'localhost', - 'port' => '3306', - 'database' => 'test', - 'username' => 'homestead', - 'password' => 'secret', - 'charset' => 'utf8', - 'collation' => 'utf8_unicode_ci', - 'prefix' => '', - 'strict' => false, - 'engine' => null, - 'write' => [ - 'port' => 3307, - ], - 'read' => [ - [ - 'port' => 3308, - 'database' => 'test2', - ], - [ - 'host' => 'localhost2', - 'port' => 3309 - ], - ], - ]; - // Case #0. Simple valid configuration, everything should go well. - $out[] = [$dummyInputConfig]; + $out[] = [$this->getDummyBaseInputConfig()]; //Case #1. No read DBs set. - $inputConfig = $dummyInputConfig; + $inputConfig = $this->getDummyBaseInputConfig(); unset($inputConfig['read']); $out[] = [ @@ -752,7 +724,7 @@ public function getTestMasterSlaveConnectionData() ]; //Case #2. 'read' isn't an array - $inputConfig = $dummyInputConfig; + $inputConfig = $this->getDummyBaseInputConfig(); $inputConfig['read'] = 'test'; $out[] = [ @@ -762,7 +734,7 @@ public function getTestMasterSlaveConnectionData() ]; //Case #3. 'read' has non array entries. - $inputConfig = $dummyInputConfig; + $inputConfig = $this->getDummyBaseInputConfig(); $inputConfig['read'][] = 'test'; $out[] = [ @@ -771,8 +743,8 @@ public function getTestMasterSlaveConnectionData() "Parameter 'read' must be an array containing multiple arrays." ]; - //Case #4. 'read' has empty entries - $inputConfig = $dummyInputConfig; + //Case #4. 'read' has empty entries. + $inputConfig = $this->getDummyBaseInputConfig(); $inputConfig['read'][] = []; $out[] = [ @@ -781,6 +753,16 @@ public function getTestMasterSlaveConnectionData() "Parameter 'read' config no. 2 is empty." ]; + //Case #5. 'read' has empty first entry. (reported by maxbrokman.) + $inputConfig = $this->getDummyBaseInputConfig(); + $inputConfig['read'][0] = []; + + $out[] = [ + $inputConfig, + \InvalidArgumentException::class, + "Parameter 'read' config no. 0 is empty." + ]; + return $out; } @@ -846,6 +828,41 @@ protected function tearDown() { m::close(); } + + /** + * Returns dummy base config for testing. + * + * @return array + */ + private function getDummyBaseInputConfig() + { + return [ + 'driver' => 'mysql', + 'host' => 'localhost', + 'port' => '3306', + 'database' => 'test', + 'username' => 'homestead', + 'password' => 'secret', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + 'engine' => null, + 'write' => [ + 'port' => 3307, + ], + 'read' => [ + [ + 'port' => 3308, + 'database' => 'test2', + ], + [ + 'host' => 'localhost2', + 'port' => 3309 + ], + ], + ]; + } } class FilterStub