InMemory::listing()   B
last analyzed

Complexity

Conditions 7
Paths 8

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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