. */ namespace Doctrine\ORM\Mapping; use ReflectionClass, ReflectionProperty; /** * 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 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) { parent::__construct($entityName); $this->reflClass = new ReflectionClass($entityName); $this->namespace = $this->reflClass->getNamespaceName(); $this->table['name'] = $this->reflClass->getShortName(); } /** * Gets the ReflectionPropertys of the mapped class. * * @return array An array of ReflectionProperty instances. */ public function getReflectionProperties() { return $this->reflFields; } /** * 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 { $value = $this->reflFields[$this->identifier[0]]->getValue($entity); if ($value !== null) { return array($this->identifier[0] => $value); } return array(); } } /** * Populates the entity identifier of an entity. * * @param object $entity * @param mixed $id * @todo Rename to assignIdentifier() */ public function setIdentifierValues($entity, array $id) { foreach ($id as $idField => $idValue) { $this->reflFields[$idField]->setValue($entity, $idValue); } } /** * 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); } /** * 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); } /** * 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; } /** * 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->table['quoted']) ? $platform->quoteIdentifier($this->table['name']) : $this->table['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. * * 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. * * Parts that are also 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() { // This metadata is always serialized/cached. $serialized = array( 'associationMappings', 'columnNames', //TODO: Not really needed. Can use fieldMappings[$fieldName]['columnName'] 'fieldMappings', 'fieldNames', 'identifier', 'isIdentifierComposite', // TODO: REMOVE 'name', 'namespace', // TODO: REMOVE 'table', 'rootEntityName', 'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime. ); // 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->generatorType == self::GENERATOR_TYPE_SEQUENCE) { $serialized[] = 'sequenceGeneratorDefinition'; } } if ($this->isMappedSuperclass) { $serialized[] = 'isMappedSuperclass'; } if ($this->isVersioned) { $serialized[] = 'isVersioned'; $serialized[] = 'versionField'; } if ($this->lifecycleCallbacks) { $serialized[] = 'lifecycleCallbacks'; } return $serialized; } /** * 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['declared'])) { $reflField = new ReflectionProperty($mapping['declared'], $field); } else { $reflField = $this->reflClass->getProperty($field); } $reflField->setAccessible(true); $this->reflFields[$field] = $reflField; } foreach ($this->associationMappings as $field => $mapping) { if ($mapping->declared) { $reflField = new ReflectionProperty($mapping->declared, $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; } }