<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Orm on Murat Useinov</title>
    <link>https://useinov.com/tags/orm/</link>
    <description>Recent content in Orm on Murat Useinov</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 09 Apr 2025 22:35:00 +0300</lastBuildDate>
    <atom:link href="https://useinov.com/tags/orm/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Native lazy objects, the ORM view</title>
      <link>https://useinov.com/posts/2025/doctrine-lazy-objects/</link>
      <pubDate>Wed, 09 Apr 2025 22:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/doctrine-lazy-objects/</guid>
      <description>&lt;p&gt;Open the Doctrine proxy folder of any project older than a year. A generated subclass for every entity, getters overridden, one file per class in the cache dir. It worked for fifteen years, and the seams always showed: final classes, public typed properties, &lt;code&gt;instanceof&lt;/code&gt; surprises, a folder of code you pretend not to see.&lt;/p&gt;
&lt;p&gt;PHP 8.4 lazy objects were designed with exactly this in mind. The native way needs no generated class:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Lazy objects move into the engine</title>
      <link>https://useinov.com/posts/2024/lazy-objects/</link>
      <pubDate>Sat, 23 Nov 2024 12:50:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2024/lazy-objects/</guid>
      <description>&lt;p&gt;Fifty ghosts initialized in a loop are still fifty one queries.&lt;/p&gt;
&lt;p&gt;PHP 8.4 is out since Thursday. Property hooks get the headlines, but the feature I keep coming back to is native lazy objects. It legalizes a trick ORMs have done with generated code for fifteen years.&lt;/p&gt;
&lt;p&gt;The trick: you load an order, &lt;code&gt;$order-&amp;gt;customer&lt;/code&gt; should be a Customer, and you do not want a query until someone actually touches it. Doctrine generates a proxy class at build time, a subclass that overrides every method with &amp;ldquo;initialize first, then call parent&amp;rdquo;. It works. It is also a pile of magic: generated files, edge cases with final classes and private properties, strange things in var_dump.&lt;/p&gt;</description>
    </item>
    <item>
      <title>N&#43;1 hides on your laptop</title>
      <link>https://useinov.com/posts/2019/n-plus-one-production/</link>
      <pubDate>Fri, 05 Jul 2019 22:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/n-plus-one-production/</guid>
      <description>&lt;p&gt;Ten rows in the dev database, on the same machine, 0.1 ms per query. Ten extra queries is one millisecond. The page feels instant, the code ships. Production has a thousand rows and the database one network hop away. Round trip is about a millisecond even in a good datacenter. A thousand queries is a second of pure network waiting. Not slow SQL. Each query is fast. The plural is slow.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Eloquent observers and hidden control flow</title>
      <link>https://useinov.com/posts/2019/eloquent-events-cost/</link>
      <pubDate>Wed, 27 Feb 2019 12:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/eloquent-events-cost/</guid>
      <description>&lt;p&gt;Three thousand welcome emails. A colleague ran a user import on one project last month: loop over a CSV, &lt;code&gt;$user-&amp;gt;save()&lt;/code&gt;, go home. Next morning we found the script had also warmed the search index three thousand times and invalidated cache after every row. Nobody wrote that in the import script. The observers did.&lt;/p&gt;
&lt;p&gt;Laravel 5.8 came out yesterday, and reading the changelog brought that evening back, so here is the note.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Doctrine and the 100 000 row import</title>
      <link>https://useinov.com/posts/2016/doctrine-batch-memory/</link>
      <pubDate>Sun, 19 Jun 2016 20:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/doctrine-batch-memory/</guid>
      <description>&lt;p&gt;Row sixty thousand. &lt;code&gt;Allowed memory size exhausted&lt;/code&gt;. The import script on one project died there, and the code was the obvious loop: read a row, &lt;code&gt;persist()&lt;/code&gt; an entity, next row, &lt;code&gt;flush()&lt;/code&gt; at the end. On a hundred test rows it worked perfectly. It was my loop.&lt;/p&gt;
&lt;p&gt;The reason is the Unit of Work. Doctrine keeps every managed entity in memory, plus a snapshot of its original data for change tracking. Persist a hundred thousand entities and you hold a hundred thousand objects twice. This is not a bug. It is the price of the ORM&amp;rsquo;s main feature, and on a normal web request the price is invisible because the request dies young.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Where Doctrine spends your milliseconds</title>
      <link>https://useinov.com/posts/2015/doctrine-latency/</link>
      <pubDate>Thu, 23 Apr 2015 10:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/doctrine-latency/</guid>
      <description>&lt;p&gt;&lt;code&gt;$order-&amp;gt;getCustomer()-&amp;gt;getName()&lt;/code&gt; inside a &lt;code&gt;foreach&lt;/code&gt;. Every time I open a slow Doctrine endpoint, I find some version of this line within ten minutes. The PHP is rarely slow. The shape of the data access is.&lt;/p&gt;
&lt;p&gt;Same few places every time.&lt;/p&gt;
&lt;p&gt;Lazy loading. The classic N+1. Doctrine proxies make it invisible: you touch the relation in a loop and every touch is a query. The profiler query count tells the truth. The code does not.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Kohana ORM and my first real N&#43;1</title>
      <link>https://useinov.com/posts/2014/kohana-orm-n-plus-one/</link>
      <pubDate>Wed, 12 Nov 2014 20:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/kohana-orm-n-plus-one/</guid>
      <description>&lt;p&gt;Fifty one queries. That is what the SQL panel of the Kohana profiler showed for one catalog page. Fifty posts on the page.&lt;/p&gt;
&lt;p&gt;Nothing changed in the code. The page took two seconds because there was more data than in spring, and the code was written for spring.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$post-&amp;gt;author-&amp;gt;name&lt;/code&gt; in a loop. One query for the list of posts, then one more for the author of every post, inside the &lt;code&gt;foreach&lt;/code&gt;. The line looks innocent, and that is the problem: lazy loading hides the price. You write the relation, you go home early, and the bill comes in six months.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
