background

January 14, 2022

Magento 2 SearchResult or SearchResults?

Yireo Blog Post

One thing that has confused me for ages is that in your own custom Magento 2 repository, you can make use of a SearchResultInterface (singular) and a SearchResultsInterface (plural) - both work fine, but which one to actually use? A little writing to copy this from brain to blog.

Backgrounds

With a custom repository, you are going to create kind-of-like-a front to your own backend classes - most commonly, a data model (aka Data Transfer Object), a resource model and a collection, but others could be involved as well. The repository class commonly contains methods like getById(), save(), delete() and getList().

The method getList() receives an instance of Magento\Framework\Api\SearchCriteriaInterface (created with a builder) and returns search results. It's those search results that this blog post is about: Should it be an instance of Magento\Framework\Api\SearchResultsInterface or could it be an instance of Magento\Framework\Api\Search\SearchResult\SearchResultInterface?

What does the core do?

The core repositories are actually pretty clear in this: The ProductRepositoryInterface::getList() call returns an instance Magento\Catalog\Api\Data\ProductSearchResultsInterface which extends Magento\Framework\Api\SearchResultsInterface (so the plural version). Another example, the PageRepositoryInterface its getList() method returns Magento\Cms\Api\Data\PageSearchResultsInterface which again extends Magento\Framework\Api\SearchResultsInterface.

As a note: In most cases, the SearchResultsInterface is extended with a custom interface, not to add new functionality but to override the type hints: This makes it easier to extract for instance a listing of products, instead of just a listing of items.

In short, all core repositories implement Magento\Framework\Api\SearchResultsInterface and not Magento\Framework\Api\Search\SearchResult\SearchResultInterface. When creating your own repository, it is best to follow this. However, I made a mistake with this in the past. And it simply worked. Let's see how and why.

But in your own custom repository ...

In your own repository, your own getList() method is probably meant to interpret search criteria, so that the right search results are generated. In the method implementation, usually a new collection and the search criteria are processed by a collection processor. But in the end, normal collection logic flows into a search results object that is then returned:

public function getList(SearchCriteriaInterface $criteria)
{
    $collection = $this->collectionFactory->create();

    $this->collectionProcessor->process($criteria, $collection);

    $searchResults = $this->searchResultsFactory->create();
    $searchResults->setSearchCriteria($criteria);
    $searchResults->setItems($collection->getItems());
    $searchResults->setTotalCount($collection->getSize());
    return $searchResults;
}

The collection is specific to the used entity: Product, category, CMS page or your own entity. And therefore the $searchResults object is containing multiple items of the right entity type. But the $searchResults object is unaware of this: This is where the type hinting (which I mentioned earlier) could come in handy, mainly for ease of use in your favorite IDE (which is PHPStorm).

Implementing SearchResultsInterface

The instance type of $searchResults is determined by the $searchResultsFactory which you inject in your constructor via DI. The DI preference for the SearchResultsInterface is Magento\Framework\Api\SearchResults. The core does not really implement another instance of SearchResultsInterface - there is only one interface and one class implementing that interface.

However, nothing prevents you created another class implementation for that same interface and use that in your own repository.

And now the other one: SearchResultInterface

And this kind of brings me to the other interface in this blog: Magento\Framework\Api\Search\SearchResult\SearchResultInterface extends the discussed SearchResultsInterface. And just like you could inject a factory of the original interface, you could also inject a factory of this specific interface as well.

Well, that's what I did in the past. And it worked just fine.

The SearchResultsInterface interface contains the following methods:

  • getItems()
  • setItems(array $items)
  • getSearchCriteria()
  • setSearchCriteria(SearchCriteriaInterface $searchCriteria)
  • getTotalCount()
  • setTotalCount($totalCount)

On top of this, the Magento\Framework\Api\Search\SearchResult\SearchResultInterface adds two methods:

  • getAggregations()
  • setAggregations($aggregations) (where the argument is an instance of AggregationInterface)

In the past, I simply implemented this method with nothing (not setting something internally, not returning anything either). Or I simply injected the SearchResultInterfaceFactory in my repository to be used in the getList() method.

And it worked, because my repository never called upon those aggregations methods. It simply worked.

What is this SearchResultInterface about?

But why? Why these two interface? And what are aggregations? The secret lies in the full namespace of Magento\Framework\Api\Search\SearchResult\SearchResultInterface: The Magento framework is dealing with many many different things. One of those things is allowing a repository to search and another is to allow a search engine to be used. Both use the ambiguous word search.

In the second case, it is referring to a search engine, like for instance the default ElasticSearch engine that Magento 2.4 requires. Zooming into the AggregationInterface, we see terminology buckets, documents and aggregations: Terminology that is closely related to ElasticSearch.

The generic namespace Magento\Framework\Api\Search for a non-generic approach

This on itself is already confusing to me: The namespace Magento\Framework\Api\Search suggests functionality that would fit any search engine (ElasticSearch, SOLR, Sphinx, Algolia, etc). However, as of yet, the code of all of this is only oriented towards ElasticSearch, it is not fitting the other engines. This is a pity. But it is not the focus of this post.

The focus is instead that apparently you can implement this SearchResultInterface and use it as if it is SearchResultsInterface. It works, but it feels wrong. The SearchResultInterface should only be use for search results in the context of a search engine, not in a repository.

Conclusion

When dealing with repositories, use SearchResultsInterface. The only reason you should touch upon SearchResultInterface is when you're dealing with ElasticSearch or another search engine.

To me, this entire story shows a nasty choice of naming classes: On their own, the namespaces seem fine but when layed down besides each other, they are just way to similar. The main evil here is the word search that is used to refer to repository search and external search engines. Renaming \Search\ to \SearchEngine\ would have made more sense to me.

It is also weird (to say the least) that the parent interface refers to multiple search results (SearchResultsInterface) while the child interface that extends it refers to a single search result SearchResultInterface) while it actually returns multiple results.

Brain off, blog closed. Hope you find it fun and useful.

Posted on January 14, 2022

About the author

Author Jisse Reitsma

Jisse Reitsma is the founder of Yireo, extension developer, developer trainer and 3x Magento Master. His passion is for technology and open source. And he loves talking as well.

Sponsor Yireo

Looking for a training in-house?

Let's get to it!

We don't write too commercial stuff, we focus on the technology (which we love) and we regularly come up with innovative solutions. Via our newsletter, you can keep yourself up to date on all of this coolness. Subscribing only takes seconds.

Do not miss out on what we say

This will be the most interesting spam you have ever read

We don't write too commercial stuff, we focus on the technology (which we love) and we regularly come up with innovative solutions. Via our newsletter, you can keep yourself up to date on all of this coolness. Subscribing only takes seconds.