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