ClassMetadata.php 11.5 KB
Newer Older
1
<?php
romanb's avatar
romanb committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * 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
17
 * <http://www.doctrine-project.org>.
romanb's avatar
romanb committed
18
 */
19

20
namespace Doctrine\ORM\Mapping;
21

22 23
use ReflectionClass, ReflectionProperty;

24
/**
25 26
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
 * of an entity and it's associations.
27 28
 * 
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
29
 *
30 31 32 33 34 35 36 37
 * <b>IMPORTANT NOTE:</b>
 *
 * The fields of this class are only public for 2 reasons:
 * 1) To allow fast READ access.
 * 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).
 *
38
 * @author Roman Borschel <roman@code-factory.org>
39
 * @author Jonathan H. Wage <jonwage@gmail.com>
40
 * @since 2.0
41
 */
romanb's avatar
romanb committed
42
class ClassMetadata extends ClassMetadataInfo
43
{
44 45 46 47 48
    /**
     * The ReflectionProperty instances of the mapped class.
     *
     * @var array
     */
49
    public $reflFields = array();
50 51 52 53 54 55 56
    
    /**
     * The prototype from which new instances of the mapped class are created.
     * 
     * @var object
     */
    private $_prototype;
57

58
    /**
59 60
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
     * metadata of the class with the given name.
61
     *
62
     * @param string $entityName The name of the entity class the new instance is used for.
63
     */
64
    public function __construct($entityName)
65
    {
Roman S. Borschel's avatar
Roman S. Borschel committed
66 67
        parent::__construct($entityName);
        $this->reflClass = new ReflectionClass($entityName);
68
        $this->namespace = $this->reflClass->getNamespaceName();
69
        $this->table['name'] = $this->reflClass->getShortName();
70
    }
71

72 73 74 75 76
    /**
     * Gets the ReflectionPropertys of the mapped class.
     *
     * @return array An array of ReflectionProperty instances.
     */
77 78
    public function getReflectionProperties()
    {
79
        return $this->reflFields;
80 81
    }

82 83 84 85 86 87
    /**
     * Gets a ReflectionProperty for a specific field of the mapped class.
     *
     * @param string $name
     * @return ReflectionProperty
     */
88
    public function getReflectionProperty($name)
romanb's avatar
romanb committed
89
    {
90
        return $this->reflFields[$name];
romanb's avatar
romanb committed
91
    }
92

93
    /**
94
     * Gets the ReflectionProperty for the single identifier field.
95
     *
96
     * @return ReflectionProperty
97
     * @throws BadMethodCallException If the class has a composite identifier.
98
     */
99 100
    public function getSingleIdReflectionProperty()
    {
101
        if ($this->isIdentifierComposite) {
102
            throw new \BadMethodCallException("Class " . $this->name . " has a composite identifier.");
103
        }
104
        return $this->reflFields[$this->identifier[0]];
105
    }
106
    
107
    /**
108
     * Validates & completes the given field mapping.
109
     *
110 111
     * @param array $mapping  The field mapping to validated & complete.
     * @return array  The validated and completed field mapping.
112 113
     * 
     * @throws MappingException
114
     */
115
    protected function _validateAndCompleteFieldMapping(array &$mapping)
116
    {
117 118 119
        parent::_validateAndCompleteFieldMapping($mapping);

        // Store ReflectionProperty of mapped field
120 121 122
        $refProp = $this->reflClass->getProperty($mapping['fieldName']);
        $refProp->setAccessible(true);
        $this->reflFields[$mapping['fieldName']] = $refProp;
123
    }
124

125
    /**
126 127 128 129
     * Extracts the identifier values of an entity of this class.
     * 
     * For composite identifiers, the identifier values are returned as an array
     * with the same order as the field order in {@link identifier}.
130
     *
131
     * @param object $entity
romanb's avatar
romanb committed
132
     * @return array
133
     */
134
    public function getIdentifierValues($entity)
135
    {
136 137 138 139 140
        if ($this->isIdentifierComposite) {
            $id = array();
            foreach ($this->identifier as $idField) {
                $value = $this->reflFields[$idField]->getValue($entity);
                if ($value !== null) {
141
                    $id[$idField] = $value;
142 143 144
                }
            }
            return $id;
145
        } else {
146
            return array($this->identifier[0] => $this->reflFields[$this->identifier[0]]->getValue($entity));
147 148 149
        }
    }

romanb's avatar
romanb committed
150
    /**
151
     * Populates the entity identifier of an entity.
romanb's avatar
romanb committed
152
     *
153 154 155
     * @param object $entity
     * @param mixed $id
     * @todo Rename to assignIdentifier()
romanb's avatar
romanb committed
156
     */
157
    public function setIdentifierValues($entity, $id)
158
    {
159
        if ($this->isIdentifierComposite) {
Roman S. Borschel's avatar
Roman S. Borschel committed
160
            foreach ($id as $idField => $idValue) {
161 162 163 164 165
                $this->reflFields[$idField]->setValue($entity, $idValue);
            }
        } else {
            $this->reflFields[$this->identifier[0]]->setValue($entity, $id);
        }
166
    }
167

168
    /**
169
     * Sets the specified field to the specified value on the given entity.
170
     *
171 172 173
     * @param object $entity
     * @param string $field
     * @param mixed $value
174
     */
175
    public function setFieldValue($entity, $field, $value)
176
    {
177
        $this->reflFields[$field]->setValue($entity, $value);
178
    }
179

180 181 182 183 184 185 186 187 188 189 190
    /**
     * Gets the specified field's value off the given entity.
     *
     * @param object $entity
     * @param string $field
     */
    public function getFieldValue($entity, $field)
    {
        return $this->reflFields[$field]->getValue($entity);
    }

191 192 193
    /**
     * Stores the association mapping.
     *
194
     * @param AssociationMapping $assocMapping
195
     */
196
    protected function _storeAssociationMapping(AssociationMapping $assocMapping)
197
    {
198
        parent::_storeAssociationMapping($assocMapping);
199 200

        // Store ReflectionProperty of mapped field
201
        $sourceFieldName = $assocMapping->sourceFieldName;
202 203 204 205

        $refProp = $this->reflClass->getProperty($sourceFieldName);
        $refProp->setAccessible(true);
        $this->reflFields[$sourceFieldName] = $refProp;
206
    }
207

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
    /**
     * Gets the (possibly quoted) column name of a mapped field for safe use
     * in an SQL statement.
     * 
     * @param string $field
     * @param AbstractPlatform $platform
     * @return string
     */
    public function getQuotedColumnName($field, $platform)
    {
        return isset($this->fieldMappings[$field]['quoted']) ?
                $platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
                $this->fieldMappings[$field]['columnName'];
    }
    
    /**
     * Gets the (possibly quoted) primary table name of this class for safe use
     * in an SQL statement.
     * 
     * @param AbstractPlatform $platform
     * @return string
     */
    public function getQuotedTableName($platform)
    {
232 233 234
        return isset($this->table['quoted']) ?
                $platform->quoteIdentifier($this->table['name']) :
                $this->table['name'];
235
    }
236

237 238 239 240
    /**
     * Creates a string representation of this instance.
     *
     * @return string The string representation of this instance.
241
     * @todo Construct meaningful string representation.
242
     */
243 244
    public function __toString()
    {
245
        return __CLASS__ . '@' . spl_object_hash($this);
246
    }
247 248 249
    
    /**
     * Determines which fields get serialized.
250 251 252 253
     *
     * It is only serialized what is necessary for best unserialization performance.
     * That means any metadata properties that are not set or empty or simply have
     * their default value are NOT serialized.
254
     * 
255
     * Parts that are also NOT serialized because they can not be properly unserialized:
256 257 258 259 260 261 262
     *      - reflClass (ReflectionClass)
     *      - reflFields (ReflectionProperty array)
     * 
     * @return array The names of all the fields that should be serialized.
     */
    public function __sleep()
    {
263 264 265
        // This metadata is always serialized/cached.
        $serialized = array(
            'associationMappings',
romanb's avatar
romanb committed
266
            'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName']
267 268
            'fieldMappings',
            'fieldNames',
269
            'identifier',
270
            'isIdentifierComposite', // TODO: REMOVE
271
            'name',
Roman S. Borschel's avatar
Roman S. Borschel committed
272
            'namespace', // TODO: REMOVE
273
            'table',
274
            'rootEntityName',
275
            'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime.
276
        );
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313

        // The rest of the metadata is only serialized if necessary.
        if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
            $serialized[] = 'changeTrackingPolicy';
        }

        if ($this->customRepositoryClassName) {
            $serialized[] = 'customRepositoryClassName';
        }

        if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) {
            $serialized[] = 'inheritanceType';
            $serialized[] = 'discriminatorColumn';
            $serialized[] = 'discriminatorValue';
            $serialized[] = 'discriminatorMap';
            $serialized[] = 'parentClasses';
            $serialized[] = 'subClasses';
        }

        if ($this->generatorType != self::GENERATOR_TYPE_NONE) {
            $serialized[] = 'generatorType';
        }

        if ($this->isMappedSuperclass) {
            $serialized[] = 'isMappedSuperclass';
        }

        if ($this->isVersioned) {
            $serialized[] = 'isVersioned';
            $serialized[] = 'versionField';
        }

        if ($this->lifecycleCallbacks) {
            $serialized[] = 'lifecycleCallbacks';
        }

        return $serialized;
314
    }
315

316
    /**
317
     * Restores some state that can not be serialized/unserialized.
318 319 320 321 322 323
     * 
     * @return void
     */
    public function __wakeup()
    {
        // Restore ReflectionClass and properties
324
        $this->reflClass = new ReflectionClass($this->name);
Roman S. Borschel's avatar
Roman S. Borschel committed
325

326
        foreach ($this->fieldMappings as $field => $mapping) {
Roman S. Borschel's avatar
Roman S. Borschel committed
327 328
            if (isset($mapping['declared'])) {
                $reflField = new ReflectionProperty($mapping['declared'], $field);
329 330 331
            } else {
                $reflField = $this->reflClass->getProperty($field);
            }
332 333
            $reflField->setAccessible(true);
            $this->reflFields[$field] = $reflField;
334
        }
335

336
        foreach ($this->associationMappings as $field => $mapping) {
Roman S. Borschel's avatar
Roman S. Borschel committed
337 338
            if ($mapping->declared) {
                $reflField = new ReflectionProperty($mapping->declared, $field);
339 340 341
            } else {
                $reflField = $this->reflClass->getProperty($field);
            }
342

343 344
            $reflField->setAccessible(true);
            $this->reflFields[$field] = $reflField;
345 346
        }
    }
347
    
348 349 350 351 352 353 354 355 356 357 358 359
    /**
     * Creates a new instance of the mapped class, without invoking the constructor.
     * 
     * @return object
     */
    public function newInstance()
    {
        if ($this->_prototype === null) {
            $this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
        }
        return clone $this->_prototype;
    }
360
}