I have an Task
entity. It could be repeated like Google Calendar. I add rrule
column to store iCal RFC5545 format. And using https://github.com/tplaner/When third party library to parse the recurrence task list. Now I have obstacle with pagination. I need to show all tasks in a list view page. Order by due_date
property ASC: overdue -> today -> future. Show 10 tasks per page. The problem here is I have to query all tasks in database, then I loop in results set, then check if task has recurrence (is_recurrence
= true), then using third party to parse into a recurrence tasks. I have not solution for pagination here.
Task
entity:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Controller\TaskController;
use App\Repository\TaskRepository;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
collectionOperations: [
'get' => [
'controller' => TaskController::class
]
],
itemOperations: [
'get'
],
shortName: 'task',
denormalizationContext: [
'groups' => ['task:write']
],
normalizationContext: [
'groups' => ['task:read']
]
)]
#[ORM\Entity(repositoryClass: TaskRepository::class)]
#[ORM\Table]
#[ORM\Index(columns: ['uuid'], name: 'uuid_idx')]
#[UniqueEntity('uuid')]
#[ORM\HasLifecycleCallbacks]
class Task extends BaseEntity
{
public const STATUS_DRAFT = 1;
public const STATUS_IN_PROGRESS = 2;
public const STATUS_COMPLETED = 3;
public const STATUS_CANCELED = 4;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
#[Groups(['task:read', 'task:write'])]
#[Assert\NotBlank]
private $title;
#[ORM\Column(type: 'datetime_immutable')]
#[Groups(['task:read', 'task:write'])]
#[Assert\NotBlank]
private $dueDate;
#[ORM\Column(type: 'smallint')]
#[Groups(['task:read', 'task:write'])]
private $status = self::STATUS_DRAFT;
// getter and setter method.
}
TaskController
:
<?php
namespace App\Controller;
use App\Repository\TaskRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
final class TaskController extends AbstractController
{
public function __construct(
private TaskRepository $taskRepository
) {
}
public function __invoke(Request $request)
{
if ($request->get('_api_collection_operation_name') === 'get') {
return $this->getCollection($request);
}
}
private function getCollection(Request $request)
{
$page = (int) $request->query->get('page', 1);
return $this->taskRepository->getTaskPaginator($page);
}
}
TaskRepository
:
<?php
namespace App\Repository;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
use App\Entity\Task;
use DateTime;
use DateTimeImmutable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use When\When;
/**
* @method Task|null find($id, $lockMode = null, $lockVersion = null)
* @method Task|null findOneBy(array $criteria, array $orderBy = null)
* @method Task[] findAll()
* @method Task[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class TaskRepository extends ServiceEntityRepository
{
const ITEMS_PER_PAGE = 10;
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Task::class);
}
public function getTaskPaginator(int $page)
{
$firstResult = ($page - 1) * self::ITEMS_PER_PAGE;
$queryBuilder = $this->createQueryBuilder('t');
$queryBuilder->orderBy('t.dueDate', 'ASC');
$criteria = Criteria::create()
->setFirstResult($firstResult)
->setMaxResults(self::ITEMS_PER_PAGE);
$queryBuilder->addCriteria($criteria);
$doctrinePaginator = new DoctrinePaginator($queryBuilder);
$paginator = new Paginator($doctrinePaginator);
return $paginator;
}
private function getRecurrenceTasks(Task $task)
{
$results = [];
if ($task->getRecurrenceRule() !== null) {
$recurrence = new When();
$dateTime = new DateTime();
$dateTime->setTimestamp($task->getDueDate()->getTimestamp());
$recurrence->startDate($dateTime)
->rrule($task->getRecurrenceRule())
->generateOccurrences();
foreach ($recurrence->occurrences as $occurrence) {
$cloneTask = clone $task;
$dateTimeImmutable = DateTimeImmutable::createFromMutable($occurrence);
$cloneTask->setDueDate($dateTimeImmutable);
$results[] = $cloneTask;
}
}
return $results;
}
}
I wrote the getRecurrenceTasks
function to use third party library to generate the recurrence tasks based on RRULE. But I do not any idea to combine the non_recurrence tasks (query from database) and recurrence tasks (generate from php code), and return pagination.