0

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.

0 Answers0