Passed
Push — master ( 749b16...167cfa )
by Gabor
04:47
created

MySQLAdapter::getWhereExpression()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 15
cts 15
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 16
nc 8
nop 2
crap 7
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 10
    public function setDataGroup(string $dataGroup) : DataAdapterInterface
79
    {
80 10
        $this->dataGroup = $dataGroup;
81
82 10
        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 2
    public function setIdKey(string $idKey) : DataAdapterInterface
92
    {
93 2
        $this->idKey = $idKey;
94
95 2
        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
        $queryBind = [];
109
110
        $query = $this->getSelectQueryForExpression([$this->idKey => $identifier], $queryBind, 1, 0);
111
        $statement = $this->dataDriver->prepare($query);
112
        $this->bindValuesToStatement($statement, $queryBind);
113
        $statement->execute();
114
115
        return $statement->fetch(PDO::FETCH_ASSOC);
116
    }
117
118
    /**
119
     * Get a set of data according to the expression and the chunk.
120
     *
121
     * @param array $expression
122
     * @param int   $limit
123
     * @param int   $offset
124
     * @return array
125
     *
126
     * @codeCoverageIgnore Don't test external library.
127
     */
128
    public function getDataSet(array $expression, int $limit = PHP_INT_MAX, int $offset = 0) : array
129
    {
130
        $queryBind = [];
131
132
        $query = $this->getSelectQueryForExpression($expression, $queryBind, $limit, $offset);
133
        $statement = $this->dataDriver->prepare($query);
134
        $this->bindValuesToStatement($statement, $queryBind);
135
        $statement->execute();
136
137
        return $statement->fetchAll(PDO::FETCH_ASSOC);
138
    }
139
140
    /**
141
     * Get the number of matched data in the set according to the expression.
142
     *
143
     * @param array $expression
144
     * @return int
145
     *
146
     * @codeCoverageIgnore Don't test external library.
147
     */
148
    public function getDataCardinality(array $expression) : int
149
    {
150
        $queryBind = [];
151
152
        $query = $this->getSelectQueryForExpression($expression, $queryBind);
153
        $statement = $this->dataDriver->prepare($query);
154
        $this->bindValuesToStatement($statement, $queryBind);
155
        $statement->execute();
156
157
        return $statement->rowCount();
158
    }
159
160
    /**
161
     * Builds SQL query from the expression.
162
     *
163
     * @param array $expression
164
     * @param array $queryBind
165
     * @param int   $limit
166
     * @param int   $offset
167
     * @return string
168
     */
169 8
    protected function getSelectQueryForExpression(
170
        array $expression,
171
        array &$queryBind,
172
        int $limit = PHP_INT_MAX,
173
        int $offset = 0
174
    ) : string {
175 8
        $query = "SELECT * FROM {$this->dataGroup}";
176
177
        // Prepare WHERE expression.
178 8
        if (!empty($expression)) {
179 7
            $query .= $this->getWhereExpression($expression, $queryBind);
180
        }
181
182 8
        $query .= " LIMIT {$limit}";
183 8
        $query .= " OFFSET {$offset}";
184
185 8
        return $query;
186
    }
187
188
    /**
189
     * Creates a WHERE expression for the SQL query.
190
     *
191
     * @param array $expression
192
     * @param array $queryBind
193
     * @return string
194
     */
195 11
    protected function getWhereExpression(array $expression, array &$queryBind) : string
196
    {
197 11
        $whereExpression = '';
198 11
        $queryParams = [];
199
200 11
        foreach ($expression as $column => $value) {
201 10
            if (is_array($value)) {
202 4
                $queryParams[] = $this->getInColumnCondition($column, count($value));
203 4
                $queryBind = array_merge($queryBind, $value);
204 10
            } elseif (strpos($column, ' LIKE') !== false || (is_string($value) && strpos($value, '%') !== false)) {
205 4
                $queryParams[] = $this->getLikeColumnCondition($column);
206 4
                $queryBind[] = $value;
207
            } else {
208 10
                $queryParams[] = $this->getSimpleColumnCondition($column);
209 10
                $queryBind[] = $value;
210
            }
211
        }
212
213 11
        if (!empty($queryParams)) {
214 10
            $whereExpression = ' WHERE '.implode(' AND ', $queryParams);
215
        }
216
217 11
        return $whereExpression;
218
    }
219
220
    /**
221
     * Gets a simple condition for the column.
222
     *
223
     * @param string $column
224
     * @return string 'my_column = ?'
225
     */
226 10
    protected function getSimpleColumnCondition(string $column) : string
227
    {
228 10
        return strpos($column, '?') === false ? "{$column} = ?" : $column;
229
    }
230
231
    /**
232
     * Gets a 'LIKE' condition for the column.
233
     *
234
     * Allows special cases:
235
     * @example  ['my_column LIKE ?' => 'some value%']
236
     * @example  ['my_column LIKE' => 'some value%']
237
     * @example  ['my_column' => 'some value%']
238
     *
239
     * @param string $column
240
     * @return string 'my_column LIKE ?'
241
     */
242 4
    protected function getLikeColumnCondition(string $column) : string
243
    {
244 4
        list($columnNameOnly) = explode(' ', $column);
245
246 4
        return $columnNameOnly.' LIKE ?';
247
    }
248
249
    /**
250
     * Gets an 'IN' condition for the column.
251
     *
252
     * Allows special cases:
253
     * @example  ['my_column IN (?)' => [1,2,3]]
254
     * @example  ['my_column IN ?' => [1,2,3]]
255
     * @example  ['my_column IN' => [1,2,3]]
256
     * @example  ['my_column' => [1,2,3]]
257
     *
258
     * @param string $column
259
     * @param int    $parameterCount
260
     * @return string 'my_column IN (?,?,?)'
261
     */
262 4
    protected function getInColumnCondition(string $column, int $parameterCount = 1) : string
263
    {
264 4
        list($columnNameOnly) = explode(' ', $column);
265
266 4
        $inParameters = str_repeat('?,', $parameterCount - 1).'?';
267
268 4
        return $columnNameOnly.' IN ('.$inParameters.')';
269
    }
270
271
    /**
272
     * Insert or update entity in the storage.
273
     *
274
     * @param int $identifier
275
     * @param array $data
276
     * @throws RuntimeException
277
     * @return int The ID of the saved entity in the storage
278
     *
279
     * @codeCoverageIgnore Don't test external library.
280
     */
281
    public function saveData(?int $identifier = null, array $data) : int
282
    {
283
        if (empty($identifier)) {
284
            $query = "INSERT INTO {$this->dataGroup}";
285
        } else {
286
            $query = "UPDATE {$this->dataGroup}";
287
        }
288
289
        $queryData = [];
290
        $queryBind = [];
291
292
        foreach ($data as $fieldName => $value) {
293
            $queryData[] = "{$fieldName} = ?";
294
            $queryBind[] = $value;
295
        }
296
297
        $query .= ' SET '.implode(', ', $queryData);
298
299
        if (!empty($identifier)) {
300
            $query .= " WHERE {$this->idKey} = ?";
301
            $queryBind[] = $identifier;
302
        }
303
304
        $statement = $this->dataDriver->prepare($query);
305
        if (!$statement) {
306
            throw new RuntimeException('Query error', 1002);
307
        }
308
        $this->bindValuesToStatement($statement, $queryBind);
309
        $statement->execute();
310
311
        return empty($identifier) ? (int)$this->dataDriver->lastInsertId() : $identifier;
312
    }
313
314
    /**
315
     * Binds values to the statement.
316
     *
317
     * @param PDOStatement $statement
318
     * @param array        $queryBind
319
     * @return void
320
     *
321
     * @codeCoverageIgnore Don't test external library.
322
     */
323
    protected function bindValuesToStatement(PDOStatement&$statement, array $queryBind) : void
324
    {
325
        foreach ($queryBind as $index => $data) {
326
            $paramType = PDO::PARAM_STR;
327
328
            if (is_null($data)) {
329
                $paramType = PDO::PARAM_NULL;
330
            } elseif (is_numeric($data)) {
331
                $paramType = PDO::PARAM_INT;
332
            }
333
334
            $statement->bindValue($index + 1, $data, $paramType);
335
        }
336
    }
337
338
    /**
339
     * Removes an entity from the storage.
340
     *
341
     * @param int $identifier
342
     * @return bool
343
     *
344
     * @codeCoverageIgnore Don't test external library.
345
     */
346
    public function deleteData(int $identifier) : bool
347
    {
348
        $statement = $this->dataDriver->prepare("DELETE FROM WHERE {$this->idKey} = ?");
349
350
        return $statement->execute([$identifier]);
351
    }
352
}
353