Repositories encapsulate query logic for specific entities, providing reusable methods for data access. They're optional - use them when query logic benefits from centralization, or work directly with EntityManager for simple cases.
A repository is a class that handles all queries for a specific entity type. Instead of writing the same ObjectQuel queries repeatedly throughout your application, you write them once in the repository:
// Without repository - query scattered everywhere
$products = $entityManager->executeQuery("
range of p is App\\Entity\\ProductEntity
retrieve (p) where p.price >= :minPrice
sort by p.price desc
", ['minPrice' => 100.00]);
// With repository - query centralized
$products = $productRepository->findExpensiveProducts(100.00);
When to use repositories:
When to use EntityManager directly:
Repositories extend the base Repository class and add custom query methods:
use Quellabs\ObjectQuel\Repository;
class ProductRepository extends Repository {
public function __construct(EntityManager $entityManager) {
parent::__construct($entityManager, ProductEntity::class);
}
public function findExpensiveProducts(float $minPrice = 100.00): array {
return $this->entityManager->executeQuery("
range of p is App\\Entity\\ProductEntity
retrieve (p) where p.price >= :minPrice
sort by p.price desc
", ['minPrice' => $minPrice]);
}
public function findByCategory(string $categoryName): array {
return $this->entityManager->executeQuery("
range of p is App\\Entity\\ProductEntity
range of c is App\\Entity\\CategoryEntity via p.categoryId = c.categoryId
retrieve (p) where c.name = :category
sort by p.name asc
", ['category' => $categoryName]);
}
}
All repositories inherit these methods from the base Repository class:
// Find by primary key
$product = $productRepository->find(101);
// Find by criteria
$products = $productRepository->findBy(['featured' => true]);
$products = $productRepository->findBy([
'status' => 'active',
'price' => 29.99
]);
Repository methods can return aggregated data:
class ProductRepository extends Repository {
public function countInPriceRange(float $minPrice, float $maxPrice): int {
$results = $this->entityManager->executeQuery("
range of p is App\\Entity\\ProductEntity
retrieve (count(p)) where p.price >= :min and p.price <= :max
", ['min' => $minPrice, 'max' => $maxPrice]);
return $results[0]['count(p)'];
}
public function getAveragePrice(): float {
$results = $this->entityManager->executeQuery("
range of p is App\\Entity\\ProductEntity
retrieve (avg(p.price))
");
return $results[0]['avg(p.price)'] ?? 0.0;
}
}
Repositories handle complex entity relationships:
class OrderRepository extends Repository {
public function findRecentByCustomer(int $customerId, int $days = 30): array {
$since = (new DateTime())->sub(new DateInterval("P{$days}D"));
return $this->entityManager->executeQuery("
range of o is App\\Entity\\OrderEntity
retrieve (o) where o.customerId = :customerId
and o.orderDate >= :since
sort by o.orderDate desc
", ['customerId' => $customerId, 'since' => $since]);
}
public function findLargeOrders(float $minAmount = 1000.00): array {
return $this->entityManager->executeQuery("
range of o is App\\Entity\\OrderEntity
range of oi is App\\Entity\\OrderItemEntity via oi.orderId = o.orderId
range of p is App\\Entity\\ProductEntity via p.productId = oi.productId
retrieve (o, oi, p) where o.totalAmount >= :minAmount
sort by o.totalAmount desc
", ['minAmount' => $minAmount]);
}
}
Unlike some ORMs that require repositories for all data access, ObjectQuel makes them optional. You can use EntityManager directly anywhere in your code:
// Direct EntityManager usage - perfectly valid
$products = $entityManager->executeQuery("
range of p is App\\Entity\\ProductEntity
retrieve (p) where p.featured = true
");
// Or use repositories for better organization
$products = $productRepository->findBy(['featured' => true]);
This flexibility lets you choose the right tool for each situation. Start simple with EntityManager, add repositories when complexity demands it.