# Source for file Collection.php

Documentation is available at Collection.php

1. <?php
2. /*
3. * $Id: Collection.php 2282 2007-08-28 16:45:22Z jackbravo $
4. *
5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16. *
17. * This software consists of voluntary contributions made by many individuals
18. * and is licensed under the LGPL. For more information, see
19. * <http://www.phpdoctrine.com>.
20. */
21. Doctrine::autoload('Doctrine_Access');
22. /**
23. * Doctrine_Collection
24. * Collection of Doctrine_Record objects.
25. *
26. * @package Doctrine
27. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
28. * @category Object Relational Mapping
29. * @link www.phpdoctrine.com
30. * @since 1.0
31. * @version $Revision: 2282 $
32. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
33. */
34. class Doctrine_Collection extends Doctrine_Access implements Countable, IteratorAggregate, Serializable
35. {
36. /**
37. * @var array $data an array containing the records of this collection
38. */
39. protected $data = array();
40. /**
41. * @var Doctrine_Table $table each collection has only records of specified table
42. */
43. protected $_table;
44. /**
45. * @var array $_snapshot a snapshot of the fetched data
46. */
47. protected $_snapshot = array();
48. /**
49. * @var Doctrine_Record $reference collection can belong to a record
50. */
51. protected $reference;
52. /**
53. * @var string $referenceField the reference field of the collection
54. */
55. protected $referenceField;
56. /**
57. * @var Doctrine_Relation the record this collection is related to, if any
58. */
59. protected $relation;
60. /**
61. * @var string $keyColumn the name of the column that is used for collection key mapping
62. */
63. protected $keyColumn;
64. /**
65. * @var Doctrine_Null $null used for extremely fast null value testing
66. */
67. protected static $null;
70. /**
71. * constructor
72. *
73. * @param Doctrine_Table|string$table
74. */
75. public function __construct($table)
76. {
77. if ( ! ($table instanceof Doctrine_Table)) {
78. $table = Doctrine_Manager::getInstance()
79. ->getTable($table);
80. }
81. $this->_table = $table;
83. $name = $table->getAttribute(Doctrine::ATTR_COLL_KEY);
84. if ($name !== null) {
85. $this->keyColumn = $name;
86. }
87. }
88. /**
89. * initNullObject
90. * initializes the null object for this collection
91. *
92. * @return void
93. */
94. public static function initNullObject(Doctrine_Null $null)
95. {
96. self::$null = $null;
97. }
98. /**
99. * getTable
100. * returns the table this collection belongs to
101. *
102. * @return Doctrine_Table
103. */
104. public function getTable()
105. {
106. return $this->_table;
107. }
108. /**
109. * setData
110. *
111. * @param array $data
112. * @return Doctrine_Collection
113. */
114. public function setData(array $data)
115. {
116. $this->data = $data;
117. }
118. /**
119. /**
120. * this method is automatically called when this Doctrine_Collection is serialized
121. *
122. * @return array
123. */
124. public function serialize()
125. {
126. $vars = get_object_vars($this);
128. unset($vars['reference']);
129. unset($vars['reference_field']);
130. unset($vars['relation']);
131. unset($vars['expandable']);
132. unset($vars['expanded']);
133. unset($vars['generator']);
135. $vars['_table'] = $vars['_table']->getComponentName();
137. return serialize($vars);
138. }
139. /**
140. * unseralize
141. * this method is automatically called everytime a Doctrine_Collection object is unserialized
142. *
143. * @return void
144. */
145. public function unserialize($serialized)
146. {
147. $manager = Doctrine_Manager::getInstance();
148. $connection = $manager->getCurrentConnection();
150. $array = unserialize($serialized);
152. foreach ($array as $name => $values) {
153. $this->$name = $values;
154. }
156. $this->_table = $connection->getTable($this->_table);
159. $name = $this->_table->getAttribute(Doctrine::ATTR_COLL_KEY);
160. if ($name !== null) {
161. $this->keyColumn = $name;
162. }
163. }
164. /**
165. * setKeyColumn
166. * sets the key column for this collection
167. *
168. * @param string $column
169. * @return Doctrine_Collection
170. */
171. public function setKeyColumn($column)
172. {
173. $this->keyColumn = $column;
175. return $this;
176. }
177. /**
178. * getKeyColumn
179. * returns the name of the key column
180. *
181. * @return string
182. */
183. public function getKeyColumn()
184. {
185. return $this->column;
186. }
187. /**
188. * getData
189. * returns all the records as an array
190. *
191. * @return array
192. */
193. public function getData()
194. {
195. return $this->data;
196. }
197. /**
198. * getFirst
199. * returns the first record in the collection
200. *
201. * @return mixed
202. */
203. public function getFirst()
204. {
205. return reset($this->data);
206. }
207. /**
208. * getLast
209. * returns the last record in the collection
210. *
211. * @return mixed
212. */
213. public function getLast()
214. {
215. return end($this->data);
216. }
217. /**
218. * setReference
219. * sets a reference pointer
220. *
221. * @return void
222. */
223. public function setReference(Doctrine_Record $record, Doctrine_Relation $relation)
224. {
225. $this->reference = $record;
226. $this->relation = $relation;
228. if ($relation instanceof Doctrine_Relation_ForeignKey
229. || $relation instanceof Doctrine_Relation_LocalKey
230. ) {
232. $this->referenceField = $relation->getForeign();
234. $value = $record->get($relation->getLocal());
236. foreach ($this->data as $record) {
237. if ($value !== null) {
238. $record->set($this->referenceField, $value, false);
239. } else {
240. $record->set($this->referenceField, $this->reference, false);
241. }
242. }
243. } elseif ($relation instanceof Doctrine_Relation_Association) {
245. }
246. }
247. /**
248. * getReference
249. *
250. * @return mixed
251. */
252. public function getReference()
253. {
254. return $this->reference;
255. }
256. /**
257. * remove
258. * removes a specified collection element
259. *
260. * @param mixed $key
261. * @return boolean
262. */
263. public function remove($key)
264. {
265. $removed = $this->data[$key];
267. unset($this->data[$key]);
268. return $removed;
269. }
270. /**
271. * contains
272. * whether or not this collection contains a specified element
273. *
274. * @param mixed $key the key of the element
275. * @return boolean
276. */
277. public function contains($key)
278. {
279. return isset($this->data[$key]);
280. }
281. public function search(Doctrine_Record $record)
282. {
283. return array_search($record, $this->data, true);
284. }
285. /**
286. * get
287. * returns a record for given key
288. *
289. * There are two special cases:
290. *
291. * 1. if null is given as a key a new record is created and attached
292. * at the end of the collection
293. *
294. * 2. if given key does not exist, then a new record is create and attached
295. * to the given key
296. *
297. * Collection also maps referential information to newly created records
298. *
299. * @param mixed $key the key of the element
300. * @return Doctrine_Record return a specified record
301. */
302. public function get($key)
303. {
304. if ($key === null || ! isset($this->data[$key])) {
305. $record = $this->_table->create();
307. if (isset($this->referenceField)) {
308. $value = $this->reference->get($this->relation->getLocal());
310. if ($value !== null) {
311. $record->set($this->referenceField, $value, false);
312. } else {
313. $record->set($this->referenceField, $this->reference, false);
314. }
315. }
317. $this->data[] = $record;
319. return $record;
320. }
322. return $this->data[$key];
323. }
325. /**
326. * @return array an array containing all primary keys
327. */
328. public function getPrimaryKeys()
329. {
330. $list = array();
331. $name = $this->_table->getIdentifier();
333. foreach ($this->data as $record) {
334. if (is_array($record) && isset($record[$name])) {
335. $list[] = $record[$name];
336. } else {
337. $list[] = $record->getIncremented();
338. }
339. }
340. return $list;
341. }
342. /**
343. * returns all keys
344. * @return array
345. */
346. public function getKeys()
347. {
348. return array_keys($this->data);
349. }
350. /**
351. * count
352. * this class implements interface countable
353. * returns the number of records in this collection
354. *
355. * @return integer
356. */
357. public function count()
358. {
359. return count($this->data);
360. }
361. /**
362. * set
363. * @param integer $key
364. * @param Doctrine_Record $record
365. * @return void
366. */
367. public function set($key, Doctrine_Record $record)
368. {
369. if (isset($this->referenceField)) {
370. $record->set($this->referenceField, $this->reference, false);
371. }
373. $this->data[$key] = $record;
374. }
375. /**
376. * adds a record to collection
377. * @param Doctrine_Record $record record to be added
378. * @param string $key optional key for the record
379. * @return boolean
380. */
381. public function add(Doctrine_Record $record, $key = null)
382. {
383. if (isset($this->referenceField)) {
384. $value = $this->reference->get($this->relation->getLocal());
386. if ($value !== null) {
387. $record->set($this->referenceField, $value, false);
388. } else {
389. $record->set($this->referenceField, $this->reference, false);
390. }
391. }
392. /**
393. * for some weird reason in_array cannot be used here (php bug ?)
394. *
395. * if used it results in fatal error : [ nesting level too deep ]
396. */
397. foreach ($this->data as $val) {
398. if ($val === $record) {
399. return false;
400. }
401. }
403. if (isset($key)) {
404. if (isset($this->data[$key])) {
405. return false;
406. }
407. $this->data[$key] = $record;
408. return true;
409. }
411. if (isset($this->keyColumn)) {
412. $value = $record->get($this->keyColumn);
413. if ($value === null) {
414. throw new Doctrine_Collection_Exception("Couldn't create collection index. Record field '".$this->keyColumn."' was null.");
415. }
416. $this->data[$value] = $record;
417. } else {
418. $this->data[] = $record;
419. }
420. return true;
421. }
422. /**
423. * loadRelated
424. *
425. * @param mixed $name
426. * @return boolean
427. */
428. public function loadRelated($name = null)
429. {
430. $list = array();
431. $query = new Doctrine_Query($this->_table->getConnection());
433. if ( ! isset($name)) {
434. foreach ($this->data as $record) {
435. $value = $record->getIncremented();
436. if ($value !== null) {
437. $list[] = $value;
438. }
439. }
440. $query->from($this->_table->getComponentName() . '(' . implode(", ",$this->_table->getPrimaryKeys()) . ')');
441. $query->where($this->_table->getComponentName() . '.id IN (' . substr(str_repeat("?, ", count($list)),0,-2) . ')');
443. return $query;
444. }
446. $rel = $this->_table->getRelation($name);
448. if ($rel instanceof Doctrine_Relation_LocalKey || $rel instanceof Doctrine_Relation_ForeignKey) {
449. foreach ($this->data as $record) {
450. $list[] = $record[$rel->getLocal()];
451. }
452. } else {
453. foreach ($this->data as $record) {
454. $value = $record->getIncremented();
455. if ($value !== null) {
456. $list[] = $value;
457. }
458. }
459. }
461. $dql = $rel->getRelationDql(count($list), 'collection');
463. $coll = $query->query($dql, $list);
465. $this->populateRelated($name, $coll);
466. }
467. /**
468. * populateRelated
469. *
470. * @param string $name
471. * @param Doctrine_Collection $coll
472. * @return void
473. */
474. public function populateRelated($name, Doctrine_Collection $coll)
475. {
476. $rel = $this->_table->getRelation($name);
477. $table = $rel->getTable();
478. $foreign = $rel->getForeign();
479. $local = $rel->getLocal();
481. if ($rel instanceof Doctrine_Relation_LocalKey) {
482. foreach ($this->data as $key => $record) {
483. foreach ($coll as $k => $related) {
484. if ($related[$foreign] == $record[$local]) {
485. $this->data[$key]->setRelated($name, $related);
486. }
487. }
488. }
489. } elseif ($rel instanceof Doctrine_Relation_ForeignKey) {
490. foreach ($this->data as $key => $record) {
491. if ( ! $record->exists()) {
492. continue;
493. }
494. $sub = new Doctrine_Collection($table);
496. foreach ($coll as $k => $related) {
497. if ($related[$foreign] == $record[$local]) {
498. $sub->add($related);
499. $coll->remove($k);
500. }
501. }
503. $this->data[$key]->setRelated($name, $sub);
504. }
505. } elseif ($rel instanceof Doctrine_Relation_Association) {
506. $identifier = $this->_table->getIdentifier();
507. $asf = $rel->getAssociationFactory();
508. $name = $table->getComponentName();
510. foreach ($this->data as $key => $record) {
511. if ( ! $record->exists()) {
512. continue;
513. }
514. $sub = new Doctrine_Collection($table);
515. foreach ($coll as $k => $related) {
516. if ($related->get($local) == $record[$identifier]) {
517. $sub->add($related->get($name));
518. }
519. }
520. $this->data[$key]->setRelated($name, $sub);
522. }
523. }
524. }
525. /**
526. * getNormalIterator
527. * returns normal iterator - an iterator that will not expand this collection
528. *
529. * @return Doctrine_Iterator_Normal
530. */
531. public function getNormalIterator()
532. {
533. return new Doctrine_Collection_Iterator_Normal($this);
534. }
535. /**
536. * takeSnapshot
537. * takes a snapshot from this collection
538. *
539. * snapshots are used for diff processing, for example
540. * when a fetched collection has three elements, then two of those
541. * are being removed the diff would contain one element
542. *
543. * Doctrine_Collection::save() attaches the diff with the help of last
544. * snapshot.
545. *
546. * @return Doctrine_Collection
547. */
548. public function takeSnapshot()
549. {
550. $this->_snapshot = $this->data;
552. return $this;
553. }
554. /**
555. * getSnapshot
556. * returns the data of the last snapshot
557. *
558. * @return array returns the data in last snapshot
559. */
560. public function getSnapshot()
561. {
562. return $this->_snapshot;
563. }
564. /**
565. * processDiff
566. * processes the difference of the last snapshot and the current data
567. *
568. * an example:
569. * Snapshot with the objects 1, 2 and 4
570. * Current data with objects 2, 3 and 5
571. *
572. * The process would remove object 4
573. *
574. * @return Doctrine_Collection
575. */
576. public function processDiff()
577. {
578. foreach (array_diff($this->_snapshot, $this->data) as $record) {
579. $record->delete();
580. }
582. return $this;
583. }
584. /**
585. * toArray
586. * Mimics the result of a $query->execute(array(), Doctrine::FETCH_ARRAY);
587. *
588. * @param boolean $deep
589. */
590. public function toArray($deep = false)
591. {
592. if ($deep) {
593. $data = array();
594. foreach ($this->data as $key => $record) {
595. $data[$key] = $record->toArray($deep);
596. }
597. return $data;
598. } else {
599. // this is preserved for backwards compatibility
600. // but could be replaced with above code
601. return $this->data;
602. }
603. }
604. public function getDeleteDiff()
605. {
606. return array_diff($this->_snapshot, $this->data);
607. }
608. public function getInsertDiff()
609. {
610. return array_diff($this->data, $this->_snapshot);
611. }
612. /**
613. * save
614. * saves all records of this collection and processes the
615. * difference of the last snapshot and the current data
616. *
617. * @param Doctrine_Connection $conn optional connection parameter
618. * @return Doctrine_Collection
619. */
620. public function save(Doctrine_Connection $conn = null)
621. {
622. if ($conn == null) {
623. $conn = $this->_table->getConnection();
624. }
625. $conn->beginTransaction();
626. $conn->transaction->addCollection($this);
628. $this->processDiff();
630. foreach ($this->getData() as $key => $record) {
631. $record->save($conn);
632. }
634. $conn->commit();
636. return $this;
637. }
638. /**
639. * delete
640. * single shot delete
641. * deletes all records from this collection
642. * and uses only one database query to perform this operation
643. *
644. * @return Doctrine_Collection
645. */
646. public function delete(Doctrine_Connection $conn = null)
647. {
648. if ($conn == null) {
649. $conn = $this->_table->getConnection();
650. }
652. $conn->beginTransaction();
653. $conn->transaction->addCollection($this);
655. foreach ($this as $key => $record) {
656. $record->delete($conn);
657. }
659. $conn->commit();
661. $this->data = array();
663. return $this;
664. }
665. /**
666. * getIterator
667. * @return object ArrayIterator
668. */
669. public function getIterator()
670. {
671. $data = $this->data;
672. return new ArrayIterator($data);
673. }
674. /**
675. * returns a string representation of this object
676. */
677. public function __toString()
678. {
679. return Doctrine_Lib::getCollectionAsString($this);
680. }
681. }