N
N
nepster-web2016-11-13 18:12:00
symfony
nepster-web, 2016-11-13 18:12:00

What is the correct way to work with doctrine fetch objects in Symfony?

There is Symfony3 , respectively doctrine2 and a few incomprehensible moments.
The following code was written for the test:

$product = $this->getDoctrine()
            ->getRepository('AppBundle:Product')
            ->findById(1);

        $products = $this->getDoctrine()
            ->getRepository('AppBundle:Product')
            ->findAll();

        dump($product);

        foreach ($product->getImages() as $image) {
            dump($image);
        }

As an example, the classic version with a product.
<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Product
 *
 * @ORM\Table(name="products")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
 */
class Product
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var int
     *
     * @ORM\Column(name="status", type="integer")
     */
    private $status = 0;

    /**
     * @var int
     *
     * @ORM\Column(name="is_visible", type="integer")
     */
    private $is_visible = 1;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=100)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="desc", type="string")
     */
    private $desc;

    /**
     * @ORM\OneToMany(targetEntity="Image", mappedBy="product")
     */
    protected $images;


    public function __construct()
    {
        $this->images = new ArrayCollection();
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set status
     *
     * @param integer $status
     *
     * @return Product
     */
    public function setStatus($status)
    {
        $this->status = $status;

        return $this;
    }

    /**
     * Get status
     *
     * @return integer
     */
    public function getStatus()
    {
        return $this->status;
    }

    /**
     * Set isVisible
     *
     * @param integer $isVisible
     *
     * @return Product
     */
    public function setIsVisible($isVisible)
    {
        $this->is_visible = $isVisible;

        return $this;
    }

    /**
     * Get isVisible
     *
     * @return integer
     */
    public function getIsVisible()
    {
        return $this->is_visible;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Product
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set desc
     *
     * @param string $desc
     *
     * @return Product
     */
    public function setDesc($desc)
    {
        $this->desc = $desc;

        return $this;
    }

    /**
     * Get desc
     *
     * @return string
     */
    public function getDesc()
    {
        return $this->desc;
    }

    /**
     * Add image
     *
     * @param \AppBundle\Entity\Image $image
     *
     * @return Product
     */
    public function addImage(\AppBundle\Entity\Image $image)
    {
        $this->images[] = $image;

        return $this;
    }

    /**
     * Remove image
     *
     * @param \AppBundle\Entity\Image $image
     */
    public function removeImage(\AppBundle\Entity\Image $image)
    {
        $this->images->removeElement($image);
    }

    /**
     * Get images
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getImages()
    {
        return $this->images;
    }
}

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Image
 *
 * @ORM\Table(name="products_images")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ImageRepository")
 */
class Image
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var int
     *
     * @ORM\Column(name="product_id", type="integer")
     */
    private $product_id;

    /**
     * @var string
     *
     * @ORM\Column(name="preview_url", type="string")
     */
    private $preview_url;

    /**
     * @var string
     *
     * @ORM\Column(name="review_url", type="string")
     */
    private $review_url;

    /**
     * @ORM\ManyToOne(targetEntity="Product", inversedBy="images")
     * @ORM\JoinColumn(name="product_id", referencedColumnName="id")
     */
    protected $product;

    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set productId
     *
     * @param integer $productId
     *
     * @return Image
     */
    public function setProductId($productId)
    {
        $this->product_id = $productId;

        return $this;
    }

    /**
     * Get productId
     *
     * @return integer
     */
    public function getProductId()
    {
        return $this->product_id;
    }

    /**
     * Set previewUrl
     *
     * @param string $previewUrl
     *
     * @return Image
     */
    public function setPreviewUrl($previewUrl)
    {
        $this->preview_url = $previewUrl;

        return $this;
    }

    /**
     * Get previewUrl
     *
     * @return string
     */
    public function getPreviewUrl()
    {
        return $this->preview_url;
    }

    /**
     * Set reviewUrl
     *
     * @param string $reviewUrl
     *
     * @return Image
     */
    public function setReviewUrl($reviewUrl)
    {
        $this->review_url = $reviewUrl;

        return $this;
    }

    /**
     * Get reviewUrl
     *
     * @return string
     */
    public function getReviewUrl()
    {
        return $this->review_url;
    }
}

Everything is working well. But 2 things bother me.
Firstly, it is such a healthy object of the images relation that it can only be debugged using the dump function.
632f48bca94c421bb24f6f3729daacb0.png
That is, why such a fat object and how can you just get an array of entities?
I forked an image, and each image has a product relation, which in turn has an image relation. It turns out that if I have 1 product, it has 10 images, it will be a lot of extra data in one object, which is very memory-consuming.
35193003d0af40ff8a7b4a566517ffc8.png
How can this be avoided?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
S
Sergey, 2016-11-13
@nepster-web

I will add Yuri 's answer
All entities are wrapped in proxy objects so that "magic" like lazy loading, etc. would work. That is why in essences there is "more" than there really is.
Regarding collections, Doctrine has such a thing as Collection. You must understand that in the doctrine you operate not with tables in the database, but with objects. Build exactly the object model of your system. In this vein, you can read what an "aggregate of entities" is. In your case, your aggregate will consist of two entities. Product and its Image. For example, if you want to add pictures, you can do this:

/**
 * usage: $product->addImage($image);
 */
public function addImage(Image $image)
{
    $this->images->add($image);
}

And the collection itself will persist the new entity. Thus, the number of repositories is reduced to the number of entity aggregate roots. In your example, the "root", i.e. the top of the object relationship graph in the context of products, is the product itself. That is why we will make a repository only for products. Everything else inside it is destroyed either with the help of collections.
When working with doctrine, it is generally useful to imagine that you do not have any database. That the data just lives between requests somewhere in memory. This should help you "abstract" and stop confusing "entities" and "tables".
For example, "novices" in the doctrine like to persist essence even for renewal. They confuse `persist` and `save`. So, if you loaded the entity from the database through the doctrine, then the entity is already included in the unit of work. And you no longer need to do persist, this method is only for the doctrine to learn about something new. And so she already knows about this entity. In the end, you can just change something and call flush. That is, a repository is a stupid repository. The repository knows how to store. It cannot change what it stores.
I also recommend reading this on the topic of repositories:
www.whitewashing.de/2013/03/04/doctrine_repositori...
Well, in general.
https://www.youtube.com/watch?v=rzGeNYC3oz0 - a report on how to prepare a doctrine from the authors of this.
From myself, I will only add simple rules:
- Do not directly use Doctrine repositories. Write your own, and use Doctrine in them. Do not smear the doctrine throughout the project, then it will be unrealistic to support.
- Do not inherit from EntityRepository. This is the internal mechanism of the general purpose doctrine. Use them in your repositories with your own interface, increasing specificity and tightening control over who uses what.
- Try to use the entity manager only in your repositories and some small services. Don't smear everything all over the place.
Doctrine guarantees you that there will always be only one entity instance in memory. That is, if you have 10 objects of the same type and have one object, they will all be references to one entity. In your case, you just have a circular link between products and images. dump doesn't really know how to do circular references.
This is a logical restriction, so that there are no situations that you updated something in one entity instance and something in another, and only part of the changes will get into the database. For details, read the doctrine documentation for UnitOrWork and Identity Map.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question