Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.73% covered (success)
95.73%
112 / 117
94.74% covered (success)
94.74%
18 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Base
95.73% covered (success)
95.73%
112 / 117
94.74% covered (success)
94.74%
18 / 19
34
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 init
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getWriter
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getReader
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 fetchPreparedStatement
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 startExecute
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 pdoExceptionHandler
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 fetchStatement
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 fetchOne
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 fetchRow
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 fetchValue
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 fetchAll
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 fetchHandle
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 fetchList
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 writeItem
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 deleteItem
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 perform
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 fetchAffected
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 readResolvedReferences
n/a
0 / 0
n/a
0 / 0
1
 hashStringValue
50.00% covered (danger)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
8.12
1<?php
2
3namespace BO\Zmsdb;
4
5/**
6 * @SuppressWarnings(NumberOfChildren)
7 * @SuppressWarnings(Public)
8 *
9 */
10abstract 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}