0

So in my Symfony 3 project I need to create a sort of price calculator with takes 3 params: date_from, date_to, guest_number. Table in MySql looks like:

-----------------------------------------------------------------
id    |    date_from    |    date_to     |   people   |     price
-----------------------------------------------------------------
1     |    2016-01-15   |    2016-04-20  |   1        |     100
-----------------------------------------------------------------
2     |    2016-04-20   |    2016-08-15  |   1        |     200
-----------------------------------------------------------------
3     |    2016-04-20   |    2016-08-15  |   2        |     250

For example, someone choose 2016-01-01 till 2016-01-10 for 1 guest. Calculator should return 1000. And it is no big deal to create SQL statement for this

Example 2, someone choose 2016-04-15 til 2016-04-25 for 1 person.

The question is how can I build with Doctrine QueryBuilder statement which would calculate 'n' days from one period multiply price and 'n' days from another period multiply corresponding price?

lomboboo
  • 1,221
  • 1
  • 12
  • 27

1 Answers1

0

Let's say you have an entity Calendar:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="CalendarRepository")
 * @ORM\Table(name="calendar")
 */
class Calendar
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="date")
     */
    private $dateFrom;

    /**
     * @ORM\Column(type="date")
     */
    private $dateTo;

    /**
     * @ORM\Column(type="integer")
     */
    private $people;

    /**
     * @ORM\Column(type="integer")
     */
    private $price;
}

Then your repository class could look like this:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\EntityRepository;

class CalendarRepository extends EntityRepository
{
    public function findPriceFor(\DateTime $dateFrom, \DateTime $dateTo, $nrOfPeople)
    {
        $qb = $this->createQueryBuilder('calendar');
        $qb->select('SUM(
                        CASE 
                            WHEN calendar.dateFrom >= :dateFromChosen AND calendar.dateTo >= :dateToChosen THEN DATE_DIFF(:dateToChosen, calendar.dateFrom)
                            WHEN calendar.dateFrom <= :dateFromChosen AND calendar.dateTo >= :dateToChosen THEN DATE_DIFF(:dateToChosen, :dateFromChosen)
                            WHEN calendar.dateFrom <= :dateFromChosen AND calendar.dateTo <= :dateToChosen THEN DATE_DIFF(calendar.dateTo, :dateFromChosen)
                            WHEN calendar.dateFrom >= :dateFromChosen AND calendar.dateTo <= :dateToChosen THEN DATE_DIFF(calendar.dateTo, calendar.dateFrom)
                            ELSE 0
                        END
                    )*calendar.price AS intervalPrice');

        $qb->andWhere('calendar.people = :nrOfPeople')
            ->andWhere(
                $qb->expr()->andX(
                    $qb->expr()->lt('calendar.dateFrom', ':dateToChosen'),
                    $qb->expr()->gt('calendar.dateTo', ':dateFromChosen')
                )
            );

        $qb->setParameter('nrOfPeople', $nrOfPeople)
            ->setParameter('dateFromChosen', $dateFrom->format('Y-m-d'))
            ->setParameter('dateToChosen', $dateTo->format('Y-m-d'));

        $qb->groupBy('calendar.id');

        $query = $qb->getQuery();

        $resultArray = $query->execute();

        $totalPrice = array_sum(array_column($resultArray, 'intervalPrice'));

        return $totalPrice;
    }
}

If we took your example MySQL table, and decide to calculate a price for 1 person, from "2016-04-15" to "2016-04-25", then result would be this:

result array and total price

Matko Đipalo
  • 1,676
  • 1
  • 11
  • 23
  • 1
    Thanks. That works!!! Could you please explain this part ->andWhere( $qb->expr()->andX( $qb->expr()->lt('calendar.dateFrom', ':dateToChosen'), $qb->expr()->gt('calendar.dateTo', ':dateFromChosen') ) – lomboboo Nov 01 '16 at 08:17
  • I could (or should XD ) write that condition a little bit less complicated :) I think this would work to: ... ->andWhere('calendar.dateFrom < :dateToChosen AND calendar.dateTo > :dateFromChosen') ... We are choosing all the intervals from the DB, where the beginning of the DB interval is before the end of a chosen interval, and the end of the DB interval is after the beginning of the chosen interval. Maybe this could be helpful http://stackoverflow.com/a/2546046/5431686 – Matko Đipalo Nov 01 '16 at 10:09
  • Here you can find out more about building expressions in Doctrine2 http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html#building-expressions – Matko Đipalo Nov 06 '16 at 10:16