Manager::getDriverId()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * Phossa Project
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  Library
8
 * @package   Phossa2\Db
9
 * @copyright Copyright (c) 2016 phossa.com
10
 * @license   http://mit-license.org/ MIT License
11
 * @link      http://www.phossa.com/
12
 */
13
/*# declare(strict_types=1); */
14
15
namespace Phossa2\Db;
16
17
use Phossa2\Db\Message\Message;
18
use Phossa2\Shared\Base\ObjectAbstract;
19
use Phossa2\Db\Interfaces\DriverInterface;
20
use Phossa2\Db\Interfaces\ManagerInterface;
21
use Phossa2\Db\Exception\NotFoundException;
22
use Phossa2\Shared\Aware\TagAwareInterface;
23
24
/**
25
 * Manager
26
 *
27
 * Db driver manager implementation with tag support
28
 *
29
 * - Added 'weight factor' for round-robin fashion of load balancing
30
 *
31
 * - driver supports tags, may pick driver by tag
32
 *
33
 * ```php
34
 * $dbm = new Manager();
35
 *
36
 * // db writer with weighting factor 1
37
 * $dbm->addDriver($driver1->addTag('RW'), 1);
38
 *
39
 * // db reader with weighting factor 5
40
 * $dbm->addDriver($driver2->addTag('RO'), 5);
41
 *
42
 * // get whatever reader or writer
43
 * $db = $dbm->getDriver();
44
 *
45
 * // get driver by tag
46
 * $dbReader = $dbm->getDriver('RO');
47
 * ```
48
 *
49
 * @package Phossa2\Db
50
 * @author  Hong Zhang <[email protected]>
51
 * @see     ObjectAbstract
52
 * @see     ManagerInterface
53
 * @version 2.0.0
54
 * @since   2.0.0 added
55
 */
56
class Manager extends ObjectAbstract implements ManagerInterface
57
{
58
    /**
59
     * drivers
60
     *
61
     * @var    DriverInterface[]
62
     * @access protected
63
     */
64
    protected $drivers = [];
65
66
    /**
67
     * driver weight factor
68
     *
69
     * @var    array
70
     * @access protected
71
     */
72
    protected $factors = [];
73
74
    /**
75
     * Ping driver before returns it
76
     *
77
     * @var    bool
78
     * @access protected
79
     */
80
    protected $ping_driver = false;
81
82
    /**
83
     * Constructor
84
     *
85
     * @param  array $properties
86
     * @access public
87
     */
88
    public function __construct(array $properties = [])
89
    {
90
        $this->setProperties($properties);
91
    }
92
93
    /**
94
     * Specify a weighting factor for the driver. normally 1 - 10
95
     *
96
     * {@inheritDoc}
97
     * @param  int $factor weight factor for round-robin
98
     */
99
    public function addDriver(DriverInterface $driver, /*# int */ $factor = 1)
100
    {
101
        // get unique driver id
102
        $id = $this->getDriverId($driver);
103
104
        // fix factor range: 1 - 10
105
        $this->factors[$id] = $this->fixFactor($factor);
106
107
        // add to the pool
108
        $this->drivers[$id] = $driver;
109
110
        return $this;
111
    }
112
113
    /**
114
     * Get a driver with a tag matched
115
     *
116
     * {@inheritDoc}
117
     */
118
    public function getDriver(/*# string */ $tag = '')/*# : DriverInterface */
119
    {
120
        // match drivers
121
        $matched = $this->driverMatcher($tag);
122
123
        if (count($matched) > 0) {
124
            return $this->drivers[$matched[rand(1, count($matched)) - 1]];
125
        } else {
126
            throw new NotFoundException(
127
                Message::get(Message::DB_DRIVER_NOTFOUND),
128
                Message::DB_DRIVER_NOTFOUND
129
            );
130
        }
131
    }
132
133
    /**
134
     * Return a unique string id of the driver
135
     *
136
     * @param  DriverInterface $driver
137
     * @return string
138
     * @access protected
139
     */
140
    protected function getDriverId(DriverInterface $driver)/*# : string */
141
    {
142
        return spl_object_hash($driver);
143
    }
144
145
    /**
146
     * Make sure factor in the range of 1 - 10
147
     *
148
     * @param  int $factor
149
     * @return int
150
     * @access protected
151
     */
152
    protected function fixFactor(/*# int */ $factor)/*# : int */
153
    {
154
        $f = (int) $factor;
155
        return $f > 10 ? 10 : ($f < 1 ? 1 : $f);
156
    }
157
158
    /**
159
     * Match drivers with tag
160
     *
161
     * @param  string $tag tag to match
162
     * @return array
163
     * @access protected
164
     */
165
    protected function driverMatcher(/*# string */ $tag)/*# : array */
166
    {
167
        $matched = [];
168
        foreach ($this->drivers as $id => $driver) {
169
            if ($this->tagMatched($tag, $driver) && $this->pingDriver($driver)) {
170
                $this->expandWithFactor($matched, $id);
171
            }
172
        }
173
        return $matched;
174
    }
175
176
    /**
177
     * Expand into $matched with factor weight
178
     *
179
     * @param  array &$matched
180
     * @param  string $id
181
     * @access protected
182
     */
183
    protected function expandWithFactor(array &$matched, /*# string */ $id)
184
    {
185
        // repeat $f times in $matched
186
        $f = $this->factors[$id];
187
        for ($i = 0; $i < $f; ++$i) {
188
            $matched[] = $id;
189
        }
190
    }
191
192
    /**
193
     * Match driver with $tag
194
     *
195
     * @param  string $tag
196
     * @param  DriverInterface $driver
197
     * @return bool
198
     * @access protected
199
     */
200
    protected function tagMatched(
201
        /*# string */ $tag,
202
        DriverInterface $driver
203
    )/*# bool */ {
204
        // '' matches all
205
        if ('' === $tag) {
206
            return true;
207
        }
208
209
        // tag matched
210
        if ($driver instanceof TagAwareInterface && $driver->hasTag($tag)) {
211
            return true;
212
        }
213
214
        return false;
215
    }
216
217
    /**
218
     * Ping driver before match it
219
     *
220
     * @param  DriverInterface $driver
221
     * @access protected
222
     */
223
    protected function pingDriver(DriverInterface $driver)/*# : bool */
224
    {
225
        return $this->ping_driver ? $driver->ping(true) : true;
226
    }
227
}
228