Completed
Push — master ( ccd642...ef610a )
by Gabor
02:56
created

InMemoryAdapter::checkWildcardMatch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
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 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 = null, $offset = null)
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
            if ($this->isExpressionMatch($expression, $data)) {
154 2
                if (!is_null($limit) && $limitCounter >= $limit) {
155 1
                    break;
156
                }
157
158 2
                if (!is_null($offset) && $offsetCounter++ < $offset) {
159 2
                    continue;
160
                }
161
162 2
                $limitCounter++;
163 2
                $result[] = $data;
164 2
            }
165 2
        }
166
167 2
        return $result;
168
    }
169
170
    /**
171
     * Checks the data (row) array against the expressions.
172
     *
173
     * @param array $expression
174
     * @param array $data
175
     *
176
     * @return bool
177
     */
178 2
    private function isExpressionMatch(array $expression, array $data)
179
    {
180 2
        $match = true;
181
182 2
        foreach ($expression as $pattern => $subject) {
183 1
            $dataKey = '';
184 1
            $expressionType = $this->getExpressionType($pattern, $dataKey);
185
186 1
            if ($expressionType == self::EXPRESSION_WILDCARD) {
187 1
                $match = $this->checkWildcardMatch($data[$dataKey], $subject);
188 1
            } elseif ($expressionType == self::EXPRESSION_IN_ARRAY) {
189 1
                $match = $this->checkInArrayMatch($data[$dataKey], $subject);
190 1
            } else {
191 1
                $match = $this->checkRelation($expressionType, $data[$dataKey], $subject);
192
            }
193
194
            // First false means some expression is failing for the data row, so the whole expression set is failing.
195 1
            if (!$match) {
196 1
                break;
197
            }
198 2
        }
199
200 2
        return (bool)$match;
201
    }
202
203
    /**
204
     * @param mixed $data
205
     * @param mixed $subject
206
     *
207
     * @return bool
208
     */
209 1
    private function checkWildcardMatch($data, $subject)
210
    {
211 1
        $subject = str_replace('%', '.*', $subject);
212 1
        return preg_match('/^' . $subject . '$/', $data);
213
    }
214
215
    /**
216
     * @param mixed $data
217
     * @param mixed $subject
218
     *
219
     * @return bool
220
     */
221 1
    private function checkInArrayMatch($data, $subject)
222
    {
223 1
        return in_array($data, (array)$subject);
224
    }
225
226
    /**
227
     * @param string $relation
228
     * @param mixed  $data
229
     * @param mixed  $subject
230
     *
231
     * @return bool
232
     */
233 1
    private function checkRelation($relation, $data, $subject)
234
    {
235
        $expressionMap = [
236 1
            '<'  => $data < $subject,
237 1
            '<=' => $data <= $subject,
238 1
            '>'  => $data > $subject,
239 1
            '>=' => $data >= $subject,
240 1
            '<>' => $data != $subject,
241
            '='  => $data == $subject
242 1
        ];
243
244 1
        return $expressionMap[$relation];
245
    }
246
247
    /**
248
     * Gets expression type and also sets the expression subject.
249
     *
250
     * @param string $pattern
251
     * @param string $subject
252
     *
253
     * @return string
254
     */
255 1
    private function getExpressionType($pattern, &$subject)
256
    {
257 1
        $type = '=';
258 1
        $subject = $pattern;
259
260
        $regularExpressions = [
261 1
            '/^(?P<subject>[^\s]+)\s+(?P<relation>(\<\>|\<=|\>=|=|\<|\>))\s+\?$/',
262 1
            '/^(?P<subject>[^\s]+)\s+(?P<relation>LIKE)\s+\?$/',
263
            '/^(?P<subject>[^\s]+)\s+(?P<relation>IN)\s?\(?\?\)?$/'
264 1
        ];
265
266 1
        foreach ($regularExpressions as $regexPattern) {
267 1
            $matches = [];
268
269 1
            if (preg_match($regexPattern, $subject, $matches)) {
270 1
                $type = $matches['relation'];
271 1
                $subject = $matches['subject'];
272 1
                break;
273
            }
274 1
        }
275
276 1
        return $type;
277
    }
278
279
280
281
    /**
282
     * Get the number of matched data in the set according to the expression.
283
     *
284
     * @param array $expression
285
     *
286
     * @return int
287
     */
288 1
    public function getDataCardinality(array $expression)
289
    {
290 1
        $list = $this->getDataSet($expression);
291
292 1
        return count($list);
293
    }
294
295
    /**
296
     * Insert or update entity in the storage.
297
     *
298
     * @param mixed $identifier
299
     * @param array $data
300
     *
301
     * @return mixed The ID of the saved entity in the storage
302
     */
303 5
    public function saveData($identifier, array $data)
304
    {
305 5
        $dataStorage = $this->getDataStorage()[$this->dataGroup];
306
307 5
        if (empty($dataStorage) && empty($identifier)) {
308 5
            $identifier = 1;
309 5
        }
310
311 5
        if (empty($identifier)) {
312 5
            $keys = array_keys($dataStorage);
313 5
            $maxKey = array_pop($keys);
314
315 5
            if (is_numeric($maxKey)) {
316 5
                $identifier = (int) $maxKey + 1;
317 5
            } else {
318 1
                $identifier = $maxKey . '_1';
319
            }
320 5
        }
321
322
        // To make it sure, we always apply changes on the exact property.
323 5
        $this->dataStorage[$this->dataGroup][$identifier] = $data;
324
325 5
        return $identifier;
326
    }
327
328
    /**
329
     * Removes an entity from the storage.
330
     *
331
     * @param mixed $identifier
332
     *
333
     * @return bool
334
     */
335 1
    public function deleteData($identifier)
336
    {
337 1
        $result = false;
338 1
        $dataFound = $this->getData($identifier);
339 1
        if (!empty($dataFound)) {
340 1
            unset($this->dataStorage[$this->dataGroup][$identifier]);
341 1
            $result = true;
342 1
        }
343
344 1
        return $result;
345
    }
346
}
347