As your application grows, the methods of your repositories start to accumulate. The more methods, the harder it gets to decorate them with additional features. Looking at these methods you might find topics that will help you extract sets of them into cute queries. It's easier to decorate a class with three methods to add a caching layer, than a whole repository. It will also help in making your application more maintainable.

A case of recipe indexing

At HelloFresh, I use Elasticsearch to store projected recipes that are ready to be presented—they are rendered as JSON during the indexing. I (re)index recipes, or remove them from the index, when something happens to them or the menus using them. The most important thing is that only recipes used by an active (published) menu can be part of the index, that's because it is used by our customers. Thus, when I index a recipe I need to know if it's used by active menus, and I use them to enrich the recipe data. All is well when I'm indexing a couple of recipes, but things start to get intense when I index a whole country, which happens from time to time.

I'm using a repository to query active menus, and in order to improve performance my idea was to decorate that repository with a caching layer to reuse data while indexing a whole country, avoiding many trips to the database. The problem decorating that repository is passing all these other methods I don't care about. It's tedious and I would have to update it every time the interface of the repository changes. In brief, its surface is too large, it's time to break it down.

I've already extracted most of my queries from my repositories, so it was another opportunity to continue this effort in separating concerns.

Extracting active menu queries from the menu repository

Consider the following interface. I'm not displaying the EntityRepository interface because it's not really relevant, let's just say it as a few methods of its own:

<?php

// …

interface MenuRepository extends EntityRepository
{
    /**
     * @param Recipe $recipe
     *
     * @return bool
     */
    public function activeMenuForRecipeExists(Recipe $recipe): bool;

    /**
     * @param Country $country
     *
     * @return Menu[]
     */
    public function findActiveMenusByCountry(Country $country): array;

    /**
     * @param Recipe $recipe
     *
     * @return Menu[]
     */
    public function findActiveMenusByRecipe(Recipe $recipe): array;
}

It's pretty easy to spot that something is going on with active menus, let's extract these methods to an ActiveMenuQueries interface:

<?php

// …

interface ActiveMenuQueries
{
    /**
     * @param Country $country
     *
     * @return Menu[]
     */
    public function findByCountry(Country $country): array;

    /**
     * @param Recipe $recipe
     *
     * @return Menu[]
     */
    public function findByRecipe(Recipe $recipe): array;

    /**
     * @param Recipe $recipe
     *
     * @return bool
     */
    public function existsForRecipe(Recipe $recipe): bool;
}

Because the queries are about active menus, it allow us to rename the methods nicely, and because they are focused on a single topic it's gonna be super easy to write a decorator implementing a caching layer… but that's for another article.

tl;dr

Extract query methods that share a same topic from your repository so you have a focused class that you can easily decorate to your heart's content.