public function testParsingBasics() { $expr = new OrderExpression(array('property' => 'foo', 'direction' => 'ASC')); $this->assertEquals('foo', $expr->getProperty()); $this->assertEquals('ASC', $expr->getDirection()); $expr = new OrderExpression(array()); $this->assertNull($expr->getProperty()); $this->assertNull($expr->getDirection()); }
/** * @throws \RuntimeException * * @param string $entityFqcn Root fetch entity fully-qualified-class-name * @param array $params Parameters that were sent from client-side * @param SortingFieldResolverInterface $primarySortingFieldResolver * * @return \Doctrine\ORM\QueryBuilder */ public function buildQueryBuilder($entityFqcn, array $params, SortingFieldResolverInterface $primarySortingFieldResolver = null) { $sortingFieldResolver = new ChainSortingFieldResolver(); if ($primarySortingFieldResolver) { $sortingFieldResolver->add($primarySortingFieldResolver); } $sortingFieldResolver->add($this->sortingFieldResolver); $qb = $this->em->createQueryBuilder(); $expressionManager = new ExpressionManager($entityFqcn, $this->em); $dqlCompiler = new DqlCompiler($expressionManager); $binder = new DoctrineQueryBuilderParametersBinder($qb); $hasFetch = isset($params['fetch']) && is_array($params['fetch']) && count($params['fetch']) > 0; /* @var Expression[] $fetchExpressions */ $fetchExpressions = array(); if ($hasFetch) { foreach ($params['fetch'] as $statement => $groupExpr) { $fetchExpressions[] = new Expression($groupExpr, $statement); } } $orderStmts = array(); // contains ready DQL orderBy statement that later will be joined together if (isset($params['sort'])) { foreach ($params['sort'] as $entry) { // sanitizing and filtering $orderExpression = new OrderExpression($entry); if (!$orderExpression->isValid()) { continue; } $statement = null; // if expression cannot be directly resolved again the model we will check // if there's an alias introduced in "fetch" and then allow to use it if (!$expressionManager->isValidExpression($orderExpression->getProperty()) && $hasFetch && isset($params['fetch'][$orderExpression->getProperty()])) { $statement = $orderExpression->getProperty(); } else { if ($expressionManager->isValidExpression($orderExpression->getProperty())) { $statement = $expressionManager->getDqlPropertyName($this->resolveExpression($entityFqcn, $orderExpression->getProperty(), $sortingFieldResolver, $expressionManager)); } } if (null === $statement) { continue; } $orderStmts[] = $statement . ' ' . strtoupper($orderExpression->getDirection()); } } $hasGroupBy = isset($params['groupBy']) && is_array($params['groupBy']) && count($params['groupBy']) > 0; /* @var Expression[] $groupByExpressions */ $groupByExpressions = array(); if ($hasGroupBy) { foreach ($params['groupBy'] as $groupExpr) { $groupByExpressions[] = new Expression($groupExpr); } } $addRootFetch = isset($params['fetchRoot']) && true == $params['fetchRoot'] || !isset($params['fetchRoot']); if ($addRootFetch) { $qb->add('select', 'e'); } foreach ($fetchExpressions as $expression) { if ($expression->getFunction() || $expression->getAlias()) { $qb->add('select', $dqlCompiler->compile($expression, $binder), true); } else { if (!$expressionManager->isAssociation($expression->getExpression())) { $qb->add('select', $expressionManager->getDqlPropertyName($expression->getExpression()), true); } } } $qb->add('from', $entityFqcn . ' e'); if (isset($params['start'])) { $start = $params['start']; if (isset($params['page']) && isset($params['limit'])) { $start = ($params['page'] - 1) * $params['limit']; } $qb->setFirstResult($start); } if (isset($params['limit'])) { $qb->setMaxResults($params['limit']); } if (isset($params['filter'])) { $andExpr = $qb->expr()->andX(); foreach (new Filters($params['filter']) as $filter) { /* @var FilterInterface $filter */ if (!$filter->isValid()) { continue; } if ($filter instanceof OrFilter) { $orExpr = $qb->expr()->orX(); foreach ($filter->getFilters() as $filter) { $this->processFilter($expressionManager, $orExpr, $qb, $binder, $filter); } $andExpr->add($orExpr); } else { $this->processFilter($expressionManager, $andExpr, $qb, $binder, $filter); } } if ($andExpr->count() > 0) { $qb->where($andExpr); } } if ($hasFetch) { $expressionManager->injectFetchSelects($qb, $fetchExpressions); } else { $expressionManager->injectJoins($qb, false); } if ($hasGroupBy) { foreach ($groupByExpressions as $groupExpr) { if ($groupExpr->getFunction()) { $qb->addGroupBy($dqlCompiler->compile($groupExpr, $binder)); } else { $dqlExpression = null; if ($expressionManager->isValidExpression($groupExpr->getExpression())) { $dqlExpression = $expressionManager->getDqlPropertyName($groupExpr->getExpression()); } else { // we need to have something like this due to a limitation imposed by DQL. Basically, // we cannot write a query which would look akin to this one: // SELECT COUNT(e.id), DAY(e.createdAt) FROM FooEntity e GROUP BY DAY(e.createdAt) // If you try to use a function call in a GROUP BY clause an exception will be thrown. // To workaround this problem we need to introduce an alias, for example: // SELECT COUNT(e.id), DAY(e.createdAt) AS datDay FROM FooEntity e GROUP BY datDay foreach ($fetchExpressions as $fetchExpr) { if ($groupExpr->getExpression() == $fetchExpr->getAlias()) { $dqlExpression = $fetchExpr->getAlias(); } } } if (!$dqlExpression) { throw new \RuntimeException(sprintf('Unable to resolve grouping expression "%s" for entity %s', $groupExpr->getExpression(), $entityFqcn)); } $qb->addGroupBy($dqlExpression); } } $expressionManager->injectJoins($qb, false); } if (count($orderStmts) > 0) { $qb->add('orderBy', implode(', ', $orderStmts)); } $binder->injectParameters(); return $qb; }