public function testValidation()
 {
     $expr = new OrderExpression(array('property' => 'foo', 'direction' => 'ASC'));
     $this->assertTrue($expr->isValid());
     $expr = new OrderExpression(array('property' => 'foo.bar', 'direction' => 'DESC'));
     $this->assertTrue($expr->isValid());
     $expr = new OrderExpression(array('property' => 'foo', 'direction' => 'XXX'));
     $this->assertFalse($expr->isValid());
     $expr = new OrderExpression(array());
     $this->assertFalse($expr->isValid());
     $expr = new OrderExpression(array('property' => null, 'direction' => null));
     $this->assertFalse($expr->isValid());
     $expr = new OrderExpression(array('property' => 'foo'));
     $this->assertFalse($expr->isValid());
     $expr = new OrderExpression(array('direction' => 'ASC'));
     $this->assertFalse($expr->isValid());
     $expr = new OrderExpression(array('property' => 'xxx', 'direction' => 'asc'));
     $this->assertTrue($expr->isValid());
 }
 /**
  * @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;
 }