Skip to content

Commit 5f7523c

Browse files
Zigmas Satkevičiuspatrickbrouwers
authored andcommitted
[FEATURE] Master/slave connection support. (#214)
* Added master/slave connection support. * Fixed master/slave validation not checking first slave configuration. * Refactored master/slave connection tests.
1 parent 7d85114 commit 5f7523c

File tree

4 files changed

+697
-12
lines changed

4 files changed

+697
-12
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
namespace LaravelDoctrine\ORM\Configuration\Connections;
4+
5+
use Doctrine\DBAL\Connections\MasterSlaveConnection as MasterSlaveDoctrineWrapper;
6+
use Illuminate\Contracts\Config\Repository;
7+
8+
/**
9+
* Handles master slave connection settings.
10+
*/
11+
class MasterSlaveConnection extends Connection
12+
{
13+
/**
14+
* @var array|Connection
15+
*/
16+
private $resolvedBaseSettings;
17+
18+
/**
19+
* @var array Ignored configuration fields for master slave configuration.
20+
*/
21+
private $masterSlaveConfigIgnored = ['driver'];
22+
23+
/**
24+
* MasterSlaveConnection constructor.
25+
*
26+
* @param Repository $config
27+
* @param array|Connection $resolvedBaseSettings
28+
*/
29+
public function __construct(Repository $config, $resolvedBaseSettings)
30+
{
31+
parent::__construct($config);
32+
33+
$this->resolvedBaseSettings = $resolvedBaseSettings;
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function resolve(array $settings = [])
40+
{
41+
$driver = $this->resolvedBaseSettings['driver'];
42+
43+
return [
44+
'wrapperClass' => MasterSlaveDoctrineWrapper::class,
45+
'driver' => $driver,
46+
'master' => $this->getConnectionData(isset($settings['write']) ? $settings['write'] : [], $driver),
47+
'slaves' => $this->getSlavesConfig($settings['read'], $driver),
48+
];
49+
}
50+
51+
/**
52+
* Returns config for slave connections.
53+
*
54+
* @param array $slaves
55+
* @param string $driver
56+
*
57+
* @return array
58+
*/
59+
public function getSlavesConfig(array $slaves, $driver)
60+
{
61+
$handledSlaves = [];
62+
foreach ($slaves as $slave) {
63+
$handledSlaves[] = $this->getConnectionData($slave, $driver);
64+
}
65+
66+
return $handledSlaves;
67+
}
68+
69+
/**
70+
* Returns single connection (slave or master) config.
71+
*
72+
* @param array $connection
73+
* @param string $driver
74+
*
75+
* @return array
76+
*/
77+
private function getConnectionData(array $connection, $driver)
78+
{
79+
$connection = $this->replaceKeyIfExists($connection, 'database', $driver === 'pdo_sqlite' ? 'path' : 'dbname');
80+
$connection = $this->replaceKeyIfExists($connection, 'username', 'user');
81+
82+
return array_merge($this->getFilteredConfig(), $connection);
83+
}
84+
85+
/**
86+
* Returns filtered configuration to use in slaves/masters.
87+
*
88+
* @return array
89+
*/
90+
private function getFilteredConfig()
91+
{
92+
return array_diff_key($this->resolvedBaseSettings, array_flip($this->masterSlaveConfigIgnored));
93+
}
94+
95+
/**
96+
* Replaces key in array if it exists.
97+
*
98+
* @param array $array
99+
* @param string $oldKey
100+
* @param string $newKey
101+
*
102+
* @return array
103+
*/
104+
private function replaceKeyIfExists(array $array, $oldKey, $newKey)
105+
{
106+
if (!isset($array[$oldKey])) {
107+
return $array;
108+
}
109+
110+
$array[$newKey] = $array[$oldKey];
111+
unset($array[$oldKey]);
112+
113+
return $array;
114+
}
115+
}

src/EntityManagerFactory.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use InvalidArgumentException;
1515
use LaravelDoctrine\ORM\Configuration\Cache\CacheManager;
1616
use LaravelDoctrine\ORM\Configuration\Connections\ConnectionManager;
17+
use LaravelDoctrine\ORM\Configuration\Connections\MasterSlaveConnection;
1718
use LaravelDoctrine\ORM\Configuration\LaravelNamingStrategy;
1819
use LaravelDoctrine\ORM\Configuration\MetaData\MetaData;
1920
use LaravelDoctrine\ORM\Configuration\MetaData\MetaDataManager;
@@ -109,6 +110,11 @@ public function create(array $settings = [])
109110
$driver
110111
);
111112

113+
if ($this->isMasterSlaveConfigured($driver)) {
114+
$this->hasValidMasterSlaveConfig($driver);
115+
$connection = (new MasterSlaveConnection($this->config, $connection))->resolve($driver);
116+
}
117+
112118
$this->setNamingStrategy($settings, $configuration);
113119
$this->setCustomFunctions($configuration);
114120
$this->setCacheSettings($configuration);
@@ -440,4 +446,40 @@ protected function registerMappingTypes(array $settings = [], EntityManagerInter
440446
$manager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping($dbType, $doctrineType);
441447
}
442448
}
449+
450+
/**
451+
* Check if master slave connection was being configured.
452+
*
453+
* @param array $driverConfig
454+
*
455+
* @return bool
456+
*/
457+
private function isMasterSlaveConfigured(array $driverConfig)
458+
{
459+
// Setting read is mandatory for master/slave configuration. Setting write is optional.
460+
// But if write was set and read wasn't, it means configuration is incorrect and we must inform the user.
461+
return isset($driverConfig['read']) || isset($driverConfig['write']);
462+
}
463+
464+
/**
465+
* Check if slave configuration is valid.
466+
*
467+
* @param array $driverConfig
468+
*/
469+
private function hasValidMasterSlaveConfig(array $driverConfig)
470+
{
471+
if (!isset($driverConfig['read'])) {
472+
throw new \InvalidArgumentException("Parameter 'read' must be set for read/write config.");
473+
}
474+
475+
$slaves = $driverConfig['read'];
476+
477+
if (!is_array($slaves) || in_array(false, array_map('is_array', $slaves))) {
478+
throw new \InvalidArgumentException("Parameter 'read' must be an array containing multiple arrays.");
479+
}
480+
481+
if (($key = array_search(0, array_map('count', $slaves))) !== false) {
482+
throw new \InvalidArgumentException("Parameter 'read' config no. {$key} is empty.");
483+
}
484+
}
443485
}

0 commit comments

Comments
 (0)