<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Doctrine ORM Archives - Angry dev thoughts</title>
	<atom:link href="https://angry-dev-thoughts.com/category/doctrine-orm/feed/" rel="self" type="application/rss+xml" />
	<link>https://angry-dev-thoughts.com/category/doctrine-orm/</link>
	<description>Software developers are sometimes angry</description>
	<lastBuildDate>Sun, 02 Feb 2025 13:25:07 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.7.1</generator>

<image>
	<url>https://angry-dev-thoughts.com/wp-content/uploads/2024/09/cropped-android-chrome-512x512-3-32x32.png</url>
	<title>Doctrine ORM Archives - Angry dev thoughts</title>
	<link>https://angry-dev-thoughts.com/category/doctrine-orm/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How to do performant count of child entities in rich entity with doctrine?</title>
		<link>https://angry-dev-thoughts.com/2025/02/02/how-to-do-performant-count-of-child-entities-in-rich-entity-with-doctrine/</link>
					<comments>https://angry-dev-thoughts.com/2025/02/02/how-to-do-performant-count-of-child-entities-in-rich-entity-with-doctrine/#respond</comments>
		
		<dc:creator><![CDATA[angryDev]]></dc:creator>
		<pubDate>Sun, 02 Feb 2025 13:25:06 +0000</pubDate>
				<category><![CDATA[Doctrine ORM]]></category>
		<category><![CDATA[Domain Driven Design]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Software development]]></category>
		<category><![CDATA[Test Driven Development]]></category>
		<category><![CDATA[Testing]]></category>
		<guid isPermaLink="false">https://angry-dev-thoughts.com/?p=80</guid>

					<description><![CDATA[<p>TLDR: Use Extra Lazy feature from Doctrine ORM: https://www.doctrine-project.org/projects/doctrine-orm/en/3.3/tutorials/extra-lazy-associations.html &#8212; Let&#8217;s imagine that we&#8217;re working on e-commerce platform. Each user can sold maximum 100 items How to do it properly? How to introduce domain limit on doctrine relationships? How can I introduce it? Let&#8217;s use rich entities (trying to follow Domain-Driven-Design and Test-Driven-Development rules): &#60;?php [&#8230;]</p>
<p>The post <a href="https://angry-dev-thoughts.com/2025/02/02/how-to-do-performant-count-of-child-entities-in-rich-entity-with-doctrine/">How to do performant count of child entities in rich entity with doctrine?</a> appeared first on <a href="https://angry-dev-thoughts.com">Angry dev thoughts</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>TLDR: Use Extra Lazy feature from Doctrine ORM: https://www.doctrine-project.org/projects/doctrine-orm/en/3.3/tutorials/extra-lazy-associations.html</p>



<p>&#8212;</p>



<p>Let&#8217;s imagine that we&#8217;re working on e-commerce platform. Each user can sold maximum 100 items How to do it properly? How to introduce domain limit on doctrine relationships? How can I introduce it? Let&#8217;s use rich entities (trying to follow Domain-Driven-Design and Test-Driven-Development rules):</p>



<pre class="wp-block-preformatted">&lt;?php

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

#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int|null $id = null;

    #[ORM\Column(type: 'string', length: 255)]
    private string $name;

    /** @var Collection&lt;int, Item&gt; */
    #[ORM\OneToOne(inversedBy: 'createdBy', targetEntity: Item::class)]
    private Collection $items;

    public function __construct(string $name)
    {
        $this-&gt;name = $name;
        $this-&gt;items = new ArrayCollection();
    }

    public function addItem(Item $item): void
    {
        $this-&gt;items[] = $item;
    }

    public function getItems(): Collection
    {
        return $this-&gt;items;
    }
}</pre>



<p>and item:</p>



<pre class="wp-block-code"><code>&lt;?php

use Doctrine\ORM\Mapping as ORM;

class Item
{
    #&#91;ORM\Id]
    #&#91;ORM\GeneratedValue]
    #&#91;ORM\Column(type: 'integer')]
    private int|null $id = null;

    #&#91;ORM\Column(type: 'string')]
    private string $description;

    #&#91;ORM\Column(type: 'integer')]
    private int $price;

    #&#91;ORM\ManyToOne(targetEntity: User::class, inversedBy: 'items')]
    private User $createdBy;

    public function __construct(string $description, int $price)
    {

    }

    public function setCreator(User $createdBy): void
    {
        $createdBy-&gt;addItem($this);
        $this-&gt;createdBy = $createdBy;
    }
}</code></pre>



<p>And basic test for that:</p>



<pre class="wp-block-code"><code>&lt;?php

namespace tests;

use Item;
use PHPUnit\Framework\TestCase;
use User;

final class ItemsLimitPerUserTest extends TestCase
{
    public function testItemsLimitPerUser(): void
    {
        $user = new User('John');
        $itemA = new Item('My aswesome item A', 20);
        $itemB = new Item('My aswesome item B', 20);
        $itemC = new Item('My aswesome item C', 20);

        $itemA-&gt;setCreator($user);
        $itemB-&gt;setCreator($user);
        $itemC-&gt;setCreator($user);

        $this-&gt;assertEquals(
            3,
            $user-&gt;getItems()-&gt;count()
        );
    }
}</code></pre>



<p>Now let&#8217;s add new test case that adding fourth item will fail (let&#8217;s set limit = 3). Basically we will implement items limit per user logic.</p>



<pre class="wp-block-preformatted">public function testItemsLimitPerUserIsMet(): void<br>{<br>    $user = new User('John');<br>    $itemA = new Item('My aswesome item A', 20);<br>    $itemB = new Item('My aswesome item B', 20);<br>    $itemC = new Item('My aswesome item C', 20);<br>    $itemD = new Item('My aswesome item D', 20);<br><br>    $itemA-&gt;setCreator($user);<br>    $itemB-&gt;setCreator($user);<br>    $itemC-&gt;setCreator($user);<br><br>    try {<br>        $itemD-&gt;setCreator($user);<br>    } catch (Exception $exception) {<br>        $this-&gt;assertInstanceOf(LimitMetException::class, $exception);<br>    }<br><br>    $this-&gt;assertEquals(<br>        3,<br>        $user-&gt;getItems()-&gt;count()<br>    );<br>}</pre>



<p>Which obviously will fail for that moment:</p>



<pre class="wp-block-code"><code>~/Development/doctrine-playground$ ./vendor/bin/phpunit tests
PHPUnit 11.5.6 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.27

.F                                                                  2 / 2 (100%)

Time: 00:00.007, Memory: 8.00 MB

There was 1 failure:

1) tests\ItemsLimitPerUserTest::testItemsLimitPerUserIsMet
Failed asserting that 4 matches expected 3.

/home/adt/Development/doctrine-playground/tests/ItemsLimitPerUserTest.php:48

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
</code></pre>



<p>Now let&#8217;s try to implement this requirement (git diff): </p>



<pre class="wp-block-code"><code>diff --git a/src/User.php b/src/User.php
index ec83b61..d8dcde8 100644
--- a/src/User.php
+++ b/src/User.php
@@ -8,6 +8,8 @@ use Doctrine\ORM\Mapping as ORM;
 #&#91;ORM\Table(name: 'users')]
 class User
 {
+    public const ITEMS_LIMIT = 3;
+
     #&#91;ORM\Id]
     #&#91;ORM\GeneratedValue]
     #&#91;ORM\Column(type: 'integer')]
@@ -26,8 +28,15 @@ class User
         $this-&gt;items = new ArrayCollection();
     }
 
+    /**
+     * @throws LimitMetException
+     */
     public function addItem(Item $item): void
     {
+        if ($this-&gt;items-&gt;count() &gt;= self::ITEMS_LIMIT) {
+            throw new LimitMetException();
+        }
+
         $this-&gt;items&#91;] = $item;
     }
</code></pre>



<p>Now, all tests are passing:</p>



<pre class="wp-block-code"><code>~/Development/doctrine-playground$ ./vendor/bin/phpunit tests
PHPUnit 11.5.6 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.27

..                                                                  2 / 2 (100%)

Time: 00:00.003, Memory: 8.00 MB

OK (2 tests, 3 assertions)</code></pre>



<p>But we have one issue, during adding new item doctrine by default will load whole items collection. It&#8217;s not good to load it only to do the count. For that we can use Extra lazy feature from Doctrine ORM:</p>



<pre class="wp-block-code"><code>    /** @var Collection&lt;int, Item> */
    #&#91;ORM\OneToOne(inversedBy: 'createdBy', targetEntity: Item::class, fetch: 'EXTRA_LAZY')]
    private Collection $items;</code></pre>



<p>git diff:</p>



<pre class="wp-block-code"><code>diff --git a/src/User.php b/src/User.php
index d8dcde8..194fe67 100644
--- a/src/User.php
+++ b/src/User.php
@@ -19,7 +19,7 @@ class User
     private string $name;
 
     /** @var Collection&lt;int, Item> */
-    #&#91;ORM\OneToOne(inversedBy: 'createdBy', targetEntity: Item::class)]
+    #&#91;ORM\OneToOne(inversedBy: 'createdBy', targetEntity: Item::class, fetch: 'EXTRA_LAZY')]
     private Collection $items;</code></pre>



<p>Now doctrine won&#8217;t load all entities into memory to simply count them, it will do dedicate count SQL query. In that way you can introduce limit for really large entity collections and avoid hitting memory limit and performance issues.</p>
<p>The post <a href="https://angry-dev-thoughts.com/2025/02/02/how-to-do-performant-count-of-child-entities-in-rich-entity-with-doctrine/">How to do performant count of child entities in rich entity with doctrine?</a> appeared first on <a href="https://angry-dev-thoughts.com">Angry dev thoughts</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://angry-dev-thoughts.com/2025/02/02/how-to-do-performant-count-of-child-entities-in-rich-entity-with-doctrine/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
