Completed
Pull Request — master (#36)
by Thomas
02:53
created

Dbal   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 38
lcom 2
cbo 5
dl 0
loc 313
ccs 104
cts 104
cp 1
rs 8.3999
c 0
b 0
f 0

20 Methods

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