Completed
Push — master ( 7e3127...a09c74 )
by Gabor
04:27
created

InMemoryAdapter::getDataSet()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 0
loc 29
ccs 19
cts 19
cp 1
rs 8.439
cc 6
eloc 17
nc 3
nop 3
crap 6
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\InMemory;
13
14
use WebHemi\Adapter\Data\DataAdapterInterface;
15
use WebHemi\Adapter\Exception\InitException;
16
use WebHemi\Adapter\Exception\InvalidArgumentException;
17
18
/**
19
 * Class InMemoryAdapter.
20
 */
21
class InMemoryAdapter implements DataAdapterInterface
22
{
23
    const EXPRESSION_IN_ARRAY = 'IN';
24
    const EXPRESSION_WILDCARD = 'LIKE';
25
26
    /** @var array */
27
    private $dataStorage;
28
    /** @var string */
29
    private $dataGroup = 'default';
30
    /** @var string */
31
    private $idKey = 'id';
32
33
    /**
34
     * PDOAdapter constructor.
35
     *
36
     * @param mixed $dataStorage
37
     *
38
     * @throws InvalidArgumentException
39
     */
40 12
    public function __construct($dataStorage = null)
41
    {
42 12
        if (empty($dataStorage)) {
43 8
            $dataStorage = [];
44 8
        }
45
46 12
        if (!is_array($dataStorage)) {
47 1
            throw new InvalidArgumentException('The constructor parameter must be empty or an array.');
48
        }
49
50 11
        foreach ($dataStorage as $rowData) {
51 3
            if (!is_array($rowData)) {
52 1
                throw new InvalidArgumentException('The constructor parameter if present must be an array of arrays.');
53
            }
54 10
        }
55
56 10
        $this->dataStorage[$this->dataGroup] = $dataStorage;
57 10
    }
58
59
    /**
60
     * Returns the Data Storage instance.
61
     *
62
     * @return array
63
     */
64 9
    public function getDataStorage()
65
    {
66 9
        return $this->dataStorage;
67
    }
68
69
    /**
70
     * Set adapter data group.
71
     *
72
     * @param string $dataGroup
73
     *
74
     * @throws InitException
75
     *
76
     * @return InMemoryAdapter
77
     */
78 6
    public function setDataGroup($dataGroup)
79
    {
80
        // Allow to change only once.
81 6
        if ($this->dataGroup !== 'default') {
82 1
            throw new InitException('Can\'t re-initialize dataGroup property. Property is already set.');
83
        }
84
85 6
        $this->dataGroup = $dataGroup;
86
87
        // Copy all previous init data.
88 6
        $this->dataStorage[$dataGroup] = $this->dataStorage['default'];
89 6
        unset($this->dataStorage['default']);
90
91 6
        return $this;
92
    }
93
94
    /**
95
     * Set adapter ID key. For Databases this can be the Primary key. Only simple key is allowed.
96
     *
97
     * @param string $idKey
98
     *
99
     * @throws InitException
100
     *
101
     * @return $this
102
     */
103 6
    public function setIdKey($idKey)
104
    {
105
        // Allow to change only once.
106 6
        if ($this->idKey !== 'id') {
107 1
            throw new InitException('Can\'t re-initialize idKey property. Property is already set.');
108
        }
109
110 6
        $this->idKey = $idKey;
111
112 6
        return $this;
113
    }
114
115
    /**
116
     * Get exactly one "row" of data according to the expression.
117
     *
118
     * @param mixed $identifier
119
     *
120
     * @return array
121
     */
122 2
    public function getData($identifier)
123
    {
124 2
        $result = [];
125
126 2
        $dataStorage = $this->getDataStorage()[$this->dataGroup];
127
128 2
        if (isset($dataStorage[$identifier])) {
129 2
            $result = $dataStorage[$identifier];
130 2
        }
131
132 2
        return $result;
133
    }
134
135
    /**
136
     * Get a set of data according to the expression and the chunk.
137
     *
138
     * @param array $expression
139
     * @param int   $limit
140
     * @param int   $offset
141
     *
142
     * @return array
143
     */
144 2
    public function getDataSet(array $expression, $limit = self::DATA_SET_RECORD_LIMIT, $offset = 0)
145
    {
146 2
        $result = [];
147
148 2
        $dataStorage = $this->getDataStorage()[$this->dataGroup];
149 2
        $limitCounter = 0;
150 2
        $offsetCounter = 0;
151
152 2
        foreach ($dataStorage as $data) {
153 2
            $match = $this->isExpressionMatch($expression, $data);
154 2
            $offsetReached = $offsetCounter >= $offset;
155
156 2
            if ($limitCounter >= $limit) {
157 1
                break;
158
            }
159
160 2
            if ($match && !$offsetReached) {
161 2
                $offsetCounter++;
162 2
                continue;
163
            }
164
165 2
            if ($match) {
166 2
                $limitCounter++;
167 2
                $result[] = $data;
168 2
            }
169 2
        }
170
171 2
        return $result;
172
    }
173
174
    /**
175
     * Checks the data (row) array against the expressions.
176
     *
177
     * @param array $expression
178
     * @param array $data
179
     *
180
     * @return bool
181
     */
182 2
    private function isExpressionMatch(array $expression, array $data)
183
    {
184 2
        foreach ($expression as $pattern => $subject) {
185 1
            $dataKey = '';
186 1
            $expressionType = $this->getExpressionType($pattern, $dataKey);
187
188
            // First false means some expression is failing for the data row, so the whole expression set is failing.
189 1
            if (!$this->match($expressionType, $data[$dataKey], $subject)) {
190 1
                return false;
191
            }
192 2
        }
193
194 2
        return true;
195
    }
196
197
    /**
198
     * Matches data against the subject according to the expression type.
199
     *
200
     * @param string $expressionType
201
     * @param mixed  $data
202
     * @param mixed $subject
203
     *
204
     * @todo handle 'NOT IN' and 'NOT LIKE' expressions too.
205
     *
206
     * @return bool
207
     */
208 1
    private function match($expressionType, $data, $subject)
209
    {
210 1
        if ($expressionType == self::EXPRESSION_WILDCARD) {
211 1
            $match = $this->checkWildcardMatch($data, $subject);
212 1
        } elseif ($expressionType == self::EXPRESSION_IN_ARRAY) {
213 1
            $match = $this->checkInArrayMatch($data, $subject);
214 1
        } else {
215 1
            $match = $this->checkRelation($expressionType, $data, $subject);
216
        }
217
218 1
        return $match;
219
    }
220
221
    /**
222
     * @param mixed $data
223
     * @param mixed $subject
224
     *
225
     * @return bool
226
     */
227 1
    private function checkWildcardMatch($data, $subject)
228
    {
229 1
        $subject = str_replace('%', '.*', $subject);
230 1
        return preg_match('/^'.$subject.'$/', $data);
231
    }
232
233
    /**
234
     * @param mixed $data
235
     * @param mixed $subject
236
     *
237
     * @return bool
238
     */
239 1
    private function checkInArrayMatch($data, $subject)
240
    {
241 1
        return in_array($data, (array)$subject);
242
    }
243
244
    /**
245
     * @param string $relation
246
     * @param mixed  $data
247
     * @param mixed  $subject
248
     *
249
     * @return bool
250
     */
251 1
    private function checkRelation($relation, $data, $subject)
252
    {
253
        $expressionMap = [
254 1
            '<'  => $data < $subject,
255 1
            '<=' => $data <= $subject,
256 1
            '>'  => $data > $subject,
257 1
            '>=' => $data >= $subject,
258 1
            '<>' => $data != $subject,
259
            '='  => $data == $subject
260 1
        ];
261
262 1
        return $expressionMap[$relation];
263
    }
264
265
    /**
266
     * Gets expression type and also sets the expression subject.
267
     *
268
     * @param string $pattern
269
     * @param string $subject
270
     *
271
     * @return string
272
     */
273 1
    private function getExpressionType($pattern, &$subject)
274
    {
275 1
        $type = '=';
276 1
        $subject = $pattern;
277
278
        $regularExpressions = [
279 1
            '/^(?P<subject>[^\s]+)\s+(?P<relation>(\<\>|\<=|\>=|=|\<|\>))\s+\?$/',
280 1
            '/^(?P<subject>[^\s]+)\s+(?P<relation>LIKE)\s+\?$/',
281
            '/^(?P<subject>[^\s]+)\s+(?P<relation>IN)\s?\(?\?\)?$/'
282 1
        ];
283
284 1
        foreach ($regularExpressions as $regexPattern) {
285 1
            $matches = [];
286
287 1
            if (preg_match($regexPattern, $subject, $matches)) {
288 1
                $type = $matches['relation'];
289 1
                $subject = $matches['subject'];
290 1
                break;
291
            }
292 1
        }
293
294 1
        return $type;
295
    }
296
297
298
299
    /**
300
     * Get the number of matched data in the set according to the expression.
301
     *
302
     * @param array $expression
303
     *
304
     * @return int
305
     */
306 1
    public function getDataCardinality(array $expression)
307
    {
308 1
        $list = $this->getDataSet($expression);
309
310 1
        return count($list);
311
    }
312
313
    /**
314
     * Insert or update entity in the storage.
315
     *
316
     * @param mixed $identifier
317
     * @param array $data
318
     *
319
     * @return mixed The ID of the saved entity in the storage
320
     */
321 5
    public function saveData($identifier, array $data)
322
    {
323 5
        $dataStorage = $this->getDataStorage()[$this->dataGroup];
324
325 5
        if (empty($dataStorage) && empty($identifier)) {
326 5
            $identifier = 1;
327 5
        }
328
329 5
        if (empty($identifier)) {
330 5
            $keys = array_keys($dataStorage);
331 5
            $maxKey = array_pop($keys);
332
333 5
            if (is_numeric($maxKey)) {
334 5
                $identifier = (int) $maxKey + 1;
335 5
            } else {
336 1
                $identifier = $maxKey.'_1';
337
            }
338 5
        }
339
340
        // To make it sure, we always apply changes on the exact property.
341 5
        $this->dataStorage[$this->dataGroup][$identifier] = $data;
342
343 5
        return $identifier;
344
    }
345
346
    /**
347
     * Removes an entity from the storage.
348
     *
349
     * @param mixed $identifier
350
     *
351
     * @return bool
352
     */
353 1
    public function deleteData($identifier)
354
    {
355 1
        $result = false;
356 1
        $dataFound = $this->getData($identifier);
357 1
        if (!empty($dataFound)) {
358 1
            unset($this->dataStorage[$this->dataGroup][$identifier]);
359 1
            $result = true;
360 1
        }
361
362 1
        return $result;
363
    }
364
}
365