DDC-1640: Actual classMetadata is not used when entity with DiscriminatorMap is hydrated, instead AbstractClass metaData is used. #2282

Closed
doctrinebot opened this Issue Feb 8, 2012 · 4 comments

2 participants

@doctrinebot

Jira issue originally created by user jelte@marlon:

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.

@doctrinebot

Comment created by jelte@marlon:

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;
    }
@doctrinebot

Comment created by jelte@marlon:

Changed summery to better reflect the bug.

@doctrinebot

Comment created by @beberlei:

Fixed.

@doctrinebot

Issue was closed with resolution "Fixed"

@beberlei beberlei was assigned by doctrinebot Dec 6, 2015
@doctrinebot doctrinebot added this to the 2.2.1 milestone Dec 6, 2015
@doctrinebot doctrinebot closed this Dec 6, 2015
@doctrinebot doctrinebot added the Bug label Dec 7, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment