performance tuning on timesheets (#874)

This commit is contained in:
Kevin Papst
2019-06-20 16:05:31 +02:00
committed by GitHub
parent bb8572a364
commit 9fbbaf9b88
11 changed files with 290 additions and 50 deletions

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository\Loader;
interface LoaderInterface
{
/**
* Prepares the given database results, so no lazy loading will be performed.
*
* @param array $results
*/
public function loadResults(array $results): void;
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository\Loader;
use App\Entity\Project;
use App\Entity\Timesheet;
use Doctrine\ORM\EntityManagerInterface;
final class TimesheetIdLoader implements LoaderInterface
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param int[] $ids
*/
public function loadResults(array $ids): void
{
if (empty($ids)) {
return;
}
$em = $this->entityManager;
$qb = $em->createQueryBuilder();
$projects = $qb->select('PARTIAL t.{id}', 'project')
->from(Timesheet::class, 't')
->leftJoin('t.project', 'project')
->andWhere($qb->expr()->in('t.id', $ids))
->getQuery()
->execute();
if (!empty($projects)) {
$projectIds = array_map(function (Timesheet $timesheet) {
return $timesheet->getProject()->getId();
}, $projects);
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL p.{id}', 'customer')
->from(Project::class, 'p')
->leftJoin('p.customer', 'customer')
->andWhere($qb->expr()->in('p.id', $projectIds))
->getQuery()
->execute();
}
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL t.{id}', 'activity')
->from(Timesheet::class, 't')
->leftJoin('t.activity', 'activity')
->andWhere($qb->expr()->in('t.id', $ids))
->getQuery()
->execute();
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL t.{id}', 'user')
->from(Timesheet::class, 't')
->leftJoin('t.user', 'user')
->andWhere($qb->expr()->in('t.id', $ids))
->getQuery()
->execute();
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL t.{id}', 'tags')
->from(Timesheet::class, 't')
->leftJoin('t.tags', 'tags')
->andWhere($qb->expr()->in('t.id', $ids))
->getQuery()
->execute();
/*
$qb = $em->createQueryBuilder();
$qb->select('PARTIAL t.{id}', 'meta')
->from(Timesheet::class, 't')
->leftJoin('t.meta', 'meta')
->andWhere($qb->expr()->in('t.id', $ids))
->getQuery()
->execute();
*/
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository\Loader;
use App\Entity\Timesheet;
use Doctrine\ORM\EntityManagerInterface;
final class TimesheetLoader implements LoaderInterface
{
/**
* @var TimesheetIdLoader
*/
private $loader;
public function __construct(EntityManagerInterface $entityManager)
{
$this->loader = new TimesheetIdLoader($entityManager);
}
/**
* @param Timesheet[] $timesheets
*/
public function loadResults(array $timesheets): void
{
$ids = array_map(function (Timesheet $timesheet) {
return $timesheet->getId();
}, $timesheets);
$this->loader->loadResults($ids);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Repository\Paginator;
use App\Repository\Loader\TimesheetLoader;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Adapter\AdapterInterface;
final class TimesheetPaginator implements AdapterInterface
{
/**
* @var QueryBuilder
*/
private $query;
/**
* @var int
*/
private $results = 0;
/**
* @var TimesheetLoader
*/
private $loader;
public function __construct(QueryBuilder $query, int $results)
{
$this->query = $query;
$this->results = $results;
$this->loader = new TimesheetLoader($query->getEntityManager());
}
/**
* {@inheritdoc}
*/
public function getNbResults()
{
return $this->results;
}
private function getResults(Query $query)
{
$results = $query->execute();
$this->loader->loadResults($results);
return $results;
}
/**
* {@inheritdoc}
*/
public function getSlice($offset, $length)
{
$query = $this->query
->getQuery()
->setFirstResult($offset)
->setMaxResults($length);
return $this->getResults($query);
}
public function getAll()
{
return $this->getResults($this->query->getQuery());
}
}

View File

@@ -16,13 +16,16 @@ use App\Model\Statistic\Day;
use App\Model\Statistic\Month;
use App\Model\Statistic\Year;
use App\Model\TimesheetStatistic;
use App\Repository\Loader\TimesheetLoader;
use App\Repository\Paginator\TimesheetPaginator;
use App\Repository\Query\TimesheetQuery;
use DateTime;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Pagerfanta\Pagerfanta;
class TimesheetRepository extends AbstractRepository
class TimesheetRepository extends EntityRepository
{
public const STATS_QUERY_DURATION = 'duration';
public const STATS_QUERY_RATE = 'rate';
@@ -352,7 +355,7 @@ class TimesheetRepository extends AbstractRepository
->join('t.project', 'p')
->join('p.customer', 'c')
->leftJoin('t.tags', 'tags')
->where($qb->expr()->isNotNull('t.begin'))
->andWhere($qb->expr()->isNotNull('t.begin'))
->andWhere($qb->expr()->isNull('t.end'))
->orderBy('t.begin', 'DESC');
@@ -402,22 +405,45 @@ class TimesheetRepository extends AbstractRepository
return $counter;
}
public function getPagerfantaForQuery(TimesheetQuery $query): Pagerfanta
{
$paginator = new Pagerfanta($this->getPaginatorForQuery($query));
$paginator->setMaxPerPage($query->getPageSize());
$paginator->setCurrentPage($query->getPage());
return $paginator;
}
protected function getPaginatorForQuery(TimesheetQuery $query): TimesheetPaginator
{
$qb = $this->getQueryBuilderForQuery($query);
$qb->select($qb->expr()->countDistinct('t.id'))->resetDQLPart('orderBy');
$counter = (int) $qb->getQuery()->getSingleScalarResult();
$qb = $this->getQueryBuilderForQuery($query);
$qb->select('t');
$paginator = new TimesheetPaginator($qb, $counter);
return $paginator;
}
/**
* @param TimesheetQuery $query
* @return QueryBuilder|Pagerfanta|array
* @return Timesheet[]
*/
public function findByQuery(TimesheetQuery $query)
public function getTimesheetsForQuery(TimesheetQuery $query): array
{
$paginator = $this->getPaginatorForQuery($query);
return $paginator->getAll();
}
protected function getQueryBuilderForQuery(TimesheetQuery $query): QueryBuilder
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('t', 'a', 'p', 'c', 'u', 'tags')
->from(Timesheet::class, 't')
->leftJoin('t.activity', 'a')
->leftJoin('t.user', 'u')
->leftJoin('t.project', 'p')
->leftJoin('p.customer', 'c')
->leftJoin('t.tags', 'tags')
->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
$qb->from(Timesheet::class, 't');
if (null !== $query->getUser()) {
$qb->andWhere('t.user = :user')
@@ -456,6 +482,7 @@ class TimesheetRepository extends AbstractRepository
$qb->andWhere('t.project = :project')
->setParameter('project', $query->getProject());
} elseif (null !== $query->getCustomer()) {
$qb->join('t.project', 'p');
$qb->andWhere('p.customer = :customer')
->setParameter('customer', $query->getCustomer());
}
@@ -467,7 +494,9 @@ class TimesheetRepository extends AbstractRepository
->setParameter('tags', $query->getTags());
}
return $this->getBaseQueryResult($qb, $query);
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
return $qb;
}
/**
@@ -516,16 +545,17 @@ class TimesheetRepository extends AbstractRepository
$ids = array_column($results, 'maxid');
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('t', 'a', 'p', 'c', 'tags')
$qb->select('t')
->from(Timesheet::class, 't')
->join('t.activity', 'a')
->join('t.project', 'p')
->join('p.customer', 'c')
->leftJoin('t.tags', 'tags')
->andWhere($qb->expr()->in('t.id', $ids))
->orderBy('t.end', 'DESC')
;
return $qb->getQuery()->getResult();
$results = $qb->getQuery()->getResult();
$loader = new TimesheetLoader($qb->getEntityManager());
$loader->loadResults($results);
return $results;
}
}