-
Notifications
You must be signed in to change notification settings - Fork 238
/
Collection.php
409 lines (365 loc) · 10.9 KB
/
Collection.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
<?php
/**
* Lithium: the most rad php framework
*
* @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
* @license http://opensource.org/licenses/bsd-license.php The BSD License
*/
namespace lithium\data;
/**
* The `Collection` class extends the generic `lithium\util\Collection` class to provide
* context-specific features for working with sets of data persisted by a backend data store. This
* is a general abstraction that operates on arbitrary sets of data from either relational or
* non-relational data stores.
*/
abstract class Collection extends \lithium\util\Collection {
/**
* A reference to this object's parent `Document` object.
*
* @var object
*/
protected $_parent = null;
/**
* If this `Collection` instance has a parent document (see `$_parent`), this value indicates
* the key name of the parent document that contains it.
*
* @see lithium\data\Collection::$_parent
* @var string
*/
protected $_pathKey = null;
/**
* The fully-namespaced class name of the model object to which this entity set is bound. This
* is usually the model that executed the query which created this object.
*
* @var string
*/
protected $_model = null;
/**
* A reference to the query object that originated this entity set; usually an instance of
* `lithium\data\model\Query`.
*
* @see lithium\data\model\Query
* @var object
*/
protected $_query = null;
/**
* A pointer or resource that is used to load entities from the backend data source that
* originated this collection.
*
* @var resource
*/
protected $_result = null;
/**
* Indicates whether the current position is valid or not. This overrides the default value of
* the parent class.
*
* @var boolean
* @see lithium\util\Collection::valid()
*/
protected $_valid = true;
/**
* Contains an array of backend-specific statistics generated by the query that produced this
* `Collection` object. These stats are accessible via the `stats()` method.
*
* @see lithium\data\Collection::stats()
* @var array
*/
protected $_stats = array();
/**
* By default, query results are not fetched until the collection is iterated. Set to `true`
* when the collection has begun iterating and fetching entities.
*
* @see lithium\data\Collection::rewind()
* @see lithium\data\Collection::_populate()
* @var boolean
*/
protected $_hasInitialized = false;
protected $_schema = array();
/**
* Holds an array of values that should be processed on initialization.
*
* @var array
*/
protected $_autoConfig = array(
'data', 'model', 'result', 'query', 'parent', 'stats', 'pathKey', 'schema'
);
/**
* Class constructor.
*
* @param array $config
*/
public function __construct(array $config = array()) {
$defaults = array('data' => array(), 'model' => null);
parent::__construct($config + $defaults);
}
protected function _init() {
parent::_init();
foreach (array('data', 'classes', 'model', 'result', 'query') as $key) {
unset($this->_config[$key]);
}
if ($model = $this->_model) {
$options = array(
'pathKey' => $this->_pathKey,
'schema' => $model::schema(),
'exists' => isset($this->_config['exists']) ? $this->_config['exists'] : null
);
$this->_data = $model::connection()->cast($this, $this->_data, $options);
}
}
/**
* Configures protected properties of a `Collection` so that it is parented to `$parent`.
*
* @param object $parent
* @param array $config
* @return void
*/
public function assignTo($parent, array $config = array()) {
foreach ($config as $key => $val) {
$this->{'_' . $key} = $val;
}
$this->_parent =& $parent;
}
/**
* Returns the model which this particular collection is based off of.
*
* @return string The fully qualified model class name.
*/
public function model() {
return $this->_model;
}
/**
* Returns the object's parent `Document` object.
*
* @return object
*/
public function parent() {
return $this->_parent;
}
public function schema($field = null) {
$schema = array();
switch (true) {
case ($this->_schema):
$schema = $this->_schema;
break;
case ($model = $this->_model):
$schema = $model::schema();
break;
}
if ($field) {
return isset($self->_schema[$field]) ? $self->_schema[$field] : null;
}
return $schema;
}
/**
* Returns a boolean indicating whether an offset exists for the
* current `Collection`.
*
* @param string $offset String or integer indicating the offset or
* index of an entity in the set.
* @return boolean Result.
*/
public function offsetExists($offset) {
return ($this->offsetGet($offset) !== null);
}
/**
* Reset the set's iterator and return the first entity in the set.
* The next call of `current()` will get the first entity in the set.
*
* @return object Returns the first `Entity` instance in the set.
*/
public function rewind() {
$this->_valid = (reset($this->_data) || count($this->_data));
if (!$this->_valid && !$this->_hasInitialized) {
$this->_hasInitialized = true;
if ($entity = $this->_populate()) {
$this->_valid = true;
return $entity;
}
}
return current($this->_data);
}
/**
* Returns meta information for this `Collection`.
*
* @return array
*/
public function meta() {
return array('model' => $this->_model);
}
/**
* Applies a callback to all data in the collection.
*
* Overridden to load any data that has not yet been loaded.
*
* @param callback $filter The filter to apply.
* @return object This collection instance.
*/
public function each($filter) {
if (!$this->closed()) {
while ($this->next()) {}
}
return parent::each($filter);
}
/**
* Applies a callback to a copy of all data in the collection
* and returns the result.
*
* Overriden to load any data that has not yet been loaded.
*
* @param callback $filter The filter to apply.
* @param array $options The available options are:
* - `'collect'`: If `true`, the results will be returned wrapped
* in a new `Collection` object or subclass.
* @return object The filtered data.
*/
public function map($filter, array $options = array()) {
$defaults = array('collect' => true);
$options += $defaults;
if (!$this->closed()) {
while ($this->next()) {}
}
$data = parent::map($filter, $options);
if ($options['collect']) {
foreach (array('_model', '_schema', '_pathKey') as $key) {
$data->{$key} = $this->{$key};
}
}
return $data;
}
/**
* Reduce, or fold, a collection down to a single value
*
* Overridden to load any data that has not yet been loaded.
*
* @param callback $filter The filter to apply.
* @param mixed $initial Initial value
* @return mixed A single reduced value
*/
public function reduce($filter, $initial = false) {
if (!$this->closed()) {
while ($this->next()) {}
}
return parent::reduce($filter);
}
/**
* Sorts the objects in the collection, useful in situations where
* you are already using the underlying datastore to sort results.
*
* Overriden to load any data that has not yet been loaded.
*
* @param mixed $field The field to sort the data on, can also be a callback
* to a custom sort function.
* @param array $options The available options are:
* - No options yet implemented
* @return $this, useful for chaining this with other methods.
*/
public function sort($field = 'id', array $options = array()) {
$this->offsetGet(null);
if (is_string($field)) {
$sorter = function ($a, $b) use ($field) {
if (is_array($a)) {
$a = (object) $a;
}
if (is_array($b)) {
$b = (object) $b;
}
return strcmp($a->$field, $b->$field);
};
} else if (is_callable($field)) {
$sorter = $field;
}
return parent::sort($sorter, $options);
}
/**
* Converts the current state of the data structure to an array.
*
* @return array Returns the array value of the data in this `Collection`.
*/
public function data() {
return $this->to('array');
}
/**
* Adds the specified object to the `Collection` instance, and assigns associated metadata to
* the added object.
*
* @param string $offset The offset to assign the value to.
* @param mixed $data The entity object to add.
* @return mixed Returns the set `Entity` object.
*/
public function offsetSet($offset, $data) {
if (is_array($data) && ($model = $this->_model)) {
$data = $model::connection()->cast($this, $data);
} elseif (is_object($data)) {
$data->assignTo($this);
}
return $this->_data[] = $data;
}
/**
* Return's the pointer or resource that is used to load entities from the backend
* data source that originated this collection. This is useful in many cases for
* additional methods related to debugging queries.
*
* @return object The pointer or resource from the data source
*/
public function result() {
return $this->_result;
}
/**
* Gets the stat or stats associated with this `Collection`.
*
* @param string $name Stat name.
* @return mixed Single stat if `$name` supplied, else all stats for this
* `Collection`.
*/
public function stats($name = null) {
if ($name) {
return isset($this->_stats[$name]) ? $this->_stats[$name] : null;
}
return $this->_stats;
}
/**
* Executes when the associated result resource pointer reaches the end of its data set. The
* resource is freed by the connection, and the reference to the connection is unlinked.
*
* @return void
*/
public function close() {
if (!empty($this->_result)) {
$this->_result = null;
}
}
/**
* Checks to see if this entity has already fetched all available entities and freed the
* associated result resource.
*
* @return boolean Returns true if all entities are loaded and the database resources have been
* freed, otherwise returns false.
*/
public function closed() {
return empty($this->_result);
}
/**
* Ensures that the data set's connection is closed when the object is destroyed.
*
* @return void
*/
public function __destruct() {
$this->close();
}
/**
* A method to be implemented by concrete `Collection` classes which, provided a reference to a
* backend data source, and a resource representing a query result cursor, fetches new result
* data and wraps it in the appropriate object type, which is added into the `Collection` and
* returned.
*
* @param mixed $data Data (in an array or object) that is manually added to the data
* collection. If `null`, data is automatically fetched from the associated backend
* data source, if available.
* @param mixed $key String, integer or array key representing the unique key of the data
* object. If `null`, the key will be extracted from the data passed or fetched,
* using the associated `Model` class.
* @return object Returns a `Record` or `Document` object, or other `Entity` object.
*/
abstract protected function _populate($data = null, $key = null);
}
?>