You’ve probably come across a Repository like the following:
class DocumentaryRepository
{
public function findFeaturedDocumentaries() {
//sql..
}
public function findPublishedDocumentariesInCategoryOrdededByCreated(Category $category) {
//sql
}
public function findMostPopularDocumentaries() {
//sql
}
public function findMostDiscussedDocumentaries() {
//sql
}
public function findMostWatchlistedDocumentaries() {
//sql
}
public function findRandomDocumentariesInCategory(Category $category) {
//sql
}
public function findPublishedDocumentariesOrderedByCreated() {
//sql
}
public function getLatestDocumentariesOrderedByCreated() {
//sql
}
}
This eventually can become cumbersome.
Another way to look at this is using the Criteria Pattern where you just have one function in the repository to handle the querying of the database.
First lets build our DocumentaryCriteria class:
<?php
namespace App\Criteria;
use App\Entity\Category;
use App\Entity\User;
use App\Entity\VideoSource;
class DocumentaryCriteria
{
/**
* @var bool
*/
private $featured;
/**
* @var string
*/
private $status;
/**
* @var Category
*/
private $category;
/**
* @var VideoSource
*/
private $videoSource;
/**
* @var int
*/
private $year;
/**
* @var string
*/
private $duration;
/**
* @var User
*/
private $addedBy;
/**
* @var array
*/
private $sort;
/**
* @var int
*/
private $limit;
//getter and setters
}
Another class required is DocumentaryOrderBy
<?php
namespace App\Enum;
class DocumentaryOrderBy
{
const CREATED_AT = "createdAt";
const UPDATED_AT = "updatedAt";
const VIEWS = "views";
const COMMENT_COUNT = "commentCount";
const WATCHLIST_COUNT = "watchlistCount";
const YEAR = "year";
}
and Order
<?php
namespace App\Enum;
class Order
{
const ASC = "ASC";
const DESC = "DESC";
}
Now lets look at the DocumentaryRepository and add the criteria function
/**
* @param DocumentaryCriteria $criteria
* @return QueryBuilder
*/
public function findDocumentariesByCriteriaQueryBuilder(DocumentaryCriteria $criteria)
{
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
$qb->select('documentary')
->from('App\Entity\Documentary', 'documentary');
if ($criteria->isFeatured() != null) {
$qb->andWhere('documentary.featured = :featured')
->setParameter('featured', $criteria->isFeatured());
}
if ($criteria->getStatus()) {
$qb->andWhere('documentary.status = :status')
->setParameter('status', $criteria->getStatus());
}
if ($criteria->getCategory()) {
$qb->andWhere('documentary.category = :category')
->setParameter('category', $criteria->getCategory());
}
if ($criteria->getVideoSource()) {
$qb->andWhere('documentary.videoSource = :videoSource')
->setParameter('videoSource', $criteria->getVideoSource());
}
if ($criteria->getAddedBy()) {
$qb->andWhere('documentary.addedBy = :addedBy')
->setParameter('addedBy', $criteria->getAddedBy());
}
if ($criteria->getYear()) {
$qb->andWhere('documentary.year = :year')
->setParameter('year', $criteria->getYear());
}
if ($criteria->getSort()) {
foreach ($criteria->getSort() as $column => $direction) {
$qb->addOrderBy($qb->getRootAliases()[0] . '.' . $column, $direction);
}
}
if ($criteria->getLimit()) {
$qb->setMaxResults($criteria->getLimit());
}
return $qb;
}
We’re naming the function findDocumentariesByCriteriaQueryBuilder for a reason (which will come in handy later when we implement pagination).
Lets add another function:
/**
* @param DocumentaryCriteria $criteria
* @return ArrayCollection|Documentary[]
*/
public function findDocumentariesByCriteria(DocumentaryCriteria $criteria)
{
$qb = $this->findDocumentariesByCriteriaQueryBuilder($criteria);
$query = $qb->getQuery();
$result = $query->getResult();
return $result;
}
Now we can add DocumentaryService which will host more finely grained functions but it’s much simpler with DocumentaryCriteria:
/**
* @param DocumentaryCriteria $criteria
* @return QueryBuilder
*/
public function getDocumentariesByCriteriaQueryBuilder(DocumentaryCriteria $criteria)
{
return $this->documentaryRepository->findDocumentariesByCriteriaQueryBuilder($criteria);
}
/**
* @return ArrayCollection|Documentary[]
*/
public function getFeaturedDocumentaries()
{
$criteria = new DocumentaryCriteria();
$criteria->setFeatured(true);
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$documentaries = $this->documentaryRepository->findDocumentariesByCriteria($criteria);
shuffle($documentaries);
return $documentaries;
}
/**
* @param Category $category
* @return ArrayCollection|Documentary[]
*/
public function getPublishedDocumentariesInCategory(Category $category)
{
$criteria = new DocumentaryCriteria();
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$criteria->setCategory($category);
$criteria->setSort([
DocumentaryOrderBy::CREATED_AT => Order::DESC
]);
return $this->documentaryRepository->findDocumentariesByCriteria($criteria);
}
/**
* @param int $limit
* @return ArrayCollection|Documentary[]
*/
public function getMostPopularDocumentaries(int $limit)
{
$criteria = new DocumentaryCriteria();
$criteria->setLimit($limit);
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$criteria->setSort([
DocumentaryOrderBy::VIEWS => Order::DESC
]);
return $this->documentaryRepository->findDocumentariesByCriteria($criteria);
}
/**
* @param int $limit
* @return ArrayCollection|Documentary[]
*/
public function getMostDiscussedDocumentaries(int $limit)
{
$criteria = new DocumentaryCriteria();
$criteria->setLimit($limit);
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$criteria->setSort([
DocumentaryOrderBy::COMMENT_COUNT => Order::DESC
]);
return $this->documentaryRepository->findDocumentariesByCriteria($criteria);
}
/**
* @param int $limit
* @return ArrayCollection|Documentary[]
*/
public function getMostWatchlistedDocumentaries(int $limit) : array
{
$criteria = new DocumentaryCriteria();
$criteria->setLimit($limit);
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$criteria->setSort([
DocumentaryOrderBy::WATCHLIST_COUNT => Order::DESC
]);
return $this->documentaryRepository->findDocumentariesByCriteria($criteria);
}
/**
* @return ArrayCollection|Documentary[]
*/
public function getPublishedDocumentaries()
{
$criteria = new DocumentaryCriteria();
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$criteria->setSort([
DocumentaryOrderBy::CREATED_AT => Order::DESC
]);
return $this->documentaryRepository->findDocumentariesByCriteria($criteria);
}
/**
* @param int $limit
* @return ArrayCollection|Documentary[]
*/
public function getLatestDocumentaries(int $limit)
{
$criteria = new DocumentaryCriteria();
$criteria->setStatus(DocumentaryStatus::PUBLISH);
$criteria->setLimit($limit);
$criteria->setSort([
DocumentaryOrderBy::CREATED_AT => Order::DESC
]);
return $this->documentaryRepository->findDocumentariesByCriteria($criteria);
}
Now you can see how the criteria can make your life easier and we can easily add pagination with the QueryBuilder and Pagerfanta.
/**
* @FOSRest\Get("/documentary", name="get_documentary_list", options={ "method_prefix" = false })
*
* @param Request $request
* @throws \Doctrine\ORM\ORMException
*/
public function listAction(Request $request)
{
$page = $request->query->get('page', 1);
$criteria = new DocumentaryCriteria();
$isRoleAdmin = $this->isGranted('ROLE_ADMIN');
if ($isRoleAdmin) {
$videoSourceId = $request->query->get('videoSource');
if (isset($videoSourceId)) {
$videoSource = $this->videoSourceService->getVideoSourceById($videoSourceId);
$criteria->setVideoSource($videoSource);
}
$status = $request->query->get('status');
if (isset($status)) {
$criteria->setStatus($status);
}
$featured = $request->query->get('featured');
if (isset($featured)) {
$featured = $featured === 'true' ? true: false;
$criteria->setFeatured($featured);
}
}
if (!$isRoleAdmin) {
$criteria->setStatus(DocumentaryStatus::PUBLISH);
}
$categorySlug = $request->query->get('category');
if (isset($categorySlug)) {
$category = $this->categoryService->getCategoryBySlug($categorySlug);
$criteria->setCategory($category);
}
$year = $request->query->get('year');
if (isset($year)) {
$criteria->setYear($year);
}
$duration = $request->query->get('duration');
if (isset($duration)) {
$criteria->setDuration($duration);
}
$addedBy = $request->query->get('addedBy');
if (isset($addedBy)) {
$user = $this->userService->getUserByUsername($addedBy);
$criteria->setAddedBy($user);
}
$sort = $request->query->get('sort');
if (isset($sort)) {
$exploded = explode("-", $sort);
$sort = [$exploded[0] => $exploded[1]];
$criteria->setSort($sort);
} else {
$criteria->setSort([
DocumentaryOrderBy::CREATED_AT => Order::DESC
]);
}
$qb = $this->documentaryService->getDocumentariesByCriteriaQueryBuilder($criteria);
$adapter = new DoctrineORMAdapter($qb, false);
$pagerfanta = new Pagerfanta($adapter);
$pagerfanta->setMaxPerPage($amountPerPage);
$pagerfanta->setCurrentPage($page);
$items = (array) $pagerfanta->getCurrentPageResults();
$serialized = [];
foreach ($items as $item) {
$serialized[] = $this->serializeDocumentary($item);
}
$data = [
'items' => $serialized,
'count_results' => $pagerfanta->getNbResults(),
'current_page' => $pagerfanta->getCurrentPage(),
'number_of_pages' => $pagerfanta->getNbPages(),
'next' => ($pagerfanta->hasNextPage()) ? $pagerfanta->getNextPage() : null,
'prev' => ($pagerfanta->hasPreviousPage()) ? $pagerfanta->getPreviousPage() : null,
'paginate' => $pagerfanta->haveToPaginate(),
];
return new JsonResponse($data, 200, array('Access-Control-Allow-Origin'=> '*'));
}
Feedback appreciated.