1
|
|
|
<?php defined('SYSPATH') OR die('No direct script access.'); |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Represents an array of models. Lazy loaded from a Jam_Query_Builder_Collection, and can be changed, checked and saved at once. |
5
|
|
|
* |
6
|
|
|
* @package Jam |
7
|
|
|
* @category Associations |
8
|
|
|
* @author Ivan Kerin |
9
|
|
|
* @copyright (c) 2011-2012 Despark Ltd. |
10
|
|
|
* @license http://www.opensource.org/licenses/isc-license.txt |
11
|
|
|
*/ |
12
|
|
|
abstract class Kohana_Jam_Array_Model extends Jam_Array { |
13
|
|
|
|
14
|
|
|
public static function factory() |
15
|
|
|
{ |
16
|
|
|
return new Jam_Array_Model(); |
17
|
|
|
} |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Convert a collection to an array, keep an array or make a Jam_Model to an array(Jam_Model) |
21
|
|
|
* @param mixed $collection |
22
|
|
|
* @return array |
23
|
|
|
*/ |
24
|
20 |
|
public static function convert_collection_to_array($collection) |
25
|
|
|
{ |
26
|
20 |
|
if ($collection instanceof Jam_Query_Builder_Collection OR $collection instanceof Jam_Array_Model) |
27
|
|
|
{ |
28
|
6 |
|
$array = $collection->as_array(); |
29
|
|
|
} |
30
|
19 |
|
elseif ( ! is_array($collection)) |
31
|
|
|
{ |
32
|
12 |
|
$array = array($collection); |
33
|
|
|
} |
34
|
|
|
else |
35
|
|
|
{ |
36
|
9 |
|
$array = $collection; |
37
|
|
|
} |
38
|
20 |
|
return array_values(array_filter($array)); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* A collection used to load the content |
43
|
|
|
* @var Jam_Query_Builder_Collection |
44
|
|
|
*/ |
45
|
|
|
protected $_collection; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* The name of the models in this iterator |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
protected $_model; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* The original content, loaded from the database |
55
|
|
|
* @var array |
56
|
|
|
*/ |
57
|
|
|
protected $_original; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* This is set if the whole collection has been replaced. |
61
|
|
|
* @var boolean |
62
|
|
|
*/ |
63
|
|
|
protected $_replace = FALSE; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Getter / Setter of the model name |
67
|
|
|
* @param string $model |
68
|
|
|
* @return string |
69
|
|
|
*/ |
70
|
754 |
|
public function model($model = NULL) |
71
|
|
|
{ |
72
|
754 |
|
if ($model !== NULL) |
73
|
|
|
{ |
74
|
754 |
|
$this->_model = $model; |
75
|
754 |
|
return $this; |
76
|
|
|
} |
77
|
24 |
|
return $this->_model; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Getter of the meta object for this iterator (based on $_model) |
82
|
|
|
* @return Jam_Model |
83
|
|
|
* @throws Kohana_Exception If $_model not present |
84
|
|
|
*/ |
85
|
18 |
|
public function meta() |
86
|
|
|
{ |
87
|
18 |
|
if ( ! $this->model()) |
88
|
|
|
throw new Kohana_Exception('Model not set'); |
89
|
|
|
|
90
|
18 |
|
return Jam::meta($this->model()); |
|
|
|
|
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Getter / Setter of the collection, used to lazy load the data for this iterator |
95
|
|
|
* @param Jam_Query_Builder_Collection $collection |
96
|
|
|
* @return Jam_Query_Builder_Collection |
97
|
|
|
*/ |
98
|
18 |
|
public function collection(Jam_Query_Builder_Collection $collection = NULL) |
99
|
|
|
{ |
100
|
18 |
|
if ($collection !== NULL) |
101
|
|
|
{ |
102
|
18 |
|
$this->_collection = $collection; |
103
|
18 |
|
return $this; |
|
|
|
|
104
|
|
|
} |
105
|
|
|
|
106
|
16 |
|
return $this->_collection; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Load the content from the database, using $_collection. |
111
|
|
|
* If some items have been added to the iterator before it has been loaded, merge the results |
112
|
|
|
* @throws Kohana_Exception If the $_collection has not been loaded |
113
|
|
|
*/ |
114
|
27 |
|
protected function _load_content() |
115
|
|
|
{ |
116
|
27 |
|
if ($this->_original === NULL) |
117
|
|
|
{ |
118
|
27 |
|
if ( ! $this->collection()) |
119
|
|
|
throw new Kohana_Exception('Cannot load content because collection not loaded for Jam_Array(:model)', array(':model' => $this->model())); |
120
|
|
|
|
121
|
27 |
|
$collection = clone $this->collection(); |
122
|
|
|
|
123
|
27 |
|
$this->_original = $collection->result()->as_array(); |
124
|
|
|
|
125
|
27 |
|
if ( ! $this->_replace) |
126
|
|
|
{ |
127
|
22 |
|
if ($this->_content !== NULL) |
128
|
|
|
{ |
129
|
1 |
|
$this->_content = array_merge($this->_content, $this->_original); |
130
|
|
|
} |
131
|
|
|
else |
132
|
|
|
{ |
133
|
21 |
|
$this->_content = $this->_original; |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
} |
137
|
27 |
|
} |
138
|
|
|
|
139
|
|
|
public function reload() |
140
|
|
|
{ |
141
|
|
|
$this->_original = NULL; |
|
|
|
|
142
|
|
|
$this->_replace = FALSE; |
143
|
|
|
return parent::reload(); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Load an item from the database, based on a unique key |
148
|
|
|
* @param string $key |
149
|
|
|
* @return Jam_Model |
150
|
|
|
*/ |
151
|
|
|
protected function _find_item($key) |
152
|
|
|
{ |
153
|
|
|
return Jam::find($this->model(), $key); |
|
|
|
|
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Convert an item from the $_content to a Jam_Model |
158
|
|
|
* @param mixed $value |
159
|
|
|
* @param boolean $is_changed |
160
|
|
|
* @param int $offset |
161
|
|
|
* @return Jam_Model |
162
|
|
|
*/ |
163
|
19 |
|
protected function _load_item($value, $is_changed, $offset) |
164
|
|
|
{ |
165
|
19 |
|
if ($value instanceof Jam_Model OR ! $value) |
166
|
|
|
{ |
167
|
12 |
|
$item = $value; |
168
|
|
|
} |
169
|
12 |
|
elseif ( ! is_array($value)) |
170
|
|
|
{ |
171
|
|
|
$item = $this->_find_item($value); |
172
|
|
|
} |
173
|
12 |
|
elseif (is_array($value) AND $is_changed) |
174
|
|
|
{ |
175
|
|
|
$key = $this->meta()->primary_key(); |
176
|
|
|
if (isset($value[$key]) AND $value[$key]) |
177
|
|
|
{ |
178
|
|
|
$item_key = is_array($value[$key]) ? reset($value[$key]) : $value[$key]; |
179
|
|
|
|
180
|
|
|
$model = $this->_find_item($item_key); |
181
|
|
|
if ( ! $model) |
182
|
|
|
{ |
183
|
|
|
$model = clone Jam::build_template($this->model(), $value); |
184
|
|
|
} |
185
|
|
|
else |
186
|
|
|
{ |
187
|
|
|
unset($value[$key]); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
else |
191
|
|
|
{ |
192
|
|
|
$model = clone Jam::build_template($this->model(), $value); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$item = $model->set($value); |
196
|
|
|
} |
197
|
|
|
else |
198
|
|
|
{ |
199
|
12 |
|
$item = clone Jam::build_template($this->model(), $value); |
200
|
12 |
|
$item = $item->load_fields($value); |
201
|
|
|
} |
202
|
|
|
|
203
|
19 |
|
$this->_content[$offset] = $item; |
204
|
|
|
|
205
|
19 |
|
return $item; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Find out the primary_key of an item of the $_content |
210
|
|
|
* @param mixed $value |
211
|
|
|
* @return int |
212
|
|
|
*/ |
213
|
21 |
|
protected function _id($value) |
214
|
|
|
{ |
215
|
21 |
|
if ($value instanceof Jam_Model) |
216
|
14 |
|
return $value->id(); |
217
|
|
|
|
218
|
15 |
|
if (is_numeric($value)) |
219
|
6 |
|
return (int) $value; |
220
|
|
|
|
221
|
13 |
|
if (is_string($value) AND $item = $this->_find_item($value)) |
222
|
|
|
return $item->id(); |
223
|
|
|
|
224
|
13 |
|
if (is_array($value) AND isset($value[$this->meta()->primary_key()])) |
225
|
13 |
|
return (int) $value[$this->meta()->primary_key()]; |
226
|
|
|
} |
227
|
|
|
|
228
|
15 |
|
public function search($item) |
229
|
|
|
{ |
230
|
15 |
|
$search_id = $this->_id($item); |
231
|
|
|
|
232
|
15 |
|
if ( ! $search_id) |
233
|
6 |
|
return NULL; |
234
|
|
|
|
235
|
10 |
|
$this->_load_content(); |
236
|
|
|
|
237
|
10 |
|
if ($this->_content) |
|
|
|
|
238
|
|
|
{ |
239
|
10 |
|
foreach ($this->_content as $offset => $current) |
240
|
|
|
{ |
241
|
10 |
|
if ($this->_id($current) === $search_id) |
242
|
|
|
{ |
243
|
10 |
|
return (int) $offset; |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
5 |
|
return NULL; |
249
|
|
|
} |
250
|
|
|
|
251
|
12 |
|
public function as_array($key = NULL, $value = NULL) |
252
|
|
|
{ |
253
|
12 |
|
$results = array(); |
254
|
12 |
|
$key = Jam_Query_Builder::resolve_meta_attribute($key, $this->meta()); |
255
|
12 |
|
$value = Jam_Query_Builder::resolve_meta_attribute($value, $this->meta()); |
256
|
|
|
|
257
|
12 |
|
foreach ($this as $i => $item) |
258
|
|
|
{ |
259
|
9 |
|
$results[$key ? $item->$key : $i] = $value ? $item->$value : $item; |
260
|
|
|
} |
261
|
|
|
|
262
|
12 |
|
return $results; |
263
|
|
|
} |
264
|
|
|
|
265
|
6 |
|
public function ids() |
266
|
|
|
{ |
267
|
6 |
|
$this->_load_content(); |
268
|
|
|
|
269
|
6 |
|
return $this->_content ? array_filter(array_map(array($this, '_id'), $this->_content)) : array(); |
270
|
|
|
} |
271
|
|
|
|
272
|
7 |
|
public function load_fields(array $data) |
273
|
|
|
{ |
274
|
7 |
|
$this->_content = $this->_original = $data; |
275
|
7 |
|
return $this; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
public function original() |
279
|
|
|
{ |
280
|
|
|
return $this->_original; |
281
|
|
|
} |
282
|
|
|
|
283
|
1 |
|
public function original_ids() |
284
|
|
|
{ |
285
|
1 |
|
$this->_load_content(); |
286
|
|
|
|
287
|
1 |
|
return $this->_original ? array_filter(array_map(array($this, '_id'), $this->_original)) : array(); |
288
|
|
|
} |
289
|
|
|
|
290
|
3 |
|
public function has($item) |
291
|
|
|
{ |
292
|
3 |
|
return $this->search($item) !== NULL; |
293
|
|
|
} |
294
|
|
|
|
295
|
8 |
|
public function set($items) |
296
|
|
|
{ |
297
|
8 |
|
$this->_content = Jam_Array_Model::convert_collection_to_array($items); |
298
|
8 |
|
$this->_changed = count($this->_content) ? array_fill(0, count($this->_content), TRUE) : array(); |
299
|
8 |
|
$this->_replace = TRUE; |
300
|
8 |
|
$this->_removed = TRUE; |
301
|
|
|
|
302
|
8 |
|
return $this; |
303
|
|
|
} |
304
|
|
|
|
305
|
8 |
|
public function add($items) |
306
|
|
|
{ |
307
|
8 |
|
$items = Jam_Array_Model::convert_collection_to_array($items); |
308
|
|
|
|
309
|
8 |
|
foreach ($items as $item) |
310
|
|
|
{ |
311
|
8 |
|
$this->offsetSet($this->search($item), $item); |
312
|
|
|
} |
313
|
|
|
|
314
|
8 |
|
return $this; |
315
|
|
|
} |
316
|
|
|
|
317
|
6 |
|
public function remove($items) |
318
|
|
|
{ |
319
|
6 |
|
$items = Jam_Array_Model::convert_collection_to_array($items); |
320
|
|
|
|
321
|
6 |
|
foreach ($items as $item) |
322
|
|
|
{ |
323
|
6 |
|
if (($offset = $this->search($item)) !== NULL) |
324
|
|
|
{ |
325
|
6 |
|
$this->offsetUnset($offset); |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
6 |
|
return $this; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* Build a new Jam Model, add it to the collection and return the newly built model |
334
|
|
|
* @param array $values set values on the new model |
335
|
|
|
* @return Jam_Model |
336
|
|
|
*/ |
337
|
1 |
|
public function build(array $values = NULL) |
338
|
|
|
{ |
339
|
1 |
|
$item = clone Jam::build_template($this->model(), $values); |
340
|
1 |
|
if ($values) |
341
|
|
|
{ |
342
|
1 |
|
$item->set($values); |
343
|
|
|
} |
344
|
1 |
|
$this->add($item); |
345
|
|
|
|
346
|
1 |
|
return $item; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* The same as build but saves the model in the database |
351
|
|
|
* @param array $values |
352
|
|
|
* @return Jam_Model |
353
|
|
|
*/ |
354
|
|
|
public function create(array $values = NULL) |
355
|
|
|
{ |
356
|
|
|
return $this->build($values)->save(); |
357
|
|
|
} |
358
|
|
|
|
359
|
1 |
|
public function check_changed() |
360
|
|
|
{ |
361
|
1 |
|
$check = TRUE; |
362
|
|
|
|
363
|
1 |
|
foreach ($this->_changed as $offset => $is_changed) |
364
|
|
|
{ |
365
|
1 |
|
$item = $this->offsetGet($offset); |
366
|
1 |
|
if ($is_changed AND $item AND ! $item->is_validating() AND ! $item->check()) |
367
|
|
|
{ |
368
|
1 |
|
$check = FALSE; |
369
|
|
|
} |
370
|
|
|
} |
371
|
|
|
|
372
|
1 |
|
return $check; |
373
|
|
|
} |
374
|
|
|
|
375
|
1 |
|
public function save_changed() |
376
|
|
|
{ |
377
|
1 |
|
foreach ($this->_changed as $offset => $is_changed) |
378
|
|
|
{ |
379
|
1 |
|
$item = $this->offsetGet($offset); |
380
|
1 |
|
if ($is_changed AND $item AND ! $item->is_saving()) |
381
|
|
|
{ |
382
|
1 |
|
$item->save(); |
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
|
386
|
1 |
|
return $this; |
387
|
|
|
} |
388
|
|
|
|
389
|
1 |
|
public function clear() |
390
|
|
|
{ |
391
|
1 |
|
$this->_changed = |
392
|
1 |
|
$this->_original = |
393
|
1 |
|
$this->_content = array(); |
394
|
|
|
|
395
|
1 |
|
return $this; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
public function clear_changed() |
399
|
|
|
{ |
400
|
|
|
$this->_changed = array(); |
401
|
|
|
$this->_original = $this->_content; |
402
|
|
|
|
403
|
|
|
return $this; |
404
|
|
|
} |
405
|
|
|
|
406
|
16 |
|
public function __toString() |
407
|
|
|
{ |
408
|
16 |
|
if ( ! $this->collection()) |
409
|
|
|
return 'Jam_Array ('.$this->model().')[NOT LOADED COLLECTION]'; |
410
|
|
|
|
411
|
16 |
|
return $this->collection()->__toString(); |
412
|
|
|
} |
413
|
|
|
|
414
|
2 |
|
public function __call($method, $args) |
415
|
|
|
{ |
416
|
2 |
|
if ( ! $this->collection()) |
417
|
|
|
throw new Kohana_Exception('Cannot call method :method on collection because collection not loaded for Jam_Array(:model)', array(':method' => $method, ':model' => $this->model())); |
418
|
|
|
|
419
|
2 |
|
return call_user_func_array(array(clone $this->collection(), $method), $args); |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* Getter for the changed array - check if any or a particular item has been changed |
424
|
|
|
* @param int $offset |
425
|
|
|
* @return bool |
426
|
|
|
*/ |
427
|
6 |
|
public function changed($offset = NULL) |
428
|
|
|
{ |
429
|
6 |
|
if ($this->_content) |
|
|
|
|
430
|
|
|
{ |
431
|
6 |
|
foreach ($this->_content as $key => $value) |
432
|
|
|
{ |
433
|
6 |
|
if ( ! isset($this->_changed[$key]) AND $value instanceof Jam_Model AND $value->changed()) |
434
|
|
|
{ |
435
|
6 |
|
$this->_changed[$key] = TRUE; |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
} |
439
|
|
|
|
440
|
6 |
|
return parent::changed($offset); |
441
|
|
|
} |
442
|
|
|
|
443
|
754 |
|
public function serialize() |
444
|
|
|
{ |
445
|
754 |
|
$this->_load_content(); |
446
|
|
|
|
447
|
|
|
|
448
|
754 |
|
return serialize(array( |
449
|
754 |
|
'original' => $this->_original, |
450
|
754 |
|
'model' => $this->_model, |
451
|
754 |
|
'replace' => $this->_replace, |
452
|
754 |
|
'changed' => $this->_changed, |
453
|
754 |
|
'content' => $this->_content, |
454
|
754 |
|
'removed' => $this->_removed, |
455
|
754 |
|
'current' => $this->_current, |
456
|
|
|
)); |
457
|
|
|
} |
458
|
|
|
|
459
|
754 |
|
public function unserialize($data) |
460
|
|
|
{ |
461
|
754 |
|
$data = unserialize($data); |
462
|
|
|
|
463
|
754 |
|
$this->_original = $data['original']; |
464
|
754 |
|
$this->_model = $data['model']; |
465
|
754 |
|
$this->_replace = $data['replace']; |
466
|
754 |
|
$this->_changed = $data['changed']; |
467
|
754 |
|
$this->_content = $data['content']; |
468
|
754 |
|
$this->_removed = $data['removed']; |
469
|
754 |
|
$this->_current = $data['current']; |
470
|
754 |
|
} |
471
|
|
|
} |
472
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.