ModelManagerTest::testCollections()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\DoctrineMongoDBAdminBundle\Tests\Model;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
18
use Doctrine\Common\Persistence\ObjectManager;
19
use Doctrine\ODM\MongoDB\DocumentManager;
20
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
21
use PHPUnit\Framework\MockObject\Stub;
22
use PHPUnit\Framework\TestCase;
23
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
24
use Sonata\AdminBundle\Datagrid\Datagrid;
25
use Sonata\AdminBundle\Datagrid\DatagridInterface;
26
use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription;
27
use Sonata\DoctrineMongoDBAdminBundle\Model\ModelManager;
28
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\AbstractDocument;
29
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\AssociatedDocument;
30
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\ContainerDocument;
31
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\EmbeddedDocument;
32
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\ProtectedDocument;
33
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\SimpleDocument;
34
use Sonata\DoctrineMongoDBAdminBundle\Tests\Fixtures\Document\SimpleDocumentWithPrivateSetter;
35
use Symfony\Bridge\Doctrine\ManagerRegistry;
36
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
37
use Symfony\Component\PropertyAccess\PropertyAccess;
38
use Symfony\Component\PropertyAccess\PropertyAccessor;
39
40
final class ModelManagerTest extends TestCase
41
{
42
    /**
43
     * @var PropertyAccessor
44
     */
45
    private $propertyAccessor;
46
47
    /**
48
     * @var Stub&ManagerRegistry
49
     */
50
    private $registry;
51
52
    protected function setUp(): void
53
    {
54
        parent::setUp();
55
56
        $this->registry = $this->createStub(ManagerRegistry::class);
57
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
58
    }
59
60
    /**
61
     * @dataProvider getWrongDocuments
62
     *
63
     * @param mixed $document
64
     */
65
    public function testNormalizedIdentifierException($document): void
66
    {
67
        $manager = new ModelManager($this->registry, $this->propertyAccessor);
68
69
        $this->expectException(\RuntimeException::class);
70
71
        $manager->getNormalizedIdentifier($document);
72
    }
73
74
    public function getWrongDocuments(): iterable
75
    {
76
        yield [0];
77
        yield [1];
78
        yield [false];
79
        yield [true];
80
        yield [[]];
81
        yield [''];
82
        yield ['sonata-project'];
83
    }
84
85
    public function testGetNormalizedIdentifierNull(): void
86
    {
87
        $manager = new ModelManager($this->registry, $this->propertyAccessor);
88
89
        $this->assertNull($manager->getNormalizedIdentifier(null));
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
90
    }
91
92
    public function testSortParameters(): void
93
    {
94
        $manager = new ModelManager($this->registry, $this->propertyAccessor);
95
96
        $datagrid1 = $this->createStub(Datagrid::class);
97
        $datagrid2 = $this->createStub(Datagrid::class);
98
99
        $field1 = new FieldDescription();
100
        $field1->setName('field1');
101
102
        $field2 = new FieldDescription();
103
        $field2->setName('field2');
104
105
        $field3 = new FieldDescription();
106
        $field3->setName('field3');
107
        $field3->setOption('sortable', 'field3sortBy');
108
109
        $datagrid1
110
            ->method('getValues')
111
            ->willReturn([
112
                '_sort_by' => $field1,
113
                '_sort_order' => 'ASC',
114
            ]);
115
116
        $datagrid2
117
            ->method('getValues')
118
            ->willReturn([
119
                '_sort_by' => $field3,
120
                '_sort_order' => 'ASC',
121
            ]);
122
123
        $parameters = $manager->getSortParameters($field1, $datagrid1);
124
125
        $this->assertSame('DESC', $parameters['filter']['_sort_order']);
126
        $this->assertSame('field1', $parameters['filter']['_sort_by']);
127
128
        $parameters = $manager->getSortParameters($field2, $datagrid1);
129
130
        $this->assertSame('ASC', $parameters['filter']['_sort_order']);
131
        $this->assertSame('field2', $parameters['filter']['_sort_by']);
132
133
        $parameters = $manager->getSortParameters($field3, $datagrid1);
134
135
        $this->assertSame('ASC', $parameters['filter']['_sort_order']);
136
        $this->assertSame('field3sortBy', $parameters['filter']['_sort_by']);
137
138
        $parameters = $manager->getSortParameters($field3, $datagrid2);
139
140
        $this->assertSame('DESC', $parameters['filter']['_sort_order']);
141
        $this->assertSame('field3sortBy', $parameters['filter']['_sort_by']);
142
    }
143
144
    public function testGetParentMetadataForProperty(): void
145
    {
146
        $containerDocumentClass = ContainerDocument::class;
147
        $associatedDocumentClass = AssociatedDocument::class;
148
        $embeddedDocumentClass = EmbeddedDocument::class;
149
150
        $dm = $this->createStub(DocumentManager::class);
151
152
        $modelManager = new ModelManager($this->registry, $this->propertyAccessor);
153
154
        $this->registry
155
            ->method('getManagerForClass')
156
            ->willReturn($dm);
157
158
        $metadataFactory = $this->createStub(ClassMetadataFactory::class);
159
160
        $dm
161
            ->method('getMetadataFactory')
162
            ->willReturn($metadataFactory);
163
164
        $containerDocumentMetadata = $this->getMetadataForContainerDocument();
165
        $associatedDocumentMetadata = $this->getMetadataForAssociatedDocument();
166
        $embeddedDocumentMetadata = $this->getMetadataForEmbeddedDocument();
167
168
        $metadataFactory->method('getMetadataFor')
169
            ->willReturnMap(
170
                [
171
                    [$containerDocumentClass, $containerDocumentMetadata],
172
                    [$embeddedDocumentClass, $embeddedDocumentMetadata],
173
                    [$associatedDocumentClass, $associatedDocumentMetadata],
174
                ]
175
            );
176
177
        /** @var ClassMetadata $metadata */
178
        [$metadata, $lastPropertyName] = $modelManager
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $lastPropertyName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
179
            ->getParentMetadataForProperty($containerDocumentClass, 'plainField');
180
        $this->assertSame($metadata->fieldMappings[$lastPropertyName]['type'], 'integer');
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
181
182
        [$metadata, $lastPropertyName] = $modelManager
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
183
            ->getParentMetadataForProperty($containerDocumentClass, 'associatedDocument.plainField');
184
        $this->assertSame($metadata->fieldMappings[$lastPropertyName]['type'], 'string');
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
185
186
        [$metadata, $lastPropertyName] = $modelManager
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
187
            ->getParentMetadataForProperty($containerDocumentClass, 'embeddedDocument.plainField');
188
        $this->assertSame($metadata->fieldMappings[$lastPropertyName]['type'], 'boolean');
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
189
190
        $this->assertSame($metadata->fieldMappings[$lastPropertyName]['type'], 'boolean');
0 ignored issues
show
Bug introduced by
The variable $metadata does not exist. Did you mean $metadataFactory?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
191
    }
192
193
    public function testModelReverseTransformWithSetter(): void
194
    {
195
        $class = SimpleDocument::class;
196
197
        $manager = $this->createModelManagerForClass($class);
198
        $object = $manager->modelReverseTransform(
199
            $class,
200
            [
201
                'schmeckles' => 42,
202
                'multi_word_property' => 'hello',
203
                'schwifty' => true,
204
            ]
205
        );
206
        $this->assertInstanceOf($class, $object);
207
        $this->assertSame(42, $object->getSchmeckles());
208
        $this->assertSame('hello', $object->getMultiWordProperty());
209
        $this->assertTrue($object->schwifty);
210
    }
211
212
    public function testModelReverseTransformFailsWithPrivateSetter(): void
213
    {
214
        $class = SimpleDocumentWithPrivateSetter::class;
215
        $manager = $this->createModelManagerForClass($class);
216
217
        $this->expectException(NoSuchPropertyException::class);
218
219
        $manager->modelReverseTransform($class, ['schmeckles' => 42]);
220
    }
221
222
    public function testModelReverseTransformFailsWithPrivateProperties(): void
223
    {
224
        $class = SimpleDocument::class;
225
        $manager = $this->createModelManagerForClass($class);
226
227
        $this->expectException(NoSuchPropertyException::class);
228
229
        $manager->modelReverseTransform($class, ['plumbus' => 42]);
230
    }
231
232
    public function testModelReverseTransformFailsWithPrivateProperties2(): void
233
    {
234
        $class = SimpleDocument::class;
235
        $manager = $this->createModelManagerForClass($class);
236
237
        $this->expectException(NoSuchPropertyException::class);
238
239
        $manager->modelReverseTransform($class, ['plumbus' => 42]);
240
    }
241
242
    public function testCollections(): void
243
    {
244
        $model = new ModelManager($this->registry, $this->propertyAccessor);
245
246
        $collection = $model->getModelCollectionInstance('whyDoWeEvenHaveThisParameter');
247
        $this->assertInstanceOf(ArrayCollection::class, $collection);
248
249
        $item1 = new \stdClass();
250
        $item2 = new \stdClass();
251
        $model->collectionAddElement($collection, $item1);
0 ignored issues
show
Bug introduced by
It seems like $collection defined by $model->getModelCollecti...EvenHaveThisParameter') on line 246 can also be of type object<ArrayAccess>; however, Sonata\DoctrineMongoDBAd...:collectionAddElement() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
252
        $model->collectionAddElement($collection, $item2);
253
254
        $this->assertTrue($model->collectionHasElement($collection, $item1));
255
256
        $model->collectionRemoveElement($collection, $item1);
257
258
        $this->assertFalse($model->collectionHasElement($collection, $item1));
259
260
        $model->collectionClear($collection);
261
262
        $this->assertTrue($collection->isEmpty());
0 ignored issues
show
Bug introduced by
The method isEmpty cannot be called on $collection (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
263
    }
264
265
    public function testModelTransform(): void
266
    {
267
        $model = new ModelManager($this->registry, $this->propertyAccessor);
268
269
        $instance = new \stdClass();
270
        $result = $model->modelTransform('thisIsNotUsed', $instance);
271
272
        $this->assertSame($instance, $result);
273
    }
274
275
    public function testGetPaginationParameters(): void
276
    {
277
        $datagrid = $this->createMock(DatagridInterface::class);
278
        $fieldDescription = $this->createMock(FieldDescriptionInterface::class);
279
280
        $datagrid->expects($this->once())
281
            ->method('getValues')
282
            ->willReturn(['_sort_by' => $fieldDescription]);
283
284
        $fieldDescription->expects($this->once())
285
            ->method('getName')
286
            ->willReturn($name = 'test');
287
288
        $model = new ModelManager($this->registry, $this->propertyAccessor);
289
290
        $result = $model->getPaginationParameters($datagrid, $page = 5);
291
292
        $this->assertSame($page, $result['filter']['_page']);
293
        $this->assertSame($name, $result['filter']['_sort_by']);
294
    }
295
296
    public function testGetModelInstanceException(): void
297
    {
298
        $model = new ModelManager($this->registry, $this->propertyAccessor);
299
300
        $this->expectException(\InvalidArgumentException::class);
301
302
        $model->getModelInstance(AbstractDocument::class);
303
    }
304
305
    public function testGetModelInstanceForProtectedDocument(): void
306
    {
307
        $model = new ModelManager($this->registry, $this->propertyAccessor);
308
309
        $this->assertInstanceOf(ProtectedDocument::class, $model->getModelInstance(ProtectedDocument::class));
310
    }
311
312
    public function testFindBadId(): void
313
    {
314
        $model = new ModelManager($this->registry, $this->propertyAccessor);
315
316
        $this->assertNull($model->find('notImportant', null));
317
    }
318
319
    public function testGetUrlSafeIdentifierException(): void
320
    {
321
        $model = new ModelManager($this->registry, $this->propertyAccessor);
322
323
        $this->expectException(\RuntimeException::class);
324
325
        $model->getNormalizedIdentifier(new \stdClass());
326
    }
327
328
    public function testGetUrlSafeIdentifierNull(): void
329
    {
330
        $model = new ModelManager($this->registry, $this->propertyAccessor);
331
332
        $this->assertNull($model->getNormalizedIdentifier(null));
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
333
    }
334
335
    public function testGetNewFieldDescriptionInstanceCreatesAFieldDescription(): void
336
    {
337
        $dm = $this->createStub(DocumentManager::class);
338
339
        $this->registry
340
            ->method('getManagerForClass')
341
            ->willReturn($dm);
342
343
        $metadataFactory = $this->createStub(ClassMetadataFactory::class);
344
345
        $dm
346
            ->method('getMetadataFactory')
347
            ->willReturn($metadataFactory);
348
349
        $containerDocumentClass = ContainerDocument::class;
350
        $containerDocumentMetadata = $this->getMetadataForContainerDocument();
351
352
        $metadataFactory->method('getMetadataFor')
353
            ->willReturnMap(
354
                [
355
                    [$containerDocumentClass, $containerDocumentMetadata],
356
                ]
357
            );
358
359
        $modelManager = new ModelManager($this->registry, $this->propertyAccessor);
360
361
        $fieldDescription = $modelManager->getNewFieldDescriptionInstance($containerDocumentClass, 'plainField');
362
363
        $this->assertSame('edit', $fieldDescription->getOption('route')['name']);
364
        $this->assertSame(['fieldName' => 'plainField',
365
            'name' => 'plainField',
366
            'columnName' => 'plainField',
367
            'type' => 'integer', ], $fieldDescription->getFieldMapping());
368
    }
369
370
    private function createModelManagerForClass(string $class): ModelManager
371
    {
372
        $metadataFactory = $this->createMock(ClassMetadataFactory::class);
373
        $modelManager = $this->createMock(ObjectManager::class);
374
        $registry = $this->createMock(ManagerRegistry::class);
375
376
        $classMetadata = new ClassMetadata($class);
377
        $classMetadata->reflClass = new \ReflectionClass($class);
378
379
        $modelManager->expects($this->once())
380
            ->method('getMetadataFactory')
381
            ->willReturn($metadataFactory);
382
        $metadataFactory->expects($this->once())
383
            ->method('getMetadataFor')
384
            ->with($class)
385
            ->willReturn($classMetadata);
386
        $registry->expects($this->once())
387
            ->method('getManagerForClass')
388
            ->with($class)
389
            ->willReturn($modelManager);
390
391
        return new ModelManager($registry, $this->propertyAccessor);
392
    }
393
394
    private function getMetadataForEmbeddedDocument(): ClassMetadata
395
    {
396
        $metadata = new ClassMetadata(EmbeddedDocument::class);
397
398
        $metadata->fieldMappings = [
399
            'plainField' => [
400
                'fieldName' => 'plainField',
401
                'columnName' => 'plainField',
402
                'type' => 'boolean',
403
            ],
404
        ];
405
406
        return $metadata;
407
    }
408
409
    private function getMetadataForAssociatedDocument(): ClassMetadata
410
    {
411
        $embeddedDocumentClass = EmbeddedDocument::class;
412
413
        $metadata = new ClassMetadata(AssociatedDocument::class);
414
415
        $metadata->fieldMappings = [
416
            'plainField' => [
417
                'fieldName' => 'plainField',
418
                'name' => 'plainField',
419
                'columnName' => 'plainField',
420
                'type' => 'string',
421
            ],
422
        ];
423
424
        $metadata->mapOneEmbedded([
425
            'fieldName' => 'embeddedDocument',
426
            'name' => 'embeddedDocument',
427
            'targetDocument' => $embeddedDocumentClass,
428
        ]);
429
430
        return $metadata;
431
    }
432
433
    private function getMetadataForContainerDocument(): ClassMetadata
434
    {
435
        $containerDocumentClass = ContainerDocument::class;
436
        $associatedDocumentClass = AssociatedDocument::class;
437
        $embeddedDocumentClass = EmbeddedDocument::class;
438
439
        $metadata = new ClassMetadata($containerDocumentClass);
440
441
        $metadata->fieldMappings = [
442
            'plainField' => [
443
                'fieldName' => 'plainField',
444
                'name' => 'plainField',
445
                'columnName' => 'plainField',
446
                'type' => 'integer',
447
            ],
448
        ];
449
450
        $metadata->associationMappings['associatedDocument'] = [
451
            'fieldName' => 'associatedDocument',
452
            'name' => 'associatedDocument',
453
            'targetDocument' => $associatedDocumentClass,
454
            'sourceDocument' => $containerDocumentClass,
455
        ];
456
457
        $metadata->mapOneEmbedded([
458
            'fieldName' => 'embeddedDocument',
459
            'name' => 'embeddedDocument',
460
            'targetDocument' => $embeddedDocumentClass,
461
        ]);
462
463
        return $metadata;
464
    }
465
}
466