Passed
Push — master ( 9b63e5...2b9e2e )
by Gabor
03:08
created

MySQLAdapter::getSimpleColumnCondition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @copyright 2012 - 2017 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
declare(strict_types = 1);
13
14
namespace WebHemi\Adapter\Data\PDO;
15
16
use InvalidArgumentException;
17
use PDO;
18
use PDOStatement;
19
use RuntimeException;
20
use WebHemi\Adapter\Data\DataAdapterInterface;
21
use WebHemi\Adapter\Data\DataDriverInterface;
22
23
/**
24
 * Class MySQLAdapter.
25
 */
26
class MySQLAdapter implements DataAdapterInterface
27
{
28
    /** @var PDO */
29
    protected $dataDriver;
30
    /** @var string */
31
    protected $dataGroup = null;
32
    /** @var string */
33
    protected $idKey = null;
34
35
    /**
36
     * MySQLAdapter constructor.
37
     *
38
     * @param DataDriverInterface $dataDriver
39
     * @throws InvalidArgumentException
40
     */
41 16
    public function __construct(DataDriverInterface $dataDriver)
42
    {
43 16
        if (!$dataDriver instanceof MySQLDriver) {
44 1
            $type = gettype($dataDriver);
45
46 1
            if ($type == 'object') {
47 1
                $type = get_class($dataDriver);
48
            }
49
50 1
            $message = sprintf(
51 1
                'Can\'t create %s instance. The parameter must be an instance of MySQLDriver, %s given.',
52 1
                __CLASS__,
53
                $type
54
            );
55
56 1
            throw new InvalidArgumentException($message, 1001);
57
        }
58
59 16
        $this->dataDriver = $dataDriver;
60 16
    }
61
62
    /**
63
     * Returns the Data Storage instance.
64
     *
65
     * @return DataDriverInterface
66
     */
67 1
    public function getDataDriver() : DataDriverInterface
68
    {
69 1
        return $this->dataDriver;
70
    }
71
72
    /**
73
     * Set adapter data group. For Databases this can be the Tables.
74
     *
75
     * @param string $dataGroup
76
     * @return DataAdapterInterface
77
     */
78 11
    public function setDataGroup(string $dataGroup) : DataAdapterInterface
79
    {
80 11
        $this->dataGroup = $dataGroup;
81
82 11
        return $this;
83
    }
84
85
    /**
86
     * Set adapter ID key. For Databases this can be the Primary key. Only simple key is allowed.
87
     *
88
     * @param string $idKey
89
     * @return DataAdapterInterface
90
     */
91 3
    public function setIdKey(string $idKey) : DataAdapterInterface
92
    {
93 3
        $this->idKey = $idKey;
94
95 3
        return $this;
96
    }
97
98
    /**
99
     * Get exactly one "row" of data according to the expression.
100
     *
101
     * @param int $identifier
102
     * @return array
103
     *
104
     * @codeCoverageIgnore Don't test external library.
105
     */
106
    public function getData(int $identifier) : array
107
    {
108
        $queryBinds = [];
109
110
        $query = $this->getSelectQueryForExpression([$this->idKey => $identifier], $queryBinds, 1, 0);
111
        $statement = $this->dataDriver->prepare($query);
112
        $this->bindValuesToStatement($statement, $queryBinds);
113
        $statement->execute();
114
115
        $data = $statement->fetch(PDO::FETCH_ASSOC);
116
117
        return $data ? $data : [];
118
    }
119
120
    /**
121
     * Get a set of data according to the expression and the chunk.
122
     *
123
     * @param array $expression
124
     * @param int   $limit
125
     * @param int   $offset
126
     * @return array
127
     *
128
     * @codeCoverageIgnore Don't test external library.
129
     */
130
    public function getDataSet(array $expression, int $limit = PHP_INT_MAX, int $offset = 0) : array
131
    {
132
        $queryBinds = [];
133
134
        $query = $this->getSelectQueryForExpression($expression, $queryBinds, $limit, $offset);
135
        $statement = $this->dataDriver->prepare($query);
136
        $this->bindValuesToStatement($statement, $queryBinds);
137
        $statement->execute();
138
139
        $data = $statement->fetchAll(PDO::FETCH_ASSOC);
140
141
        return $data ? $data : [];
142
    }
143
144
    /**
145
     * Get the number of matched data in the set according to the expression.
146
     *
147
     * @param array $expression
148
     * @return int
149
     *
150
     * @codeCoverageIgnore Don't test external library.
151
     */
152
    public function getDataCardinality(array $expression) : int
153
    {
154
        $queryBinds = [];
155
156
        $query = $this->getSelectQueryForExpression($expression, $queryBinds);
157
        $statement = $this->dataDriver->prepare($query);
158
        $this->bindValuesToStatement($statement, $queryBinds);
159
        $statement->execute();
160
161
        return $statement->rowCount();
162
    }
163
164
    /**
165
     * Builds SQL query from the expression.
166
     *
167
     * @param array $expression
168
     * @param array $queryBinds
169
     * @param int   $limit
170
     * @param int   $offset
171
     * @return string
172
     */
173 9
    protected function getSelectQueryForExpression(
174
        array $expression,
175
        array&$queryBinds,
176
        int $limit = PHP_INT_MAX,
177
        int $offset = 0
178
    ) : string {
179 9
        $query = "SELECT * FROM {$this->dataGroup}";
180
181
        // Prepare WHERE expression.
182 9
        if (!empty($expression)) {
183 8
            $query .= $this->getWhereExpression($expression, $queryBinds);
184
        }
185
186 9
        $query .= " LIMIT {$limit}";
187 9
        $query .= " OFFSET {$offset}";
188
189 9
        return $query;
190
    }
191
192
    /**
193
     * Creates a WHERE expression for the SQL query.
194
     *
195
     * @param array $expression
196
     * @param array $queryBinds
197
     * @return string
198
     */
199 12
    protected function getWhereExpression(array $expression, array&$queryBinds) : string
200
    {
201 12
        $whereExpression = '';
202 12
        $queryParams = [];
203
204 12
        foreach ($expression as $column => $value) {
205 11
            $this->setParamsAndBinds($column, $value, $queryParams, $queryBinds);
206
        }
207
208 12
        if (!empty($queryParams)) {
209 11
            $whereExpression = ' WHERE '.implode(' AND ', $queryParams);
210
        }
211
212 12
        return $whereExpression;
213
    }
214
215
    /**
216
     * Set the query params and quaery bindings according to the `column` and `value`.
217
     *
218
     * @param string $column
219
     * @param mixed  $value
220
     * @param array  $queryParams
221
     * @param array  $queryBinds
222
     */
223 11
    protected function setParamsAndBinds(string $column, $value, array&$queryParams, array&$queryBinds) : void
224
    {
225 11
        if (is_array($value)) {
226 5
            $queryParams[] = $this->getInColumnCondition($column, count($value));
227 5
            $queryBinds = array_merge($queryBinds, $value);
228 11
        } elseif (strpos($column, ' LIKE') !== false || (is_string($value) && strpos($value, '%') !== false)) {
229 4
            $queryParams[] = $this->getLikeColumnCondition($column);
230 4
            $queryBinds[] = $value;
231
        } else {
232 11
            $queryParams[] = $this->getSimpleColumnCondition($column);
233 11
            $queryBinds[] = $value;
234
        }
235 11
    }
236
237
    /**
238
     * Gets a simple condition for the column.
239
     *
240
     * @param string $column
241
     * @return string 'my_column = ?'
242
     */
243 11
    protected function getSimpleColumnCondition(string $column) : string
244
    {
245 11
        return strpos($column, '?') === false ? "{$column} = ?" : $column;
246
    }
247
248
    /**
249
     * Gets a 'LIKE' condition for the column.
250
     *
251
     * Allows special cases:
252
     * @example  ['my_column LIKE ?' => 'some value%']
253
     * @example  ['my_column LIKE' => 'some value%']
254
     * @example  ['my_column' => 'some value%']
255
     *
256
     * @param string $column
257
     * @return string 'my_column LIKE ?'
258
     */
259 4
    protected function getLikeColumnCondition(string $column) : string
260
    {
261 4
        list($columnNameOnly) = explode(' ', $column);
262
263 4
        return $columnNameOnly.' LIKE ?';
264
    }
265
266
    /**
267
     * Gets an 'IN' condition for the column.
268
     *
269
     * Allows special cases:
270
     * @example  ['my_column IN (?)' => [1,2,3]]
271
     * @example  ['my_column IN ?' => [1,2,3]]
272
     * @example  ['my_column IN' => [1,2,3]]
273
     * @example  ['my_column' => [1,2,3]]
274
     *
275
     * @param string $column
276
     * @param int    $parameterCount
277
     * @return string 'my_column IN (?,?,?)'
278
     */
279 5
    protected function getInColumnCondition(string $column, int $parameterCount = 1) : string
280
    {
281 5
        list($columnNameOnly) = explode(' ', $column);
282
283 5
        $inParameters = str_repeat('?,', $parameterCount - 1).'?';
284
285 5
        return $columnNameOnly.' IN ('.$inParameters.')';
286
    }
287
288
    /**
289
     * Insert or update entity in the storage.
290
     *
291
     * @param int $identifier
292
     * @param array $data
293
     * @throws RuntimeException
294
     * @return int The ID of the saved entity in the storage
295
     *
296
     * @codeCoverageIgnore Don't test external library.
297
     */
298
    public function saveData(? int $identifier = null, array $data) : int
299
    {
300
        if (empty($identifier)) {
301
            $query = "INSERT INTO {$this->dataGroup}";
302
        } else {
303
            $query = "UPDATE {$this->dataGroup}";
304
        }
305
306
        $queryData = [];
307
        $queryBind = [];
308
309
        foreach ($data as $fieldName => $value) {
310
            $queryData[] = "{$fieldName} = ?";
311
            $queryBind[] = $value;
312
        }
313
314
        $query .= ' SET '.implode(', ', $queryData);
315
316
        if (!empty($identifier)) {
317
            $query .= " WHERE {$this->idKey} = ?";
318
            $queryBind[] = $identifier;
319
        }
320
321
        $statement = $this->dataDriver->prepare($query);
322
        if (!$statement) {
323
            throw new RuntimeException('Query error', 1002);
324
        }
325
        $this->bindValuesToStatement($statement, $queryBind);
326
        $statement->execute();
327
328
        return empty($identifier) ? (int) $this->dataDriver->lastInsertId() : $identifier;
329
    }
330
331
    /**
332
     * Binds values to the statement.
333
     *
334
     * @param PDOStatement $statement
335
     * @param array        $queryBind
336
     * @return void
337
     *
338
     * @codeCoverageIgnore Don't test external library.
339
     */
340
    protected function bindValuesToStatement(PDOStatement&$statement, array $queryBind) : void
341
    {
342
        foreach ($queryBind as $index => $data) {
343
            $paramType = PDO::PARAM_STR;
344
345
            if (is_null($data)) {
346
                $paramType = PDO::PARAM_NULL;
347
            } elseif (is_numeric($data)) {
348
                $paramType = PDO::PARAM_INT;
349
            }
350
351
            $statement->bindValue($index + 1, $data, $paramType);
352
        }
353
    }
354
355
    /**
356
     * Removes an entity from the storage.
357
     *
358
     * @param int $identifier
359
     * @return bool
360
     *
361
     * @codeCoverageIgnore Don't test external library.
362
     */
363
    public function deleteData(int $identifier) : bool
364
    {
365
        $statement = $this->dataDriver->prepare("DELETE FROM WHERE {$this->idKey} = ?");
366
367
        return $statement->execute([$identifier]);
368
    }
369
}
370