# Source for file Record.php

Documentation is available at Record.php

1. <?php
2. /*
3. * $Id: Record.php 2294 2007-08-29 22:20:30Z zYne $
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_Record_Abstract');
22. /**
23. * Doctrine_Record
24. * All record classes should inherit this super class
25. *
26. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
27. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
28. * @package Doctrine
29. * @category Object Relational Mapping
30. * @link www.phpdoctrine.com
31. * @since 1.0
32. * @version $Revision: 2294 $
33. */
34. abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Countable, IteratorAggregate, Serializable
35. {
36. /**
37. * STATE CONSTANTS
38. */
40. /**
41. * DIRTY STATE
42. * a Doctrine_Record is in dirty state when its properties are changed
43. */
44. const STATE_DIRTY = 1;
45. /**
46. * TDIRTY STATE
47. * a Doctrine_Record is in transient dirty state when it is created
48. * and some of its fields are modified but it is NOT yet persisted into database
49. */
50. const STATE_TDIRTY = 2;
51. /**
52. * CLEAN STATE
53. * a Doctrine_Record is in clean state when all of its properties are loaded from the database
54. * and none of its properties are changed
55. */
56. const STATE_CLEAN = 3;
57. /**
58. * PROXY STATE
59. * a Doctrine_Record is in proxy state when its properties are not fully loaded
60. */
61. const STATE_PROXY = 4;
62. /**
63. * NEW TCLEAN
64. * a Doctrine_Record is in transient clean state when it is created and none of its fields are modified
65. */
66. const STATE_TCLEAN = 5;
67. /**
68. * LOCKED STATE
69. * a Doctrine_Record is temporarily locked during deletes and saves
70. *
71. * This state is used internally to ensure that circular deletes
72. * and saves will not cause infinite loops
73. */
74. const STATE_LOCKED = 6;
76. /**
77. * @var Doctrine_Node_<TreeImpl> node object
78. */
79. protected $_node;
80. /**
81. * @var integer $_id the primary keys of this object
82. */
83. protected $_id = array();
84. /**
85. * @var array $_data the record data
86. */
87. protected $_data = array();
88. /**
89. * @var array $_values the values array, aggregate values and such are mapped into this array
90. */
91. protected $_values = array();
92. /**
93. * @var integer $_state the state of this record
94. * @see STATE_* constants
95. */
96. protected $_state;
97. /**
98. * @var array $_modified an array containing properties that have been modified
99. */
100. protected $_modified = array();
101. /**
102. * @var Doctrine_Validator_ErrorStack error stack object
103. */
104. protected $_errorStack;
105. /**
106. * @var Doctrine_Record_Filter the filter object
107. */
108. protected $_filter;
109. /**
110. * @var array $_references an array containing all the references
111. */
112. protected $_references = array();
113. /**
114. * @var integer $index this index is used for creating object identifiers
115. */
116. private static $_index = 1;
117. /**
118. * @var integer $oid object identifier, each Record object has a unique object identifier
119. */
120. private $_oid;
122. /**
123. * constructor
124. * @param Doctrine_Table|null$table a Doctrine_Table object or null,
125. * if null the table object is retrieved from current connection
126. *
127. * @param boolean $isNewEntry whether or not this record is transient
128. *
129. * @throws Doctrine_Connection_Exception if object is created using the new operator and there are no
130. * open connections
131. * @throws Doctrine_Record_Exception if the cleanData operation fails somehow
132. */
133. public function __construct($table = null, $isNewEntry = false)
134. {
135. if (isset($table) && $table instanceof Doctrine_Table) {
136. $this->_table = $table;
137. $exists = ( ! $isNewEntry);
138. } else {
139. $class = get_class($this);
140. // get the table of this class
141. $this->_table = Doctrine_Manager::getInstance()
142. ->getTable(get_class($this));
143. $exists = false;
144. }
146. // initialize the filter object
147. $this->_filter = new Doctrine_Record_Filter($this);
149. // Check if the current connection has the records table in its registry
150. // If not this record is only used for creating table definition and setting up
151. // relations.
153. if ($this->_table->getConnection()->hasTable($this->_table->getComponentName())) {
154. $this->_oid = self::$_index;
156. self::$_index++;
158. $keys = $this->_table->getPrimaryKeys();
160. // get the data array
161. $this->_data = $this->_table->getData();
163. // get the column count
164. $count = count($this->_data);
166. $this->_values = $this->cleanData($this->_data);
168. $this->prepareIdentifiers($exists);
170. if ( ! $exists) {
171. if ($count > 0) {
172. $this->_state = Doctrine_Record::STATE_TDIRTY;
173. } else {
174. $this->_state = Doctrine_Record::STATE_TCLEAN;
175. }
177. // set the default values for this record
178. $this->assignDefaultValues();
179. } else {
180. $this->_state = Doctrine_Record::STATE_CLEAN;
182. if ($count < $this->_table->getColumnCount()) {
183. $this->_state = Doctrine_Record::STATE_PROXY;
184. }
185. }
187. $this->_errorStack = new Doctrine_Validator_ErrorStack();
189. $repository = $this->_table->getRepository();
190. $repository->add($this);
192. $this->construct();
193. }
195. }
196. /**
197. * _index
198. *
199. * @return integer
200. */
201. public static function _index()
202. {
203. return self::$_index;
204. }
205. /**
206. * setUp
207. * this method is used for setting up relations and attributes
208. * it should be implemented by child classes
209. *
210. * @return void
211. */
212. public function setUp()
213. { }
214. /**
215. * construct
216. * Empty tempalte method to provide concrete Record classes with the possibility
217. * to hook into the constructor procedure
218. *
219. * @return void
220. */
221. public function construct()
222. { }
223. /**
224. * getOid
225. * returns the object identifier
226. *
227. * @return integer
228. */
229. public function getOid()
230. {
231. return $this->_oid;
232. }
233. /**
234. * isValid
235. *
236. * @return boolean whether or not this record passes all column validations
237. */
238. public function isValid()
239. {
240. if ( ! $this->_table->getAttribute(Doctrine::ATTR_VLD)) {
241. return true;
242. }
243. // Clear the stack from any previous errors.
244. $this->_errorStack->clear();
246. // Run validation process
247. $validator = new Doctrine_Validator();
248. $validator->validateRecord($this);
249. $this->validate();
250. if ($this->_state == self::STATE_TDIRTY || $this->_state == self::STATE_TCLEAN) {
251. $this->validateOnInsert();
252. } else {
253. $this->validateOnUpdate();
254. }
256. return $this->_errorStack->count() == 0 ? true : false;
257. }
258. /**
259. * Empty template method to provide concrete Record classes with the possibility
260. * to hook into the validation procedure, doing any custom / specialized
261. * validations that are neccessary.
262. */
263. protected function validate()
264. { }
265. /**
266. * Empty template method to provide concrete Record classes with the possibility
267. * to hook into the validation procedure only when the record is going to be
268. * updated.
269. */
270. protected function validateOnUpdate()
271. { }
272. /**
273. * Empty template method to provide concrete Record classes with the possibility
274. * to hook into the validation procedure only when the record is going to be
275. * inserted into the data store the first time.
276. */
277. protected function validateOnInsert()
278. { }
279. /**
280. * Empty template method to provide concrete Record classes with the possibility
281. * to hook into the serializing procedure.
282. */
283. public function preSerialize($event)
284. { }
285. /**
286. * Empty template method to provide concrete Record classes with the possibility
287. * to hook into the serializing procedure.
288. */
289. public function postSerialize($event)
290. { }
291. /**
292. * Empty template method to provide concrete Record classes with the possibility
293. * to hook into the serializing procedure.
294. */
295. public function preUnserialize($event)
296. { }
297. /**
298. * Empty template method to provide concrete Record classes with the possibility
299. * to hook into the serializing procedure.
300. */
301. public function postUnserialize($event)
302. { }
303. /**
304. * Empty template method to provide concrete Record classes with the possibility
305. * to hook into the saving procedure.
306. */
307. public function preSave($event)
308. { }
309. /**
310. * Empty template method to provide concrete Record classes with the possibility
311. * to hook into the saving procedure.
312. */
313. public function postSave($event)
314. { }
315. /**
316. * Empty template method to provide concrete Record classes with the possibility
317. * to hook into the deletion procedure.
318. */
319. public function preDelete($event)
320. { }
321. /**
322. * Empty template method to provide concrete Record classes with the possibility
323. * to hook into the deletion procedure.
324. */
325. public function postDelete($event)
326. { }
327. /**
328. * Empty template method to provide concrete Record classes with the possibility
329. * to hook into the saving procedure only when the record is going to be
330. * updated.
331. */
332. public function preUpdate($event)
333. { }
334. /**
335. * Empty template method to provide concrete Record classes with the possibility
336. * to hook into the saving procedure only when the record is going to be
337. * updated.
338. */
339. public function postUpdate($event)
340. { }
341. /**
342. * Empty template method to provide concrete Record classes with the possibility
343. * to hook into the saving procedure only when the record is going to be
344. * inserted into the data store the first time.
345. */
346. public function preInsert($event)
347. { }
348. /**
349. * Empty template method to provide concrete Record classes with the possibility
350. * to hook into the saving procedure only when the record is going to be
351. * inserted into the data store the first time.
352. */
353. public function postInsert($event)
354. { }
355. /**
356. * getErrorStack
357. *
358. * @return Doctrine_Validator_ErrorStack returns the errorStack associated with this record
359. */
360. public function getErrorStack()
361. {
362. return $this->_errorStack;
363. }
364. /**
365. * errorStack
366. * assigns / returns record errorStack
367. *
368. * @param Doctrine_Validator_ErrorStack errorStack to be assigned for this record
369. * @return void|Doctrine_Validator_ErrorStack returns the errorStack associated with this record
370. */
371. public function errorStack($stack = null)
372. {
373. if($stack !== null) {
374. if( ! ($stack instanceof Doctrine_Validator_ErrorStack)) {
375. throw new Doctrine_Record_Exception('Argument should be an instance of Doctrine_Validator_ErrorStack.');
376. }
377. $this->_errorStack = $stack;
378. } else {
379. return $this->_errorStack;
380. }
381. }
382. /**
383. * setDefaultValues
384. * sets the default values for records internal data
385. *
386. * @param boolean $overwrite whether or not to overwrite the already set values
387. * @return boolean
388. */
389. public function assignDefaultValues($overwrite = false)
390. {
391. if ( ! $this->_table->hasDefaultValues()) {
392. return false;
393. }
394. foreach ($this->_data as $column => $value) {
395. $default = $this->_table->getDefaultValueOf($column);
397. if ($default === null) {
398. $default = self::$_null;
399. }
401. if ($value === self::$_null || $overwrite) {
402. $this->_data[$column] = $default;
403. $this->_modified[] = $column;
404. $this->_state = Doctrine_Record::STATE_TDIRTY;
405. }
406. }
407. }
408. /**
409. * cleanData
410. *
411. * @param array $data data array to be cleaned
412. * @return integer
413. */
414. public function cleanData(&$data)
415. {
416. $tmp = $data;
417. $data = array();
419. foreach ($this->getTable()->getColumnNames() as $name) {
420. if ( ! isset($tmp[$name])) {
421. $data[$name] = self::$_null;
422. } else {
423. $data[$name] = $tmp[$name];
424. }
425. unset($tmp[$name]);
426. }
428. return $tmp;
429. }
430. /**
431. * hydrate
432. * hydrates this object from given array
433. *
434. * @param array $data
435. * @return boolean
436. */
437. public function hydrate(array $data)
438. {
439. $this->_values = $this->cleanData($data);
440. $this->_data = array_merge($this->_data, $data);
442. $this->prepareIdentifiers(true);
443. }
444. /**
445. * prepareIdentifiers
446. * prepares identifiers for later use
447. *
448. * @param boolean $exists whether or not this record exists in persistent data store
449. * @return void
450. */
451. private function prepareIdentifiers($exists = true)
452. {
453. switch ($this->_table->getIdentifierType()) {
454. case Doctrine::IDENTIFIER_AUTOINC:
455. case Doctrine::IDENTIFIER_SEQUENCE:
456. case Doctrine::IDENTIFIER_NATURAL:
457. $name = $this->_table->getIdentifier();
459. if ($exists) {
460. if (isset($this->_data[$name]) && $this->_data[$name] !== self::$_null) {
461. $this->_id[$name] = $this->_data[$name];
462. }
463. }
464. break;
465. case Doctrine::IDENTIFIER_COMPOSITE:
466. $names = $this->_table->getIdentifier();
468. foreach ($names as $name) {
469. if ($this->_data[$name] === self::$_null) {
470. $this->_id[$name] = null;
471. } else {
472. $this->_id[$name] = $this->_data[$name];
473. }
474. }
475. break;
476. }
477. }
478. /**
479. * serialize
480. * this method is automatically called when this Doctrine_Record is serialized
481. *
482. * @return array
483. */
484. public function serialize()
485. {
486. $event = new Doctrine_Event($this, Doctrine_Event::RECORD_SERIALIZE);
488. $this->preSerialize($event);
490. $vars = get_object_vars($this);
492. unset($vars['_references']);
493. unset($vars['_table']);
494. unset($vars['_errorStack']);
495. unset($vars['_filter']);
496. unset($vars['_modified']);
497. unset($vars['_node']);
499. $name = $this->_table->getIdentifier();
500. $this->_data = array_merge($this->_data, $this->_id);
502. foreach ($this->_data as $k => $v) {
503. if ($v instanceof Doctrine_Record && $this->_table->getTypeOf($k) != 'object') {
504. unset($vars['_data'][$k]);
505. } elseif ($v === self::$_null) {
506. unset($vars['_data'][$k]);
507. } else {
508. switch ($this->_table->getTypeOf($k)) {
509. case 'array':
510. case 'object':
511. $vars['_data'][$k] = serialize($vars['_data'][$k]);
512. break;
513. case 'gzip':
514. $vars['_data'][$k] = gzcompress($vars['_data'][$k]);
515. break;
516. case 'enum':
517. $vars['_data'][$k] = $this->_table->enumIndex($k, $vars['_data'][$k]);
518. break;
519. }
520. }
521. }
523. $str = serialize($vars);
525. $this->postSerialize($event);
527. return $str;
528. }
529. /**
530. * unseralize
531. * this method is automatically called everytime a Doctrine_Record object is unserialized
532. *
533. * @param string $serialized Doctrine_Record as serialized string
534. * @throws Doctrine_Record_Exception if the cleanData operation fails somehow
535. * @return void
536. */
537. public function unserialize($serialized)
538. {
539. $event = new Doctrine_Event($this, Doctrine_Event::RECORD_UNSERIALIZE);
541. $this->preUnserialize($event);
543. $manager = Doctrine_Manager::getInstance();
544. $connection = $manager->getConnectionForComponent(get_class($this));
546. $this->_oid = self::$_index;
547. self::$_index++;
549. $this->_table = $connection->getTable(get_class($this));
551. $array = unserialize($serialized);
553. foreach($array as $k => $v) {
554. $this->$k = $v;
555. }
557. foreach ($this->_data as $k => $v) {
559. switch ($this->_table->getTypeOf($k)) {
560. case 'array':
561. case 'object':
562. $this->_data[$k] = unserialize($this->_data[$k]);
563. break;
564. case 'gzip':
565. $this->_data[$k] = gzuncompress($this->_data[$k]);
566. break;
567. case 'enum':
568. $this->_data[$k] = $this->_table->enumValue($k, $this->_data[$k]);
569. break;
571. }
572. }
574. $this->_table->getRepository()->add($this);
575. $this->_filter = new Doctrine_Record_Filter($this);
577. $this->cleanData($this->_data);
579. $this->prepareIdentifiers($this->exists());
581. $this->postUnserialize($event);
582. }
583. /**
584. * state
585. * returns / assigns the state of this record
586. *
587. * @param integer|string$state if set, this method tries to set the record state to $state
588. * @see Doctrine_Record::STATE_* constants
589. *
590. * @throws Doctrine_Record_State_Exception if trying to set an unknown state
591. * @return null|integer
592. */
593. public function state($state = null)
594. {
595. if ($state == null) {
596. return $this->_state;
597. }
598. $err = false;
599. if (is_integer($state)) {
600. if ($state >= 1 && $state <= 6) {
601. $this->_state = $state;
602. } else {
603. $err = true;
604. }
605. } elseif (is_string($state)) {
606. $upper = strtoupper($state);
608. $const = 'Doctrine_Record::STATE_' . $upper;
609. if (defined($const)) {
610. $this->_state = constant($const);
611. } else {
612. $err = true;
613. }
614. }
616. if ($this->_state === Doctrine_Record::STATE_TCLEAN ||
617. $this->_state === Doctrine_Record::STATE_CLEAN) {
619. $this->_modified = array();
620. }
622. if ($err) {
623. throw new Doctrine_Record_State_Exception('Unknown record state ' . $state);
624. }
625. }
626. /**
627. * refresh
628. * refresh internal data from the database
629. *
630. * @throws Doctrine_Record_Exception When the refresh operation fails (when the database row
631. * this record represents does not exist anymore)
632. * @return boolean
633. */
634. public function refresh()
635. {
636. $id = $this->identifier();
637. if ( ! is_array($id)) {
638. $id = array($id);
639. }
640. if (empty($id)) {
641. return false;
642. }
643. $id = array_values($id);
645. $records = Doctrine_Query::create()
646. ->from($this->_table->getComponentName())
647. ->where(implode(' = ? AND ', $this->_table->getPrimaryKeys()) . ' = ?')
648. ->execute($id);
651. if (count($records) === 0) {
652. throw new Doctrine_Record_Exception('Failed to refresh. Record does not exist.');
653. }
655. $this->_modified = array();
657. $this->prepareIdentifiers();
659. $this->_state = Doctrine_Record::STATE_CLEAN;
661. return $this;
662. }
664. /**
665. * refresh
666. * refres data of related objects from the database
667. *
668. * @param string $name name of a related component.
669. * if set, this method only refreshes the specified related component
670. *
671. * @return Doctrine_Record this object
672. */
673. public function refreshRelated($name = null)
674. {
675. if (is_null($name)) {
676. foreach ($this->_table->getRelations() as $rel) {
677. $this->_references[$rel->getAlias()] = $rel->fetchRelatedFor($this);
678. }
679. } else {
680. $rel = $this->_table->getRelation($name);
681. $this->_references[$name] = $rel->fetchRelatedFor($this);
682. }
683. }
685. /**
686. * getTable
687. * returns the table object for this record
688. *
689. * @return object Doctrine_Table a Doctrine_Table object
690. */
691. public function getTable()
692. {
693. return $this->_table;
694. }
695. /**
696. * getData
697. * return all the internal data
698. *
699. * @return array an array containing all the properties
700. */
701. public function getData()
702. {
703. return $this->_data;
704. }
705. /**
706. * rawGet
707. * returns the value of a property, if the property is not yet loaded
708. * this method does NOT load it
709. *
710. * @param $name name of the property
711. * @throws Doctrine_Record_Exception if trying to get an unknown property
712. * @return mixed
713. */
715. public function rawGet($name)
716. {
717. if ( ! isset($this->_data[$name])) {
718. throw new Doctrine_Record_Exception('Unknown property '. $name);
719. }
720. if ($this->_data[$name] === self::$_null)
721. return null;
723. return $this->_data[$name];
724. }
726. /**
727. * load
728. * loads all the unitialized properties from the database
729. *
730. * @return boolean
731. */
732. public function load()
733. {
734. // only load the data from database if the Doctrine_Record is in proxy state
735. if ($this->_state == Doctrine_Record::STATE_PROXY) {
736. $this->refresh();
738. $this->_state = Doctrine_Record::STATE_CLEAN;
740. return true;
741. }
742. return false;
743. }
744. /**
745. * get
746. * returns a value of a property or a related component
747. *
748. * @param mixed $name name of the property or related component
749. * @param boolean $load whether or not to invoke the loading procedure
750. * @throws Doctrine_Record_Exception if trying to get a value of unknown property / related component
751. * @return mixed
752. */
753. public function get($name, $load = true)
754. {
755. $value = self::$_null;
756. $lower = strtolower($name);
758. $lower = $this->_table->getColumnName($lower);
760. if (isset($this->_data[$lower])) {
761. // check if the property is null (= it is the Doctrine_Null object located in self::$_null)
762. if ($this->_data[$lower] === self::$_null && $load) {
763. $this->load();
764. }
766. if ($this->_data[$lower] === self::$_null) {
767. $value = null;
768. } else {
769. $value = $this->_data[$lower];
770. }
771. return $value;
772. }
774. if (isset($this->_id[$lower])) {
775. return $this->_id[$lower];
776. }
777. if ($name === $this->_table->getIdentifier()) {
778. return null;
779. }
780. if (isset($this->_values[$lower])) {
781. return $this->_values[$lower];
782. }
784. try {
786. if ( ! isset($this->_references[$name]) && $load) {
788. $rel = $this->_table->getRelation($name);
790. $this->_references[$name] = $rel->fetchRelatedFor($this);
791. }
793. } catch(Doctrine_Table_Exception $e) {
794. throw new Doctrine_Record_Exception("Unknown property / related component '$name'.");
795. }
797. return $this->_references[$name];
798. }
799. /**
800. * mapValue
801. * This simple method is used for mapping values to $values property.
802. * Usually this method is used internally by Doctrine for the mapping of
803. * aggregate values.
804. *
805. * @param string $name the name of the mapped value
806. * @param mixed $value mixed value to be mapped
807. * @return void
808. */
809. public function mapValue($name, $value)
810. {
811. $name = strtolower($name);
812. $this->_values[$name] = $value;
813. }
814. /**
815. * set
816. * method for altering properties and Doctrine_Record references
817. * if the load parameter is set to false this method will not try to load uninitialized record data
818. *
819. * @param mixed $name name of the property or reference
820. * @param mixed $value value of the property or reference
821. * @param boolean $load whether or not to refresh / load the uninitialized record data
822. *
823. * @throws Doctrine_Record_Exception if trying to set a value for unknown property / related component
824. * @throws Doctrine_Record_Exception if trying to set a value of wrong type for related component
825. *
826. * @return Doctrine_Record
827. */
828. public function set($name, $value, $load = true)
829. {
830. $lower = strtolower($name);
832. $lower = $this->_table->getColumnName($lower);
834. if (isset($this->_data[$lower])) {
835. if ($value instanceof Doctrine_Record) {
836. $type = $this->_table->getTypeOf($name);
838. $id = $value->getIncremented();
840. if ($id !== null && $type !== 'object') {
841. $value = $id;
842. }
843. }
845. if ($load) {
846. $old = $this->get($lower, $load);
847. } else {
848. $old = $this->_data[$lower];
849. }
851. if ($old !== $value) {
852. if ($value === null) {
853. $value = self::$_null;
854. }
856. $this->_data[$lower] = $value;
857. $this->_modified[] = $lower;
858. switch ($this->_state) {
859. case Doctrine_Record::STATE_CLEAN:
860. $this->_state = Doctrine_Record::STATE_DIRTY;
861. break;
862. case Doctrine_Record::STATE_TCLEAN:
863. $this->_state = Doctrine_Record::STATE_TDIRTY;
864. break;
865. }
866. }
867. } else {
868. try {
869. $this->coreSetRelated($name, $value);
870. } catch(Doctrine_Table_Exception $e) {
871. throw new Doctrine_Record_Exception("Unknown property / related component '$name'.");
872. }
873. }
874. }
876. public function coreSetRelated($name, $value)
877. {
878. $rel = $this->_table->getRelation($name);
880. // one-to-many or one-to-one relation
881. if ($rel instanceof Doctrine_Relation_ForeignKey ||
882. $rel instanceof Doctrine_Relation_LocalKey) {
883. if ( ! $rel->isOneToOne()) {
884. // one-to-many relation found
885. if ( ! ($value instanceof Doctrine_Collection)) {
886. throw new Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references.");
887. }
888. if (isset($this->_references[$name])) {
889. $this->_references[$name]->setData($value->getData());
890. return $this;
891. }
892. } else {
893. if ($value !== self::$_null) {
894. // one-to-one relation found
895. if ( ! ($value instanceof Doctrine_Record)) {
896. throw new Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Record or Doctrine_Null when setting one-to-one references.");
897. }
898. if ($rel instanceof Doctrine_Relation_LocalKey) {
899. $foreign = $rel->getForeign();
900. if (!empty($foreign) && $foreign != $value->getTable()->getIdentifier())
901. $this->set($rel->getLocal(), $value->rawGet($foreign), false);
902. else
903. $this->set($rel->getLocal(), $value, false);
904. } else {
905. $value->set($rel->getForeign(), $this, false);
906. }
907. }
908. }
910. } elseif ($rel instanceof Doctrine_Relation_Association) {
911. // join table relation found
912. if ( ! ($value instanceof Doctrine_Collection)) {
913. throw new Doctrine_Record_Exception("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting many-to-many references.");
914. }
915. }
917. $this->_references[$name] = $value;
918. }
919. /**
920. * contains
921. *
922. * @param string $name
923. * @return boolean
924. */
925. public function contains($name)
926. {
927. $lower = strtolower($name);
929. if (isset($this->_data[$lower])) {
930. return true;
931. }
932. if (isset($this->_id[$lower])) {
933. return true;
934. }
935. if (isset($this->_values[$lower])) {
936. return true;
937. }
938. if (isset($this->_references[$name]) &&
939. $this->_references[$name] !== self::$_null) {
941. return true;
942. }
943. return false;
944. }
945. /**
946. * @param string $name
947. * @return void
948. */
949. public function __unset($name)
950. {
951. if (isset($this->_data[$name])) {
952. $this->_data[$name] = array();
953. }
954. // todo: what to do with references ?
955. }
956. /**
957. * applies the changes made to this object into database
958. * this method is smart enough to know if any changes are made
959. * and whether to use INSERT or UPDATE statement
960. *
961. * this method also saves the related components
962. *
963. * @param Doctrine_Connection $conn optional connection parameter
964. * @return void
965. */
966. public function save(Doctrine_Connection $conn = null)
967. {
968. if ($conn === null) {
969. $conn = $this->_table->getConnection();
970. }
971. $conn->unitOfWork->saveGraph($this);
972. }
973. /**
974. * Tries to save the object and all its related components.
975. * In contrast to Doctrine_Record::save(), this method does not
976. * throw an exception when validation fails but returns TRUE on
977. * success or FALSE on failure.
978. *
979. * @param Doctrine_Connection $conn optional connection parameter
980. * @return TRUE if the record was saved sucessfully without errors, FALSE otherwise.
981. */
982. public function trySave(Doctrine_Connection $conn = null) {
983. try {
984. $this->save($conn);
985. return true;
986. } catch (Doctrine_Validator_Exception $ignored) {
987. return false;
988. }
989. }
990. /**
991. * replace
992. * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT
993. * query, except that if there is already a row in the table with the same
994. * key field values, the REPLACE query just updates its values instead of
995. * inserting a new row.
996. *
997. * The REPLACE type of query does not make part of the SQL standards. Since
998. * practically only MySQL and SQLIte implement it natively, this type of
999. * query isemulated through this method for other DBMS using standard types
1000. * of queries inside a transaction to assure the atomicity of the operation.
1001. *
1002. * @param Doctrine_Connection $conn optional connection parameter
1003. * @throws Doctrine_Connection_Exception if some of the key values was null
1004. * @throws Doctrine_Connection_Exception if there were no key fields
1005. * @throws PDOException if something fails at PDO level
1006. * @return integer number of rows affected
1007. */
1008. public function replace(Doctrine_Connection $conn = null)
1009. {
1010. if ($conn === null) {
1011. $conn = $this->_table->getConnection();
1012. }
1014. return $conn->replace($this->_table->getTableName(), $this->getPrepared(), $this->id);
1015. }
1016. /**
1017. * returns an array of modified fields and associated values
1018. * @return array
1019. */
1020. public function getModified()
1021. {
1022. $a = array();
1024. foreach ($this->_modified as $k => $v) {
1025. $a[$v] = $this->_data[$v];
1026. }
1027. return $a;
1028. }
1029. /**
1030. * getPrepared
1031. *
1032. * returns an array of modified fields and values with data preparation
1033. * adds column aggregation inheritance and converts Records into primary key values
1034. *
1035. * @param array $array
1036. * @return array
1037. */
1038. public function getPrepared(array $array = array())
1039. {
1040. $a = array();
1042. if (empty($array)) {
1043. $array = $this->_modified;
1044. }
1046. foreach ($array as $k => $v) {
1047. $type = $this->_table->getTypeOf($v);
1049. if ($this->_data[$v] === self::$_null) {
1050. $a[$v] = null;
1051. continue;
1052. }
1054. switch ($type) {
1055. case 'array':
1056. case 'object':
1057. $a[$v] = serialize($this->_data[$v]);
1058. break;
1059. case 'gzip':
1060. $a[$v] = gzcompress($this->_data[$v],5);
1061. break;
1062. case 'boolean':
1063. $a[$v] = $this->getTable()->getConnection()->convertBooleans($this->_data[$v]);
1064. break;
1065. case 'enum':
1066. $a[$v] = $this->_table->enumIndex($v, $this->_data[$v]);
1067. break;
1068. default:
1069. if ($this->_data[$v] instanceof Doctrine_Record) {
1070. $this->_data[$v] = $this->_data[$v]->getIncremented();
1071. }
1072. /** TODO:
1073. if ($this->_data[$v] === null) {
1074. throw new Doctrine_Record_Exception('Unexpected null value.');
1075. }
1076. */
1078. $a[$v] = $this->_data[$v];
1079. }
1080. }
1081. $map = $this->_table->inheritanceMap;
1082. foreach ($map as $k => $v) {
1083. $old = $this->get($k, false);
1085. if ((string) $old !== (string) $v || $old === null) {
1086. $a[$k] = $v;
1087. $this->_data[$k] = $v;
1088. }
1089. }
1091. return $a;
1092. }
1093. /**
1094. * count
1095. * this class implements countable interface
1096. *
1097. * @return integer the number of columns in this record
1098. */
1099. public function count()
1100. {
1101. return count($this->_data);
1102. }
1103. /**
1104. * alias for count()
1105. *
1106. * @return integer the number of columns in this record
1107. */
1108. public function columnCount()
1109. {
1110. return $this->count();
1111. }
1112. /**
1113. * toArray
1114. * returns the record as an array
1115. *
1116. * @param boolean $deep - Return also the relations
1117. * @return array
1118. */
1119. public function toArray($deep = false)
1120. {
1121. $a = array();
1123. foreach ($this as $column => $value) {
1124. if ($value === self::$_null) {
1125. $value = null;
1126. }
1127. $a[$column] = $value;
1128. }
1129. if ($this->_table->getIdentifierType() == Doctrine::IDENTIFIER_AUTOINC) {
1130. $i = $this->_table->getIdentifier();
1131. $a[$i] = $this->getIncremented();
1132. }
1133. if ($deep) {
1134. foreach ($this->_references as $key => $relation) {
1135. $a[$key] = $relation->toArray($deep);
1136. }
1137. }
1138. return array_merge($a, $this->_values);
1139. }
1140. /**
1141. * exists
1142. * returns true if this record is persistent, otherwise false
1143. *
1144. * @return boolean
1145. */
1146. public function exists()
1147. {
1148. return ($this->_state !== Doctrine_Record::STATE_TCLEAN &&
1149. $this->_state !== Doctrine_Record::STATE_TDIRTY);
1150. }
1151. /**
1152. * isModified
1153. * returns true if this record was modified, otherwise false
1154. *
1155. * @return boolean
1156. */
1157. public function isModified()
1158. {
1159. return ($this->_state === Doctrine_Record::STATE_DIRTY ||
1160. $this->_state === Doctrine_Record::STATE_TDIRTY);
1161. }
1162. /**
1163. * method for checking existence of properties and Doctrine_Record references
1164. * @param mixed $name name of the property or reference
1165. * @return boolean
1166. */
1167. public function hasRelation($name)
1168. {
1169. if (isset($this->_data[$name]) || isset($this->_id[$name])) {
1170. return true;
1171. }
1172. return $this->_table->hasRelation($name);
1173. }
1174. /**
1175. * getIterator
1176. * @return Doctrine_Record_Iterator a Doctrine_Record_Iterator that iterates through the data
1177. */
1178. public function getIterator()
1179. {
1180. return new Doctrine_Record_Iterator($this);
1181. }
1182. /**
1183. * deletes this data access object and all the related composites
1184. * this operation is isolated by a transaction
1185. *
1186. * this event can be listened by the onPreDelete and onDelete listeners
1187. *
1188. * @return boolean true on success, false on failure
1189. */
1190. public function delete(Doctrine_Connection $conn = null)
1191. {
1192. if ($conn == null) {
1193. $conn = $this->_table->getConnection();
1194. }
1195. return $conn->unitOfWork->delete($this);
1196. }
1197. /**
1198. * copy
1199. * returns a copy of this object
1200. *
1201. * @return Doctrine_Record
1202. */
1203. public function copy()
1204. {
1205. $data = $this->_data;
1207. if ($this->_table->getIdentifierType() === Doctrine::IDENTIFIER_AUTOINC) {
1208. $id = $this->_table->getIdentifier();
1210. unset($data[$id]);
1211. }
1213. $ret = $this->_table->create($data);
1214. $modified = array();
1216. foreach ($data as $key => $val) {
1217. if ( ! ($val instanceof Doctrine_Null)) {
1218. $ret->_modified[] = $key;
1219. }
1220. }
1223. return $ret;
1224. }
1225. /**
1226. * copyDeep
1227. * returns a copy of this object and all its related objects
1228. *
1229. * @return Doctrine_Record
1230. */
1231. public function copyDeep(){
1232. $copy = $this->copy();
1234. foreach ($this->_references as $key => $value) {
1235. if ($value instanceof Doctrine_Collection) {
1236. foreach ($value as $record) {
1237. $copy->{$key}[] = $record->copyDeep();
1238. }
1239. } else {
1240. $copy->set($key, $value->copyDeep());
1241. }
1242. }
1243. return $copy;
1244. }
1246. /**
1247. * assignIdentifier
1248. *
1249. * @param integer $id
1250. * @return void
1251. */
1252. public function assignIdentifier($id = false)
1253. {
1254. if ($id === false) {
1255. $this->_id = array();
1256. $this->_data = $this->_filter->cleanData($this->_data);
1257. $this->_state = Doctrine_Record::STATE_TCLEAN;
1258. $this->_modified = array();
1259. } elseif ($id === true) {
1260. $this->prepareIdentifiers(true);
1261. $this->_state = Doctrine_Record::STATE_CLEAN;
1262. $this->_modified = array();
1263. } else {
1264. $name = $this->_table->getIdentifier();
1265. $this->_id[$name] = $id;
1266. $this->_data[$name] = $id;
1267. $this->_state = Doctrine_Record::STATE_CLEAN;
1268. $this->_modified = array();
1269. }
1270. }
1271. /**
1272. * returns the primary keys of this object
1273. *
1274. * @return array
1275. */
1276. public function identifier()
1277. {
1278. return $this->_id;
1279. }
1280. /**
1281. * returns the value of autoincremented primary key of this object (if any)
1282. *
1283. * @return integer
1284. */
1285. final public function getIncremented()
1286. {
1287. $id = current($this->_id);
1288. if ($id === false) {
1289. return null;
1290. }
1292. return $id;
1293. }
1294. /**
1295. * getLast
1296. * this method is used internally be Doctrine_Query
1297. * it is needed to provide compatibility between
1298. * records and collections
1299. *
1300. * @return Doctrine_Record
1301. */
1302. public function getLast()
1303. {
1304. return $this;
1305. }
1306. /**
1307. * hasRefence
1308. * @param string $name
1309. * @return boolean
1310. */
1311. public function hasReference($name)
1312. {
1313. return isset($this->_references[$name]);
1314. }
1315. /**
1316. * obtainReference
1317. *
1318. * @param string $name
1319. * @throws Doctrine_Record_Exception if trying to get an unknown related component
1320. */
1321. public function obtainReference($name)
1322. {
1323. if (isset($this->_references[$name])) {
1324. return $this->_references[$name];
1325. }
1326. throw new Doctrine_Record_Exception("Unknown reference $name");
1327. }
1328. /**
1329. * getReferences
1330. * @return array all references
1331. */
1332. public function getReferences()
1333. {
1334. return $this->_references;
1335. }
1336. /**
1337. * setRelated
1338. *
1339. * @param string $alias
1340. * @param Doctrine_Access $coll
1341. */
1342. final public function setRelated($alias, Doctrine_Access $coll)
1343. {
1344. $this->_references[$alias] = $coll;
1345. }
1346. /**
1347. * loadReference
1348. * loads a related component
1349. *
1350. * @throws Doctrine_Table_Exception if trying to load an unknown related component
1351. * @param string $name
1352. * @return void
1353. */
1354. public function loadReference($name)
1355. {
1356. $rel = $this->_table->getRelation($name);
1357. $this->_references[$name] = $rel->fetchRelatedFor($this);
1358. }
1360. /**
1361. * merge
1362. * merges this record with an array of values
1363. *
1364. * @param array $values
1365. * @return void
1366. */
1367. public function merge(array $values)
1368. {
1369. foreach ($this->_table->getColumnNames() as $value) {
1370. try {
1371. if (isset($values[$value])) {
1372. $this->set($value, $values[$value]);
1373. }
1374. } catch(Exception $e) {
1375. // silence all exceptions
1376. }
1377. }
1378. }
1379. /**
1380. * call
1381. *
1382. * @param string|array$callback valid callback
1383. * @param string $column column name
1384. * @param mixed arg1 ... argN optional callback arguments
1385. * @return Doctrine_Record
1386. */
1387. public function call($callback, $column)
1388. {
1389. $args = func_get_args();
1390. array_shift($args);
1392. if (isset($args[0])) {
1393. $column = $args[0];
1394. $args[0] = $this->get($column);
1396. $newvalue = call_user_func_array($callback, $args);
1398. $this->_data[$column] = $newvalue;
1399. }
1400. return $this;
1401. }
1402. /**
1403. * getter for node assciated with this record
1404. *
1405. * @return mixed if tree returns Doctrine_Node otherwise returns false
1406. */
1407. public function getNode()
1408. {
1409. if ( ! $this->_table->isTree()) {
1410. return false;
1411. }
1413. if ( ! isset($this->_node)) {
1414. $this->_node = Doctrine_Node::factory($this,
1415. $this->getTable()->getOption('treeImpl'),
1416. $this->getTable()->getOption('treeOptions')
1417. );
1418. }
1420. return $this->_node;
1421. }
1422. /**
1423. * revert
1424. * reverts this record to given version, this method only works if versioning plugin
1425. * is enabled
1426. *
1427. * @throws Doctrine_Record_Exception if given version does not exist
1428. * @param integer $version an integer > 1
1429. * @return Doctrine_Record this object
1430. */
1431. public function revert($version)
1432. {
1433. $data = $this->_table
1434. ->getTemplate('Doctrine_Template_Versionable')
1435. ->getAuditLog()
1436. ->getVersion($this, $version);
1438. if ( ! isset($data[0])) {
1439. throw new Doctrine_Record_Exception('Version ' . $version . ' does not exist!');
1440. }
1442. $this->_data = $data[0];
1444. return $this;
1445. }
1446. /**
1447. * removeLinks
1448. * removes links from this record to given records
1449. *
1450. * @param string $alias related component alias
1451. * @param array $ids the identifiers of the related records
1452. * @return Doctrine_Record this object
1453. */
1454. public function unlink($alias, $ids)
1455. {
1456. $ids = (array) $ids;
1458. $q = new Doctrine_Query();
1460. $rel = $this->getTable()->getRelation($alias);
1462. if ($rel instanceof Doctrine_Relation_Association) {
1463. $q->delete()
1464. ->from($rel->getAssociationTable()->getComponentName())
1465. ->where($rel->getLocal() . ' = ?', array_values($this->identifier()))
1466. ->whereIn($rel->getForeign(), $ids);
1468. $q->execute();
1471. } elseif ($rel instanceof Doctrine_Relation_ForeignKey) {
1472. $q->update($rel->getTable()->getComponentName())
1473. ->set($rel->getForeign(), '?', array(null))
1474. ->addWhere($rel->getForeign() . ' = ?', array_values($this->identifier()))
1475. ->whereIn($rel->getTable()->getIdentifier(), $ids);
1477. $q->execute();
1478. }
1479. if (isset($this->_references[$alias])) {
1480. foreach ($this->_references[$alias] as $k => $record) {
1481. if (in_array(current($record->identifier()), $ids)) {
1482. $this->_references[$alias]->remove($k);
1483. }
1484. }
1485. $this->_references[$alias]->takeSnapshot();
1486. }
1487. return $this;
1488. }
1489. /**
1490. * __call
1491. * this method is a magic method that is being used for method overloading
1492. *
1493. * the function of this method is to try to find given method from the templates
1494. * this record is using and if it finds given method it will execute it
1495. *
1496. * So, in sense, this method replicates the usage of mixins (as seen in some programming languages)
1497. *
1498. * @param string $method name of the method
1499. * @param array $args method arguments
1500. * @return mixed the return value of the given method
1501. */
1502. public function __call($method, $args)
1503. {
1504. foreach ($this->_table->getTemplates() as $template) {
1505. if (method_exists($template, $method)) {
1506. return call_user_func_array(array($template, $method), $args);
1507. }
1508. }
1510. throw new Doctrine_Record_Exception('Unknown method ' . $method);
1511. }
1512. /**
1513. * used to delete node from tree - MUST BE USE TO DELETE RECORD IF TABLE ACTS AS TREE
1514. *
1515. */
1516. public function deleteNode() {
1517. $this->getNode()->delete();
1518. }
1519. public function toString()
1520. {
1521. return Doctrine::dump(get_object_vars($this));
1522. }
1523. /**
1524. * returns a string representation of this object
1525. */
1526. public function __toString()
1527. {
1528. return (string) $this->_oid;
1529. }
1530. }