Pierstoval
1/6/2016 - 10:53 AM

Generate instant wins depending on Prize objects in your database

Generate instant wins depending on Prize objects in your database

# app/config/config.yml
parameters:
    # Dates must respect this format: Y-m-d H:i:s
    game_start:   '2016-02-01 11:00:00'
    game_end:     '2016-03-31 23:59:59'
<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Prize
 *
 * @ORM\Table(name="prize")
 * @ORM\Entity()
 */
class Prize
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    protected $name;

    /**
     * @var float
     *
     * @ORM\Column(name="amount", type="float")
     */
    protected $amount;

    /**
     * @var string
     *
     * @ORM\Column(name="slug", type="string", length=255, unique=true)
     */
    protected $slug;

    /**
     * @var int
     *
     * @ORM\Column(name="quantity", type="integer")
     */
    protected $quantity;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param int $id
     * @return Prize
     */
    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $name
     *
     * @return Prize
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * @return float
     */
    public function getAmount()
    {
        return $this->amount;
    }

    /**
     * @param float $amount
     *
     * @return Prize
     */
    public function setAmount($amount)
    {
        $this->amount = $amount;

        return $this;
    }

    /**
     * @return string
     */
    public function getSlug()
    {
        return $this->slug;
    }

    /**
     * @param string $slug
     *
     * @return Prize
     */
    public function setSlug($slug)
    {
        $this->slug = $slug;

        return $this;
    }

    /**
     * @return int
     */
    public function getQuantity()
    {
        return $this->quantity;
    }

    /**
     * @param int $quantity
     * @return Prize
     */
    public function setQuantity($quantity)
    {
        $this->quantity = $quantity;
        return $this;
    }
}
<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * InstantWin
 *
 * @ORM\Table(name="instant_win")
 * @ORM\Entity()
 */
class InstantWin
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="winDate", type="datetime")
     */
    protected $winDate;

    /**
     * @var Prize
     *
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Prize")
     */
    protected $prize;

    /**
     * @var Participation
     *
     * @ORM\OneToOne(targetEntity="AppBundle\Entity\Participation", mappedBy="instantWin")
     * @ORM\JoinColumn(nullable=true)
     */
    protected $participation;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param \DateTime $winDate
     *
     * @return InstantWin
     */
    public function setWinDate($winDate)
    {
        $this->winDate = $winDate;

        return $this;
    }

    /**
     * @return \DateTime
     */
    public function getWinDate()
    {
        return $this->winDate;
    }

    /**
     * Set prize
     *
     * @param \AppBundle\Entity\Prize $prize
     *
     * @return InstantWin
     */
    public function setPrize(\AppBundle\Entity\Prize $prize = null)
    {
        $this->prize = $prize;

        return $this;
    }

    /**
     * Get prize
     *
     * @return \AppBundle\Entity\Prize
     */
    public function getPrize()
    {
        return $this->prize;
    }

    /**
     * Set participation
     *
     * @param Participation $participation
     *
     * @return InstantWin
     */
    public function setParticipation(Participation $participation = null)
    {
        $this->participation = $participation;

        return $this;
    }

    /**
     * Get participation
     *
     * @return Participation
     */
    public function getParticipation()
    {
        return $this->participation;
    }
}
<?php

namespace AppBundle\Command;

use AppBundle\Entity\InstantWin;
use AppBundle\Entity\Prize;
use AppBundle\EventListener\GameEndListener;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Helper\Table;

class GenerateInstantWinsCommand extends ContainerAwareCommand
{

    /**
     * @var SymfonyStyle
     */
    private $io;

    protected function configure()
    {
        $this
            ->setName('app:generate:instantwins')
            ->setDescription('Generates random InstantWin dates')
            ->addOption('truncate', null, InputOption::VALUE_NONE, 'Truncate all instant wins before processing.')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        /** @var EntityManager $em */
        $em = $this->getContainer()->get('doctrine')->getManager();

        /** @var Prize[] $basePrizes */
        $basePrizes = $em->getRepository('AppBundle:Prize')->findAllRoot('slug');

        $this->io = $io = new SymfonyStyle($input, $output);

        $instantWins = $em->getRepository('AppBundle:InstantWin')->findAll();

        if (count($instantWins)) {
            $truncate = $input->getOption('truncate');
            if (!$truncate && !$input->getOption('no-interaction')) {
                $truncate = $io->confirm('Truncate the instant wins already stored in the database?', false);
            }
        } else {
            $truncate = false;
        }

        if ($truncate) {
            $io->block('Truncating...');
            $io->progressStart(count($instantWins)+1);
            foreach ($instantWins as $instantWin) {
                $em->remove($instantWin);
                $io->progressAdvance();
            }
            $em->flush();
            $io->progressFinish();
        }

        $instantWins = null;

        $startDate = \DateTime::createFromFormat(GameEndListener::DATE_FORMAT, $this->getContainer()->getParameter('game_start'));
        $endDate = \DateTime::createFromFormat(GameEndListener::DATE_FORMAT, $this->getContainer()->getParameter('game_end'));

        // Here we add "+1" because both start and end day are included.
        $numberOfDays = $startDate->diff($endDate)->days + 1;

        // Calculate the total InstantWin to generate.
        $totalInstantWins = array_reduce($basePrizes, function ($carry, Prize $prize) {
            return $carry + $prize->getQuantity();
        }, 0);

        $io->section('Game informations');
        $io->table([], [
            ['Start date', '<info>' . $startDate->format('Y-m-d H:i:s') . '</info>'],
            ['End date', '<info>' . $endDate->format('Y-m-d H:i:s') . '</info>'],
            ['Days to play', '<info>' . $numberOfDays . '</info>'],
            ['Total InstantWins', '<info>' . $totalInstantWins . '</info>'],
        ]);

        // Here we set up
        $tableHeaders = [
            '#',
            'Quantity',
            'Qty./day',
            'Extra',
            'Extra/day',
        ];
        $prizesList = [];
        foreach ($basePrizes as $prize) {

            $quantity = $prize->getQuantity();
            $numberPerDay = floor($quantity / $numberOfDays);
            $extra = $quantity % $numberOfDays;
            $extraInterval = $extra / $numberOfDays;

            $prizesList[$prize->getSlug()] = array_combine(
                $tableHeaders,
                [
                    $prize->getId(),
                    $quantity,
                    $numberPerDay,
                    $extra,
                    $extraInterval,
                ]
            );
        }

        $io->section('Prizes distribution');
        $io->table($tableHeaders, $prizesList);

        // Hours for each instant win will be between these two values
        $startHour = 7;
        $endHour = 23;
        $intervalHour = $endHour - $startHour;

        $numberOfInstantWinsIterated = 0;

        $io->block('Processing...');

        $io->progressStart($totalInstantWins);

        // We determine instant wins for each prize.
        foreach ($basePrizes as $prize) {
            $items = $prize->getQuantity();
            $period = $numberOfDays / $items;

            $instantWinsForThisPrize = [];

            // First, let's distribute them equally for every day.
            for ($i = 0; $i < $items; $i++) {

                $dayNumber = (int) floor($i * $period);

                $date = clone $startDate;
                $date->modify('+' . $dayNumber . ' day' . ($dayNumber > 1 ? 's' : ''));
                $datestr = $date->format('Y-m-d');

                $iw = (new InstantWin())
                    ->setPrize($prize)
                    ->setWinDate($date)
                ;

                $instantWinsForThisPrize[$datestr][] = $iw;
            }

            // Then, for each day, we'll distribute equally the different instant wins during the day
            foreach ($instantWinsForThisPrize as $date => $iws) {
                /** @var InstantWin[] $iws */
                $items = count($iws);

                if (!$items) {
                    continue;
                }

                // Only one exception here:
                // If we have only one instant win for a day, its hour and minute will be determined randomly
                if (1 === $items) {
                    $instantWin = $iws[0];
                    $date = $instantWin->getWinDate();
                    $date->setTime(rand($startHour, $endHour), rand(0, 59));
                    $instantWin->setWinDate($date);
                    $em->persist($instantWin);
                    $io->progressAdvance();
                    continue;
                }

                $period = $intervalHour / ($items ?: 1);

                $i = 0;

                // Distribute each instant win equally during the day
                foreach ($iws as $instantWin) {
                    $date = $instantWin->getWinDate();
                    $date->setTime($startHour, 0, 0);
                    if ($i) {
                        $date->setTimestamp($date->getTimestamp() + intval(($i * $period * 3600)));
                    }
                    $instantWin->setWinDate($date);
                    $em->persist($instantWin);
                    $io->progressAdvance();
                    $i++;
                }

            }

        }

        $io->progressFinish();

        $io->block('Flushing...');

        $em->flush();

        $io->success('Done !');

    }
}