[DDC-1640] Actual classMetadata is not used when entity with DiscriminatorMap is hydrated, instead AbstractClass metaData is used. Created: 08/Feb/12  Updated: 17/Feb/12  Resolved: 17/Feb/12

Status: Resolved
Project: Doctrine 2 - ORM
Component/s: ORM
Affects Version/s: 2.2
Fix Version/s: 2.2.1
Security Level: All

Type: Bug Priority: Critical
Reporter: Jelte Steijaert Assignee: Benjamin Eberlei
Resolution: Fixed Votes: 0
Labels: None


 Description   

Because the className changes in ObjectHydrator:226 it might be that the ClassMetadata has not been loaded yet.
When the classMetadata has not been loaded the PostLoad events are not executed.

adding the following after the className has changed solves the issue.

$this->_getClassMetadata($className);

example:

Class Session {
    /**
     * @access private
     * @var ArrayCollection
     * @ORM\OneToMany(targetEntity="AbstractService", mappedBy="session")
     */
    public $services;
}
/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({
 * 		"A" = "ServiceA",
 * 		"B" = "ServiceB"
 * })
 */
abstract class AbstractService 
{
    /**
     * @access private
     * @var string
     * @ORM\Column()
     */
    private $_status;

    public $status;

    /**
     * @access public
     * @internal Do not use; this is for Doctrine only
     * @ORM\PostLoad
     * @return void
     */
    public function _reconstituteValueObjects()
    {
        $this->status = new Status($this->_status);
    }
}

When I would do now:

foreach ( $session->services as $service ) {
    $service->status->stop();
}

$service would be a concrete class ServceA or ServiceB, but these ClassMetadata's might not be loaded yet. If this is the cause then $service->status will be null as the @PostLoad has not been executed.



 Comments   
Comment by Jelte Steijaert [ 09/Feb/12 ]

I've have found the same issue with the SimpleObjectHydrator.

Although here it might have worse consequences.
When a row is hydrated it the class has a discriminatorMap, the classMetadata of the defined class (usually the abstract class) is used and not the actual classMetadata of the class it will be converted to.
Same issue here that the @PostLoad isn't triggered but also

$this->registerManaged($this->class,...) 

uses the wrong classMetadata is used.

should be: Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator:86

    protected function hydrateRowData(array $sqlResult, array &$cache, array &$result)
    {
        $classMetadata = $this->class;
        $entityName = $this->class->name;
        $data       = array();

        // We need to find the correct entity class name if we have inheritance in resultset
        if ($classMetadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
            $discrColumnName = $this->_platform->getSQLResultCasing($classMetadata->discriminatorColumn['name']);

            if ($sqlResult[$discrColumnName] === '') {
                throw HydrationException::emptyDiscriminatorValue(key($this->_rsm->aliasMap));
            }

            $entityName = $classMetadata->discriminatorMap[$sqlResult[$discrColumnName]];
            $classMetadata = $this->_getClassMetadata($entityName);

            unset($sqlResult[$discrColumnName]);
        }
        foreach ($sqlResult as $column => $value) {
            // Hydrate column information if not yet present
            if ( ! isset($cache[$column])) {
                if (($info = $this->hydrateColumnInfo($entityName, $column)) === null) {
                    continue;
                }

                $cache[$column] = $info;
            }

            // Convert field to a valid PHP value
            if (isset($cache[$column]['field'])) {
                $type  = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']);
                $value = $type->convertToPHPValue($value, $this->_platform);
            }

            // Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
            if (isset($cache[$column]) && ( ! isset($data[$cache[$column]['name']]) || $value !== null)) {
                $data[$cache[$column]['name']] = $value;
            }
        }

        if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
            $this->registerManaged($classMetadata, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
        }

        $uow    = $this->_em->getUnitOfWork();
        $entity = $uow->createEntity($entityName, $data, $this->_hints);

        //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here.
        if (isset($classMetadata->lifecycleCallbacks[Events::postLoad])) {
            $classMetadata->invokeLifecycleCallbacks(Events::postLoad, $entity);
        }

        $evm = $this->_em->getEventManager();

        if ($evm->hasListeners(Events::postLoad)) {
            $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
        }

        $result[] = $entity;
    }
Comment by Jelte Steijaert [ 09/Feb/12 ]

Changed summery to better reflect the bug.

Comment by Benjamin Eberlei [ 17/Feb/12 ]

Fixed.

Generated at Thu Oct 02 00:25:45 UTC 2014 using JIRA 6.2.3#6260-sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.