phpro / zf-doctrine-hydration-module

Configurable Doctrine hydrators for ZF2
18 stars 33 forks source link

Multiple properties with same strategy have field name set to last added strategy #42

Closed rkeet closed 5 years ago

rkeet commented 5 years ago

Possibly a bug, need help.

It's as if all strategies get added by reference, as such, setting the property name on the strategy, sets it for all where this same instance has been added. They all have the same object hash.

Considering the following strategy configuration for a Resource:

    'doctrine-hydrator' => [
        'Company\\V1\\Rest\\Company\\CompanyHydrator' => [
            'entity_class' => \Namespace\Path\To\Company\Entity\Company::class,
            'object_manager' => 'doctrine.entitymanager.orm_default',
            'by_value' => true,
            'strategies' => [
                'users' => \ZF\Doctrine\Hydrator\Strategy\CollectionLink::class,
                'countries' => \Application\Strategy\CollectionUniDirectionalToManyStrategy::class,
                'currencies' => \Application\Strategy\CollectionUniDirectionalToManyStrategy::class,
            ],
        ],
    ],

To have your information complete, this is the entire custom strategy:

class CollectionUniDirectionalToManyStrategy extends AllowRemoveByValue
{
    public function extract($value)
    {
        return new Collection($value ?: []);
    }
}

So, when stepping through into the strategy during hydration for Countries, I expect to see that the collectionName of $this (instance of CollectionUniDirectionalToManyStrategy), is set to countries, however, it is set to currencies. See the debug image below for reference.

image

This is due to how these are set in the DoctrineHydratorFactory, here.

$strategy = $container->get($strategyKey);

Following that into the Zend ServiceManager we find this (here):

    // We start by checking if we have cached the requested service (this
    // is the fastest method).
    if (isset($this->services[$requestedName])) {
        return $this->services[$requestedName];
    }

This causes that the same instance is always returned. So when later the collection name is applied to 1 of the these "set" instances, it's applied to all of them. Creating the situation in the screenshot.


The bug fix:

Clone the result from the Zend ServiceManager to prevent using cached instances.

$strategy = clone $container->get($strategyKey);
veewee commented 5 years ago

Another solution would be to mark the strategy as not shared in the service configuration. See shared section in the documentation: https://docs.zendframework.com/zend-servicemanager/configuring-the-service-manager/

rkeet commented 5 years ago

Well.... shit

That was a lot of hours of searching and debugging. I'd been working under the assumption that "shared" was false by default.

Didn't know about shared_by_default :|

Thanks though. :)