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
|
|
|
|
12
|
|
|
namespace Cubiche\Infrastructure\Repository\Doctrine\Tests\Units\ODM\MongoDB\Query; |
13
|
|
|
|
14
|
|
|
use Cubiche\Core\Selector\Composite; |
15
|
|
|
use Cubiche\Core\Selector\Field; |
16
|
|
|
use Cubiche\Core\Selector\Property; |
17
|
|
|
use Cubiche\Core\Specification\AndSpecification; |
18
|
|
|
use Cubiche\Core\Specification\Criteria; |
19
|
|
|
use Cubiche\Core\Specification\NotSpecification; |
20
|
|
|
use Cubiche\Core\Specification\SpecificationInterface; |
21
|
|
|
use Cubiche\Domain\Repository\Tests\Fixtures\User; |
22
|
|
|
use Cubiche\Domain\Repository\Tests\Fixtures\UserId; |
23
|
|
|
use Cubiche\Infrastructure\Repository\Doctrine\ODM\MongoDB\Query\QueryBuilder; |
24
|
|
|
use Cubiche\Infrastructure\Repository\Doctrine\ODM\MongoDB\Query\SpecificationVisitor; |
25
|
|
|
use Cubiche\Infrastructure\Repository\Doctrine\Tests\Units\ODM\MongoDB\TestCase; |
26
|
|
|
use Cubiche\Core\Visitor\VisiteeInterface; |
27
|
|
|
use Cubiche\Core\Selector\SelectorInterface; |
28
|
|
|
use Cubiche\Core\Specification\Selector; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Specification Visitor Tests Class. |
32
|
|
|
* |
33
|
|
|
* @author Karel Osorio Ramírez <[email protected]> |
34
|
|
|
*/ |
35
|
|
|
class SpecificationVisitorTests extends TestCase |
36
|
|
|
{ |
37
|
|
|
/** |
38
|
|
|
* Test visitAnd. |
39
|
|
|
*/ |
40
|
|
|
public function testVisitAnd() |
41
|
|
|
{ |
42
|
|
|
$this->visitTest( |
43
|
|
|
Criteria::property('foo')->eq(10)->andX(Criteria::property('bar')->eq(20)), |
44
|
|
|
function () { |
45
|
|
|
return $this->createQueryBuilder() |
46
|
|
|
->field('foo')->equals(10) |
47
|
|
|
->field('bar')->equals(20); |
48
|
|
|
} |
49
|
|
|
); |
50
|
|
|
|
51
|
|
|
$this->visitTest( |
52
|
|
|
Criteria::property('foo')->gt(10)->andX(Criteria::property('foo')->lt(20)), |
53
|
|
|
function () { |
54
|
|
|
return $this->createQueryBuilder() |
55
|
|
|
->field('foo')->gt(10) |
56
|
|
|
->field('foo')->lt(20); |
57
|
|
|
} |
58
|
|
|
); |
59
|
|
|
|
60
|
|
|
$this->visitTest( |
61
|
|
|
new AndSpecification( |
62
|
|
|
Criteria::property('foo')->lt(20)->orX(Criteria::property('bar')->lt(20)), |
63
|
|
|
Criteria::property('foo')->gt(10)->orX(Criteria::property('foo')->lt(25)) |
64
|
|
|
), |
65
|
|
|
function () { |
66
|
|
|
$qb = $this->createQueryBuilder(); |
67
|
|
|
|
68
|
|
|
return $qb |
69
|
|
|
->addAnd($qb->expr() |
70
|
|
|
->addOr($qb->expr()->field('foo')->lt(20)) |
71
|
|
|
->addOr($qb->expr()->field('bar')->lt(20))) |
72
|
|
|
->addAnd($qb->expr() |
73
|
|
|
->addOr($qb->expr()->field('foo')->gt(10)) |
74
|
|
|
->addOr($qb->expr()->field('foo')->lt(25))); |
75
|
|
|
} |
76
|
|
|
); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Test visitOr. |
81
|
|
|
*/ |
82
|
|
View Code Duplication |
public function testVisitOr() |
|
|
|
|
83
|
|
|
{ |
84
|
|
|
$this->visitTest( |
85
|
|
|
Criteria::property('foo')->eq(10)->orX(Criteria::property('bar')->eq(20)), |
86
|
|
|
function () { |
87
|
|
|
$qb = $this->createQueryBuilder(); |
88
|
|
|
|
89
|
|
|
return $qb |
90
|
|
|
->addOr($qb->expr()->field('foo')->equals(10)) |
91
|
|
|
->addOr($qb->expr()->field('bar')->equals(20)); |
92
|
|
|
} |
93
|
|
|
); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Test visitOr. |
98
|
|
|
*/ |
99
|
|
|
public function testVisitNot() |
100
|
|
|
{ |
101
|
|
|
$this->visitTest(new NotSpecification(Criteria::property('foo')->eq(10)), function () { |
102
|
|
|
$qb = $this->createQueryBuilder(); |
103
|
|
|
|
104
|
|
|
return $qb->not($qb->expr()->field('foo')->equals(10)); |
105
|
|
|
}); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Test visitValue. |
110
|
|
|
*/ |
111
|
|
|
public function testVisitValue() |
112
|
|
|
{ |
113
|
|
|
$this->notSupportedOperationTest(Criteria::false()->selector()); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Test visitValue. |
118
|
|
|
*/ |
119
|
|
|
public function testVisitKey() |
120
|
|
|
{ |
121
|
|
|
$this->visitFieldTest(Criteria::key('foo')->selector()); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Test visitProperty. |
126
|
|
|
*/ |
127
|
|
|
public function testVisitPorperty() |
128
|
|
|
{ |
129
|
|
|
$this->visitFieldTest(Criteria::property('foo')->selector()); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Test visitMethod. |
134
|
|
|
*/ |
135
|
|
|
public function testVisitMethod() |
136
|
|
|
{ |
137
|
|
|
$this->notSupportedOperationTest(Criteria::method('foo')->selector()); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Test visitCallback. |
142
|
|
|
*/ |
143
|
|
|
public function testVisitCallback() |
144
|
|
|
{ |
145
|
|
|
$this->notSupportedOperationTest(Criteria::callback(function () { |
146
|
|
|
|
147
|
|
|
})->selector()); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Test visitThis. |
152
|
|
|
*/ |
153
|
|
|
public function testVisitThis() |
154
|
|
|
{ |
155
|
|
|
$this->notSupportedOperationTest(Criteria::this()->selector()); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Test visitComposite. |
160
|
|
|
*/ |
161
|
|
|
public function testVisitComposite() |
162
|
|
|
{ |
163
|
|
|
$this->visitFieldTest(Criteria::property('foo')->property('bar')->selector(), 'foo.bar'); |
164
|
|
|
$this->visitFieldTest(Criteria::property('foo')->property('bar')->key('length')->selector(), 'foo.bar.length'); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Test visitCount. |
169
|
|
|
*/ |
170
|
|
|
public function testVisitCount() |
171
|
|
|
{ |
172
|
|
|
$this->notSupportedOperationTest(Criteria::count()->selector()); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Test visitGreaterThan. |
177
|
|
|
*/ |
178
|
|
|
public function tesVisittGreaterThan() |
179
|
|
|
{ |
180
|
|
|
$this->visitTest(Criteria::property('foo')->gt(10), function () { |
181
|
|
|
return $this->createQueryBuilder()->field('foo')->gt(10); |
182
|
|
|
}); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Test visitGreaterThanEqual. |
187
|
|
|
*/ |
188
|
|
|
public function testVisitGreaterThanEqual() |
189
|
|
|
{ |
190
|
|
|
$this->visitTest(Criteria::property('foo')->gte(10), function () { |
191
|
|
|
return $this->createQueryBuilder()->field('foo')->gte(10); |
192
|
|
|
}); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Test visitLessThan. |
197
|
|
|
*/ |
198
|
|
|
public function testVisitLessThan() |
199
|
|
|
{ |
200
|
|
|
$this->visitTest(Criteria::property('foo')->lt(10), function () { |
201
|
|
|
return $this->createQueryBuilder()->field('foo')->lt(10); |
202
|
|
|
}); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Test visitLessThanEqual. |
207
|
|
|
*/ |
208
|
|
|
public function testVisitLessThanEqual() |
209
|
|
|
{ |
210
|
|
|
$this->visitTest(Criteria::property('foo')->lte(10), function () { |
211
|
|
|
return $this->createQueryBuilder()->field('foo')->lte(10); |
212
|
|
|
}); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Test visitEqual. |
217
|
|
|
*/ |
218
|
|
|
public function testVisitEqual() |
219
|
|
|
{ |
220
|
|
|
$this->visitTest(Criteria::property('foo')->eq(10), function () { |
221
|
|
|
return $this->createQueryBuilder()->field('foo')->equals(10); |
222
|
|
|
}); |
223
|
|
|
|
224
|
|
|
$this->visitTest(Criteria::property('foo')->count()->eq(10), function () { |
225
|
|
|
return $this->createQueryBuilder()->field('foo')->size(10); |
226
|
|
|
}); |
227
|
|
|
|
228
|
|
|
$user = new User(UserId::next(), 'user', 20, $this->faker->email); |
229
|
|
|
|
230
|
|
|
$this->visitTest(Criteria::eq($user), function () use ($user) { |
231
|
|
|
return $this->createQueryBuilder()->field('id')->equals($user->id()->toNative()); |
232
|
|
|
}); |
233
|
|
|
|
234
|
|
|
$this->visitTest(Criteria::property('id')->eq($user->id()), function () use ($user) { |
235
|
|
|
return $this->createQueryBuilder()->field('id')->equals($user->id()->toNative()); |
236
|
|
|
}); |
237
|
|
|
|
238
|
|
|
$this->logicExceptionTest( |
239
|
|
|
Criteria::callback(function () { |
240
|
|
|
|
241
|
|
|
})->eq(10) |
242
|
|
|
); |
243
|
|
|
|
244
|
|
|
$this->logicExceptionTest(Criteria::property('foo')->eq(Criteria::property('bar'))); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Test visitNotEqual. |
249
|
|
|
*/ |
250
|
|
|
public function testVisitNotEqual() |
251
|
|
|
{ |
252
|
|
|
$this->visitTest(Criteria::property('foo')->neq(10), function () { |
253
|
|
|
return $this->createQueryBuilder()->field('foo')->notEqual(10); |
254
|
|
|
}); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Test visitSame. |
259
|
|
|
*/ |
260
|
|
|
public function testVisitSame() |
261
|
|
|
{ |
262
|
|
|
$this->visitTest(Criteria::property('foo')->same(10), function () { |
263
|
|
|
return $this->createQueryBuilder()->field('foo')->equals(10); |
264
|
|
|
}); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Test visitNotSame. |
269
|
|
|
*/ |
270
|
|
|
public function testVisitNotSame() |
271
|
|
|
{ |
272
|
|
|
$this->visitTest(Criteria::property('foo')->notsame(10), function () { |
273
|
|
|
return $this->createQueryBuilder()->field('foo')->notEqual(10); |
274
|
|
|
}); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Test visitAll. |
279
|
|
|
*/ |
280
|
|
View Code Duplication |
public function testVisitAll() |
|
|
|
|
281
|
|
|
{ |
282
|
|
|
$this->visitTest(Criteria::property('foo')->all(Criteria::property('bar')->gt(10)), function () { |
283
|
|
|
$qb = $this->createQueryBuilder(); |
284
|
|
|
|
285
|
|
|
return $qb->field('foo')->all( |
286
|
|
|
$qb->expr()->elemMatch( |
287
|
|
|
$qb->expr()->field('bar')->gt(10) |
288
|
|
|
)->getQuery() |
289
|
|
|
); |
290
|
|
|
}); |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Test visitAtLeast. |
295
|
|
|
*/ |
296
|
|
|
public function testAtLeast() |
297
|
|
|
{ |
298
|
|
|
$this->visitTest(Criteria::property('foo')->any(Criteria::property('bar')->gt(10)), function () { |
299
|
|
|
$qb = $this->createQueryBuilder(); |
300
|
|
|
|
301
|
|
|
return $qb->field('foo') |
302
|
|
|
->elemMatch( |
303
|
|
|
$qb->expr()->field('bar')->gt(10) |
304
|
|
|
); |
305
|
|
|
}); |
306
|
|
|
|
307
|
|
|
$this->notSupportedOperationTest(Criteria::property('foo')->atLeast(2, Criteria::property('bar')->gt(10))); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @param SpecificationInterface|SelectorInterface $criteria |
312
|
|
|
* @param \Closure $expectedCreate |
313
|
|
|
*/ |
314
|
|
|
protected function visitTest($criteria, \Closure $expectedCreate) |
315
|
|
|
{ |
316
|
|
|
$this |
317
|
|
|
->given($qb = $this->createQueryBuilder()) |
318
|
|
|
->given($visitor = $this->createVisitor($qb)) |
319
|
|
|
->given($criteria) |
320
|
|
|
->given($expected = $expectedCreate()) |
321
|
|
|
->when($criteria->accept($visitor)) |
322
|
|
|
->array($qb->getQueryArray()) |
323
|
|
|
->isEqualTo($expected->getQueryArray()) |
324
|
|
|
; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* @param Field|Composite $field |
329
|
|
|
* @param string $expected |
330
|
|
|
*/ |
331
|
|
|
protected function visitFieldTest($field, $expected = null) |
332
|
|
|
{ |
333
|
|
|
$expected = $expected === null && $field instanceof Field ? $field->name() : $expected; |
334
|
|
|
$this->visitTest($field, function () use ($expected) { |
335
|
|
|
return $this->createQueryBuilder()->field($expected)->equals(true); |
336
|
|
|
}); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* @param SpecificationInterface|SelectorInterface $criteria |
341
|
|
|
*/ |
342
|
|
|
protected function notSupportedOperationTest($criteria) |
343
|
|
|
{ |
344
|
|
|
$this->logicExceptionTest($criteria); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* @param VisiteeInterface $criteria |
349
|
|
|
*/ |
350
|
|
|
private function logicExceptionTest($criteria) |
351
|
|
|
{ |
352
|
|
|
$this |
353
|
|
|
->given($qb = $this->createQueryBuilder()) |
354
|
|
|
->given($visitor = $this->createVisitor($qb)) |
355
|
|
|
->exception(function () use ($visitor, $criteria) { |
356
|
|
|
$criteria->accept($visitor); |
357
|
|
|
}) |
358
|
|
|
->isInstanceOf(\LogicException::class) |
359
|
|
|
; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* @param QueryBuilder $queryBuilder |
364
|
|
|
* |
365
|
|
|
* @return \Cubiche\Infrastructure\Repository\Doctrine\ODM\MongoDB\Query\SpecificationVisitor |
366
|
|
|
*/ |
367
|
|
|
protected function createVisitor(QueryBuilder $queryBuilder) |
368
|
|
|
{ |
369
|
|
|
return new SpecificationVisitor($queryBuilder); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* @return \Cubiche\Infrastructure\Repository\Doctrine\ODM\MongoDB\Query\QueryBuilder |
374
|
|
|
*/ |
375
|
|
|
protected function createQueryBuilder() |
376
|
|
|
{ |
377
|
|
|
return new QueryBuilder($this->dm(), User::class); |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.