From 0157b3e6a4889f97988516a25f27ad427e53b5fb Mon Sep 17 00:00:00 2001 From: Peter Furesz Date: Mon, 13 Jun 2016 14:02:09 +0200 Subject: [PATCH 1/3] Add hashing array support for ArrayUtil --- src/Utilities/ArrayUtil.php | 10 ++++++++++ tests/Utilities/ArrayUtilTest.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/Utilities/ArrayUtil.php b/src/Utilities/ArrayUtil.php index 8a142241..f0393665 100644 --- a/src/Utilities/ArrayUtil.php +++ b/src/Utilities/ArrayUtil.php @@ -8,4 +8,14 @@ public static function get(&$var, $default = null) { return isset($var) ? $var : $default; } + + /** + * @param array $array + * + * @return string + */ + public static function hashArray(array $array) + { + return md5(json_encode($array)); + } } diff --git a/tests/Utilities/ArrayUtilTest.php b/tests/Utilities/ArrayUtilTest.php index 36959147..93c193a3 100644 --- a/tests/Utilities/ArrayUtilTest.php +++ b/tests/Utilities/ArrayUtilTest.php @@ -30,4 +30,35 @@ public function test_can_pass_custom_default_value() $this->assertEquals('default', ArrayUtil::get($values['key2'], 'default')); } + + /** + * @dataProvider arrayProvider + * + * @param array $array1 + * @param array $array2 + * @param bool $expectedEquals + */ + public function test_hashArray_returns_unique_hash(array $array1, array $array2, $expectedEquals) + { + $hash1 = ArrayUtil::hashArray($array1); + $hash2 = ArrayUtil::hashArray($array2); + + if ($expectedEquals) { + $this->assertEquals($hash1, $hash2); + } else { + $this->assertNotEquals($hash1, $hash2); + } + } + + /** + * @return array + */ + public function arrayProvider() + { + return [ + [[], [], true], + [['key1' => 'value1'], ['key1' => 'value1'], true], + [['key1' => 'value1'], ['key1' => 'value1-NOT-EQUALS'], false], + ]; + } } From 4b7746d71e12233c4488e82bd9aeacf31e2c99d6 Mon Sep 17 00:00:00 2001 From: Peter Furesz Date: Mon, 13 Jun 2016 14:03:12 +0200 Subject: [PATCH 2/3] Add MasterSlaveConfigParser --- src/Utilities/MasterSlaveConfigParser.php | 76 ++++++++ .../Utilities/MasterSlaveConfigParserTest.php | 174 ++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 src/Utilities/MasterSlaveConfigParser.php create mode 100644 tests/Utilities/MasterSlaveConfigParserTest.php diff --git a/src/Utilities/MasterSlaveConfigParser.php b/src/Utilities/MasterSlaveConfigParser.php new file mode 100644 index 00000000..c214c63b --- /dev/null +++ b/src/Utilities/MasterSlaveConfigParser.php @@ -0,0 +1,76 @@ + [], + 'read' => [], + ]; + + $masterSlave['write'] = [ + 'user' => array_get($config, 'write.username', $config['username']), + 'password' => array_get($config, 'write.password', $config['password']), + 'host' => array_get($config, 'write.host', $config['host']), + 'dbname' => array_get($config, 'write.database', $config['database']), + 'port' => array_get($config, 'write.port', $config['port']), + ]; + + foreach (array_get($config, 'read', []) as $slaveConfig) { + $config = [ + 'user' => array_get($slaveConfig, 'username', $config['username']), + 'password' => array_get($slaveConfig, 'password', $config['password']), + 'host' => array_get($slaveConfig, 'host', $config['host']), + 'dbname' => array_get($slaveConfig, 'database', $config['database']), + 'port' => array_get($config, 'write.port', $config['port']), + ]; + + $masterSlave['read'][] = $config; + } + + return $masterSlave; + } + + /** + * @param array $config + * + * @return bool + */ + public static function hasValidConfig(array $config) + { + if ( + array_key_exists('read', $config) + && ! empty($config['read']) + && is_array($config['read']) + //All value in $config['read'] should be an array + && ! in_array(false, array_map(function ($readConfigValue) { + return is_array($readConfigValue); + }, $config['read'])) + //All value in $config['read'] should have at least one config difference + && ! in_array(false, array_map(function (array $readConfig) { + return count($readConfig) > 0; + }, $config['read'])) + ) { + return true; + } + + return false; + } +} diff --git a/tests/Utilities/MasterSlaveConfigParserTest.php b/tests/Utilities/MasterSlaveConfigParserTest.php new file mode 100644 index 00000000..955716a5 --- /dev/null +++ b/tests/Utilities/MasterSlaveConfigParserTest.php @@ -0,0 +1,174 @@ +assertTrue(MasterSlaveConfigParser::hasValidConfig($validConfig)); + } + + /** + * @dataProvider invalidConfigProvider + * + * @param array $invalidConfig + */ + public function test_HasValidConfig_returns_false_on_missing_config(array $invalidConfig) + { + $this->assertFalse(MasterSlaveConfigParser::hasValidConfig($invalidConfig)); + } + + /** + * @return array + */ + public function validConfigProvider() + { + $valid1['config'] = array_merge( + $this->getDatabaseConfigTemplate(), + [ + 'write' => [ + 'host' => '192.168.0.1', + ], + 'read' => [ + [ + 'host' => '192.168.0.2', + 'username' => 'read_user', + ], + ], + ] + ); + $valid1['expectedWrite'] = [ + 'user' => 'write_db_username', + 'password' => 'write_db_password', + 'host' => '192.168.0.1', //Override the parent's localhost + 'dbname' => 'write_db_name', + 'port' => 3306, + ]; + $valid1['expectedRead'] = [ + [ + 'user' => 'read_user', + 'password' => 'write_db_password', + 'host' => '192.168.0.2', //Override the parent's localhost + 'dbname' => 'write_db_name', + 'port' => 3306, + ], + ]; + + $valid2['config'] = array_merge( + $this->getDatabaseConfigTemplate(), + [ + 'read' => [ + [ + 'host' => '192.168.0.1', + ], + ] + ] + ); + $valid2['expectedWrite'] = [ + 'user' => 'write_db_username', + 'password' => 'write_db_password', + 'host' => 'localhost', + 'dbname' => 'write_db_name', + 'port' => 3306, + ]; + $valid2['expectedRead'] = [ + [ + 'user' => 'write_db_username', + 'password' => 'write_db_password', + 'host' => '192.168.0.1', //Override the parent's localhost + 'dbname' => 'write_db_name', + 'port' => 3306, + ], + ]; + + return [ + [$valid1['config'], $valid1['expectedWrite'], $valid1['expectedRead']], + [$valid2['config'], $valid2['expectedWrite'], $valid2['expectedRead']], + ]; + } + + /** + * @return array + */ + public function getDatabaseConfigTemplate() + { + return [ + 'host' => 'localhost', + 'database' => 'write_db_name', + 'username' => 'write_db_username', + 'password' => 'write_db_password', + 'driver' => 'mysql', + 'port' => 3306, + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + 'strict' => false, + 'engine' => null, + ]; + } + + /** + * @return array + */ + public function invalidConfigProvider() + { + $invalidConfig1 = []; + $invalidConfig2 = array_merge( + $this->getDatabaseConfigTemplate(), + [ + 'write' => [], +// 'read' => [], //Read is missing, it's invalid + ] + ); + $invalidConfig3 = array_merge( + $this->getDatabaseConfigTemplate(), + [ + 'write' => [], + 'read' => ['host' => '123'], //Read should be an array + ] + ); + + return [ + [$invalidConfig1], + [$invalidConfig2], + [$invalidConfig3], + ]; + } + + /** + * @dataProvider invalidConfigProvider + * + * @expectedException UnexpectedValueException + * + * @param array $invalidConfig + */ + public function test_ParseConfig_throws_exception_on_invalid_config(array $invalidConfig) + { + MasterSlaveConfigParser::parseConfig($invalidConfig); + } + + /** + * @dataProvider validConfigProvider + * + * @param array $config + * @param array $expectedWrite + * @param array $expectedRead + */ + public function test_ParseConfig_can_inherit_settings( + array $config, + array $expectedWrite, + array $expectedRead + ) { + $readWriteConfig = MasterSlaveConfigParser::parseConfig($config); + + $this->assertEquals($expectedRead, $readWriteConfig['read']); + $this->assertEquals($expectedWrite, $readWriteConfig['write']); + } +} From 02beba54985427e38b8e4056d8baefc9aeea1f8d Mon Sep 17 00:00:00 2001 From: Peter Furesz Date: Mon, 13 Jun 2016 14:10:44 +0200 Subject: [PATCH 3/3] Improve EntityManagerFactory - Handling read / write connections - Cache settings to avoid read and parse config files if it's done already. (note: if same settings are provided, we always return the as well configured EntityManager . Don't need to re-create / re-parse configs) --- src/EntityManagerFactory.php | 90 +++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/src/EntityManagerFactory.php b/src/EntityManagerFactory.php index 09aefd0d..d57e518b 100644 --- a/src/EntityManagerFactory.php +++ b/src/EntityManagerFactory.php @@ -2,6 +2,8 @@ namespace LaravelDoctrine\ORM; +use Doctrine\DBAL\Connections\MasterSlaveConnection; +use Doctrine\DBAL\DriverManager; use Doctrine\ORM\Cache\DefaultCacheFactory; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; @@ -18,6 +20,8 @@ use LaravelDoctrine\ORM\Configuration\MetaData\MetaDataManager; use LaravelDoctrine\ORM\Extensions\MappingDriverChain; use LaravelDoctrine\ORM\Resolvers\EntityListenerResolver; +use LaravelDoctrine\ORM\Utilities\ArrayUtil; +use LaravelDoctrine\ORM\Utilities\MasterSlaveConfigParser; use ReflectionException; class EntityManagerFactory @@ -47,6 +51,11 @@ class EntityManagerFactory */ protected $container; + /** + * @var array + */ + protected $configCache = []; + /** * @var Setup */ @@ -91,37 +100,66 @@ public function __construct( */ public function create(array $settings = []) { - $configuration = $this->setup->createConfiguration( - array_get($settings, 'dev', false), - array_get($settings, 'proxies.path'), - $this->cache->driver() - ); - - $this->setMetadataDriver($settings, $configuration); - - $driver = $this->getConnectionDriver($settings); - - $connection = $this->connection->driver( - $driver['driver'], - $driver - ); + $settingsHash = ArrayUtil::hashArray($settings); - $this->setNamingStrategy($settings, $configuration); - $this->setCustomFunctions($configuration); - $this->setCacheSettings($configuration); - $this->configureProxies($settings, $configuration); - $this->setCustomMappingDriverChain($settings, $configuration); - $this->registerPaths($settings, $configuration); - $this->setRepositoryFactory($settings, $configuration); + if (array_key_exists($settingsHash, $this->configCache)) { + $configuration = $this->configCache[$settingsHash]['configuration']; + $connectionConfig = $this->configCache[$settingsHash]['connectionConfig']; + $slaveConfig = $this->configCache[$settingsHash]['slaveConfig']; + } else { + $configuration = $this->setup->createConfiguration( + array_get($settings, 'dev', false), + array_get($settings, 'proxies.path'), + $this->cache->driver() + ); + $configuration->setDefaultRepositoryClassName( + array_get($settings, 'repository', EntityRepository::class) + ); + $configuration->setEntityListenerResolver($this->resolver); + + $this->setMetadataDriver($settings, $configuration); + $this->setNamingStrategy($settings, $configuration); + $this->setCustomFunctions($configuration); + $this->setCacheSettings($configuration); + $this->configureProxies($settings, $configuration); + $this->setCustomMappingDriverChain($settings, $configuration); + $this->registerPaths($settings, $configuration); + $this->setRepositoryFactory($settings, $configuration); + + $driverConfig = $this->getConnectionDriver($settings); + $slaveConfig = MasterSlaveConfigParser::hasValidConfig($driverConfig) + ? MasterSlaveConfigParser::parseConfig($driverConfig) + : []; + + $connectionConfig = $this->connection->driver( + $driverConfig['driver'], + $driverConfig + ); - $configuration->setDefaultRepositoryClassName( - array_get($settings, 'repository', EntityRepository::class) - ); + $this->configCache[$settingsHash]['configuration'] = $configuration; + $this->configCache[$settingsHash]['connectionConfig'] = $connectionConfig; + $this->configCache[$settingsHash]['slaveConfig'] = $slaveConfig; + } - $configuration->setEntityListenerResolver($this->resolver); + if (empty($slaveConfig)) { + $conn = DriverManager::getConnection($connectionConfig, $configuration); + } else { + $conn = DriverManager::getConnection([ + 'wrapperClass' => MasterSlaveConnection::class, + 'driver' => $connectionConfig['driver'], + 'master' => [ + 'user' => array_get($slaveConfig, 'write.user', ''), + 'password' => array_get($slaveConfig, 'write.password', ''), + 'host' => array_get($slaveConfig, 'write.host', ''), + 'dbname' => array_get($slaveConfig, 'write.dbname', ''), + 'port' => array_get($slaveConfig, 'write.port', ''), + ], + 'slaves' => $slaveConfig['read'], + ]); + } $manager = EntityManager::create( - $connection, + $conn, $configuration );