Synergy of proxy object of doctrine and zend expressive hal

Hi, I’m working with doctrine and zend expressive hal, but there is a problem when I try to generate the response of an object using the method fromObject of the class Zend\Expressive\Hal\ResourceGenerator.
The problem here is that the object is a proxy object of doctrine and consequently the namespace is not the real one and it isn’t found in the Zend\Expressive\Hal\Metadata\MetadataMap.

One way to solve the problem would be to implement a custom class Zend\Expressive\Hal\ResourceGenerator overwriting the method fromObject in this way:

<?php

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;

class ProxyDoctrineResourceGenerator
{
    /** same code **/

    /**
     * @param object $instance An object of any type; the type will be checked
     *     against types registered in the metadata map.
     * @param ServerRequestInterface $request
     */
    public function fromObject($instance, ServerRequestInterface $request) : HalResource
    {
        if (! is_object($instance)) {
            throw Exception\InvalidObjectException::forNonObject($instance);
        }


        //$class = get_class($instance);
        // Use the static method of doctrine class utils to retrieve the real namespace of the class
        $class=\Doctrine\Common\Util\ClassUtils::getClass($instance);

        if (! $this->metadataMap->has($class)) {
            throw Exception\InvalidObjectException::forUnknownType($class);
        }

        $metadata = $this->metadataMap->get($class);
        $metadataType = get_class($metadata);

        if (! isset($this->strategies[$metadataType])) {
            throw Exception\UnknownMetadataTypeException::forMetadata($metadata);
        }

        $strategy = $this->strategies[$metadataType];
        return $strategy->createResource(
            $instance,
            $metadata,
            $this,
            $request
        );
    }
}

We need to re-implement even the Zend\Expressive\Hal\ResourceGeneratorFactory according with our new custom class:

<?php

class ProxyDoctrineResourceGeneratorFactory
{
    public function __invoke(ContainerInterface $container) : ResourceGenerator
    {
        $generator = new ProxyDoctrineResourceGenerator(
            $container->get(Metadata\MetadataMap::class),
            $container->get(HydratorPluginManager::class),
            $container->get(LinkGenerator::class)
        );

        $this->injectStrategies($container, $generator);

        return $generator;
    }
}

As you can see I don’t like very match this solution, mostly because I can’t extend the Zend\Expressive\Hal\ResourceGenerator or Zend\Expressive\Hal\ResourceGeneratorFactory and I would need to always look if any changes has been made to these classes… (I can extend but it would be useless because the properties are private and not protected and I would in any case rewriting al methods which depends on these private properties.).

Has someone ever had the same problem?

Temporarily I defined in the annotations of the entity to retrieve the associated class in EAGER mode in this way:

	/**
	 * @ORM\ManyToOne(targetEntity="Entity\User", inversedBy="identities",fetch="EAGER")
	 * @ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
	 * @SWG\Property
	 */
	protected $user;

In this way the proxy object isn’t created, but I don’t like and it’s not the right way to solve the problem…

Terrible idea to use EAGER.

Instead, why not wrap the ZF service with something that understands proxy classes?

I know, can you explain more in detail what you mean by wrapping the ZF service?

Specifically talking about the $this->metadataMap bit: that can simply be decorated with a layer that checks doctrine’s class metadata.

Something like this (note: I didn’t read the original code/interfaces, I’m making this up to show how it’s supposed to be done):

final class DoctrineMetadataMap implements MetadataMap {

    private $next;

    private $metadata;

    public function __construct(MetadataMap $next, ClassMetadataFactory $metadata)
    {
        $this->next = $next;

        $this->metadata = $metadata;
    }

    public function has(string $className) : bool
    {
        return $this->next->has($this->metadata->get($className)->getName());
    }

    public function get(string $className)
    {
        return $this->next->get($this->metadata->get($className)->getName());
    }
}

As always you are right, I was struggling to find a solution inside the Zend\Expressive\Hal\ResourceGenerator for this line of code $class = get_class($instance);when actually I can apply the logic after with my MetadataMap service.

Thanks

Hi, i had the same issue with Doctrine proxy-object and Hal, and i resolved extending the Reflection Hydrator.

  • first i created a class HydratorReflectionProxy
class HydratorReflectionProxy extends Reflection
{
  • Then added a method to get the real object from the proxy-object, found a hint here
    protected function getRealEntity(Proxy $proxy)
    {
        if ($proxy instanceof Proxy) {
            // load the proxy class
            $proxy->__load();

            $metadata = DbFactory::getEntityManager()->getMetadataFactory()->getMetadataFor(get_class($proxy));
            $class = $metadata->getName();
            $entity = new $class();
            $reflectionSourceClass = new \ReflectionClass($proxy);
            $reflectionTargetClass = new \ReflectionClass($entity);
            foreach ($metadata->getFieldNames() as $fieldName) {
                $reflectionPropertySource = $reflectionSourceClass->getParentClass()->getProperty($fieldName);
                $reflectionPropertySource->setAccessible(true);
                $reflectionPropertyTarget = $reflectionTargetClass->getProperty($fieldName);
                $reflectionPropertyTarget->setAccessible(true);
                $reflectionPropertyTarget->setValue($entity, $reflectionPropertyTarget->getValue($proxy));
            }
            return $entity;
        }
        return $proxy;
    }
  • overwritten the extract method
    public function extract($object)
    {
        $result = [];
        foreach (self::getReflProperties($object) as $property) {
            $propertyName = $this->extractName($property->getName(), $object);
            if (! $this->filterComposite->filter($propertyName)) {
                continue;
            }
            $value = $property->getValue($object);
            if (is_object($value) && $value instanceof Proxy) {
                $value = $this->getRealEntity($value);
            }
            $result[$propertyName] = $this->extractValue($propertyName, $value, $object);
        }

        return $result;
    }

and then added the reflection-class as extractor in the ConfigProvider where the Hal configuration resides

   public function getHalMetadataMap(): array
   {
       return [
           [
               '__class__'      => RouteBasedResourceMetadata::class,
               'resource_class' => Entities\TestBook::class,
               'route'          => 'testbook#getlist',
               'extractor'      => HydratorReflectionProxy::class,
           ],
           [
               '__class__'           => RouteBasedCollectionMetadata::class,
               'collection_class'    => Entities\TestBookCollection::class,
               'collection_relation' => 'book',
               'route'               => 'testbook#getlist',
               'pagination_param'  => '/page'
           ],
           [
               '__class__'      => RouteBasedResourceMetadata::class,
               'resource_class' => Entities\TestAutore::class,
               'route'          => 'testbook#getlist',
               'extractor'      => HydratorReflectionProxy::class,
           ],
           [
               '__class__'           => RouteBasedCollectionMetadata::class,
               'collection_class'    => Entities\TestAutoreCollection::class,
               'collection_relation' => 'autore',
               'route'               => 'testbook#getlist',
               'pagination_param'  => '/page'
           ],
       ];
   }

the result is an well hydrated Hal object Response instead of proxy object (which are not useful for the client consuming the api)
look to me pretty clear. Open to suggestion

Your solution is pretty clean.
What I did myself was to create a custom DoctrineMetadataMap:

class DoctrineMetadataMap extends \Zend\Expressive\Hal\Metadata\MetadataMap
{
    /**
     *
     * @param string $class
     */
    public function has(string $class): bool
    {
        $class = \Doctrine\Common\Util\ClassUtils::getRealClass($class);
        return parent::has($class);
    }

    public function get(
        string $class
    ): \Zend\Expressive\Hal\Metadata\AbstractMetadata {
        $class = \Doctrine\Common\Util\ClassUtils::getRealClass($class);
        return parent::get($class);
    }
}

The problem with my solution resides on the factory which need to reimplement all private methods of the MetadataMapFactory.