. */ namespace Doctrine\ORM\Mapping; /** * A ClassMetadata instance holds all the object-relational mapping metadata * of an entity and it's associations. * * Once populated, ClassMetadata instances are usually cached in a serialized form. * * IMPORTANT NOTE: * * 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). * * @author Roman Borschel * @author Jonathan H. Wage * @since 2.0 */ class ClassMetadata extends ClassMetadataInfo { /** * The ReflectionClass instance of the mapped class. * * @var ReflectionClass */ public $reflClass; /** * The ReflectionProperty instances of the mapped class. * * @var array */ public $reflFields = array(); /** * The prototype from which new instances of the mapped class are created. * * @var object */ private $_prototype; /** * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * * @param string $entityName The name of the entity class the new instance is used for. */ public function __construct($entityName) { $this->name = $entityName; $this->reflClass = new \ReflectionClass($entityName); $this->namespace = $this->reflClass->getNamespaceName(); $this->primaryTable['name'] = $this->reflClass->getShortName(); $this->rootEntityName = $entityName; } /** * Gets the ReflectionClass instance of the mapped class. * * @return ReflectionClass */ public function getReflectionClass() { return $this->reflClass; } /** * Gets the ReflectionPropertys of the mapped class. * * @return array An array of ReflectionProperty instances. */ public function getReflectionProperties() { return $this->reflFields; } /** * INTERNAL: * Adds a reflection property. Usually only used by the ClassMetadataFactory * while processing inheritance mappings. * * @param array $props */ public function addReflectionProperty($propName, \ReflectionProperty $property) { $this->reflFields[$propName] = $property; } /** * Gets a ReflectionProperty for a specific field of the mapped class. * * @param string $name * @return ReflectionProperty */ public function getReflectionProperty($name) { return $this->reflFields[$name]; } /** * Gets the ReflectionProperty for the single identifier field. * * @return ReflectionProperty * @throws BadMethodCallException If the class has a composite identifier. */ public function getSingleIdReflectionProperty() { if ($this->isIdentifierComposite) { throw new \BadMethodCallException("Class " . $this->name . " has a composite identifier."); } return $this->reflFields[$this->identifier[0]]; } /** * Validates & completes the given field mapping. * * @param array $mapping The field mapping to validated & complete. * @return array The validated and completed field mapping. * * @throws MappingException */ protected function _validateAndCompleteFieldMapping(array &$mapping) { parent::_validateAndCompleteFieldMapping($mapping); // Store ReflectionProperty of mapped field $refProp = $this->reflClass->getProperty($mapping['fieldName']); $refProp->setAccessible(true); $this->reflFields[$mapping['fieldName']] = $refProp; } /** * 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}. * * @param object $entity * @return array */ public function getIdentifierValues($entity) { if ($this->isIdentifierComposite) { $id = array(); foreach ($this->identifier as $idField) { $value = $this->reflFields[$idField]->getValue($entity); if ($value !== null) { $id[$idField] = $value; } } return $id; } else { return array($this->identifier[0] => $this->reflFields[$this->identifier[0]]->getValue($entity)); } } public function getColumnValues($entity, array $columns) { $values = array(); foreach ($columns as $column) { $values[] = $this->reflFields[$this->fieldNames[$column]]->getValue($entity); } return $values; } /** * Populates the entity identifier of an entity. * * @param object $entity * @param mixed $id * @todo Rename to assignIdentifier() */ public function setIdentifierValues($entity, $id) { if ($this->isIdentifierComposite) { foreach ((array)$id as $idField => $idValue) { $this->reflFields[$idField]->setValue($entity, $idValue); } } else { $this->reflFields[$this->identifier[0]]->setValue($entity, $id); } } /** * Sets the specified field to the specified value on the given entity. * * @param object $entity * @param string $field * @param mixed $value */ public function setFieldValue($entity, $field, $value) { $this->reflFields[$field]->setValue($entity, $value); } /** * Sets the field mapped to the specified column to the specified value on the given entity. * * @param object $entity * @param string $field * @param mixed $value */ public function setColumnValue($entity, $column, $value) { $this->reflFields[$this->fieldNames[$column]]->setValue($entity, $value); } /** * Stores the association mapping. * * @param AssociationMapping $assocMapping */ protected function _storeAssociationMapping(AssociationMapping $assocMapping) { parent::_storeAssociationMapping($assocMapping); // Store ReflectionProperty of mapped field $sourceFieldName = $assocMapping->sourceFieldName; $refProp = $this->reflClass->getProperty($sourceFieldName); $refProp->setAccessible(true); $this->reflFields[$sourceFieldName] = $refProp; } /** * Dispatches the lifecycle event of the given entity to the registered * lifecycle callbacks and lifecycle listeners. * * @param string $event The lifecycle event. * @param Entity $entity The Entity on which the event occured. */ public function invokeLifecycleCallbacks($lifecycleEvent, $entity) { foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) { $entity->$callback(); } } /** * 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) { return isset($this->primaryTable['quoted']) ? $platform->quoteIdentifier($this->primaryTable['name']) : $this->primaryTable['name']; } /** * Gets the (possibly quoted) name of the discriminator column for safe use * in an SQL statement. * * @param AbstractPlatform $platform * @return string */ public function getQuotedDiscriminatorColumnName($platform) { return isset($this->discriminatorColumn['quoted']) ? $platform->quoteIdentifier($this->discriminatorColumn['name']) : $this->discriminatorColumn['name']; } /** * Creates a string representation of this instance. * * @return string The string representation of this instance. * @todo Construct meaningful string representation. */ public function __toString() { return __CLASS__ . '@' . spl_object_hash($this); } /** * Determines which fields get serialized. * * Parts that are NOT serialized because they can not be properly unserialized: * - reflClass (ReflectionClass) * - reflFields (ReflectionProperty array) * * @return array The names of all the fields that should be serialized. */ public function __sleep() { return array( 'associationMappings', // unserialization "bottleneck" with many associations 'changeTrackingPolicy', 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] 'customRepositoryClassName', 'discriminatorColumn', 'discriminatorValue', 'discriminatorMap', 'fieldMappings', 'fieldNames', //TODO: Not all of this stuff needs to be serialized. Only type, columnName and fieldName. 'generatorType', 'identifier', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. 'inheritanceType', 'inheritedAssociationFields', 'inverseMappings', //TODO: Remove! 'isIdentifierComposite', 'isMappedSuperclass', 'isVersioned', 'lifecycleCallbacks', 'name', 'parentClasses', 'primaryTable', 'rootEntityName', 'subClasses', 'versionField' ); } /** * Restores some state that can not be serialized/unserialized. * * @return void */ public function __wakeup() { // Restore ReflectionClass and properties $this->reflClass = new \ReflectionClass($this->name); foreach ($this->fieldMappings as $field => $mapping) { if (isset($mapping['inherited'])) { $reflField = new \ReflectionProperty($mapping['inherited'], $field); } else { $reflField = $this->reflClass->getProperty($field); } $reflField->setAccessible(true); $this->reflFields[$field] = $reflField; } foreach ($this->associationMappings as $field => $mapping) { if (isset($this->inheritedAssociationFields[$field])) { $reflField = new \ReflectionProperty($this->inheritedAssociationFields[$field], $field); } else { $reflField = $this->reflClass->getProperty($field); } $reflField->setAccessible(true); $this->reflFields[$field] = $reflField; } } /** * 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; } }