Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.73% |
112 / 117 |
|
94.74% |
18 / 19 |
CRAP | |
0.00% |
0 / 1 |
Base | |
95.73% |
112 / 117 |
|
94.74% |
18 / 19 |
34 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
init | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getWriter | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getReader | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
fetchPreparedStatement | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
startExecute | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
pdoExceptionHandler | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
fetchStatement | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
fetchOne | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
fetchRow | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
fetchValue | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
fetchAll | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
fetchHandle | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
fetchList | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
writeItem | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
deleteItem | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
perform | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
fetchAffected | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
readResolvedReferences | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
hashStringValue | |
50.00% |
5 / 10 |
|
0.00% |
0 / 1 |
8.12 |
1 | <?php |
2 | |
3 | namespace BO\Zmsdb; |
4 | |
5 | /** |
6 | * @SuppressWarnings(NumberOfChildren) |
7 | * @SuppressWarnings(Public) |
8 | * |
9 | */ |
10 | abstract class Base |
11 | { |
12 | /** |
13 | * @var \PDO $writeDb Database connection |
14 | */ |
15 | protected $writeDb = null; |
16 | |
17 | /** |
18 | * @var \PDO $readDb Database connection |
19 | */ |
20 | protected $readDb = null; |
21 | |
22 | /** |
23 | * Cache prepared statements |
24 | * |
25 | */ |
26 | protected static $preparedCache = []; |
27 | |
28 | /** |
29 | * Make sure, we do not cache statements on different connections |
30 | * |
31 | */ |
32 | protected static $preparedConnectionId = null; |
33 | |
34 | /** |
35 | * @param \PDO $writeConnection |
36 | * @param \PDO $readConnection |
37 | */ |
38 | public function __construct(\PDO $writeConnection = null, \PDO $readConnection = null) |
39 | { |
40 | $this->writeDb = $writeConnection; |
41 | $this->readDb = $readConnection; |
42 | } |
43 | |
44 | public static function init(\PDO $writeConnection = null, \PDO $readConnection = null) |
45 | { |
46 | $instance = new static($writeConnection, $readConnection); |
47 | return $instance; |
48 | } |
49 | |
50 | /** |
51 | * @return \PDO |
52 | */ |
53 | public function getWriter() |
54 | { |
55 | if (null === $this->writeDb) { |
56 | $this->writeDb = Connection\Select::getWriteConnection(); |
57 | $this->readDb = $this->writeDb; |
58 | } |
59 | return $this->writeDb; |
60 | } |
61 | |
62 | /** |
63 | * @return \PDO |
64 | */ |
65 | public function getReader() |
66 | { |
67 | if (null !== $this->writeDb) { |
68 | // if readDB gets a reset, still use writeDB |
69 | return $this->writeDb; |
70 | } |
71 | if (null === $this->readDb) { |
72 | $this->readDb = Connection\Select::getReadConnection(); |
73 | } |
74 | return $this->readDb; |
75 | } |
76 | |
77 | public function fetchPreparedStatement($query) |
78 | { |
79 | $sql = "$query"; |
80 | $reader = $this->getReader(); |
81 | if (spl_object_hash($reader) != static::$preparedConnectionId) { |
82 | // do not use prepared statements on a different connection |
83 | static::$preparedCache = []; |
84 | static::$preparedConnectionId = spl_object_hash($reader); |
85 | } |
86 | if (!isset(static::$preparedCache[$sql])) { |
87 | $prepared = $this->getReader()->prepare($sql); |
88 | static::$preparedCache[$sql] = $prepared; |
89 | } |
90 | return static::$preparedCache[$sql]; |
91 | } |
92 | |
93 | public function startExecute($statement, $parameters) |
94 | { |
95 | $statement = static::pdoExceptionHandler(function () use ($statement, $parameters) { |
96 | $statement->execute($parameters); |
97 | return $statement; |
98 | }); |
99 | return $statement; |
100 | } |
101 | |
102 | protected static function pdoExceptionHandler(\Closure $pdoFunction, $parameters = []) |
103 | { |
104 | try { |
105 | $statement = $pdoFunction($parameters); |
106 | } catch (\PDOException $pdoException) { |
107 | if (stripos($pdoException->getMessage(), 'Lock wait timeout') !== false) { |
108 | throw new Exception\Pdo\LockTimeout(); |
109 | } |
110 | //@codeCoverageIgnoreStart |
111 | if (stripos($pdoException->getMessage(), 'Deadlock found') !== false) { |
112 | throw new Exception\Pdo\DeadLockFound(); |
113 | } |
114 | //@codeCoverageIgnoreEnd |
115 | $message = "SQL: " |
116 | . " Err: " |
117 | . $pdoException->getMessage() |
118 | //. " || Statement: " |
119 | //.$statement->queryString |
120 | //." || Parameters=". var_export($parameters, true) |
121 | ; |
122 | throw new Exception\Pdo\PDOFailed($message, 0, $pdoException); |
123 | } |
124 | return $statement; |
125 | } |
126 | |
127 | public function fetchStatement(Query\Base $query) |
128 | { |
129 | $parameters = $query->getParameters(); |
130 | $statement = $this->startExecute($this->fetchPreparedStatement($query), $parameters); |
131 | return $statement; |
132 | } |
133 | |
134 | public function fetchOne(Query\Base $query, \BO\Zmsentities\Schema\Entity $entity) |
135 | { |
136 | $statement = $this->fetchStatement($query); |
137 | $data = $statement->fetch(\PDO::FETCH_ASSOC); |
138 | if ($data) { |
139 | $entity->exchangeArray($query->postProcessJoins($data)); |
140 | $entity->setResolveLevel($query->getResolveLevel()); |
141 | } |
142 | $statement->closeCursor(); |
143 | return $entity; |
144 | } |
145 | |
146 | public function fetchRow($query, $parameters = null) |
147 | { |
148 | return static::pdoExceptionHandler(function () use ($query, $parameters) { |
149 | $prepared = $this->fetchPreparedStatement($query); |
150 | $prepared->execute($parameters); |
151 | $row = $prepared->fetch(\PDO::FETCH_ASSOC); |
152 | $prepared->closeCursor(); |
153 | return $row; |
154 | }); |
155 | } |
156 | |
157 | public function fetchValue($query, $parameters = null) |
158 | { |
159 | return static::pdoExceptionHandler(function () use ($query, $parameters) { |
160 | $prepared = $this->fetchPreparedStatement($query); |
161 | $prepared->execute($parameters); |
162 | $value = $prepared->fetchColumn(); |
163 | $prepared->closeCursor(); |
164 | return $value; |
165 | }); |
166 | } |
167 | |
168 | public function fetchAll($query, $parameters = null) |
169 | { |
170 | return static::pdoExceptionHandler(function () use ($query, $parameters) { |
171 | $prepared = $this->fetchPreparedStatement($query); |
172 | $prepared->execute($parameters); |
173 | $list = $prepared->fetchAll(\PDO::FETCH_ASSOC); |
174 | $prepared->closeCursor(); |
175 | return $list; |
176 | }); |
177 | } |
178 | |
179 | public function fetchHandle($query, $parameters = null) |
180 | { |
181 | return static::pdoExceptionHandler(function () use ($query, $parameters) { |
182 | $prepared = $this->fetchPreparedStatement($query); |
183 | $prepared->execute($parameters); |
184 | return $prepared; |
185 | }); |
186 | } |
187 | |
188 | public function fetchList(Query\Base $query, \BO\Zmsentities\Schema\Entity $entity, $resultList = []) |
189 | { |
190 | $statement = $this->fetchStatement($query); |
191 | while ($data = $statement->fetch(\PDO::FETCH_ASSOC)) { |
192 | $dataEntity = clone $entity; |
193 | $dataEntity->exchangeArray($query->postProcessJoins($data)); |
194 | $dataEntity->setResolveLevel($query->getResolveLevel()); |
195 | $resultList[] = $dataEntity; |
196 | } |
197 | $statement->closeCursor(); |
198 | return $resultList; |
199 | } |
200 | |
201 | /** |
202 | * Write an Item to database - Insert, Update |
203 | * |
204 | * @param Query\Base $query |
205 | * @return bool |
206 | */ |
207 | public function writeItem(Query\Base $query) |
208 | { |
209 | return static::pdoExceptionHandler(function () use ($query) { |
210 | $this->getWriter(); //Switch to writer for write/delete |
211 | $statement = $this->fetchPreparedStatement($query); |
212 | $status = $statement->execute($query->getParameters()); |
213 | $statement->closeCursor(); |
214 | return $status; |
215 | }); |
216 | } |
217 | |
218 | /** |
219 | * @param Query\Base $query |
220 | * @return bool |
221 | */ |
222 | public function deleteItem(Query\Base $query) |
223 | { |
224 | return $this->writeItem($query); |
225 | } |
226 | |
227 | public function perform($sql, $parameters = null) |
228 | { |
229 | return static::pdoExceptionHandler(function () use ($sql, $parameters) { |
230 | $this->getWriter(); //Switch to writer for perform |
231 | $prepared = $this->fetchPreparedStatement($sql); |
232 | $status = $prepared->execute($parameters); |
233 | $prepared->closeCursor(); |
234 | return $status; |
235 | }); |
236 | } |
237 | |
238 | public function fetchAffected($sql, $parameters = null) |
239 | { |
240 | return static::pdoExceptionHandler(function () use ($sql, $parameters) { |
241 | $this->getWriter(); //Switch to writer for perform |
242 | $prepared = $this->fetchPreparedStatement($sql); |
243 | $prepared->execute($parameters); |
244 | $count = $prepared->rowCount(); |
245 | $prepared->closeCursor(); |
246 | return $count; |
247 | }); |
248 | } |
249 | |
250 | /** |
251 | * @SuppressWarnings(Param) |
252 | * @codeCoverageIgnore |
253 | * |
254 | */ |
255 | public function readResolvedReferences(\BO\Zmsentities\Schema\Entity $entity, $resolveReferences) |
256 | { |
257 | return $entity; |
258 | } |
259 | |
260 | /** |
261 | * This function produces a hash value that contains info for comparison |
262 | * |
263 | * @param string $value |
264 | * @param callable|NULL $c (function to be used for hashing) |
265 | * @return string |
266 | */ |
267 | public function hashStringValue(string $value, callable $callable = null): string |
268 | { |
269 | if ($callable === null) { |
270 | $callable = 'sha1'; |
271 | } |
272 | |
273 | if (is_callable($callable)) { |
274 | $hash = $callable($value); |
275 | |
276 | if (is_string($callable)) { |
277 | return $callable . ':' . $hash; |
278 | } |
279 | if ($callable instanceof \Closure) { |
280 | return 'closure:' . $hash; |
281 | } |
282 | // else |
283 | return 'custom:' . $hash; |
284 | } |
285 | |
286 | throw new \InvalidArgumentException('hashStringValue() should be called with a callable as second parameter.'); |
287 | } |
288 | } |