|
1
|
|
|
<?php |
|
2
|
|
|
namespace Darya\Storage; |
|
3
|
|
|
|
|
4
|
|
|
use Darya\Storage\Aggregational; |
|
5
|
|
|
use Darya\Storage\Filterer; |
|
6
|
|
|
use Darya\Storage\Readable; |
|
7
|
|
|
use Darya\Storage\Modifiable; |
|
8
|
|
|
use Darya\Storage\Query; |
|
9
|
|
|
use Darya\Storage\Queryable; |
|
10
|
|
|
use Darya\Storage\Result; |
|
11
|
|
|
use Darya\Storage\Searchable; |
|
12
|
|
|
use Darya\Storage\Sorter; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* Darya's in-memory storage interface. |
|
16
|
|
|
* |
|
17
|
|
|
* Useful for unit testing! |
|
18
|
|
|
* |
|
19
|
|
|
* @author Chris Andrew <[email protected]> |
|
20
|
|
|
*/ |
|
21
|
|
|
class InMemory implements Readable, Modifiable, Searchable, Aggregational, Queryable |
|
22
|
|
|
{ |
|
23
|
|
|
/** |
|
24
|
|
|
* The in-memory data. |
|
25
|
|
|
* |
|
26
|
|
|
* @var array |
|
27
|
|
|
*/ |
|
28
|
|
|
protected $data; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* Filters results in-memory. |
|
32
|
|
|
* |
|
33
|
|
|
* @var Filterer |
|
34
|
|
|
*/ |
|
35
|
|
|
protected $filterer; |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* Sorts results in-memory. |
|
39
|
|
|
* |
|
40
|
|
|
* @var Sorter |
|
41
|
|
|
*/ |
|
42
|
|
|
protected $sorter; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Create a new in-memory storage interface with the given data. |
|
46
|
|
|
* |
|
47
|
|
|
* @param array $data [optional] |
|
48
|
|
|
*/ |
|
49
|
|
|
public function __construct(array $data = array()) |
|
50
|
|
|
{ |
|
51
|
|
|
$this->data = $data; |
|
52
|
|
|
$this->filterer = new Filterer; |
|
53
|
|
|
$this->sorter = new Sorter; |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* Limit the given data to the given length and offset. |
|
58
|
|
|
* |
|
59
|
|
|
* @param array $data |
|
60
|
|
|
* @param int $limit [optional] |
|
61
|
|
|
* @param int $offset [optional] |
|
62
|
|
|
* @return array |
|
63
|
|
|
*/ |
|
64
|
|
|
protected static function limit(array $data, $limit = 0, $offset = 0) |
|
65
|
|
|
{ |
|
66
|
|
|
return array_slice($data, $offset, $limit ?: null); |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Retrieve resource data using the given criteria. |
|
71
|
|
|
* |
|
72
|
|
|
* Returns an array of associative arrays. |
|
73
|
|
|
* |
|
74
|
|
|
* @param string $resource |
|
75
|
|
|
* @param array $filter [optional] |
|
76
|
|
|
* @param array|string $order [optional] |
|
77
|
|
|
* @param int $limit [optional] |
|
78
|
|
|
* @param int $offset [optional] |
|
79
|
|
|
* @return array |
|
80
|
|
|
*/ |
|
81
|
|
|
public function read($resource, array $filter = array(), $order = array(), $limit = 0, $offset = 0) |
|
82
|
|
|
{ |
|
83
|
|
|
if (empty($this->data[$resource])) { |
|
84
|
|
|
return array(); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
$data = $this->filterer->filter($this->data[$resource], $filter); |
|
88
|
|
|
|
|
89
|
|
|
$data = $this->sorter->sort($data, $order); |
|
90
|
|
|
|
|
91
|
|
|
$data = static::limit($data, $limit, $offset); |
|
92
|
|
|
|
|
93
|
|
|
return $data; |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
/** |
|
97
|
|
|
* Retrieve specific fields of a resource. |
|
98
|
|
|
* |
|
99
|
|
|
* Returns an array of associative arrays. |
|
100
|
|
|
* |
|
101
|
|
|
* @param string $resource |
|
102
|
|
|
* @param array|string $fields |
|
103
|
|
|
* @param array $filter [optional] |
|
104
|
|
|
* @param array|string $order [optional] |
|
105
|
|
|
* @param int $limit [optional] |
|
106
|
|
|
* @param int $offset [optional] |
|
107
|
|
|
* @return array |
|
108
|
|
|
*/ |
|
109
|
|
|
public function listing($resource, $fields, array $filter = array(), $order = array(), $limit = 0, $offset = 0) |
|
110
|
|
|
{ |
|
111
|
|
|
$data = $this->read($resource, $filter, $order, $limit, $offset); |
|
112
|
|
|
|
|
113
|
|
|
if (empty($fields) || $fields === '*') { |
|
114
|
|
|
return $data; |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
$fields = (array) $fields; |
|
118
|
|
|
|
|
119
|
|
|
$result = array(); |
|
120
|
|
|
|
|
121
|
|
|
foreach ($data as $row) { |
|
122
|
|
|
$new = array(); |
|
123
|
|
|
|
|
124
|
|
|
foreach ($row as $field => $value) { |
|
125
|
|
|
if (in_array($field, $fields)) { |
|
126
|
|
|
$new[$field] = $value; |
|
127
|
|
|
} |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
if (!empty($new)) { |
|
131
|
|
|
$result[] = $new; |
|
132
|
|
|
} |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
return $result; |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
/** |
|
139
|
|
|
* Count the given resource with an optional filter. |
|
140
|
|
|
* |
|
141
|
|
|
* @param string $resource |
|
142
|
|
|
* @param array $filter [optional] |
|
143
|
|
|
* @return int |
|
144
|
|
|
*/ |
|
145
|
|
|
public function count($resource, array $filter = array()) |
|
|
|
|
|
|
146
|
|
|
{ |
|
147
|
|
|
if (empty($this->data[$resource])) { |
|
148
|
|
|
return 0; |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
return count($this->filterer->filter($this->data[$resource], $filter)); |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
/** |
|
155
|
|
|
* Create resource instances in the data store. |
|
156
|
|
|
* |
|
157
|
|
|
* @param string $resource |
|
158
|
|
|
* @param array $data |
|
159
|
|
|
* @return bool |
|
160
|
|
|
*/ |
|
161
|
|
|
public function create($resource, $data) |
|
162
|
|
|
{ |
|
163
|
|
|
if (!isset($this->data[$resource])) { |
|
164
|
|
|
$this->data[$resource] = array(); |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
$this->data[$resource][] = $data; |
|
168
|
|
|
|
|
169
|
|
|
return true; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
/** |
|
173
|
|
|
* Update resource instances in the data store. |
|
174
|
|
|
* |
|
175
|
|
|
* @param string $resource |
|
176
|
|
|
* @param array $data |
|
177
|
|
|
* @param array $filter [optional] |
|
178
|
|
|
* @param int $limit [optional] |
|
179
|
|
|
* @return int|bool |
|
180
|
|
|
*/ |
|
181
|
|
|
public function update($resource, $data, array $filter = array(), $limit = 0) |
|
182
|
|
|
{ |
|
183
|
|
|
if (empty($this->data[$resource])) { |
|
184
|
|
|
return; |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
$affected = 0; |
|
188
|
|
|
|
|
189
|
|
|
$this->data[$resource] = $this->filterer->map( |
|
190
|
|
|
$this->data[$resource], |
|
191
|
|
|
$filter, |
|
192
|
|
|
function ($row) use ($data, &$affected) { |
|
193
|
|
|
foreach ($data as $key => $value) { |
|
194
|
|
|
$row[$key] = $value; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
$affected++; |
|
198
|
|
|
|
|
199
|
|
|
return $row; |
|
200
|
|
|
}, |
|
201
|
|
|
$limit |
|
202
|
|
|
); |
|
203
|
|
|
|
|
204
|
|
|
return $affected; |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Delete resource instances from the data store. |
|
209
|
|
|
* |
|
210
|
|
|
* @param string $resource |
|
211
|
|
|
* @param array $filter [optional] |
|
212
|
|
|
* @param int $limit [optional] |
|
213
|
|
|
* @return int|bool |
|
214
|
|
|
*/ |
|
215
|
|
|
public function delete($resource, array $filter = array(), $limit = null) |
|
|
|
|
|
|
216
|
|
|
{ |
|
217
|
|
|
if (empty($this->data[$resource])) { |
|
218
|
|
|
return; |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
$this->data[$resource] = $this->filterer->reject($this->data[$resource], $filter); |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
/** |
|
225
|
|
|
* Search for resource data with fields that match the given query and |
|
226
|
|
|
* criteria. |
|
227
|
|
|
* |
|
228
|
|
|
* @param string $resource |
|
229
|
|
|
* @param string $query |
|
230
|
|
|
* @param array|string $fields |
|
231
|
|
|
* @param array $filter [optional] |
|
232
|
|
|
* @param array|string $order [optional] |
|
233
|
|
|
* @param int $limit [optional] |
|
234
|
|
|
* @param int $offset [optional] |
|
235
|
|
|
* @return array |
|
236
|
|
|
*/ |
|
237
|
|
|
public function search($resource, $query, $fields, array $filter = array(), $order = array(), $limit = null, $offset = 0) |
|
238
|
|
|
{ |
|
239
|
|
|
if (empty($query) || empty($resource)) { |
|
240
|
|
|
return $this->read($resource, $filter, $order, $limit, $offset); |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
$fields = (array) $fields; |
|
244
|
|
|
$search = array('or' => array()); |
|
245
|
|
|
|
|
246
|
|
|
foreach ($fields as $field) { |
|
247
|
|
|
$search['or']["$field like"] = "%$query%"; |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
$filter = array_merge($filter, $search); |
|
251
|
|
|
|
|
252
|
|
|
return $this->read($resource, $filter, $order, $limit, $offset); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/** |
|
256
|
|
|
* Retrieve the distinct values of the given resource's field. |
|
257
|
|
|
* |
|
258
|
|
|
* Returns a flat array of values. |
|
259
|
|
|
* |
|
260
|
|
|
* @param string $resource |
|
261
|
|
|
* @param string $field |
|
262
|
|
|
* @param array $filter [optional] |
|
263
|
|
|
* @param array $order [optional] |
|
264
|
|
|
* @param int $limit [optional] |
|
265
|
|
|
* @param int $offset [optional] |
|
266
|
|
|
* @return array |
|
267
|
|
|
*/ |
|
268
|
|
|
public function distinct($resource, $field, array $filter = array(), $order = array(), $limit = 0, $offset = 0) |
|
269
|
|
|
{ |
|
270
|
|
|
$list = array(); |
|
271
|
|
|
|
|
272
|
|
|
$listing = $this->listing($resource, $field, $filter, $order, $limit, $offset); |
|
273
|
|
|
|
|
274
|
|
|
foreach ($listing as $item) { |
|
275
|
|
|
$list[] = $item[$field]; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
return array_unique($list); |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
/** |
|
282
|
|
|
* Execute the given query. |
|
283
|
|
|
* |
|
284
|
|
|
* @param Query $query |
|
285
|
|
|
* @return Result |
|
286
|
|
|
*/ |
|
287
|
|
|
public function run(Query $query) |
|
288
|
|
|
{ |
|
289
|
|
|
$data = array(); |
|
290
|
|
|
$info = array(); |
|
291
|
|
|
|
|
292
|
|
|
switch ($query->type) { |
|
293
|
|
|
case Query::CREATE: |
|
294
|
|
|
$this->create($query->resource, $query->data); |
|
295
|
|
|
break; |
|
296
|
|
|
case Query::READ: |
|
297
|
|
|
$data = $this->listing( |
|
298
|
|
|
$query->resource, |
|
299
|
|
|
$query->fields, |
|
300
|
|
|
$query->filter, |
|
301
|
|
|
$query->order, |
|
302
|
|
|
$query->limit, |
|
303
|
|
|
$query->offset |
|
304
|
|
|
); |
|
305
|
|
|
break; |
|
306
|
|
|
case Query::UPDATE: |
|
307
|
|
|
$info['affected'] = $this->update( |
|
308
|
|
|
$query->resource, |
|
309
|
|
|
$query->data, |
|
310
|
|
|
$query->filter, |
|
311
|
|
|
$query->limit |
|
312
|
|
|
); |
|
313
|
|
|
break; |
|
314
|
|
|
case Query::DELETE: |
|
315
|
|
|
$this->delete( |
|
316
|
|
|
$query->resource, |
|
317
|
|
|
$query->filter, |
|
318
|
|
|
$query->limit |
|
319
|
|
|
); |
|
320
|
|
|
break; |
|
321
|
|
|
} |
|
322
|
|
|
|
|
323
|
|
|
return new Result($query, $data, $info); |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
/** |
|
327
|
|
|
* Open a query on the given resource. |
|
328
|
|
|
* |
|
329
|
|
|
* @param string $resource |
|
330
|
|
|
* @param array|string $fields [optional] |
|
331
|
|
|
* @return Query\Builder |
|
332
|
|
|
*/ |
|
333
|
|
|
public function query($resource, $fields = array()) |
|
334
|
|
|
{ |
|
335
|
|
|
return new Query\Builder(new Query($resource, (array) $fields), $this); |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
/** |
|
339
|
|
|
* Retrieve the error that occured with the last operation. |
|
340
|
|
|
* |
|
341
|
|
|
* Returns false if there was no error. |
|
342
|
|
|
* |
|
343
|
|
|
* @return string|bool |
|
344
|
|
|
*/ |
|
345
|
|
|
public function error() |
|
346
|
|
|
{ |
|
347
|
|
|
return false; |
|
348
|
|
|
} |
|
349
|
|
|
} |
|
350
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.