Completed
Push — master ( 54b2f0...ccd642 )
by Gabor
03:11
created

InMemoryAdapter::saveData()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 8.5125
cc 5
eloc 13
nc 6
nop 2
crap 5
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
        }
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 3
                throw new InvalidArgumentException('The constructor parameter if present must be an array of arrays.');
53
            }
54
        }
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
        }
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
            }
165
        }
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
185 1
            switch ($this->getExpressionType($pattern, $dataKey)) {
186 1
                case self::EXPRESSION_WILDCARD:
187 1
                    $subject = str_replace('%', '.*', $subject);
188 1
                    $match = preg_match('/^' . $subject . '$/', $data[$dataKey]);
189 1
                    break;
190
191 1
                case self::EXPRESSION_IN_ARRAY:
192 1
                    $match = in_array($data[$dataKey], (array)$subject);
193 1
                    break;
194
195 1
                case '<':
196 1
                    $match = $data[$dataKey] < $subject;
197 1
                    break;
198
199 1
                case '<=':
200 1
                    $match = $data[$dataKey] <= $subject;
201 1
                    break;
202
203 1
                case '>':
204 1
                    $match = $data[$dataKey] > $subject;
205 1
                    break;
206
207 1
                case '>=':
208 1
                    $match = $data[$dataKey] >= $subject;
209 1
                    break;
210
211 1
                case '<>':
212 1
                    $match = $data[$dataKey] != $subject;
213 1
                    break;
214
215
                default:
216 1
                    $match = $data[$dataKey] == $subject;
217
            }
218
219
            // First false means some expression is failing for the data row, so the whole expression set is failing.
220 1
            if (!$match) {
221 1
                break;
222
            }
223
        }
224
225 2
        return (bool)$match;
226
    }
227
228
    /**
229
     * Gets expression type and also sets the expression subject.
230
     *
231
     * @param string $pattern
232
     * @param string $subject
233
     *
234
     * @return string
235
     */
236 1
    private function getExpressionType($pattern, &$subject)
237
    {
238 1
        $type = '=';
239 1
        $subject = $pattern;
240 1
        $matches = [];
241
242 1
        if (preg_match('/^(?P<subject>[^\s]+)\s+(?P<relation>(\<\>|\<=|\>=|=|\<|\>))\s+\?$/', $pattern, $matches)) {
243 1
            $type = $matches['relation'];
244 1
            $subject = $matches['subject'];
245 1
        } elseif (preg_match('/^(?P<subject>[^\s]+)\s+(?P<relation>LIKE)\s+\?$/', $pattern, $matches)) {
246 1
            $type = self::EXPRESSION_WILDCARD;
247 1
            $subject = $matches['subject'];
248 1
        } elseif (preg_match('/^(?P<subject>[^\s]+)\s+(?P<relation>IN)\s?\(?\?\)?$/', $pattern, $matches)) {
249 1
            $type = self::EXPRESSION_IN_ARRAY;
250 1
            $subject = $matches['subject'];
251
        }
252
253 1
        return $type;
254
    }
255
256
257
258
    /**
259
     * Get the number of matched data in the set according to the expression.
260
     *
261
     * @param array $expression
262
     *
263
     * @return int
264
     */
265 1
    public function getDataCardinality(array $expression)
266
    {
267 1
        $list = $this->getDataSet($expression);
268
269 1
        return count($list);
270
    }
271
272
    /**
273
     * Insert or update entity in the storage.
274
     *
275
     * @param mixed $identifier
276
     * @param array $data
277
     *
278
     * @return mixed The ID of the saved entity in the storage
279
     */
280 5
    public function saveData($identifier, array $data)
281
    {
282 5
        $dataStorage = $this->getDataStorage()[$this->dataGroup];
283
284 5
        if (empty($dataStorage) && empty($identifier)) {
285 5
            $identifier = 1;
286
        }
287
288 5
        if (empty($identifier)) {
289 5
            $keys = array_keys($dataStorage);
290 5
            $maxKey = array_pop($keys);
291
292 5
            if (is_numeric($maxKey)) {
293 5
                $identifier = (int) $maxKey + 1;
294
            } else {
295 1
                $identifier = $maxKey . '_1';
296
            }
297
        }
298
299
        // To make it sure, we always apply changes on the exact property.
300 5
        $this->dataStorage[$this->dataGroup][$identifier] = $data;
301
302 5
        return $identifier;
303
    }
304
305
    /**
306
     * Removes an entity from the storage.
307
     *
308
     * @param mixed $identifier
309
     *
310
     * @return bool
311
     */
312 1
    public function deleteData($identifier)
313
    {
314 1
        $result = false;
315 1
        $dataFound = $this->getData($identifier);
316 1
        if (!empty($dataFound)) {
317 1
            unset($this->dataStorage[$this->dataGroup][$identifier]);
318 1
            $result = true;
319
        }
320
321 1
        return $result;
322
    }
323
}
324