Passed
Push — master ( 64ab61...b96172 )
by Gabor
09:36
created

MySQLAdapter::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

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