OneToOneMapping.php 7.59 KB
Newer Older
romanb's avatar
romanb committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
romanb's avatar
romanb committed
20 21
 */

22
namespace Doctrine\ORM\Mapping;
romanb's avatar
romanb committed
23 24 25 26 27

/**
 * A one-to-one mapping describes a uni-directional mapping from one entity 
 * to another entity.
 *
28 29 30
 * <b>IMPORTANT NOTE:</b>
 *
 * The fields of this class are only public for 2 reasons:
31
 * 1) To allow fast READ access.
32 33 34
 * 2) To drastically reduce the size of a serialized instance (private/protected members
 *    get the whole class name, namespace inclusive, prepended to every property in
 *    the serialized representation).
35 36 37
 *    
 * Instances of this class are stored serialized in the metadata cache together with the
 * owning <tt>ClassMetadata</tt> instance.
38
 *
romanb's avatar
romanb committed
39 40
 * @since 2.0
 * @author Roman Borschel <roman@code-factory.org>
41
 * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
romanb's avatar
romanb committed
42
 */
43
class OneToOneMapping extends AssociationMapping
romanb's avatar
romanb committed
44 45
{
    /**
46
     * READ-ONLY: Maps the source foreign/primary key columns to the target primary/foreign key columns.
romanb's avatar
romanb committed
47 48 49
     * i.e. source.id (pk) => target.user_id (fk).
     * Reverse mapping of _targetToSourceKeyColumns.
     */
50
    public $sourceToTargetKeyColumns = array();
romanb's avatar
romanb committed
51 52

    /**
53
     * READ-ONLY: Maps the target primary/foreign key columns to the source foreign/primary key columns.
romanb's avatar
romanb committed
54 55 56
     * i.e. target.user_id (fk) => source.id (pk).
     * Reverse mapping of _sourceToTargetKeyColumns.
     */
57
    public $targetToSourceKeyColumns = array();
romanb's avatar
romanb committed
58 59
    
    /**
60
     * READ-ONLY: Whether to delete orphaned elements (when nulled out, i.e. $foo->other = null)
romanb's avatar
romanb committed
61 62 63
     * 
     * @var boolean
     */
64
    public $orphanRemoval = false;
65 66

    /**
67
     * READ-ONLY: The join column definitions. Only present on the owning side.
68 69 70
     *
     * @var array
     */
71
    public $joinColumns = array();
romanb's avatar
romanb committed
72
    
73
    /**
74
     * READ-ONLY: A map of join column names to field names that are used in cases
75 76 77 78
     * when the join columns are fetched as part of the query result.
     * 
     * @var array
     */
79
    public $joinColumnFieldNames = array();
80

romanb's avatar
romanb committed
81
    /**
82
     * {@inheritdoc}
romanb's avatar
romanb committed
83 84 85 86 87 88 89 90 91
     *
     * @param array $mapping  The mapping to validate & complete.
     * @return array  The validated & completed mapping.
     * @override
     */
    protected function _validateAndCompleteMapping(array $mapping)
    {
        parent::_validateAndCompleteMapping($mapping);
        
92 93 94 95 96
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
            $this->isOwningSide = true;
        }
        
        if ($this->isOwningSide) {
97 98 99 100 101 102
            if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) {
                // Apply default join column
                $mapping['joinColumns'] = array(array(
                    'name' => $this->sourceFieldName . '_id',
                    'referencedColumnName' => 'id'
                ));
103
            }
104
            foreach ($mapping['joinColumns'] as $joinColumn) {
105
                $this->sourceToTargetKeyColumns[$joinColumn['name']] = $joinColumn['referencedColumnName'];
106 107
                $this->joinColumnFieldNames[$joinColumn['name']] = isset($joinColumn['fieldName'])
                        ? $joinColumn['fieldName'] : $joinColumn['name'];
romanb's avatar
romanb committed
108
            }
109
            $this->joinColumns = $mapping['joinColumns'];
110
            $this->targetToSourceKeyColumns = array_flip($this->sourceToTargetKeyColumns);
romanb's avatar
romanb committed
111
        }
112 113

        //TODO: if orphanRemoval, cascade=remove is implicit!
114 115
        $this->orphanRemoval = isset($mapping['orphanRemoval']) ?
                (bool) $mapping['orphanRemoval'] : false;
116

romanb's avatar
romanb committed
117 118
        return $mapping;
    }
119

romanb's avatar
romanb committed
120
    /**
121
     * {@inheritdoc}
romanb's avatar
romanb committed
122 123 124 125 126 127 128 129
     *
     * @return boolean
     * @override
     */
    public function isOneToOne()
    {
        return true;
    }
130

romanb's avatar
romanb committed
131
    /**
132
     * {@inheritdoc}
romanb's avatar
romanb committed
133
     *
134 135
     * @param object $sourceEntity      the entity source of this association
     * @param object $targetEntity      the entity to load data in
136
     * @param EntityManager $em
137
     * @param array $joinColumnValues  Values of the join columns of $sourceEntity.
romanb's avatar
romanb committed
138
     */
139
    public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues = array())
romanb's avatar
romanb committed
140
    {
141
        $targetClass = $em->getClassMetadata($this->targetEntityName);
142

143
        if ($this->isOwningSide) {
144 145 146
            $inverseField = isset($targetClass->inverseMappings[$this->sourceEntityName][$this->sourceFieldName]) ?
                    $targetClass->inverseMappings[$this->sourceEntityName][$this->sourceFieldName]->sourceFieldName
                    : false;
147
            
romanb's avatar
romanb committed
148 149
            // Mark inverse side as fetched in the hints, otherwise the UoW would
            // try to load it in a separate query (remember: to-one inverse sides can not be lazy). 
150 151
            $hints = array();
            if ($inverseField) {
romanb's avatar
romanb committed
152 153 154 155 156 157
                $hints['fetched'][$targetClass->name][$inverseField] = true;
                if ($targetClass->subClasses) {
                    foreach ($targetClass->subClasses as $targetSubclassName) {
                        $hints['fetched'][$targetSubclassName][$inverseField] = true;
                    }
                }
158
            }
159 160 161 162 163
            /* cascade read-only status
            if ($em->getUnitOfWork()->isReadOnly($sourceEntity)) {
                $hints[Query::HINT_READ_ONLY] = true;
            }
            */
164 165

            $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($joinColumnValues, $targetEntity, $this, $hints);
166
            
romanb's avatar
romanb committed
167
            if ($targetEntity !== null && $inverseField && ! $targetClass->isCollectionValuedAssociation($inverseField)) {
168
                $targetClass->reflFields[$inverseField]->setValue($targetEntity, $sourceEntity);
169 170
            }
        } else {
171 172
            $conditions = array();
            $sourceClass = $em->getClassMetadata($this->sourceEntityName);
173
            $owningAssoc = $targetClass->getAssociationMapping($this->mappedBy);
174
            // TRICKY: since the association is specular source and target are flipped
175
            foreach ($owningAssoc->targetToSourceKeyColumns as $sourceKeyColumn => $targetKeyColumn) {
romanb's avatar
romanb committed
176
                if (isset($sourceClass->fieldNames[$sourceKeyColumn])) {
177
                    $conditions[$targetKeyColumn] = $sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);
178
                } else {
romanb's avatar
romanb committed
179 180 181
                    throw MappingException::joinColumnMustPointToMappedField(
                        $sourceClass->name, $sourceKeyColumn
                    );
182
                }
183
            }
184

185
            $targetEntity = $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity, $this);
186
            
187
            if ($targetEntity !== null) {
188
                $targetClass->setFieldValue($targetEntity, $this->mappedBy, $sourceEntity);
189
            }
romanb's avatar
romanb committed
190
        }
191

192
        return $targetEntity;
romanb's avatar
romanb committed
193
    }
194
}