VSVverkeerskunde/gvq-api

View on GitHub
src/Statistics/Repositories/TopScoreDoctrineRepository.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php declare(strict_types=1);

namespace VSV\GVQ_API\Statistics\Repositories;

use function array_chunk;
use function array_slice;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\Query\Expr\Join;
use Ramsey\Uuid\UuidInterface;
use VSV\GVQ_API\Common\Repositories\AbstractDoctrineRepository;
use VSV\GVQ_API\Company\Models\Companies;
use VSV\GVQ_API\Company\Repositories\Entities\CompanyEntity;
use VSV\GVQ_API\Statistics\Models\TopScores;
use VSV\GVQ_API\Statistics\ValueObjects\Average;
use VSV\GVQ_API\Statistics\Repositories\Entities\EmployeeParticipationEntity;
use VSV\GVQ_API\Statistics\Repositories\Entities\TopScoreEntity;
use VSV\GVQ_API\Statistics\Models\TopScore;
use VSV\GVQ_API\Statistics\ValueObjects\NaturalNumber;
use VSV\GVQ_API\User\ValueObjects\Email;

class TopScoreDoctrineRepository extends AbstractDoctrineRepository implements TopScoreRepository
{
    /**
     * @inheritdoc
     */
    protected function getRepositoryName(): string
    {
        return TopScoreEntity::class;
    }

    /**
     * @inheritdoc
     */
    public function saveWhenHigher(TopScore $topScore): void
    {
        $foundTopScore = $this->getByEmail($topScore->getEmail());

        if ($foundTopScore && $foundTopScore->getScore()->toNative() >= $topScore->getScore()->toNative()) {
            return;
        }

        $this->entityManager->merge(TopScoreEntity::fromTopScore($topScore));
        $this->entityManager->flush();
    }

    /**
     * @inheritdoc
     */
    public function getByEmail(Email $email): ?TopScore
    {
        /** @var TopScoreEntity|null $topScoreEntity */
        $topScoreEntity = $this->objectRepository->findOneBy(
            [
                'email' => $email->toNative(),
            ]
        );

        return $topScoreEntity ? $topScoreEntity->toTopScore() : null;
    }

    /**
     * @inheritdoc
     */
    public function getAllByCompany(UuidInterface $companyId): array
    {
        /** @var TopScoreEntity[] $topScoreEntities */
        $topScoreEntities = $this->entityManager->createQueryBuilder()
            ->select('topScore')
            ->from(EmployeeParticipationEntity::class, 'employee')
            ->where('employee.companyId = :companyId')
            ->setParameter('companyId', $companyId->toString())
            ->innerJoin(TopScoreEntity::class, 'topScore', Join::WITH, 'employee.email = topScore.email')
            ->getQuery()
            ->getResult();

        $topScores = [];
        foreach ($topScoreEntities as $topScoreEntity) {
            $topScores[] = $topScoreEntity->toTopScore();
        }

        return $topScores;
    }

    /**
     * @inheritdoc
     * @throws NonUniqueResultException
     */
    public function getAverage(): Average
    {
        $scalarScore = $this->entityManager->createQueryBuilder()
            ->select('avg(topScore.score)')
            ->from(TopScoreEntity::class, 'topScore')
            ->getQuery()
            ->getSingleScalarResult();

        $score = null === $scalarScore ? 0 : floatval($scalarScore);

        return new Average($score);
    }

    /**
     * @inheritdoc
     * @throws NonUniqueResultException
     */
    public function getAverageForCompany(UuidInterface $companyId): Average
    {
        $scalarScore = $this->entityManager->createQueryBuilder()
            ->select('avg(topScore.score)')
            ->from(EmployeeParticipationEntity::class, 'employee')
            ->where('employee.companyId = :companyId')
            ->setParameter('companyId', $companyId->toString())
            ->innerJoin(TopScoreEntity::class, 'topScore', Join::WITH, 'employee.email = topScore.email')
            ->getQuery()
            ->getSingleScalarResult();

        $score = null === $scalarScore ? 0 : floatval($scalarScore);

        return new Average($score);
    }

    /**
     * @inheritdoc
     */
    public function getTopCompanies(NaturalNumber $nrOfPassedEmployees): iterable
    {
        $batchSize = 10;
        $firstResult = 0;

        $minimalScoreToSucceed = 7;

        do {
            $queryBuilder = $this->entityManager->createQueryBuilder();

            $query = $queryBuilder->select('c')
                ->from(
                    CompanyEntity::class,
                    'c'
                )
                ->setMaxResults($batchSize)
                ->setFirstResult($firstResult)
                ->getQuery();

            $currentBatchItemCount = 0;

            foreach ($query->iterate() as $companyEntities) {
                $currentBatchItemCount++;
                /** @var CompanyEntity $companyEntity */
                $companyEntity = $companyEntities[0];

                $qb = $this->entityManager->createQueryBuilder();
                $succeededEmployeesCount = $qb->select('count(distinct employee.email) AS nrOfPassedEmployees')
                    ->from(EmployeeParticipationEntity::class, 'employee')
                    ->innerJoin(TopScoreEntity::class, 'topScore', Join::WITH, 'employee.email = topScore.email AND topScore.score >= :minimal_score_to_succeed')
                    ->andWhere($qb->expr()->eq('employee.companyId', ':company_id'))
                    ->setParameter('company_id', $companyEntity->getId())
                    ->setParameter('minimal_score_to_succeed', $minimalScoreToSucceed)
                    ->getQuery()
                    ->getSingleScalarResult();

                $succeededEmployeesCount = (int) $succeededEmployeesCount;

                if ($succeededEmployeesCount < $nrOfPassedEmployees->toNative()) {
                    $this->entityManager->detach($companyEntity);
                    continue;
                }

                $company = $companyEntity->toCompany();
                $this->entityManager->detach($companyEntity);

                yield $company->withNrOfPassedEmployees(
                    new NaturalNumber(
                        $succeededEmployeesCount
                    )
                );
            }

            $firstResult += $batchSize;
        } while ($currentBatchItemCount == $batchSize);
    }
}