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 CountableIteratorAggregateSerializable
  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;
  68.  
  69.  
  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;
  82.  
  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);
  127.  
  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']);
  134.  
  135.         $vars['_table'$vars['_table']->getComponentName();
  136.  
  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();
  149.  
  150.         $array unserialize($serialized);
  151.  
  152.         foreach ($array as $name => $values{
  153.             $this->$name $values;
  154.         }
  155.  
  156.         $this->_table        = $connection->getTable($this->_table);
  157.  
  158.  
  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;
  174.         
  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 $recordDoctrine_Relation $relation)
  224.     {
  225.         $this->reference       = $record;
  226.         $this->relation        = $relation;
  227.  
  228.         if ($relation instanceof Doctrine_Relation_ForeignKey
  229.            || $relation instanceof Doctrine_Relation_LocalKey
  230.         {
  231.  
  232.             $this->referenceField = $relation->getForeign();
  233.  
  234.             $value $record->get($relation->getLocal());
  235.  
  236.             foreach ($this->data as $record{
  237.                 if ($value !== null{
  238.                     $record->set($this->referenceField$valuefalse);
  239.                 else {
  240.                     $record->set($this->referenceField$this->referencefalse);
  241.                 }
  242.             }
  243.         elseif ($relation instanceof Doctrine_Relation_Association{
  244.  
  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];
  266.  
  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->datatrue);
  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();
  306.  
  307.             if (isset($this->referenceField)) {
  308.                 $value $this->reference->get($this->relation->getLocal());
  309.  
  310.                 if ($value !== null{
  311.                     $record->set($this->referenceField$valuefalse);
  312.                 else {
  313.                     $record->set($this->referenceField$this->referencefalse);
  314.                 }
  315.             }
  316.  
  317.             $this->data[$record;
  318.  
  319.             return $record;
  320.         }
  321.  
  322.         return $this->data[$key];
  323.     }
  324.  
  325.     /**
  326.      * @return array                an array containing all primary keys
  327.      */
  328.     public function getPrimaryKeys()
  329.     {
  330.         $list array();
  331.         $name $this->_table->getIdentifier();
  332.  
  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($keyDoctrine_Record $record)
  368.     {
  369.         if (isset($this->referenceField)) {
  370.             $record->set($this->referenceField$this->referencefalse);
  371.         }
  372.  
  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());
  385.  
  386.             if ($value !== null{
  387.                 $record->set($this->referenceField$valuefalse);
  388.             else {
  389.                 $record->set($this->referenceField$this->referencefalse);
  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.         }
  402.  
  403.         if (isset($key)) {
  404.             if (isset($this->data[$key])) {
  405.                 return false;
  406.             }
  407.             $this->data[$key$record;
  408.             return true;
  409.         }
  410.  
  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());
  432.  
  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')');
  442.  
  443.             return $query;
  444.         }
  445.  
  446.         $rel     $this->_table->getRelation($name);
  447.  
  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.         }
  460.  
  461.         $dql     $rel->getRelationDql(count($list)'collection');
  462.  
  463.         $coll    $query->query($dql$list);
  464.  
  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($nameDoctrine_Collection $coll)
  475.     {
  476.         $rel     $this->_table->getRelation($name);
  477.         $table   $rel->getTable();
  478.         $foreign $rel->getForeign();
  479.         $local   $rel->getLocal();
  480.  
  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);
  495.  
  496.                 foreach ($coll as $k => $related{
  497.                     if ($related[$foreign== $record[$local]{
  498.                         $sub->add($related);
  499.                         $coll->remove($k);
  500.                     }
  501.                 }
  502.  
  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();
  509.  
  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);
  521.  
  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;
  551.         
  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->dataas $record{
  579.             $record->delete();
  580.         }
  581.  
  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);
  627.  
  628.         $this->processDiff();
  629.  
  630.         foreach ($this->getData(as $key => $record{
  631.             $record->save($conn);
  632.         }
  633.  
  634.         $conn->commit();
  635.         
  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.         }
  651.  
  652.         $conn->beginTransaction();
  653.         $conn->transaction->addCollection($this);
  654.  
  655.         foreach ($this as $key => $record{
  656.             $record->delete($conn);
  657.         }
  658.  
  659.         $conn->commit();
  660.  
  661.         $this->data = array();
  662.         
  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. }