Products, categories, customers and orders
Products, categories, customers and orders are stored in dozens of related core tables. You do not read them with SQL queries: every kind of data has a repository — an interface with the same get, getList and save methods as the review repository. These interfaces do not change when Magento is updated.
Core repositories
| Data | Interface |
|---|---|
| Products | Magento\Catalog\Api\ProductRepositoryInterface |
| Categories | Magento\Catalog\Api\CategoryRepositoryInterface |
| Customers | Magento\Customer\Api\CustomerRepositoryInterface |
| Orders | Magento\Sales\Api\OrderRepositoryInterface |
A repository is requested in the constructor of your class. Modules whose interfaces you use are listed in the sequence section of module.xml.
A product by SKU
Create a class in the module that gathers information about the shop:
// app/code/Acme/Hello/Model/ShopInfo.php
namespace Acme\Hello\Model;
use Magento\Catalog\Api\ProductRepositoryInterface;
class ShopInfo
{
public function __construct(private ProductRepositoryInterface $productRepository) {}
public function productName(string $sku): string
{
$product = $this->productRepository->get($sku);
return (string) $product->getName();
}
}The get() method finds a product by its SKU, and getById() by its ID. If the product does not exist, both throw a NoSuchEntityException.
Using it
Let's call the class from the Status action of the earlier lessons:
// app/code/Acme/Hello/Controller/Index/Status.php
public function execute(): ResultInterface
{
return $this->resultFactory->create(ResultFactory::TYPE_JSON)
->setData(['name' => $this->shopInfo->productName('24-MB01')]);
}Add ShopInfo to the action constructor, clear the generated folder and open /hello/index/status. With the sample data installed the response is {"name":"Joust Duffle Bag"}.
A list of products
Search conditions are assembled by SearchCriteriaBuilder — the same way as for reviews:
// app/code/Acme/Hello/Model/ShopInfo.php
public function cheapProducts(): array
{
$criteria = $this->criteriaBuilder
->addFilter('price', 50, 'lt')
->setPageSize(10)
->create();
return $this->productRepository->getList($criteria)->getItems();
}The method returns up to ten products cheaper than 50. Each one has getSku(), getName(), getPrice(). The Magento\Framework\Api\SearchCriteriaBuilder object is added to the constructor.
Changing a product
// app/code/Acme/Hello/Model/ShopInfo.php
public function setPrice(string $sku, float $price): void
{
$product = $this->productRepository->get($sku);
$product->setPrice($price);
$this->productRepository->save($product);
}The product is saved with all core checks, and indexes and the cache are refreshed automatically.
Category, customer, order
// app/code/Acme/Hello/Model/ShopInfo.php
public function summary(int $categoryId, string $email, int $orderId): array
{
$category = $this->categoryRepository->get($categoryId);
$customer = $this->customerRepository->get($email);
$order = $this->orderRepository->get($orderId);
return [
'category' => $category->getName(),
'customer' => $customer->getFirstname() . ' ' . $customer->getLastname(),
'order' => $order->getIncrementId(),
'total' => $order->getGrandTotal(),
];
}A category and an order are found by ID, a customer by email address or through getById(). The getIncrementId() method returns the order number the customer sees, and getGrandTotal() the final amount.
A customer's orders
// app/code/Acme/Hello/Model/ShopInfo.php
public function ordersOf(string $email): array
{
$criteria = $this->criteriaBuilder
->addFilter('customer_email', $email)
->create();
return $this->orderRepository->getList($criteria)->getItems();
}The method returns all orders with the given email address.
Common mistake
A repository is called in a loop: for a hundred reviews $this->productRepository->getById() runs a hundred times. Every call loads a whole product with all its attributes, and the page takes several seconds to open. Collect the IDs and fetch the products with a single getList() call using the in condition on the entity_id field.