Repository Interface
Repository Pattern
Repositories provide a clean API for data access. They hide database details and expose a contract that can be consumed via API or internal code.
Standard Repository Interface
<?php
namespace Amazon\Prep\Api;
use Amazon\Prep\Api\Data\WarrantyInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
class WarrantyRepositoryInterface
{
public function save(WarrantyInterface $warranty): WarrantyInterface;
public function get(int $id): WarrantyInterface;
public function delete(WarrantyInterface $warranty): bool;
public function deleteById(int $id): bool;
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface;
}
Data Interface
<?php
namespace Amazon\Prep\Api\Data;
interface WarrantyInterface
{
const ID = 'warranty_id';
const NAME = 'name';
const DURATION = 'duration';
const STATUS = 'status';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
public function getId(): ?int;
public function getName(): ?string;
public function getDuration(): ?int;
public function getStatus(): ?string;
public function getCreatedAt(): ?string;
public function getUpdatedAt(): ?string;
public function setId(?int $id): WarrantyInterface;
public function setName(?string $name): WarrantyInterface;
public function setDuration(?int $duration): WarrantyInterface;
public function setStatus(?string $status): WarrantyInterface;
public function setCreatedAt(?string $createdAt): WarrantyInterface;
public function setUpdatedAt(?string $updatedAt): WarrantyInterface;
}
di.xml Binding
<preference for="Amazon\Prep\Api\WarrantyRepositoryInterface"
type="Amazon\Prep\Model\WarrantyRepository"/>
Repository Implementation
Complete Repository Class
<?php
namespace Amazon\Prep\Model;
use Amazon\Prep\Api\WarrantyRepositoryInterface;
use Amazon\Prep\Api\Data\WarrantyInterface;
use Amazon\Prep\Model\WarrantyFactory;
use Amazon\Prep\Model\ResourceModel\Warranty as WarrantyResource;
use Amazon\Prep\Model\ResourceModel\Warranty\CollectionFactory;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterfaceFactory;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
class WarrantyRepository implements WarrantyRepositoryInterface
{
public function __construct(
private WarrantyFactory $warrantyFactory,
private WarrantyResource $resource,
private CollectionFactory $collectionFactory,
private SearchResultsInterfaceFactory $searchResultsFactory
) {}
public function save(WarrantyInterface $warranty): WarrantyInterface
{
try {
$this->resource->save($warranty);
} catch (\Exception $e) {
throw new CouldNotSaveException(
__('Could not save warranty: %1', $e->getMessage()),
$e
);
}
return $warranty;
}
public function get(int $id): WarrantyInterface
{
$warranty = $this->warrantyFactory->create();
$this->resource->load($warranty, $id);
if (!$warranty->getId()) {
throw new NoSuchEntityException(
__('Warranty with ID %1 not found', $id)
);
}
return $warranty;
}
public function delete(WarrantyInterface $warranty): bool
{
try {
$this->resource->delete($warranty);
} catch (\Exception $e) {
throw new CouldNotDeleteException(
__('Could not delete warranty: %1', $e->getMessage()),
$e
);
}
return true;
}
public function deleteById(int $id): bool
{
return $this->delete($this->get($id));
}
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
{
$collection = $this->collectionFactory->create();
$this->addSearchCriteriaToCollection($searchCriteria, $collection);
$searchResults = $this->searchResultsFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
$searchResults->setItems($collection->getItems());
$searchResults->setTotalCount($collection->getSize());
return $searchResults;
}
private function addSearchCriteriaToCollection(
SearchCriteriaInterface $searchCriteria,
\Amazon\Prep\Model\ResourceModel\Warranty\Collection $collection
): void {
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
$conditions = [];
foreach ($filterGroup->getFilters() as $filter) {
$conditions[] = [
'attribute' => $filter->getField(),
$filter->getConditionType() => $filter->getValue(),
];
}
if (!empty($conditions)) {
$collection->addFieldToFilter($conditions);
}
}
if ($searchCriteria->getSortOrders()) {
foreach ($searchCriteria->getSortOrders() as $sortOrder) {
$collection->addOrder(
$sortOrder->getField(),
[$sortOrder->getDirection()]
);
}
}
$collection->setPageSize($searchCriteria->getPageSize() ?: 20);
$collection->setCurPage($searchCriteria->getCurrentPage() ?: 1);
}
}
SearchCriteria
SearchCriteria Overview
SearchCriteria is Magento's standard way to build queries. It's used by repositories and the API.
Creating SearchCriteria
use Magento\Framework\Api\SearchCriteriaBuilder;
// Using the builder
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('status', 'active')
->addFilter('duration', 12, 'gteq')
->setSortOrders([
$this->sortOrderBuilder
->setField('created_at')
->setDirection('DESC')
->create()
])
->setPageSize(10)
->setCurrentPage(1)
->create();
$warranties = $this->warrantyRepository->getList($searchCriteria);
Filter Conditions
| Condition | Meaning |
|---|---|
| eq | Equal |
| neq | Not equal |
| gteq | Greater than or equal |
| lteq | Less than or equal |
| gt | Greater than |
| lt | Less than |
| like | SQL LIKE |
| in | In array |
| nin | Not in array |
| null | Is null |
| nnull | Is not null |
| to | Less than or equal (date) |
| from | Greater than or equal (date) |
SearchResults
$searchResults = $this->warrantyRepository->getList($searchCriteria);
// Access results
$items = $searchResults->getItems(); // Array of WarrantyInterface
$totalCount = $searchResults->getTotalCount(); // Total matching records
// Access individual warranty
foreach ($items as $warranty) {
echo $warranty->getName();
echo $warranty->getDuration();
}
API Usage
SearchCriteria works with REST API:
GET /rest/V1/warranty?
searchCriteria[filterGroups][0][filters][0][field]=status&
searchCriteria[filterGroups][0][filters][0][value]=active&
searchCriteria[sortOrders][0][field]=created_at&
searchCriteria[sortOrders][0][direction]=DESC&
searchCriteria[pageSize]=10&
searchCriteria[currentPage]=1
Repository Best Practices
Custom Repository Methods
Add domain-specific methods:
class WarrantyRepository implements WarrantyRepositoryInterface
{
public function getBySku(string $sku): WarrantyInterface
{
$collection = $this->collectionFactory->create();
$collection->addFieldToFilter('sku', $sku)->setPageSize(1);
$items = $collection->getItems();
if (empty($items)) {
throw new NoSuchEntityException(
__('Warranty with SKU %1 not found', $sku)
);
}
return reset($items);
}
public function getActiveWarranties(): array
{
$collection = $this->collectionFactory->create();
$collection->addFieldToFilter('status', 'active');
return $collection->getItems();
}
}
Error Handling
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\NoSuchEntityException;
// In repository methods
public function save(WarrantyInterface $warranty): WarrantyInterface
{
try {
$this->validate($warranty);
$this->resource->save($warranty);
} catch (\Exception $e) {
throw new CouldNotSaveException(
__('Could not save warranty: %1', $e->getMessage()),
$e
);
}
return $warranty;
}
Testing Repositories
use PHPUnit\Framework\TestCase;
class WarrantyRepositoryTest extends TestCase
{
public function testGetReturnsWarranty(): void
{
$warranty = $this->createMock(WarrantyInterface::class);
$warranty->method('getId')->willReturn(1);
$factory = $this->createMock(WarrantyFactory::class);
$factory->method('create')->willReturn($warranty);
$resource = $this->createMock(WarrantyResource::class);
$resource->method('load')->willReturnCallback(
fn($model, $id) => $model->setId($id)
);
$repo = new WarrantyRepository(
$factory,
$resource,
$this->createMock(CollectionFactory::class),
$this->createMock(SearchResultsInterfaceFactory::class)
);
$result = $repo->get(1);
$this->assertEquals(1, $result->getId());
}
}
Quiz
1. What does a repository provide?
2. What does SearchCriteria control?
3. What exception should be thrown when a record is not found?
Flashcards
Question
What does a repository implement?
Click to reveal answer
Answer
RepositoryInterface with save(), get(), delete(), getList() methods
Question
What is SearchCriteria?
Click to reveal answer
Answer
A standard query builder for filtering, sorting, and pagination
Question
What does getList() return?
Click to reveal answer
Answer
SearchResultsInterface with items and total count
Question
What exception for missing records?
Click to reveal answer
Answer
NoSuchEntityException
Question
How do you bind interface to implementation?
Click to reveal answer
Answer
di.xml preference: <preference for="Interface" type="Implementation"/>
Revision Notes
Key Takeaways
- 1. Repositories implement RepositoryInterface for CRUD operations
- 2. SearchCriteria provides standard filtering, sorting, pagination
- 3. SearchResults returns items with total count
- 4. Use standard exceptions: CouldNotSaveException, NoSuchEntityException
- 5. Repositories are bound to interfaces via di.xml preferences
Interview Tips
- • Explain the repository pattern and why it exists
- • Know how to build SearchCriteria with filters and sorting
- • Be ready to implement a complete repository class
- • Discuss how repositories enable API access
Cheat Sheet
Repository interface:
save(WarrantyInterface $warranty)
get(int $id)
delete(WarrantyInterface $warranty)
getList(SearchCriteriaInterface $criteria)
SearchCriteria:
->addFilter('field', 'value', 'condition')
->setSortOrders([...])
->setPageSize(10)
->create()
Exceptions:
CouldNotSaveException, NoSuchEntityException