<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrea Sprega</title>
    <description>The latest articles on DEV Community by Andrea Sprega (@andreasprega).</description>
    <link>https://dev.to/andreasprega</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F739729%2F7d529233-977f-44c0-b29b-3677f2aa8dcd.jpg</url>
      <title>DEV Community: Andrea Sprega</title>
      <link>https://dev.to/andreasprega</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andreasprega"/>
    <language>en</language>
    <item>
      <title>Entity repositories for humans: part 1</title>
      <dc:creator>Andrea Sprega</dc:creator>
      <pubDate>Sun, 18 Feb 2024 18:34:35 +0000</pubDate>
      <link>https://dev.to/andreasprega/entity-repositories-for-humans-part-1-2mnf</link>
      <guid>https://dev.to/andreasprega/entity-repositories-for-humans-part-1-2mnf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; this article describes a pattern for creating entity repositories that we use at &lt;a href="https://slope.it"&gt;Slope&lt;/a&gt;, but first it explains other strategies that we previously explored and discarded. If you're only interested in the outcome, you can look directly at the &lt;a href="https://github.com/slope-it/entity-repositories-for-humans"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fetching entities from a database is a necessity that pretty much every web application has. In case of fairly structured applications, this often happens through the use of repositories (if you have never heard of the repository pattern &lt;a href="https://martinfowler.com/eaaCatalog/repository.html"&gt;this is a pretty good introduction&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you use Doctrine ORM, most likely you have already used its repositories. In case you're not familiar with them, you can get an idea by looking at &lt;a href="https://symfony.com/doc/current/doctrine.html#querying-for-objects-the-repository"&gt;the examples in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; as Slope is built using Symfony, the approach described here is based on Doctrine. If you use Laravel, however, it should be possible to use the same approach also with Eloquent, with a few adaptations, by building some abstraction around its &lt;a href="https://laravel.com/docs/10.x/eloquent#retrieving-models"&gt;query builders&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;

&lt;p&gt;As the business domain becomes more complicated, properties and relationships between entities increase. With them, the queries that you need to fulfill the use cases of your application become increasingly more complex.&lt;br&gt;
With necessities increasing over time, you will probably need to switch from default Doctrine repositories to custom ones. The moment you cross that line, a world of different possibilities/implementations opens up.&lt;/p&gt;

&lt;p&gt;At Slope we constantly strive for the best possible developer experience. This case made no exception: we wanted an approach that was elegant, easy to use but at the same time flexible and that left room for optimizations when our DBMS (PostgreSQL) needed them.&lt;/p&gt;

&lt;p&gt;Let's go step by step: before presenting our current solution, it makes sense to mention the various approaches that we have tried previously.&lt;/p&gt;


&lt;h2&gt;
  
  
  Approach #1: standard Doctrine repositories with magic methods
&lt;/h2&gt;

&lt;p&gt;This is most likely the first approach that you discover once you start working with Doctrine.&lt;br&gt;
By default, Doctrine provides repository classes in which you don't have to implement any method. You can just use their dynamic interfaces that define several &lt;code&gt;findBy*&lt;/code&gt;, &lt;code&gt;findOneBy*&lt;/code&gt;, &lt;code&gt;countBy*&lt;/code&gt; methods based on entity metadata, i.e. it "adds" one method for each property and one for each association defined on its entity. Note that these methods are not statically defined, but implemented dynamically by Doctrine via a &lt;code&gt;__call&lt;/code&gt; method. The official documentation describes them &lt;a href="https://www.doctrine-project.org/projects/doctrine-orm/en/3.0/reference/working-with-objects.html#by-simple-conditions"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find this approach implemented in &lt;a href="https://github.com/slope-it/entity-repositories-for-humans/tree/approach-1"&gt;branch &lt;code&gt;approach-1&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can use them out of the box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is only possible to filter by the properties defined by the entity, so it only works with rather simple models.&lt;/li&gt;
&lt;li&gt;Combining more than one filter is very cumbersome.&lt;/li&gt;
&lt;li&gt;The fact that repositories use magic methods with associative arrays as parameters makes this approach quite "obscure"; the resulting DX is pretty bad, as you don't have autocompletion: discovering and debugging these methods is not exactly a breeze.&lt;/li&gt;
&lt;/ul&gt;


  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KEvyP0T_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://media1.tenor.com/m/zs-6k2lFHGsAAAAC/steve-carrell-magic.gif" width="498" height="371"&gt;Magic is nice. Just not in your repositories.

  



&lt;h2&gt;
  
  
  Approach #2: standard Doctrine repositories using Criteria
&lt;/h2&gt;

&lt;p&gt;The same repositories described above can also be queried via &lt;code&gt;Criteria&lt;/code&gt; objects. These objects represent specific filters (and/or combinations of them) and can be passed to the repository in order to extract matching entities. You can find more information about them &lt;a href="https://www.doctrine-project.org/projects/doctrine-orm/en/3.0/reference/working-with-objects.html#by-criteria"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This approach is demonstrated in &lt;a href="https://github.com/slope-it/entity-repositories-for-humans/tree/approach-2"&gt;branch &lt;code&gt;approach-2&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Criteria objects can be used interchangeably with repositories and entity collections for filtering.&lt;/li&gt;
&lt;li&gt;Criteria are highly composable and use much less "magic" than the previous approach.&lt;/li&gt;
&lt;li&gt;Client classes that use this approach are forced to know the internal details of the entity about how attributes and relationships are defined (because they work directly with the attribute and association names). Any optimizations, workarounds to Doctrine limitations or denormalizations can no longer be kept “secret”, and it will be necessary to modify all clients if, for example, the name of an attribute or an association changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The approach is super verbose and impractical. The more filters you need, the worse the situation becomes.&lt;/li&gt;
&lt;li&gt;Criteria objects are very cumbersome to mock in unit tests.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Approach #3: standard Doctrine repositories with custom &lt;code&gt;findBy*&lt;/code&gt; methods
&lt;/h2&gt;

&lt;p&gt;At some point we decided that complexity was too much, and we started implementing our &lt;code&gt;findBy*&lt;/code&gt;/&lt;code&gt;countBy*&lt;/code&gt; methods, one for each combination of filters we needed to fulfill our use cases.&lt;br&gt;
Each of these methods constructs its own query builder, applies its filtering logic and produces a specific result. These methods can be parametrizable as for the relationship that have to be joined in advance, plus all the various possible orderings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/slope-it/entity-repositories-for-humans/tree/approach-3"&gt;branch &lt;code&gt;approach-3&lt;/code&gt;&lt;/a&gt; shows this last intermediate approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very simple to use for client classes, because they just need a simple method call with just a few parameters (often just one).&lt;/li&gt;
&lt;li&gt;Mocking these methods in unit tests is very simple, a one-liner in most cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every method is extremely specific and does not scale well when complexity increases. We often ended up with dozens of &lt;code&gt;findBy*&lt;/code&gt; methods per repository, due to the combinatorial explosion of needed filters.&lt;/li&gt;
&lt;li&gt;As every method composes its own query builder, this pattern inevitably causes code duplication in repositories, especially if you are not diligent enough to factor common filtering logic across multiple methods.&lt;/li&gt;
&lt;li&gt;During development, when you have to add a new use case, you are often in doubt when deciding whether to complicate an existing method maybe with additional parameters, or to write a new method from scratch.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  There must be a better way!
&lt;/h2&gt;

&lt;p&gt;None of the approaches described above felt "good": the cons were always prevailing on the pros. We wanted something better, ideally based on this wishlist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repositories should be classes with "clean" interfaces (i.e. no Doctrine specific public methods)&lt;/li&gt;
&lt;li&gt;They should be dead simple to use (and to mock), ideally with just a single method call (no chaining allowed!) and a single parameter.&lt;/li&gt;
&lt;li&gt;They should guarantee complete shielding of storage/implementation details of entities: the query builder must never leave the repositories, and Doctrine Criteria are not accepted. This way, when changing internal details of an entity, you just need to change code inside its repository and not in other classe.&lt;/li&gt;
&lt;li&gt;You should not need to implement a new method every time you add a new filter to an existing entity. Adding parameters to existing methods is not acceptable either, because it complicates the interfaces and scales poorly as the filter combinations grow.&lt;/li&gt;
&lt;li&gt;There should be a handy way of prefetching relationships (to avoid &lt;a href="https://thecodingmachine.io/solving-n-plus-1-problem-in-orms"&gt;the infamous N+1 problem&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Possibility of paginating results without changing how client classes query the repository.&lt;/li&gt;
&lt;li&gt;Support for composition via subqueries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time and after various contributions by many team members, we got to define a pretty good pattern that checked all the items in the checklist above.&lt;/p&gt;

&lt;p&gt;We are happy to share the results, hoping that it can be useful for others as well. For this purpose, we created a &lt;a href="https://github.com/slope-it/entity-repositories-for-humans"&gt;GitHub repository&lt;/a&gt; with a living example of it, that will evolve when we publish more article on this series.&lt;/p&gt;

&lt;p&gt;Before you ask: the example in our repository is based on a simple domain for managing a video rental shop. You know, we're just a bunch of nostalgic millennials.&lt;/p&gt;


  &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l6O99lLO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.gifer.com/7Fnb.gif" width="480" height="270"&gt;Disclaimer: Running a movie rental shop in 2024 may not be the best business idea.
  



&lt;h2&gt;
  
  
  Introducing the “entity query”
&lt;/h2&gt;

&lt;p&gt;The fundamental idea behind this solution is that each repository has a "companion" class, which we will call &lt;em&gt;entity query&lt;/em&gt;.&lt;br&gt;
Entity queries are in fact simple DTOs that must be instantiated providing filters by classes that need to fetch entities from repositories.&lt;/p&gt;

&lt;p&gt;They are convenient to create because every filter has its named parameter in the query constructor. All of them are nullable: this means that regardless of the combination of filters you want to apply, you can always build an entity query inline and specify only the filters that you are interested in, in any order you want.&lt;/p&gt;

&lt;p&gt;Conceptually speaking, all the filters are applied in logical AND with each other to produce the result set. This is a pretty strong simplification, but it works well for us because we still manage to cover most of our typical use cases with it.&lt;/p&gt;

&lt;p&gt;I like to say that a few lines of code are better than a thousand words, so here are excerpts of the Rental entity and its entity query present in the &lt;code&gt;master&lt;/code&gt; branch of the &lt;a href="https://github.com/slope-it/entity-repositories-for-humans"&gt;GitHub repository&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="na"&gt;#[ORM\Entity]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rental&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Id]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\GeneratedValue(strategy: 'NONE')]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Column('id', type: 'guid')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\ManyToOne(targetEntity: Movie::class)]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\JoinColumn('movie_id')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Movie&lt;/span&gt; &lt;span class="nv"&gt;$movie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\ManyToOne(targetEntity: Customer::class)]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\JoinColumn('customer_id')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Customer&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\Column('rent_date', type: 'datetimetz_immutable')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$rentDate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[ORM\Column('return_date', type: 'datetimetz_immutable', nullable: true)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$returnDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RentalQuery&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractEntityQuery&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;ORDER_BY_RENT_DATE_DESC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'RENT_DATE_DESC'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?Movie&lt;/span&gt; &lt;span class="nv"&gt;$movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?Customer&lt;/span&gt; &lt;span class="nv"&gt;$customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$rentDateGreaterThan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$rentDateLessThanOrEqualTo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?bool&lt;/span&gt; &lt;span class="nv"&gt;$returned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$orderBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderBy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here are a few examples of how this entity query can be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;executeForCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RentalQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$aCustomer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;executeForManyResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RentalQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;rentDateGreaterThan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'8 days ago'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;returned&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$repository&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;executeForFirstOrNullResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RentalQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$aMovie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;orderBy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;RentalQuery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ORDER_BY_RENT_DATE_DESC&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The public interface of an entity repository
&lt;/h2&gt;

&lt;p&gt;Repositories feature a set of method that work with entity queries plus some other methods that perform unfiltered counts and fetches, as well as fetches by id.&lt;br&gt;
Note that the same entity query can be used both to get the results hydrated as entities and to get their counts. Here is an excerpt from the public interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Base class for entity repositories.
 * @template E of object
 * @template Q of AbstractEntityQuery
 */&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractEntityRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Returns a count of all entities.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;countAll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Executes the entity query returning the count of matching results.
     *
     * @param Q $entityQuery
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;executeForCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AbstractEntityQuery&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Executes the entity query returning only the first result (or null if no results found).
     *
     * @param Q $entityQuery
     * @return ?E
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;executeForFirstOrNullResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AbstractEntityQuery&lt;/span&gt; &lt;span class="nv"&gt;$entityQuery&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Executes the entity query returning many results (optionally limited using $limit parameter).
     *
     * @param Q $entityQuery
     * @return E[]
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;executeForManyResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AbstractEntityQuery&lt;/span&gt; &lt;span class="nv"&gt;$entityQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Executes the entity query expecting zero or one result.
     *
     * @param Q $entityQuery
     * @return ?E
     * @throws NonUniqueResultException If more than one entity matches the query.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;executeForOneOrNullResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AbstractEntityQuery&lt;/span&gt; &lt;span class="nv"&gt;$entityQuery&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Executes the entity query expecting exactly one result.
     *
     * @param Q $entityQuery
     * @return E
     * @throws NonUniqueResultException If more than one entity matches the query.
     * @throws NoResultException If no entity matching the query is found.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;executeForSingleResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;AbstractEntityQuery&lt;/span&gt; &lt;span class="nv"&gt;$entityQuery&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Returns all entities.
     *
     * @return E[]
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Returns a single entity by ID.
     *
     * @return E
     * @throws \Doctrine\ORM\NoResultException
     * @throws \Doctrine\ORM\NonUniqueResultException
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findByID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Returns multiple entities by their IDs.
     *
     * @param string[] $ids
     * @return E[]
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findByIDs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important to note that concrete repositories do not need to define new public methods to fetch entities: they just need to implement their own logic to configure the Doctrine query builder based on the provided entity query. Here's the code fragment that does this for &lt;code&gt;RentalRepository&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @extends AbstractEntityRepository&amp;lt;Rental, RentalQuery&amp;gt;
 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RentalRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractEntityRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;configureBuilderFromEntityQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;QueryBuilder&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;AbstractEntityQuery&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;QueryBuilder&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/* Validation */&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateLessThanOrEqualTo&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Assertion&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateGreaterThan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateLessThanOrEqualTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'rentDateGreaterThan cannot be greater than rentDateLessThanOrEqualTo.'&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="cm"&gt;/* Filtering */&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.movie = :movie'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'movie'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.customer = :customer'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateLessThanOrEqualTo&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.rentDate &amp;lt;= :rentDateLessThanOrEqualTo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rentDateLessThanOrEqualTo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateLessThanOrEqualTo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateGreaterThan&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.rentDate &amp;gt; :rentDateGreaterThan'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setParameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rentDateGreaterThan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rentDateGreaterThan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;returned&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.returnDate IS NOT NULL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;returned&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.returnDate IS NULL'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="cm"&gt;/* Ordering */&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;orderBy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$orderBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orderBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nc"&gt;RentalQuery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ORDER_BY_RENT_DATE_DESC&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                    &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addOrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'e.rentDate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DESC'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Unknown order by: &lt;/span&gt;&lt;span class="nv"&gt;$orderBy&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;


        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The greatest limitation of this approach is that, as already mentioned, all filters work with each other only in an AND fashion. So, if you set two properties on an entity query, only entities that match both filters will be returned.&lt;/p&gt;

&lt;p&gt;However, it is always possible to overcome this limitation by defining "virtual" properties that correspond to more complex filters to be implemented in the repository (for example, two properties in OR). We will discuss this in detail in a future post.&lt;/p&gt;

&lt;p&gt;Another limitation is that if you need to filter on a property using N different operators, you must define N different filters on the same property.&lt;br&gt;
Looking at the code above, &lt;code&gt;$rentDateGreaterThan&lt;/code&gt; and &lt;code&gt;$rentDateLessOrEqualTo&lt;/code&gt; are an example of this limitation.&lt;br&gt;
We believe that this is a reasonable price to pay for not having to complicate the construction of the query too much. In case this is a problem, though, it is still possible to implement "operator" classes to use in entity queries. We are not currently doing this, but as this is a possible evolution we might talk about this in a future post.&lt;/p&gt;




&lt;h2&gt;
  
  
  There’s more to come!
&lt;/h2&gt;

&lt;p&gt;The approach just described here is just a simplified version of the one we actually use in production at Slope.&lt;/p&gt;

&lt;p&gt;There are still many interesting missing pieces, which we planned to add and describe in future articles. In the next episodes we will talk about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filters with virtual properties&lt;/li&gt;
&lt;li&gt;Filters based on associated entities&lt;/li&gt;
&lt;li&gt;Support for multi-tenancy&lt;/li&gt;
&lt;li&gt;Prefetching associations&lt;/li&gt;
&lt;li&gt;Pagination of results&lt;/li&gt;
&lt;li&gt;Subqueries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure to star the &lt;a href="https://github.com/slope-it/entity-repositories-for-humans"&gt;GitHub repository&lt;/a&gt; to not miss the updates we will make as new articles in this series get published.&lt;/p&gt;

&lt;p&gt;We are eager to hear your opinions. Feel free to contact us (&lt;code&gt;engineering &amp;lt;at&amp;gt; slope.it&lt;/code&gt;), via Github or by leaving comments below!&lt;/p&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>A library-less approach to fixtures in PHP tests</title>
      <dc:creator>Andrea Sprega</dc:creator>
      <pubDate>Sat, 08 Jan 2022 12:30:09 +0000</pubDate>
      <link>https://dev.to/andreasprega/a-library-less-approach-to-fixtures-in-php-tests-54li</link>
      <guid>https://dev.to/andreasprega/a-library-less-approach-to-fixtures-in-php-tests-54li</guid>
      <description>&lt;p&gt;At &lt;a href="https://dev.to/andreasprega/clockmock-a-convenient-way-to-mock-date-and-time-in-php-5032#:~:text=this%20problem%20at-,Slope,-.%20For%20doing%20this"&gt;Slope&lt;/a&gt;, writing tests is a relevant part of our day to day work. Having to maintain many integrations and end to end (functional) tests, we are continuously required to create and manage &lt;em&gt;fixtures&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For those who are not familiar with fixtures: quite simply, &lt;strong&gt;they are stable, pre-defined sets of data that you persist to a database and then use to run a specific test&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
   State of the art (and why we questioned it)
&lt;/h2&gt;

&lt;p&gt;There are several great open source libraries that can help you doing this job. We tried out some of them, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/doctrine/data-fixtures"&gt;Doctrine Data Fixtures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nelmio/alice"&gt;Alice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fzaninotto/Faker"&gt;Faker&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They all work well for a lot of use cases, but we found ourselves always having to write too much code for preparing fixtures. Moreover, most of these libraries have their own unique ways to share, compose and reuse fixtures. In most cases it's reference-based (i.e. based on arbitrary string keys), and this makes reusing and composing these fixtures harder than it has to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We often had the impression there was too much over-engineering over this task, which instead could be simpler.&lt;/strong&gt;&lt;br&gt;
That's why we came up with our own (almost) vanilla approach, based on a few design goals. We will see actual code in a few minutes, but before let's see what that "design wishlist" looked like.&lt;/p&gt;


&lt;h2&gt;
  
  
  Design goals for our ideal fixture system
&lt;/h2&gt;

&lt;p&gt;In the next few lines, I'm going to enumerate and briefly explain the design goals that we kept in consideration while building our fixture system. Note that these are only based on our experience -- your mileage may vary.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fixtures should have stable defaults
&lt;/h3&gt;

&lt;p&gt;Fixtures are not seeds! As long as you want stable, reproducible tests, you should rely on static defaults for your fixtures. You don't want your default user name to change pseudo-randomly across your tests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Simple fixtures should require little code
&lt;/h3&gt;

&lt;p&gt;If you just need some placeholder entities and you don't care what their properties are, creating them should require very little coding effort -- ideally, one line per fixture.&lt;/p&gt;
&lt;h3&gt;
  
  
  It should be clear which defaults you are overriding
&lt;/h3&gt;

&lt;p&gt;Just by looking at the fixture code, it should be obvious which entity property defaults you are overriding. In other words, if your user email needs to be &lt;code&gt;giuseppeverdi@gmail.com&lt;/code&gt; instead of the default &lt;code&gt;mariorossi@gmail.com&lt;/code&gt;, you should be able to do that without having to re-define all the other properties for the &lt;code&gt;User&lt;/code&gt; fixture, like for example first or last name.&lt;br&gt;
While being convenient, this is also great because &lt;strong&gt;it highlights that specific property and value as the sole pre-condition of your test&lt;/strong&gt;. If you had to also define a bunch of other properties just to "make the fixture work", then you would lose this kind of clarity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Every test should have its own fixture
&lt;/h3&gt;

&lt;p&gt;Even though most libraries suggest to create fixtures as a reusable, pre-defined group of related entities, in most cases we did not actually want that.&lt;br&gt;
We found out that by avoiding shared fixtures between tests, our test suite became simpler, more stable and more maintainable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fixture code should be inline with test case
&lt;/h3&gt;

&lt;p&gt;A natural consequence of the previous principle is that &lt;strong&gt;code for creating a fixture is written directly in the body of the test case&lt;/strong&gt;. This is great because you can clearly see the connection between your fixtures, the test actions and the assertions. This means that, at a glance, you can understand what's going on without looking anywhere else in your codebase.&lt;/p&gt;
&lt;h3&gt;
  
  
   Fixtures should be easily composable
&lt;/h3&gt;

&lt;p&gt;As we don't want to reuse pre-defined group fixtures, we need a way to compose single entity fixtures that is not cumbersome and can be done "on-the-fly", directly in the test case where we need that.&lt;br&gt;
We figured out we could achieve that simply by using objects returned by the fixture class itself. The price to pay for this is that a fixture class can only create one object at a time (and that's perfectly reasonable in our case, as we don't need a ton of data for our tests).&lt;/p&gt;
&lt;h3&gt;
  
  
  Fixtures should be able to reuse business logic when needed
&lt;/h3&gt;

&lt;p&gt;In case creating a specific entity involves some business logic that lives outside of the entity class itself, it should be possible to leverage your services while creating a fixture, so that you can enforce your logic without having to duplicate it.&lt;/p&gt;


&lt;h2&gt;
  
  
   Our approach to fixtures
&lt;/h2&gt;

&lt;p&gt;Enough talking, let's jump right into some code that shows an example of what we came up with.&lt;/p&gt;

&lt;p&gt;We use Symfony and Doctrine, so you will see there are dependencies on these two projects. The important thing, though, is the approach: you can adapt the provided samples to your ORM (and framework) of choice.&lt;/p&gt;
&lt;h3&gt;
  
  
  The FixtureManager
&lt;/h3&gt;

&lt;p&gt;The starting point of all of this is a FixtureManager: basically a convenience class with a bunch of static methods that provides persistence capabilities and an accessible service container to fixture classes.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I know what you're thinking: we're not fans of hardcoded dependencies via static methods either. However, since this is just testing code, we think it's best to favor convenience over everything else. After all, this is not code we have to test.&lt;/p&gt;

&lt;h3&gt;
  
  
   Fixture classes
&lt;/h3&gt;

&lt;p&gt;Then, we create a class for every entity we need fixtures for. Here I'm showing just one of them (others are similar). Actual entity classes are not shown because they are pretty simple and not relevant to this example -- it's just constructor and basic setters and getters.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The important part of it is the &lt;code&gt;create&lt;/code&gt; method signature: it should be different for each fixture class, and should get as parameters:&lt;/p&gt;

&lt;p&gt;1) All the required ones first: we try to avoid them as much as possible, but sometimes they are needed. For example, a &lt;code&gt;User&lt;/code&gt; always requires to specify a &lt;code&gt;Tenant&lt;/code&gt; as it's not convenient to just create one if not provided. Reason is we almost always reuse the same tenant for a number of entities in the same test (in this example, &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;UserGroup&lt;/code&gt;).&lt;br&gt;
2) A &lt;code&gt;$fields&lt;/code&gt; parameter, which is just an associative array which shape differs fixture from fixture, and can be used to override the default fixture values.&lt;/p&gt;

&lt;p&gt;Most of the convenience of this approach is based on Symfony's &lt;a href="https://symfony.com/doc/current/components/options_resolver.html"&gt;OptionsResolver&lt;/a&gt;. If you don't know it, I suggest to take a look at the documentation but assuming you don't want to switch right now, let me summarize it for you: &lt;code&gt;OptionsResolver&lt;/code&gt; is &lt;code&gt;array_replace&lt;/code&gt; on steroids, that allows you to produce an array by using a set of defaults as a basis plus the ones you specify either statically or dynamically (i.e. by executing a closure). It also performs validation of the provided keys, throwing exceptions in case an unknown key is provided.&lt;/p&gt;

&lt;p&gt;This is super useful because you'll get an immediate failure when attempting to use, in the &lt;code&gt;$fields&lt;/code&gt; array, a key that is not defined in the fixture class. This way, you don't have to curse when trying to override a &lt;code&gt;firstName&lt;/code&gt; default by passing it in a &lt;code&gt;firstname&lt;/code&gt; key 🤯.&lt;/p&gt;

&lt;p&gt;It is also super easy to create dynamic defaults. In case of &lt;code&gt;UserFixture&lt;/code&gt;'s &lt;code&gt;'group'&lt;/code&gt; property, a &lt;code&gt;UserGroup&lt;/code&gt; will be created &lt;strong&gt;only in case it's not provided from the outside&lt;/strong&gt;. This because &lt;code&gt;OptionsResolver&lt;/code&gt; only executes the closure if the corresponding value is not already provided.&lt;/p&gt;

&lt;p&gt;If you want to take it to the next level, you can also tell &lt;code&gt;OptionResolver&lt;/code&gt; to validate types of the provided values. In this case I don't think it's worth the extra code as, if you have strongly typed entities (I hope you do!) you will quickly get &lt;code&gt;TypeError&lt;/code&gt;s anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; you can install &lt;a href="https://github.com/symfony/options-resolver"&gt;OptionsResolver&lt;/a&gt; even if you don't use Symfony as full stack framework.&lt;/p&gt;
&lt;h3&gt;
  
  
  An example TestCase class
&lt;/h3&gt;

&lt;p&gt;Note: I'm using three fixtures here just to show how we compose them. I chose not to show snippets of &lt;code&gt;TenantFixture&lt;/code&gt; and &lt;code&gt;UserGroupFixture&lt;/code&gt; as they wouldn't add anything new to the example.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;As you could see from the examples, this is not a library but just a set of classes and conventions that can be applied without many pre-requisites.&lt;br&gt;
This approach is battle-tested: we use it in our codebase for hundreds of test cases and it helped us a lot in keeping the fixtures complexity at a minimum.&lt;/p&gt;

&lt;p&gt;Feel free to copy-paste and tailor these snippets to your codebase.&lt;/p&gt;

&lt;p&gt;What do you think? Any feedback would be greatly appreciated!&lt;/p&gt;

</description>
      <category>php</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>ClockMock: a library to mock date and time in PHP</title>
      <dc:creator>Andrea Sprega</dc:creator>
      <pubDate>Thu, 09 Dec 2021 15:30:16 +0000</pubDate>
      <link>https://dev.to/andreasprega/clockmock-a-convenient-way-to-mock-date-and-time-in-php-5032</link>
      <guid>https://dev.to/andreasprega/clockmock-a-convenient-way-to-mock-date-and-time-in-php-5032</guid>
      <description>&lt;p&gt;If you write tests for your code (I hope you do), at some point you likely needed something to execute them with the system clock “frozen” to a specific date and time. Whoever had this need at least once knows that the matter is anything but trivial.&lt;/p&gt;

&lt;p&gt;With this article, I want to tell the story of how we dealt with this problem at &lt;a href="https://www.slope.it/"&gt;Slope&lt;/a&gt;. For doing this, I will use the following “codebase” (i.e. a pompous way to describe 2 classes — an entity and a service).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Our example codebase, using just vanilla PHP.





&lt;p&gt;I will describe 3 testing scenarios (a, b. and c.) that pretty much cover 100% of our cases/needs. Your mileage may vary, but the same concepts should apply.&lt;/p&gt;

&lt;p&gt;Our journey starts from a situation in which we simply accepted to test the following scenarios in a sub-optimal way — or to not test them at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario a: alter the creation date of an entity when preparing fixtures for integration tests
&lt;/h2&gt;

&lt;p&gt;Our “basic” go-to solution here was reflection, used to artificially modify the date (stored in a private instance property) right after calling the entity constructor. Example:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Scenario a. at the beginning of our story





&lt;h2&gt;
  
  
  Scenario b: test services that behave differently based on periods of the day/month/year
&lt;/h2&gt;

&lt;p&gt;In such situations, we usually avoided to test these services altogether. In some other cases, we modified code to allow passing the current date as a parameter. We did not like making this kind of changes, because at that point you don’t know anymore whether the current date is effectively a parameter even in “production” usage, or it was made that way only for testing.&lt;/p&gt;

&lt;p&gt;I’m not embedding any code for this scenario, because as the beginning it was simply untestable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario c: test services that set a specific date to an object with state, e.g. an entity
&lt;/h2&gt;

&lt;p&gt;In the past, we worked around this by testing that the date set by the service was “close enough” to the current date (e.g. within 500 milliseconds), with the assumption that the assertion was made shortly after SUT code execution.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Scenario c. at the beginning of our story





&lt;p&gt;It’s obvious that this kind of checks makes tests less precise, flaky and subject to failures in slow machines or environments with fluctuating computational resources (like in most CI environments).&lt;/p&gt;




&lt;p&gt;As you are probably thinking, these solutions are pretty far from being ideal. Let’s be honest: they just suck. So, one day we decided we needed to make our codebase a better place and we started looking for alternatives.&lt;/p&gt;

&lt;h1&gt;
  
  
  Our first try: Carbon
&lt;/h1&gt;

&lt;p&gt;As with every refactoring, we started with the lowest hanging fruit (in terms of complexity). We tried &lt;a href="https://carbon.nesbot.com/"&gt;Carbon&lt;/a&gt;, a great library for handling dates that also allows mocking the current date used when you create &lt;code&gt;Carbon|CarbonImmutable&lt;/code&gt; objects. In simple words, you can just do a &lt;code&gt;Carbon::setTestNow($now)&lt;/code&gt; to freeze the current time (or use the stateless counterpart &lt;code&gt;Carbon::withTestNow($now, $closure)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;As you would expect, our codebase needed some changes, and here they are:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Our example codebase, modified to use Carbon.





&lt;p&gt;NOTE: I did not modify the use of DateTimeImmutable in &lt;code&gt;Service::doSomething&lt;/code&gt; method, because I wanted to simulate use of 3rd party code outside of our control that thus cannot be changed to use Carbon.&lt;/p&gt;

&lt;p&gt;After the codebase refactoring, we are immediately able to improve some of our scenarios:&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario a: alter the creation date of an entity when preparing fixtures for integration tests
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Fixture preparation for scenario a. using Carbon





&lt;h2&gt;
  
  
  Scenario b: test services that behave differently based on periods of the day/month/year
&lt;/h2&gt;

&lt;p&gt;Because we decided we could not use Carbon in &lt;code&gt;Service::doSomething&lt;/code&gt;, we still cannot test that scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario c: test services that set a specific date to an object with state, e.g. an entity
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Test for scenario c. using Carbon








&lt;p&gt;Except the refactoring (that takes time, even though Carbon should pretty much be a drop-in replacement of DateTime), it was very easy to configure as it’s just a library you install with &lt;a href="https://getcomposer.org/"&gt;Composer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are a couple of drawbacks with this approach, though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any system function or class/method (e.g. &lt;code&gt;date()&lt;/code&gt;, &lt;code&gt;time()&lt;/code&gt;, &lt;code&gt;DateTime&lt;/code&gt;, …) will still use the actual system clock, so it’s impossible to force code outside of your control to use your mocked date and time.&lt;/li&gt;
&lt;li&gt;Your “business” code (entities and services) will have a hard dependency against &lt;code&gt;Carbon&lt;/code&gt; (instead of sticking with the standard DateTime). This is not necessarily an issue, but it becomes relevant if you, like us, strive to keep exposure of your business logic to 3rd party libraries to the minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  ext/timecop to the (temporary) rescue
&lt;/h1&gt;

&lt;p&gt;Due to the above limitations, at some point we decided we wanted to improve our situation and we came across &lt;a href="https://github.com/hnw/php-timecop"&gt;ext/timecop&lt;/a&gt;. This is basically a fork of its most famous Ruby counterpart, and thus is based on the same concept: mock the system clock via a PHP runtime extension, so that all running code uses it transparently (regardless of the library).&lt;/p&gt;

&lt;p&gt;It’s simple: you invoke &lt;code&gt;\timecop_freeze($date)&lt;/code&gt; in your test code to bring the system clock to that specific moment. You then use &lt;code&gt;\timecop_return()&lt;/code&gt; (still in your test code) when you want to go back to the actual system clock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; from here on, we go back to referring to the initial codebase, the one &lt;em&gt;without&lt;/em&gt; Carbon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario a: alter the creation date of an entity when preparing fixtures for integration tests
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Fixture preparation for scenario a. using timecop





&lt;h2&gt;
  
  
  Scenario b: test services that behave differently based on periods of the day/month/year
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Test for scenario b. using timecop





&lt;h2&gt;
  
  
  Scenario c: test services that set a specific date to an object with state, e.g. an entity
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Test for scenario c. using timecop





&lt;p&gt;Great! We can go back to using stdlib, uninstall Carbon, and still test everything.&lt;/p&gt;

&lt;p&gt;The price you have to pay, is that you need to be able to compile and install a php extension yourself. This might not be possible when you don’t have full control on your environment, like in shared hosting spaces -even though I hope you’re not running production applications there!&lt;/p&gt;

&lt;p&gt;Anyway, we were happy and life was good. At least, &lt;strong&gt;until the day to update to PHP 8.0 came. That day, we found out that ext/timecop would NOT compile anymore.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The day got even worse when, after going to the GitHub repo and looking at the issue tracker, &lt;strong&gt;we realized that the project was abandoned.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Timecop is dead. Long live ClockMock!
&lt;/h1&gt;

&lt;p&gt;We really liked the concept behind timecop, but we were forced to move on if we didn’t want to be stuck with PHP 7.4 forever. As writing and maintaining a PHP extension was outside of our possibilities, we had to find a different solution.&lt;/p&gt;

&lt;p&gt;One day, I stumbled on a not-so-popular PHP extension: &lt;a href="https://github.com/krakjoe/uopz"&gt;ext/uopz&lt;/a&gt;. It allows to modify, at runtime, implementations of methods and functions, including the ones of the standard library. Kudos to its author &lt;a href="https://github.com/krakjoe"&gt;Joe Watkins&lt;/a&gt; and contributors for the amazing job!&lt;/p&gt;

&lt;p&gt;The idea of building a time-mocking library on top of it came pretty naturally -- I immediately hacked together a proof of concept that only dealt with mocking \DateTime instances. The POC was working, and so we gave it a name and we expanded it to cover all the time-related code that PHP provided (with the help of other contributors). ClockMock was born! &lt;/p&gt;

&lt;p&gt;ClockMock does mainly 2 things whenever mocks are activated by the developer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Overwrites implementation of many date and time-related functions with ones that the current time can be manipulated.&lt;/li&gt;
&lt;li&gt;Overwrites implementation of &lt;code&gt;\DateTime&lt;/code&gt; and &lt;code&gt;\DateTimeImmutable&lt;/code&gt; with the ones of mock classes that consider the aforementioned mockable clock.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These implementations are reverted to their original versions whenever mocks are deactivated, so that there are no unintended side effects after using it.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;br&gt;
&lt;strong&gt;DISCLAIMER: due to its “experimental” nature, we recommend to never use ClockMock in production.&lt;/strong&gt;&lt;br&gt;
&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;You can use ClockMock with two different APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stateful one, in which you need to balance inline calls to activate and reset mocks (like timecop)&lt;/li&gt;
&lt;li&gt;a stateless one, that will execute a closure at a specific point in time. It does not have side effects, as the original clock is always automatically restored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see how the 3 scenarios can be rewritten with ClockMock:&lt;/p&gt;
&lt;h2&gt;
  
  
  Scenario a: alter the creation date of an entity when preparing fixtures for integration tests
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Fixture preparation for scenario a. using ClockMock





&lt;h2&gt;
  
  
  Scenario b: test services that behave differently based on periods of the day/month/year
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Test for scenario b. using ClockMock





&lt;h2&gt;
  
  
  Scenario c: test services that set a specific date to an object with state, e.g. an entity
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Test for scenario c. using ClockMock





&lt;h1&gt;
  
  
  Possible alternative: inject a ClockInterface service
&lt;/h1&gt;

&lt;p&gt;You might be thinking that a more “standard” way of solving this problem would be to inject a &lt;code&gt;ClockInterface&lt;/code&gt; service that provides a way to obtain the current time (I also learned there’s a &lt;a href="https://github.com/php-fig/fig-standards/blob/master/proposed/clock-meta.md"&gt;proposed standard&lt;/a&gt; for it). This way, the current time would be mockable like any other service.&lt;/p&gt;

&lt;p&gt;This is definitely a valid approach. Problem is, in rather complex projects (not necessarily legacy) the current time is going to be needed in a wide range of places/classes. Furthermore, sometimes you may even need to get a &lt;code&gt;\DateTime&lt;/code&gt; or a timestamp from a 3rd party library and that would “escape” your &lt;code&gt;ClockInterface&lt;/code&gt; service (in case it’s impossible to make it interoperate with said library).&lt;/p&gt;

&lt;p&gt;While it’s definitely doable to inject a Clock service in anything you manage using a DI container, it’s a lot less pragmatic to do the same for your value objects and/or entities.&lt;/p&gt;

&lt;p&gt;When you need to create &lt;code&gt;\DateTime&lt;/code&gt; objects right inside entities (in constructor or mutator methods) it would be cumbersome having to pass a Clock from the outside every time. Sometimes, use of dates could even be a private implementation detail — having to pass a clock service from the outside would leak this detail unnecessarily and make the public interface more complex.&lt;/p&gt;

&lt;p&gt;In other words, the reason why we built this library is that &lt;strong&gt;we think it’s a good tradeoff to consider the current system time as a “global state”, and we prefer to avoid injecting a service to access it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this reason, I think that this injection-based approach only makes sense when you have valid reasons for doing it &lt;strong&gt;besides&lt;/strong&gt; testing (e.g. very time-sensitive applications for which you need control over subtle details, like &lt;a href="https://itnext.io/as-a-software-developer-why-should-you-care-about-the-monotonic-clock-7d9c8533595c"&gt;monotonic wall time&lt;/a&gt;, &lt;a href="https://docs.ntpsec.org/latest/leapsmear.html"&gt;leap second smearing&lt;/a&gt;, etc…).&lt;/p&gt;

&lt;p&gt;Our application is not time sensitive, we only needed a way to mock the system clock in tests. If your needs are the same and you can afford to use ClockMock, &lt;strong&gt;you can start using it with zero changes to production code.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;As you can see from the examples above, using ClockMock feels pretty much the same as using timecop (with a bonus: the syntactic sugar of &lt;code&gt;executeAtFrozenDateTime&lt;/code&gt;). It works with PHP 8 already, and the good thing is that the uopz extension is well maintained, so ClockMock is here to stay. We encourage you to use it and report any issues or feedback you may have.&lt;/p&gt;

&lt;p&gt;The library is still incomplete, as mocks for some functions are missing. Just let us know if you want to help, contributions are welcome!&lt;/p&gt;

&lt;p&gt;You can find the library &lt;a href="https://github.com/slope-it/clock-mock"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>testing</category>
      <category>tooling</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
