Repositories
A repository is a class through which the rest of the code saves, finds and deletes records. It hides the model, the resource model and the collection behind a few clear methods. A repository is the second part of a service contract: its interface lives in the Api folder and the implementation in Model.
app/code/Acme/Hello/
├── Api/
│ ├── Data/ReviewInterface.php
│ └── ReviewRepositoryInterface.php new file
├── Model/
│ └── ReviewRepository.php new file
└── etc/
└── di.xml changedThe interface
// app/code/Acme/Hello/Api/ReviewRepositoryInterface.php
namespace Acme\Hello\Api;
use Acme\Hello\Api\Data\ReviewInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
interface ReviewRepositoryInterface
{
/**
* @param \Acme\Hello\Api\Data\ReviewInterface $review
* @return \Acme\Hello\Api\Data\ReviewInterface
*/
public function save(ReviewInterface $review): ReviewInterface;
/**
* @param int $id
* @return \Acme\Hello\Api\Data\ReviewInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getById(int $id): ReviewInterface;
/**
* @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
* @return \Magento\Framework\Api\SearchResultsInterface
*/
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface;
/**
* @param int $id
* @return bool
*/
public function deleteById(int $id): bool;
}This set of four methods is the convention in all core repositories. Types in comments are written with the full class name.
The implementation
// app/code/Acme/Hello/Model/ReviewRepository.php
namespace Acme\Hello\Model;
use Acme\Hello\Api\Data\ReviewInterface;
use Acme\Hello\Api\ReviewRepositoryInterface;
use Acme\Hello\Model\ResourceModel\Review as ReviewResource;
use Acme\Hello\Model\ResourceModel\Review\CollectionFactory;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
use Magento\Framework\Api\SearchResultsInterfaceFactory;
use Magento\Framework\Exception\NoSuchEntityException;
class ReviewRepository implements ReviewRepositoryInterface
{
public function __construct(
private ReviewFactory $reviewFactory,
private ReviewResource $resource,
private CollectionFactory $collectionFactory,
private CollectionProcessorInterface $collectionProcessor,
private SearchResultsInterfaceFactory $searchResultsFactory,
) {}
public function save(ReviewInterface $review): ReviewInterface
{
$this->resource->save($review);
return $review;
}
public function getById(int $id): ReviewInterface
{
$review = $this->reviewFactory->create();
$this->resource->load($review, $id);
if (!$review->getId()) {
throw new NoSuchEntityException(__('Review %1 was not found.', $id));
}
return $review;
}
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
{
$collection = $this->collectionFactory->create();
$this->collectionProcessor->process($searchCriteria, $collection);
$results = $this->searchResultsFactory->create();
$results->setSearchCriteria($searchCriteria);
$results->setItems($collection->getItems());
$results->setTotalCount($collection->getSize());
return $results;
}
public function deleteById(int $id): bool
{
$this->resource->delete($this->getById($id));
return true;
}
}The repository uses the three classes from the models lesson. The core CollectionProcessorInterface object applies search conditions to the collection: filters, sorting and the page number.
Bind the interface to the class in etc/di.xml and run bin/magento cache:clean:
<!-- app/code/Acme/Hello/etc/di.xml -->
<preference for="Acme\Hello\Api\ReviewRepositoryInterface" type="Acme\Hello\Model\ReviewRepository"/>Searching with SearchCriteria
Conditions for getList() are assembled by SearchCriteriaBuilder. Let's rewrite the view model with the latest reviews:
// app/code/Acme/Hello/ViewModel/LatestReviews.php
namespace Acme\Hello\ViewModel;
use Acme\Hello\Api\ReviewRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\View\Element\Block\ArgumentInterface;
class LatestReviews implements ArgumentInterface
{
public function __construct(
private ReviewRepositoryInterface $reviewRepository,
private SearchCriteriaBuilder $criteriaBuilder,
) {}
public function getReviews(): array
{
$criteria = $this->criteriaBuilder
->addFilter('rating', 4, 'gteq')
->setPageSize(5)
->create();
return $this->reviewRepository->getList($criteria)->getItems();
}
}The /hello page shows the same list, but the view model no longer knows about collections and tables. The third argument of addFilter() is a condition from the table in the previous lesson; the default is eq.
Common mistake
A repository was created, but the rest of the code keeps calling the resource model and the collection directly. Then plugins and checks that other modules attach to ReviewRepositoryInterface do not run. Outside the repository, work with records only through its interface.