Doctrine - How to extract results and their relati

2019-05-05 00:07发布

问题:

I have an entity, call it Stones and Stones has a ManyToMany relationship with Attributes.

So I query the entity to get the Stones and then I hydrate this to convert it into an array.

    $result =  $this->stoneRepository->find($stone_id);

    if ( ! $result )
    {
        return false;
    }

    $resultArray =  $this->doctrineHydrator->extract($result);

This works fine for the Stone entity however I noticed that the join (Attributes) remain as objects.

array (size=12)
  'id' => int 1
  'name' => string 'Agate' (length=5)
  'title' => string 'Title' (length=5)
'attribute' => 
    array (size=5)
      0 => 
        object(Stone\Entity\StAttribute)[1935]
          private 'id' => int 2
          private 'name' => string 'Hay fevor' (length=9)
          private 'state' => boolean true
          private 'created' => null
          private 'modified' => null
      1 => 
        object(Stone\Entity\StAttribute)[1936]
          private 'id' => int 15
          private 'name' => string 'Libra' (length=5)
          private 'state' => boolean true
          private 'created' => null
          private 'modified' => null
      2 => 

etc.

What is the process to hydrate the Attribute objects?

回答1:

Hydration is populating an object (entity) using an array which is opposite of the extraction.

Since you want the resultset in array format, you should prevent unnecessary hydration and extraction process which already occurs in the ORM level under the hood.

Try to use Query Builder Api instead of built-in find() method of the entity repository. This is not a single-line but really straightforward and faster solution, it should work:

$qb = $this->stoneRepository->createQueryBuilder('S');
$query = $qb->addSelect('A')
            ->leftJoin('S.attribute', 'A')
            ->where('S.id = :sid')
            ->setParameter('sid', (int) $stone_id)
            ->getQuery();

$resultArray = $query->getOneOrNullResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);

This way, you will also prevent running additional SQL queries against database to fetch associated entities. (StAttribute in your case)



回答2:

I thought I would follow up on this to show how this can be resolved using a CustomStrategy.

By far the easiest and fastest method was suggested by foozy. What I like about the solution is that when I use hydration in ApiGility for instance I can build custom queries which will produce the desired result in a very few lines of code.

The other solution I was working on was to add a custom strategy:

<?php
namespace Api\V1\Rest\Stone;

use DoctrineModule\Stdlib\Hydrator\Strategy\AbstractCollectionStrategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;

class CustomStrategy extends AbstractCollectionStrategy
{

    public function __construct($hydrator)
    {
        $this->hydrator = $hydrator;
    }

    /**
     * @param mixed $values
     * @return array|mixed
     */
    public function extract($values)
    {
        $returnArray = [];

        foreach ($values AS $value)
        {

            $returnArray[] =  $this->hydrator->extract($value);
        }

        return $returnArray;

    }

    /**
     * @param mixed $values
     * @return mixed
     */
    public function hydrate($values)
    {
        $returnArray = [];

        foreach ($values AS $value )
        {
            $returnArray[] = $this->hydrator->hydrate($value);
        }

        return $returnArray;
    }
}

Then from the service side I add various strategies to the hydrator like so:

$result =  $this->stoneRepository->find($stone_id);

$this->doctrineHydrator->addStrategy("product", new CustomStrategy( $this->doctrineHydrator ) );
$this->doctrineHydrator->addStrategy("attribute", new CustomStrategy( $this->doctrineHydrator ) );
$this->doctrineHydrator->addStrategy("image", new CustomStrategy( $this->doctrineHydrator ) );
$this->doctrineHydrator->addStrategy("related", new CustomStrategy( $this->doctrineHydrator ) );

$resultArray =  $this->doctrineHydrator->extract($result);

After which I created a custom entity:

<?php
namespace Api\V1\Rest\Stone;

class StoneEntity
{
    public $id;
    public $name;
    public $description;
    public $code;
    public $attribute;
    public $product;
    public $image;

    public function getArrayCopy()
    {
        return array(
            'id'          => $this->id,
            'name'        => $this->name,
            'description' => $this->description,
            'code'        => $this->code,
            'attribute'   => $this->attribute,
            'product'     => $this->product,
            'image'       => $this->image
        );
    }

    public function exchangeArray(array $array)
    {
        $this->id           = $array['id'];
        $this->name         = $array['name'];
        $this->description  = $array['description'];
        $this->code         = $array['code'];
        $this->attribute    = $array['attribute'];
        $this->product      = $array['product'];
        $this->image        = $array['image'];
    }
}

And the final part is to exchange the returned data with the custom entity:

    $entity = new StoneEntity();
    $entity->exchangeArray($resultArray);

And finally to return the result:

    return $entity;

To be honest, the above is just too long winded and my final solution as per the suggestion by foozy was this:

public function fetchOne($stone_id)
    {
        $qb = $this->stoneRepository->createQueryBuilder('S');
        $query = $qb->addSelect('A','P','I','C')
            ->leftJoin('S.attribute', 'A')
            ->innerJoin('A.category', 'C')
            ->innerJoin('S.product' , 'P')
            ->innerJoin('S.image' , 'I')
            ->where('S.id = :sid')
            ->setParameter('sid', (int) $stone_id)
            ->getQuery();

        $resultArray = $query->getOneOrNullResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);

        if ( ! $resultArray )
        {
            return false;
        }

        return $resultArray;
    }