Passed
Pull Request — master (#41)
by Thomas
03:07
created

Dbal::escapeValue()   D

Complexity

Conditions 9
Paths 16

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 18
cts 18
cp 1
rs 4.909
c 0
b 0
f 0
cc 9
eloc 20
nc 16
nop 1
crap 9
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
     */
37 704
    public function __construct(EntityManager $entityManager, $options = [])
38
    {
39 704
        $this->entityManager = $entityManager;
40
41 704
        foreach ($options as $option => $value) {
42 4
            $this->setOption($option, $value);
43
        }
44 704
    }
45
46
    /**
47
     * Set $option to $value
48
     *
49
     * @param string $option
50
     * @param mixed $value
51
     * @return self
52
     */
53 22
    public function setOption($option, $value)
54
    {
55
        switch ($option) {
56 22
            case EntityManager::OPT_IDENTIFIER_DIVIDER:
57 1
                $this->identifierDivider = $value;
58 1
                break;
59
60 21
            case EntityManager::OPT_QUOTING_CHARACTER:
61 1
                $this->quotingCharacter = $value;
62 1
                break;
63
64 20
            case EntityManager::OPT_BOOLEAN_TRUE:
65 19
                $this->booleanTrue = $value;
66 19
                break;
67
68 20
            case EntityManager::OPT_BOOLEAN_FALSE:
69 19
                $this->booleanFalse = $value;
70 19
                break;
71
        }
72 22
        return $this;
73
    }
74
75
    /**
76
     * Returns $identifier quoted for use in a sql statement
77
     *
78
     * @param string $identifier Identifier to quote
79
     * @return string
80
     */
81 184
    public function escapeIdentifier($identifier)
82
    {
83 184
        $q = $this->quotingCharacter;
84 184
        $d = $this->identifierDivider;
85 184
        return $q . str_replace($d, $q . $d . $q, $identifier) . $q;
86
    }
87
88
    /**
89
     * Returns $value formatted to use in a sql statement.
90
     *
91
     * @param  mixed  $value The variable that should be returned in SQL syntax
92
     * @return string
93
     * @throws NotScalar
94
     */
95 183
    public function escapeValue($value)
96
    {
97 183
        $type = gettype($value);
98 183
        if ($type === 'object') {
99 1
            $type = get_class($value);
100
        }
101
102
        switch ($type) {
103 183
            case 'string':
104 120
                return $this->entityManager->getConnection()->quote($value);
105
106 77
            case 'integer':
107 50
                return (string) $value;
108
109 27
            case 'double':
110 4
                return (string) $value;
111
112 23
            case 'NULL':
113 1
                return 'NULL';
114
115 22
            case 'boolean':
116 20
                return ($value) ? $this->booleanTrue : $this->booleanFalse;
117
118 2
            case 'DateTime':
119 1
                $value->setTimezone(new \DateTimeZone('UTC'));
120 1
                return $value->format('Y-m-d\TH:i:s.u\Z');
121
122
            default:
123 1
                throw new NotScalar('$value has to be scalar data type. ' . gettype($value) . ' given');
124
        }
125
    }
126
127
    /**
128
     * Describe a table
129
     *
130
     * @param string $table
131
     * @return Table|Column[]
132
     * @throws UnsupportedDriver
133
     */
134 1
    public function describe($table)
135
    {
136 1
        throw new UnsupportedDriver('Not supported for this driver');
137
    }
138
139
    /**
140
     * Inserts $entity and returns the new ID for autoincrement or true
141
     *
142
     * @param Entity $entity
143
     * @param bool   $useAutoIncrement
144
     * @return mixed
145
     * @throws UnsupportedDriver
146
     */
147 2
    public function insert($entity, $useAutoIncrement = true)
148
    {
149 2
        $statement = $this->buildInsertStatement($entity);
150
151 2
        if ($useAutoIncrement && $entity::isAutoIncremented()) {
152 1
            throw new UnsupportedDriver('Auto incremented column for this driver is not supported');
153
        }
154
155 1
        $this->entityManager->getConnection()->query($statement);
156 1
        $this->entityManager->sync($entity, true);
157 1
        return true;
158
    }
159
160
    /**
161
     * Update $entity in database
162
     *
163
     * @param Entity $entity
164
     * @return bool
165
     * @internal
166
     */
167 6
    public function update(Entity $entity)
168
    {
169 6
        $data = $entity->getData();
170 6
        $primaryKey = $entity->getPrimaryKey();
171
172 6
        $where = [];
173 6
        foreach ($primaryKey as $attribute => $value) {
174 6
            $col = $entity::getColumnName($attribute);
175 6
            $where[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value);
176 6
            if (isset($data[$col])) {
177 6
                unset($data[$col]);
178
            }
179
        }
180
181 6
        $set = [];
182 6
        foreach ($data as $col => $value) {
183 6
            $set[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value);
184
        }
185
186 6
        $statement = 'UPDATE ' . $this->escapeIdentifier($entity::getTableName()) . ' ' .
187 6
                     'SET ' . implode(',', $set) . ' ' .
188 6
                     'WHERE ' . implode(' AND ', $where);
189 6
        $this->entityManager->getConnection()->query($statement);
190
191 3
        return true;
192
    }
193
194
    /**
195
     * Delete $entity from database
196
     *
197
     * This method does not delete from the map - you can still receive the entity via fetch.
198
     *
199
     * @param Entity $entity
200
     * @return bool
201
     */
202 6
    public function delete(Entity $entity)
203
    {
204 6
        $primaryKey = $entity->getPrimaryKey();
205 6
        $where = [];
206 6
        foreach ($primaryKey as $attribute => $value) {
207 6
            $col = $entity::getColumnName($attribute);
208 6
            $where[] = $this->escapeIdentifier($col) . ' = ' . $this->escapeValue($value);
209
        }
210
211 6
        $statement = 'DELETE FROM ' . $this->escapeIdentifier($entity::getTableName()) . ' ' .
212 6
                     'WHERE ' . implode(' AND ', $where);
213 6
        $this->entityManager->getConnection()->query($statement);
214
215 4
        return true;
216
    }
217
218
    /**
219
     * Build the insert statement for $entity
220
     *
221
     * @param Entity $entity
222
     * @return string
223
     */
224 11
    protected function buildInsertStatement($entity)
225
    {
226 11
        $data = $entity->getData();
227
228
        $cols = array_map(function ($key) {
229 11
            return $this->escapeIdentifier($key);
230 11
        }, array_keys($data));
231
232 11
        $values = array_map(function ($value) use ($entity) {
233 11
            return $this->escapeValue($value);
234 11
        }, array_values($data));
235
236 11
        $statement = 'INSERT INTO ' . $this->escapeIdentifier($entity::getTableName()) . ' ' .
237 11
                     '(' . implode(',', $cols) . ') VALUES (' . implode(',', $values) . ')';
238
239 11
        return $statement;
240
    }
241
242
    /**
243
     * Normalize $type
244
     *
245
     * The type returned by mysql is for example VARCHAR(20) - this function converts it to varchar
246
     *
247
     * @param string $type
248
     * @return string
249
     */
250 79
    protected function normalizeType($type)
251
    {
252 79
        $type = strtolower($type);
253
254 79
        if (($p = strpos($type, '(')) !== false && $p > 0) {
255 33
            $type = substr($type, 0, $p);
256
        }
257
258 79
        return trim($type);
259
    }
260
261
    /**
262
     * Extract content from parenthesis in $type
263
     *
264
     * @param string $type
265
     * @return string
266
     */
267 24
    protected function extractParenthesis($type)
268
    {
269 24
        if (preg_match('/\((.+)\)/', $type, $match)) {
270 16
            return $match[1];
271
        }
272
273 8
        return null;
274
    }
275
}
276