Failed Conditions
Push — master ( e7b6d0...f491f4 )
by Denis
03:14
created

ProxyQueryBuilder::getConditionExpr()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 9
ccs 4
cts 5
cp 0.8
crap 2.032
rs 9.6666
c 0
b 0
f 0
1
<?php declare(strict_types = 1);
2
3
namespace Artprima\QueryFilterBundle\Query;
4
5
use Artprima\QueryFilterBundle\Exception\InvalidArgumentException;
6
use Artprima\QueryFilterBundle\Query\Condition\ConditionInterface;
7
use Doctrine\ORM\QueryBuilder;
8
use Doctrine\ORM\Query as DoctrineQuery;
9
10
/**
11
 * Class ProxyQueryBuilder
12
 *
13
 * @author Denis Voytyuk <[email protected]>
14
 */
15
class ProxyQueryBuilder
16
{
17
    /**
18
     * @var QueryBuilder
19
     */
20
    private $queryBuilder;
21
22
    /**
23
     * @var ConditionManager
24
     */
25
    private $conditionManager;
26
27 3
    public function __construct(QueryBuilder $queryBuilder, ConditionManager $conditionManager)
28
    {
29 3
        $this->queryBuilder = $queryBuilder;
30 3
        $this->conditionManager = $conditionManager;
31 3
    }
32
33
    /**
34
     * @param int $index parameter id
35
     * @param Filter $filter
36
     * @return DoctrineQuery\Expr\Comparison|DoctrineQuery\Expr\Func|string
37
     */
38 3
    private function getConditionExpr(int $index, Filter $filter)
39
    {
40 3
        if (!$this->conditionManager->offsetExists($filter->getType())) {
41
            throw new InvalidArgumentException(sprintf('Condition "%s" is not registered', $filter->getType()));
42
        }
43
44 3
        $expr = $this->conditionManager[$filter->getType()]->getExpr($this->queryBuilder, $index, $filter);
0 ignored issues
show
Bug introduced by
The method getExpr() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

44
        /** @scrutinizer ignore-call */ 
45
        $expr = $this->conditionManager[$filter->getType()]->getExpr($this->queryBuilder, $index, $filter);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
45
46 3
        return $expr;
47
    }
48
49
    /**
50
     * Get neighbor (prev or next) record id for use in navigation
51
     *
52
     * @param int $id record id
53
     * @param boolean $prev if true - get prev id, otherwise - next id
54
     * @param DoctrineQuery\Expr|null $extraAndWhereCondition
55
     * @return int|null neighbor id or null if empty result
56
     * @throws \RuntimeException
57
     * @throws \Doctrine\ORM\NonUniqueResultException
58
     */
59
    public function getNeighborRecordId(int $id, bool $prev, ?DoctrineQuery\Expr $extraAndWhereCondition = null): ?int
60
    {
61
        $sign = $prev ? '<' : '>';
62
        $order = $prev ? 'DESC' : 'ASC';
63
        $rootEntities = $this->queryBuilder->getRootEntities();
64
65
        if (count($rootEntities) >= 0) {
66
            throw new InvalidArgumentException('QueryBuilder must contain exactly one root entity');
67
        }
68
69
        $rootEntity = reset($rootEntities);
70
        $qb = new QueryBuilder($this->queryBuilder->getEntityManager());
71
        $qb
72
            ->select('c.id') // assuming that the entities index must be always called `id`
73
            ->from($rootEntity, 'c')
74
            ->where('c.id '.$sign.' :id')
75
            ->setParameter(':id', $id)
76
            ->orderBy('c.id', $order)
77
        ;
78
79
        if ($extraAndWhereCondition !== null) {
80
            $qb->andWhere($extraAndWhereCondition);
81
        }
82
83
        $query = $qb->getQuery();
84
        $query->setMaxResults(1);
85
        $result = $query->getOneOrNullResult();
86
87
        return $result;
88
    }
89
90
    /**
91
     * Get prev and next record ids for the given record id
92
     *
93
     * @param int $id record id
94
     * @return array prev and next records id in an array with 'prev' and 'next' keys. One or both items can be null in case of no records.
95
     * @throws \RuntimeException
96
     * @throws \Doctrine\ORM\NonUniqueResultException
97
     */
98
    public function getNeighborRecordIds(int $id): array
99
    {
100
        $prev = $this->getNeighborRecordId($id, true);
101
        $next = $this->getNeighborRecordId($id, false);
102
103
        return compact('prev', 'next');
104
    }
105
106
    /**
107
     * Get connector expression based on `and`, `or` or `null`
108
     *
109
     * @param $prev
110
     * @param $connector
111
     * @param $condition
112
     * @return DoctrineQuery\Expr\Andx|DoctrineQuery\Expr\Orx
113
     * @throws InvalidArgumentException
114
     */
115 3
    private function getConnectorExpr($prev, $connector, $condition)
116
    {
117 3
        $qb = $this->queryBuilder;
118
119 3
        if ($prev === null) {
120 3
            $expr = $condition;
121 2
        } elseif ($connector === null || $connector === 'and') {
122 1
            $expr = $qb->expr()->andX($prev, $condition);
123 1
        } elseif ($connector === 'or') {
124 1
            $expr = $qb->expr()->orX($prev, $condition);
125
        } else {
126
            throw new InvalidArgumentException(sprintf('Wrong connector type: %s', $connector));
127
        }
128
129 3
        return $expr;
130
    }
131
132 3
    private function addQueryFilters(QueryBuilder $qb, array $filterBy): QueryBuilder
133
    {
134 3
        if (empty($filterBy)) {
135
            return $qb;
136
        }
137
138 3
        $i = 0;
139 3
        $where = null;
140 3
        $having = null;
141
142
        /** @var Filter $val */
143 3
        foreach ($filterBy as $val) {
144 3
            if (!($val instanceof Filter)) {
145
                throw new InvalidArgumentException(sprintf('Unexpected val php type ("%s")', gettype($val)));
146
            }
147
148 3
            $i++;
149
150 3
            $condition = $this->getConditionExpr($i, $val);
151
152 3
            if (empty($val->isHaving())) {
153 3
                $where = $this->getConnectorExpr($where, $val->getConnector() ?? 'and', $condition);
154
            } else {
155 3
                $having = $this->getConnectorExpr($having, $val->getConnector() ?? 'and', $condition);
156
            }
157
        }
158
159 3
        if ($where) {
160 3
            $qb->add('where', $where);
161
        }
162
163 3
        if ($having) {
164
            $qb->add('having', $having);
165
        }
166
167 3
        return $qb;
168
    }
169
170
    public function registerCondition(ConditionInterface $condition, string $name)
171
    {
172
        $this->conditionManager[$name] = $condition;
173
    }
174
175
    /**
176
     * Add filter and order by conditions to the given QueryBuilder
177
     *
178
     * Example data
179
     *
180
     * array(
181
     *  'searchBy' => array(
182
     *    'e.name' => array(
183
     *      'type' => 'like',
184
     *      'val' => 'a',
185
     *    ),
186
     *    'e.city' => array(
187
     *      'type' => 'like',
188
     *      'val' => 'd',
189
     *    ),
190
     *    'c.name' => array(
191
     *      'type' => 'like',
192
     *      'val' => 'a',
193
     *    ),
194
     *    'concat(concat(concat(concat(p.firstname, ' '), p.middlename), ' '), p.lastname)' => array(
195
     *      'having' => TRUE
196
     *      'type' => 'like'
197
     *      'val' => 'a'
198
     *    )
199
     *    'year' => array(
200
     *      'type' => 'between',
201
     *      'val' => 2015,
202
     *      'x' => 'YEAR(e.startDate)',
203
     *      'y' => 'YEAR(e.endDate)'
204
     *    ),
205
     *  ),
206
     *  'sortData' => array(
207
     *      'e.name' => 'asc'
208
     *  )
209
     * )
210
     *
211
     * @param array $filterBy
212
     * @param array $orderBy
213
     *
214
     * @return QueryBuilder
215
     */
216 3
    public function getSortedAndFilteredQueryBuilder(array $filterBy, array $orderBy): QueryBuilder
217
    {
218 3
        $qb = $this->queryBuilder;
219
220 3
        foreach ($orderBy as $field => $dir) {
221 3
            $qb->addOrderBy($field, strtoupper($dir));
222
        }
223
224 3
        return $this->addQueryFilters($qb, $filterBy);
225
    }
226
}
227