Passed
Push — master ( 050c29...4fa43a )
by Thomas
58s
created

Dbal   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 35
c 1
b 0
f 0
lcom 1
cbo 4
dl 0
loc 327
ccs 104
cts 104
cp 1
rs 9

18 Methods

Rating   Name   Duplication   Size   Complexity  
B update() 0 26 4
A delete() 0 15 2
A __construct() 0 8 2
B setOption() 0 21 5
A escapeIdentifier() 0 6 1
A escapeValue() 0 11 3
A describe() 0 4 1
A insert() 0 11 3
A buildInsertStatement() 0 23 1
A updateAutoincrement() 0 8 1
A normalizeType() 0 10 3
A extractParenthesis() 0 8 2
A escapeString() 0 4 1
A escapeInteger() 0 4 1
A escapeDouble() 0 4 1
A escapeNULL() 0 4 1
A escapeBoolean() 0 4 2
A escapeDateTime() 0 5 1
1
<?php
2
3
namespace ORM\Dbal;
4
5
use ORM\Entity;
6
use ORM\EntityManager;
7
use ORM\Exception\NotScalar;
8
use ORM\Exception\UnsupportedDriver;
9
10
/**
11
 * Base class for database abstraction
12
 *
13
 * @package ORM
14
 * @author  Thomas Flori <[email protected]>
15
 */
16
abstract class Dbal
17
{
18
    /** @var array */
19
    protected static $typeMapping = [];
20
21
    /** @var EntityManager */
22
    protected $entityManager;
23
    /** @var string */
24
    protected $quotingCharacter = '"';
25
    /** @var string */
26
    protected $identifierDivider = '.';
27
    /** @var string */
28
    protected $booleanTrue = '1';
29
    /** @var string */
30
    protected $booleanFalse = '0';
31
32
    /**
33
     * Dbal constructor.
34
     *
35
     * @param EntityManager $entityManager
36
     * @param array         $options
37
     */
38 742
    public function __construct(EntityManager $entityManager, array $options = [])
39
    {
40 742
        $this->entityManager = $entityManager;
41
42 742
        foreach ($options as $option => $value) {
43 4
            $this->setOption($option, $value);
44
        }
45 742
    }
46
47
    /**
48
     * Set $option to $value
49
     *
50
     * @param string $option
51
     * @param mixed  $value
52
     * @return self
53
     */
54 22
    public function setOption($option, $value)
55
    {
56
        switch ($option) {
57 22
            case EntityManager::OPT_IDENTIFIER_DIVIDER:
58 1
                $this->identifierDivider = $value;
59 1
                break;
60
61 21
            case EntityManager::OPT_QUOTING_CHARACTER:
62 1
                $this->quotingCharacter = $value;
63 1
                break;
64
65 20
            case EntityManager::OPT_BOOLEAN_TRUE:
66 19
                $this->booleanTrue = $value;
67 19
                break;
68
69 19
            case EntityManager::OPT_BOOLEAN_FALSE:
70 19
                $this->booleanFalse = $value;
71 19
                break;
72
        }
73 22
        return $this;
74
    }
75
76
    /**
77
     * Returns $identifier quoted for use in a sql statement
78
     *
79
     * @param string $identifier Identifier to quote
80
     * @return string
81
     */
82 191
    public function escapeIdentifier($identifier)
83
    {
84 191
        $q = $this->quotingCharacter;
85 191
        $d = $this->identifierDivider;
86 191
        return $q . str_replace($d, $q . $d . $q, $identifier) . $q;
87
    }
88
89
    /**
90
     * Returns $value formatted to use in a sql statement.
91
     *
92
     * @param  mixed $value The variable that should be returned in SQL syntax
93
     * @return string
94
     * @throws NotScalar
95
     */
96 184
    public function escapeValue($value)
97
    {
98 184
        $type   = is_object($value) ? get_class($value) : gettype($value);
99 184
        $method = [ $this, 'escape' . ucfirst($type) ];
100
101 184
        if (is_callable($method)) {
102 183
            return call_user_func($method, $value);
103
        } else {
104 1
            throw new NotScalar('$value has to be scalar data type. ' . gettype($value) . ' given');
105
        }
106
    }
107
108
    /**
109
     * Describe a table
110
     *
111
     * @param string $table
112
     * @return Table|Column[]
113
     * @throws UnsupportedDriver
114
     */
115 1
    public function describe($table)
116
    {
117 1
        throw new UnsupportedDriver('Not supported for this driver');
118
    }
119
120
    /**
121
     * Inserts $entity in database and returns success
122
     *
123
     * @param Entity $entity
124
     * @param bool   $useAutoIncrement
125
     * @return bool
126
     * @throws UnsupportedDriver
127
     */
128 2
    public function insert(Entity $entity, $useAutoIncrement = true)
129
    {
130 2
        $statement = $this->buildInsertStatement($entity);
131
132 2
        if ($useAutoIncrement && $entity::isAutoIncremented()) {
133 1
            throw new UnsupportedDriver('Auto incremented column for this driver is not supported');
134
        }
135
136 1
        $this->entityManager->getConnection()->query($statement);
137 1
        return $this->entityManager->sync($entity, true);
138
    }
139
140
    /**
141
     * Update $entity in database and returns success
142
     *
143
     * @param Entity $entity
144
     * @return bool
145
     * @internal
146
     */
147 6
    public function update(Entity $entity)
148
    {
149 6
        $data       = $entity->getData();
150 6
        $primaryKey = $entity->getPrimaryKey();
151
152 6
        $where = [];
153 6
        foreach ($primaryKey as $attribute => $value) {
154 6
            $col     = $entity::getColumnName($attribute);
155 6
            $where[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value);
156 6
            if (isset($data[$col])) {
157 6
                unset($data[$col]);
158
            }
159
        }
160
161 6
        $set = [];
162 6
        foreach ($data as $col => $value) {
163 6
            $set[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value);
164
        }
165
166 6
        $statement = 'UPDATE ' . $this->escapeIdentifier($entity::getTableName()) . ' ' .
167 6
                     'SET ' . implode(',', $set) . ' ' .
168 6
                     'WHERE ' . implode(' AND ', $where);
169 6
        $this->entityManager->getConnection()->query($statement);
170
171 3
        return $this->entityManager->sync($entity, true);
172
    }
173
174
    /**
175
     * Delete $entity from database
176
     *
177
     * This method does not delete from the map - you can still receive the entity via fetch.
178
     *
179
     * @param Entity $entity
180
     * @return bool
181
     */
182 6
    public function delete(Entity $entity)
183
    {
184 6
        $primaryKey = $entity->getPrimaryKey();
185 6
        $where      = [];
186 6
        foreach ($primaryKey as $attribute => $value) {
187 6
            $col     = $entity::getColumnName($attribute);
188 6
            $where[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value);
189
        }
190
191 6
        $statement = 'DELETE FROM ' . $this->escapeIdentifier($entity::getTableName()) . ' ' .
192 6
                     'WHERE ' . implode(' AND ', $where);
193 6
        $this->entityManager->getConnection()->query($statement);
194
195 4
        return true;
196
    }
197
198
    /**
199
     * Build the insert statement for $entity
200
     *
201
     * @param Entity $entity
202
     * @return string
203
     */
204 12
    protected function buildInsertStatement($entity)
205
    {
206 12
        $data = $entity->getData();
207
208 12
        $cols = array_map(
209
            function ($key) {
210 11
                return $this->escapeIdentifier($key);
211 12
            },
212
            array_keys($data)
213
        );
214
215 12
        $values = array_map(
216 12
            function ($value) use ($entity) {
217 11
                return $this->escapeValue($value);
218 12
            },
219
            array_values($data)
220
        );
221
222 12
        $statement = 'INSERT INTO ' . $this->escapeIdentifier($entity::getTableName()) . ' ' .
223 12
                     '(' . implode(',', $cols) . ') VALUES (' . implode(',', $values) . ')';
224
225 12
        return $statement;
226
    }
227
228
    /**
229
     * Update the autoincrement value
230
     *
231
     * @param Entity     $entity
232
     * @param int|string $value
233
     */
234 3
    protected function updateAutoincrement(Entity $entity, $value)
235
    {
236 3
        $var    = $entity::getPrimaryKeyVars()[0];
237 3
        $column = $entity::getColumnName($var);
238
239 3
        $entity->setOriginalData(array_merge($entity->getData(), [ $column => $value ]));
240 3
        $entity->__set($var, $value);
241 3
    }
242
243
    /**
244
     * Normalize $type
245
     *
246
     * The type returned by mysql is for example VARCHAR(20) - this function converts it to varchar
247
     *
248
     * @param string $type
249
     * @return string
250
     */
251 79
    protected function normalizeType($type)
252
    {
253 79
        $type = strtolower($type);
254
255 79
        if (($p = strpos($type, '(')) !== false && $p > 0) {
256 33
            $type = substr($type, 0, $p);
257
        }
258
259 79
        return trim($type);
260
    }
261
262
    /**
263
     * Extract content from parenthesis in $type
264
     *
265
     * @param string $type
266
     * @return string
267
     */
268 24
    protected function extractParenthesis($type)
269
    {
270 24
        if (preg_match('/\((.+)\)/', $type, $match)) {
271 16
            return $match[1];
272
        }
273
274 8
        return null;
275
    }
276
277
    /**
278
     * Escape a string for query
279
     *
280
     * @param string $value
281
     * @return string
282
     */
283 120
    protected function escapeString($value)
284
    {
285 120
        return $this->entityManager->getConnection()->quote($value);
286
    }
287
288
    /**
289
     * Escape an integer for query
290
     *
291
     * @param int $value
292
     * @return string
293
     */
294 51
    protected function escapeInteger($value)
295
    {
296 51
        return (string) $value;
297
    }
298
299
    /**
300
     * Escape a double for Query
301
     *
302
     * @param double $value
303
     * @return string
304
     */
305 4
    protected function escapeDouble($value)
306
    {
307 4
        return (string) $value;
308
    }
309
310
    /**
311
     * Escape NULL for query
312
     *
313
     * @return string
314
     */
315 1
    protected function escapeNULL()
316
    {
317 1
        return 'NULL';
318
    }
319
320
    /**
321
     * Escape a boolean for query
322
     *
323
     * @param bool $value
324
     * @return string
325
     */
326 20
    protected function escapeBoolean($value)
327
    {
328 20
        return ($value) ? $this->booleanTrue : $this->booleanFalse;
329
    }
330
331
    /**
332
     * Escape a date time object for query
333
     *
334
     * @param \DateTime $value
335
     * @return mixed
336
     */
337 1
    protected function escapeDateTime(\DateTime $value)
338
    {
339 1
        $value->setTimezone(new \DateTimeZone('UTC'));
340 1
        return $value->format('Y-m-d\TH:i:s.u\Z');
341
    }
342
}
343