SpecificationVisitor::visitValue()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * This file is part of the Cubiche package.
5
 *
6
 * Copyright (c) Cubiche
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Cubiche\Infrastructure\Repository\Doctrine\ODM\MongoDB\Query;
12
13
use Cubiche\Core\Collections\ArrayCollection\ArrayHashMap;
14
use Cubiche\Core\Delegate\Delegate;
15
use Cubiche\Core\Selector\Callback;
16
use Cubiche\Core\Selector\Composite;
17
use Cubiche\Core\Selector\Count;
18
use Cubiche\Core\Selector\Field;
19
use Cubiche\Core\Selector\Key;
20
use Cubiche\Core\Selector\Method;
21
use Cubiche\Core\Selector\Property;
22
use Cubiche\Core\Selector\SelectorInterface;
23
use Cubiche\Core\Selector\This;
24
use Cubiche\Core\Selector\Value;
25
use Cubiche\Core\Specification\AndSpecification;
26
use Cubiche\Core\Specification\Constraint\BinaryConstraintOperator;
27
use Cubiche\Core\Specification\Constraint\Equal;
28
use Cubiche\Core\Specification\Constraint\GreaterThan;
29
use Cubiche\Core\Specification\Constraint\GreaterThanEqual;
30
use Cubiche\Core\Specification\Constraint\LessThan;
31
use Cubiche\Core\Specification\Constraint\LessThanEqual;
32
use Cubiche\Core\Specification\Constraint\NotEqual;
33
use Cubiche\Core\Specification\Constraint\NotSame;
34
use Cubiche\Core\Specification\Constraint\Same;
35
use Cubiche\Core\Specification\Criteria;
36
use Cubiche\Core\Specification\NotSpecification;
37
use Cubiche\Core\Specification\OrSpecification;
38
use Cubiche\Core\Specification\Quantifier\All;
39
use Cubiche\Core\Specification\Quantifier\AtLeast;
40
use Cubiche\Core\Specification\Selector;
41
use Cubiche\Core\Specification\SpecificationInterface;
42
use Cubiche\Core\Visitor\Visitor;
43
use Cubiche\Domain\Model\IdInterface;
44
45
/**
46
 * Specification Visitor Class.
47
 *
48
 * @author Karel Osorio Ramírez <[email protected]>
49
 */
50
class SpecificationVisitor extends Visitor
51
{
52
    use VisitorTrait;
53
54
    /**
55
     * @param QueryBuilder $queryBuilder
56
     */
57
    public function __construct(QueryBuilder $queryBuilder)
58
    {
59
        parent::__construct();
60
61
        $this->queryBuilder = $queryBuilder;
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function visitAndSpecification(AndSpecification $specification)
68
    {
69
        $leftQueryBuilder = $this->queryBuilderFromSpecification($specification->left());
70
        $rightQueryBuilder = $this->queryBuilderFromSpecification($specification->right());
71
72
        if ($this->hasSameOperator($leftQueryBuilder, $rightQueryBuilder)) {
73
            $this->queryBuilder->addAnd($leftQueryBuilder->getExpr());
74
            $this->queryBuilder->addAnd($rightQueryBuilder->getExpr());
75
        } else {
76
            $this->queryBuilder->addSearchCriteria($specification->left());
77
            $this->queryBuilder->addSearchCriteria($specification->right());
78
        }
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function visitOrSpecification(OrSpecification $specification)
85
    {
86
        $leftQueryBuilder = $this->queryBuilderFromSpecification($specification->left());
87
        $rightQueryBuilder = $this->queryBuilderFromSpecification($specification->right());
88
89
        $this->queryBuilder->addOr($leftQueryBuilder->getExpr());
90
        $this->queryBuilder->addOr($rightQueryBuilder->getExpr());
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function visitNotSpecification(NotSpecification $specification)
97
    {
98
        $specificationQueryBuilder = $this->queryBuilderFromSpecification($specification->specification());
99
        $this->queryBuilder->not($specificationQueryBuilder->getExpr());
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function visitSelector(Selector $selector)
106
    {
107
        return $selector->selector()->accept($this);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function visitValue(Value $specification)
114
    {
115
        throw $this->notSupportedException($specification);
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function visitKey(Key $specification)
122
    {
123
        $this->visitField($specification);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function visitProperty(Property $specification)
130
    {
131
        $this->visitField($specification);
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function visitMethod(Method $specification)
138
    {
139
        throw $this->notSupportedException($specification);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function visitThis(This $specification)
146
    {
147
        throw $this->notSupportedException($specification);
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public function visitCallback(Callback $specification)
154
    {
155
        throw $this->notSupportedException($specification);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function visitComposite(Composite $specification)
162
    {
163
        $this->visitField($this->createFieldFromComposite($specification));
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    public function visitCount(Count $specification)
170
    {
171
        throw $this->notSupportedException($specification);
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function visitGreaterThan(GreaterThan $specification)
178
    {
179
        $this->visitRelationalOperator($specification, Delegate::fromMethod($this->queryBuilder, 'gt'));
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185
    public function visitGreaterThanEqual(GreaterThanEqual $specification)
186
    {
187
        $this->visitRelationalOperator($specification, Delegate::fromMethod($this->queryBuilder, 'gte'));
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    public function visitLessThan(LessThan $specification)
194
    {
195
        $this->visitRelationalOperator($specification, Delegate::fromMethod($this->queryBuilder, 'lt'));
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function visitLessThanEqual(LessThanEqual $specification)
202
    {
203
        $this->visitRelationalOperator($specification, Delegate::fromMethod($this->queryBuilder, 'lte'));
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function visitEqual(Equal $specification)
210
    {
211
        $this->visitEqualityOperator($specification);
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function visitNotEqual(NotEqual $specification)
218
    {
219
        $this->visitNotEqualityOperator($specification);
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function visitSame(Same $specification)
226
    {
227
        $this->visitEqualityOperator($specification);
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233
    public function visitNotSame(NotSame $specification)
234
    {
235
        $this->visitNotEqualityOperator($specification);
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241
    public function visitAll(All $specification)
242
    {
243
        $field = $this->createField($specification->selector());
244
        $specificationQueryBuilder = $this->queryBuilderFromSpecification($specification->specification());
245
        $this->queryBuilder
246
            ->field($field->name())->all(
247
                $this->queryBuilder->expr()->elemMatch($specificationQueryBuilder->getExpr())->getQuery()
248
            );
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public function visitAtLeast(AtLeast $specification)
255
    {
256
        if ($specification->count() === 1) {
257
            $field = $this->createField($specification->selector());
258
            $specificationQueryBuilder = $this->queryBuilderFromSpecification($specification->specification());
259
            $this->queryBuilder->field($field->name())->elemMatch($specificationQueryBuilder->getExpr());
260
        } else {
261
            throw $this->notSupportedException($specification);
262
        }
263
    }
264
265
    /**
266
     * @param QueryBuilder $queryBuilder1
267
     * @param QueryBuilder $queryBuilder2
268
     *
269
     * @return bool
270
     */
271
    protected function hasSameOperator(QueryBuilder $queryBuilder1, QueryBuilder $queryBuilder2)
272
    {
273
        $intersection = new ArrayHashMap(
274
            \array_intersect_key($queryBuilder1->getQueryArray(), $queryBuilder2->getQueryArray())
275
        );
276
277
        return $intersection->keys()->findOne(Criteria::callback(function ($value) {
278
            return \strpos($value, '$') === 0;
279
        })) !== null;
280
    }
281
282
    /**
283
     * @param BinaryConstraintOperator $operator
284
     * @param Delegate                 $addOperator
285
     */
286
    protected function visitRelationalOperator(BinaryConstraintOperator $operator, Delegate $addOperator)
287
    {
288
        $this->addFieldValueOperator($operator->left(), $operator->right(), $addOperator);
289
    }
290
291
    /**
292
     * @param BinaryConstraintOperator $operator
293
     */
294
    protected function visitEqualityOperator(BinaryConstraintOperator $operator)
295
    {
296
        $selector = $operator->left();
297
        if ($selector instanceof Composite && $selector->applySelector() instanceof Count) {
298
            $this->addFieldValueOperator(
299
                $selector->valueSelector(),
300
                $operator->right(),
301
                Delegate::fromMethod($this->queryBuilder, 'size')
302
            );
303
        } else {
304
            $this->visitRelationalOperator($operator, Delegate::fromMethod($this->queryBuilder, 'equals'));
305
        }
306
    }
307
308
    /**
309
     * @param BinaryConstraintOperator $operator
310
     */
311
    protected function visitNotEqualityOperator(BinaryConstraintOperator $operator)
312
    {
313
        $this->visitRelationalOperator($operator, Delegate::fromMethod($this->queryBuilder, 'notEqual'));
314
    }
315
316
    /**
317
     * @param SelectorInterface $selector
318
     * @param SelectorInterface $value
319
     * @param Delegate          $addOperator
320
     */
321
    private function addFieldValueOperator(
322
        SelectorInterface $selector,
323
        SelectorInterface $value,
324
        Delegate $addOperator
325
    ) {
326
        $actualValue = $this->createValue($value);
327
        $isEntityValue = false;
328
        if ($actualValue instanceof IdInterface) {
329
            $actualValue = $actualValue->toNative();
330
            $isEntityValue = true;
331
        }
332
333
        $field = $this->createField($selector, $isEntityValue);
334
        if ($field !== null) {
335
            $this->queryBuilder->field($field->name());
336
        }
337
        $addOperator($actualValue);
338
    }
339
340
    /**
341
     * @param Field $field
342
     */
343
    protected function visitField(Field $field)
344
    {
345
        $this->queryBuilder->field($field->name())->equals(true);
346
    }
347
348
    /**
349
     * @param SpecificationInterface $specification
350
     *
351
     * @return QueryBuilder
352
     */
353
    protected function queryBuilderFromSpecification(SpecificationInterface $specification)
354
    {
355
        return $this->queryBuilder->createQueryBuilder()->addSearchCriteria($specification);
356
    }
357
}
358