Passed
Push — master ( 0756b4...22a454 )
by Gabor
06:47
created

InMemoryAdapter::getDataCardinality()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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