Failed Conditions
Push — master ( 425385...57fb75 )
by Povilas
02:33
created

ListQueryBuilder::applyGroup()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Povs\ListerBundle\Service;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use Doctrine\ORM\QueryBuilder;
7
use Povs\ListerBundle\Declaration\ListInterface;
8
use Povs\ListerBundle\Declaration\ListValueInterface;
9
use Povs\ListerBundle\DependencyInjection\Locator\QueryTypeLocator;
10
use Povs\ListerBundle\DependencyInjection\Locator\SelectorTypeLocator;
11
use Povs\ListerBundle\Exception\ListFieldException;
12
use Povs\ListerBundle\Mapper\FilterField;
13
use Povs\ListerBundle\Mapper\FilterMapper;
14
use Povs\ListerBundle\Mapper\JoinField;
15
use Povs\ListerBundle\Mapper\JoinMapper;
16
use Povs\ListerBundle\Mapper\ListField;
17
use Povs\ListerBundle\Mapper\ListMapper;
18
use Povs\ListerBundle\Type\QueryType\QueryTypeInterface;
19
use Povs\ListerBundle\Type\SelectorType\SelectorTypeInterface;
20
use Symfony\Component\OptionsResolver\OptionsResolver;
21
22
/**
23
 * @author Povilas Margaiatis <[email protected]>
24
 */
25
class ListQueryBuilder
26
{
27
    public const IDENTIFIER_ALIAS = 'list_identifier';
28
29
    /**
30
     * @var EntityManagerInterface
31
     */
32
    private $em;
33
34
    /**
35
     * @var QueryBuilder
36
     */
37
    private $queryBuilder;
38
39
    /**
40
     * @var QueryTypeLocator
41
     */
42
    private $queryTypeLocator;
43
44
    /**
45
     * @var SelectorTypeLocator
46
     */
47
    private $selectorTypeLocator;
48
49
    /**
50
     * @var ConfigurationResolver
51
     */
52
    private $configuration;
53
54
    /**
55
     * @var bool
56
     */
57
    private $hasAggregation = false;
58
59
    /**
60
     * ListQueryBuilder constructor.
61
     *
62
     * @param EntityManagerInterface $entityManager
63
     * @param QueryTypeLocator       $queryTypeLocator
64
     * @param SelectorTypeLocator    $selectorTypeLocator
65
     * @param ConfigurationResolver  $configuration
66 8
     */
67
    public function __construct(
68
        EntityManagerInterface $entityManager,
69
        QueryTypeLocator $queryTypeLocator,
70
        SelectorTypeLocator $selectorTypeLocator,
71
        ConfigurationResolver $configuration
72 8
    ) {
73 8
        $this->em = $entityManager;
74 8
        $this->queryTypeLocator = $queryTypeLocator;
75 8
        $this->selectorTypeLocator = $selectorTypeLocator;
76 8
        $this->configuration = $configuration;
77
    }
78
79
    /**
80
     * @param ListInterface   $list
81
     * @param JoinMapper      $joinMapper
82
     * @param ListMapper      $listMapper
83
     * @param FilterMapper    $filterMapper
84
     * @param ListValueInterface $listValue
85
     *
86
     * @return QueryBuilder
87 6
     */
88
    public function buildQuery(
89
        ListInterface $list,
90
        JoinMapper $joinMapper,
91
        ListMapper $listMapper,
92
        FilterMapper $filterMapper,
93
        ListValueInterface $listValue
94 6
    ): QueryBuilder {
95 6
        $this->hasAggregation = false;
96 6
        $this->queryBuilder = $this->em->createQueryBuilder()
97
            ->from($list->getDataClass(), $this->configuration->getAlias());
98 6
99 6
        $this->applyJoins($joinMapper, false);
100 4
        $this->applySelects($listMapper, $joinMapper, false);
101 3
        $this->applyFilter($filterMapper, $joinMapper);
102 3
        $this->applyGroup();
103
        $list->configureQuery($this->queryBuilder, $listValue);
104 3
105
        return $this->queryBuilder;
106
    }
107
108
    /**
109
     * @param ListInterface $list
110
     * @param JoinMapper    $joinMapper
111
     * @param ListMapper    $listMapper
112
     *
113
     * @return QueryBuilder|null
114 2
     */
115
    public function buildLazyQuery(
116
        ListInterface $list,
117
        JoinMapper $joinMapper,
118
        ListMapper $listMapper
119 2
    ): ?QueryBuilder {
120 1
        if ($listMapper->getFields(true)->isEmpty()) {
121
            return null;
122
        }
123 1
124 1
        $this->hasAggregation = false;
125 1
        $this->queryBuilder = $this->em->createQueryBuilder()
126 1
            ->from($list->getDataClass(), $this->configuration->getAlias());
127 1
        $this->applyJoins($joinMapper, true);
128 1
        $this->applySelects($listMapper, $joinMapper, true);
129
        $this->applyGroup();
130 1
131
        return $this->queryBuilder;
132
    }
133
134
    /**
135
     * Adds join dql parts.
136
     *
137
     * @param JoinMapper $joinMapper
138
     * @param bool       $lazy
139 7
     */
140
    private function applyJoins(JoinMapper $joinMapper, bool $lazy): void
141 7
    {
142 1
        foreach ($joinMapper->getFields($lazy) as $field) {
143
            $joinPath = $field->getJoinPath($this->configuration->getAlias());
144 1
145 1
            if ($field->getOption(JoinField::OPTION_JOIN_TYPE) === JoinField::JOIN_INNER) {
146
                $this->queryBuilder->innerJoin($joinPath, $field->getAlias());
147 1
            } else {
148
                $this->queryBuilder->leftJoin($joinPath, $field->getAlias());
149
            }
150 7
        }
151
    }
152
153
    /**
154
     * Adds select and sort DQL parts
155
     *
156
     * @param ListMapper $listMapper
157
     * @param JoinMapper $joinMapper
158
     * @param bool       $lazy
159 7
     */
160
    private function applySelects(ListMapper $listMapper, JoinMapper $joinMapper, bool $lazy): void
161 7
    {
162 6
        if ($lazy === false) {
163 6
            $idSelector = sprintf(
164 6
                '%s.%s as %s',
165 6
                $this->configuration->getAlias(),
166 6
                $this->configuration->getIdentifier(),
167
                self::IDENTIFIER_ALIAS
168 6
            );
169
            $this->queryBuilder->addSelect($idSelector);
170
        }
171 7
172 3
        foreach ($listMapper->getFields($lazy) as $field) {
173 2
            $paths = $this->parsePaths($joinMapper, $field->getPaths(), $lazy);
174
            $selectorType = $field->getOption(ListField::OPTION_SELECTOR);
175 2
176 1
            if (!$this->selectorTypeLocator->has($selectorType)) {
177
                throw ListFieldException::invalidType($field->getId(), $selectorType, SelectorTypeInterface::class);
178
            }
179 1
180 1
            $selectorType = $this->selectorTypeLocator->get($selectorType);
181
            $selectorType->apply($this->queryBuilder, $paths, $field->getId());
182 1
183 1
            if ($selectorType->hasAggregation()) {
184
                $this->hasAggregation = true;
185
            }
186 1
187 1
            if (
188
                false === $lazy && $field->getOption(ListField::OPTION_SORTABLE) &&
189 1
                ($dir = $field->getOption(ListField::OPTION_SORT_VALUE))
190 1
            ) {
191
                if ($sortPath = $field->getOption(ListField::OPTION_SORT_PATH)) {
192 1
                    $select = $this->parsePaths($joinMapper, (array) $sortPath, false)[0];
193
                } else {
194
                    $select = $selectorType->getSortPath($field->getId());
195 1
                }
196
197
                $this->queryBuilder->addOrderBy($select, $dir);
198 5
            }
199
        }
200
    }
201
202
    /**
203
     * @param FilterMapper $filterMapper
204 4
     * @param JoinMapper   $joinMapper
205
     */
206 4
    private function applyFilter(FilterMapper $filterMapper, JoinMapper $joinMapper): void
207 2
    {
208 1
        foreach ($filterMapper->getFields() as $field) {
209
            if (!$field->hasValue() || false === $field->getOption(FilterField::OPTION_MAPPED)) {
210
                continue;
211 2
            }
212
213 2
            $queryType = $field->getOption(FilterField::OPTION_QUERY_TYPE);
214 1
215
            if (!$this->queryTypeLocator->has($queryType)) {
216
                throw ListFieldException::invalidType($field->getId(), $queryType, QueryTypeInterface::class);
217 1
            }
218 1
219 1
            $paths = $this->parsePaths($joinMapper, $field->getPaths(), false);
220 1
            $queryType = $this->queryTypeLocator->get($queryType);
221 1
            $resolver = new OptionsResolver();
222 1
            $queryType->configureOptions($resolver);
223
            $queryType->setOptions($resolver->resolve($field->getOption(FilterField::OPTION_QUERY_OPTIONS)));
224 1
            $queryType->filter($this->queryBuilder, $paths, $field->getId(), $field->getValue());
225 1
226
            if ($queryType->hasAggregation()) {
227
                $this->hasAggregation = true;
228 3
            }
229
        }
230
    }
231
232
    /**
233 4
     * Applies group by identifier if query has aggregations
234
     */
235 4
    private function applyGroup(): void
236 2
    {
237 2
        if ($this->hasAggregation) {
238
            $statement = sprintf('%s.%s', $this->configuration->getAlias(), $this->configuration->getIdentifier());
239 2
            $this->queryBuilder->groupBy($statement);
240
        } else {
241 4
            $this->queryBuilder->distinct();
242
        }
243
    }
244
245
    /**
246
     * @param JoinMapper $joinMapper
247
     * @param array      $paths
248
     * @param bool       $lazy
249
     *
250 4
     * @return array
251
     */
252 4
    private function parsePaths(JoinMapper $joinMapper, array $paths, bool $lazy): array
253
    {
254 4
        $parsedPaths = [];
255 3
256
        foreach ($paths as $path) {
257 3
            $pathElements = explode('.', $path);
258 2
259 2
            if (count($pathElements) === 1) {
260
                $prop = $pathElements[0];
261 3
                $path = null;
262 3
            } else {
263
                $prop = array_pop($pathElements);
264
                $path = implode('.', $pathElements);
265 3
            }
266 3
267 1
            if (null !== $path) {
268
                if (!$joinField = $joinMapper->getByPath($path, $lazy)) {
269
                    throw ListFieldException::invalidPath($path);
270 2
                }
271
272 2
                $alias = $joinField->getAlias();
273
            } else {
274
                $alias = $this->configuration->getAlias();
275 2
            }
276
277
            $parsedPaths[] = sprintf('%s.%s', $alias, $prop);
278 3
        }
279
280
        return $parsedPaths;
281
    }
282
}
283