Completed
Pull Request — 3.x (#368)
by
unknown
01:20
created

ModelManager::collectionClear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\Model;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Persistence\Mapping\ClassMetadata as CommonClassMetadata;
18
use Doctrine\ODM\MongoDB\Query\Builder;
19
use Doctrine\Persistence\Mapping\ClassMetadata;
20
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
21
use Sonata\AdminBundle\Datagrid\DatagridInterface;
22
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
23
use Sonata\AdminBundle\Model\ModelManagerInterface;
24
use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription;
25
use Sonata\DoctrineMongoDBAdminBundle\Datagrid\ProxyQuery;
26
use Sonata\Exporter\Source\DoctrineODMQuerySourceIterator;
27
use Symfony\Bridge\Doctrine\ManagerRegistry;
28
use Symfony\Component\PropertyAccess\PropertyAccess;
29
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
30
31
class ModelManager implements ModelManagerInterface
32
{
33
    public const ID_SEPARATOR = '-';
34
35
    /**
36
     * @var ManagerRegistry
37
     */
38
    protected $registry;
39
40
    /**
41
     * @var PropertyAccessorInterface
42
     */
43
    private $propertyAccessor;
44
45
    public function __construct(ManagerRegistry $registry, ?PropertyAccessorInterface $propertyAccessor = null)
46
    {
47
        $this->registry = $registry;
48
        $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function getMetadata($class)
55
    {
56
        return $this->getDocumentManager($class)->getMetadataFactory()->getMetadataFor($class);
57
    }
58
59
    /**
60
     * Returns the model's metadata holding the fully qualified property, and the last
61
     * property name.
62
     *
63
     * @param string $baseClass        The base class of the model holding the fully qualified property
64
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
65
     *                                 property string)
66
     *
67
     * @return array(
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
68
     *                \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $parentMetadata,
69
     *                string $lastPropertyName,
70
     *                array $parentAssociationMappings
71
     *                )
72
     */
73
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
74
    {
75
        $nameElements = explode('.', $propertyFullName);
76
        $lastPropertyName = array_pop($nameElements);
77
        $class = $baseClass;
78
        $parentAssociationMappings = [];
79
80
        foreach ($nameElements as $nameElement) {
81
            $metadata = $this->getMetadata($class);
82
            $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
83
            $class = $metadata->getAssociationTargetClass($nameElement);
84
        }
85
86
        return [$this->getMetadata($class), $lastPropertyName, $parentAssociationMappings];
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function hasMetadata($class)
93
    {
94
        return $this->getDocumentManager($class)->getMetadataFactory()->hasMetadataFor($class);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function getNewFieldDescriptionInstance($class, $name, array $options = [])
101
    {
102
        if (!\is_string($name)) {
103
            throw new \RuntimeException('The name argument must be a string');
104
        }
105
106
        if (!isset($options['route']['name'])) {
107
            $options['route']['name'] = 'edit';
108
        }
109
110
        if (!isset($options['route']['parameters'])) {
111
            $options['route']['parameters'] = [];
112
        }
113
114
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
115
116
        $fieldDescription = new FieldDescription();
117
        $fieldDescription->setName($name);
118
        $fieldDescription->setOptions($options);
119
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
120
121
        /* @var ClassMetadata */
122
        if (isset($metadata->associationMappings[$propertyName])) {
123
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
124
        }
125
126
        if (isset($metadata->fieldMappings[$propertyName])) {
127
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
128
        }
129
130
        return $fieldDescription;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function create($object)
137
    {
138
        $documentManager = $this->getDocumentManager($object);
139
        $documentManager->persist($object);
140
        $documentManager->flush();
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function update($object)
147
    {
148
        $documentManager = $this->getDocumentManager($object);
149
        $documentManager->persist($object);
150
        $documentManager->flush();
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function delete($object)
157
    {
158
        $documentManager = $this->getDocumentManager($object);
159
        $documentManager->remove($object);
160
        $documentManager->flush();
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function find($class, $id)
167
    {
168
        if (!isset($id)) {
169
            return null;
170
        }
171
172
        $documentManager = $this->getDocumentManager($class);
173
174
        if (is_numeric($id)) {
175
            $value = $documentManager->getRepository($class)->find((int) $id);
176
177
            if (!empty($value)) {
178
                return $value;
179
            }
180
        }
181
182
        return $documentManager->getRepository($class)->find($id);
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function findBy($class, array $criteria = [])
189
    {
190
        return $this->getDocumentManager($class)->getRepository($class)->findBy($criteria);
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196
    public function findOneBy($class, array $criteria = [])
197
    {
198
        return $this->getDocumentManager($class)->getRepository($class)->findOneBy($criteria);
199
    }
200
201
    /**
202
     * @param object|string $class
203
     *
204
     * @throw \RuntimeException
205
     *
206
     * @return \Doctrine\ODM\MongoDB\DocumentManager
207
     */
208
    public function getDocumentManager($class)
209
    {
210
        if (\is_object($class)) {
211
            $class = \get_class($class);
212
        }
213
214
        $dm = $this->registry->getManagerForClass($class);
215
216
        if (!$dm) {
217
            throw new \RuntimeException(sprintf('No document manager defined for class %s', $class));
218
        }
219
220
        return $dm;
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226
    public function getParentFieldDescription($parentAssociationMapping, $class)
227
    {
228
        $fieldName = $parentAssociationMapping['fieldName'];
229
230
        $metadata = $this->getMetadata($class);
231
232
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
233
234
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
235
        $fieldDescription->setName($parentAssociationMapping);
0 ignored issues
show
Documentation introduced by
$parentAssociationMapping is of type array, but the function expects a string.

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...
236
        $fieldDescription->setAssociationMapping($associatingMapping);
237
238
        return $fieldDescription;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244
    public function createQuery($class, $alias = 'o')
245
    {
246
        $repository = $this->getDocumentManager($class)->getRepository($class);
247
248
        return new ProxyQuery($repository->createQueryBuilder());
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public function executeQuery($query)
255
    {
256
        if ($query instanceof Builder) {
257
            return $query->getQuery()->execute();
258
        }
259
260
        return $query->execute();
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266
    public function getModelIdentifier($class)
267
    {
268
        return $this->getMetadata($class)->identifier;
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274
    public function getIdentifierValues($document)
275
    {
276
        return [$this->getDocumentManager($document)->getUnitOfWork()->getDocumentIdentifier($document)];
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function getIdentifierFieldNames($class)
283
    {
284
        return [$this->getMetadata($class)->getIdentifier()];
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    public function getNormalizedIdentifier($document)
291
    {
292
        if (null === $document) {
293
            return null;
294
        }
295
296
        if (!\is_object($document)) {
297
            throw new \RuntimeException('Invalid argument, object or null required');
298
        }
299
300
        // the document is not managed
301
        if (!$this->getDocumentManager($document)->getUnitOfWork()->isInIdentityMap($document)) {
302
            return null;
303
        }
304
305
        $values = $this->getIdentifierValues($document);
306
307
        return implode(self::ID_SEPARATOR, $values);
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313
    public function getUrlSafeIdentifier($document)
314
    {
315
        return $this->getNormalizedIdentifier($document);
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
322
    {
323
        $queryBuilder = $queryProxy->getQueryBuilder();
324
        $queryBuilder->field('_id')->in($idx);
325
    }
326
327
    /**
328
     * {@inheritdoc}
329
     */
330
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
331
    {
332
        /** @var Query $queryBuilder */
333
        $queryBuilder = $queryProxy->getQuery();
334
335
        $documentManager = $this->getDocumentManager($class);
336
337
        $i = 0;
338
        foreach ($queryBuilder->execute() as $object) {
339
            $documentManager->remove($object);
340
341
            if (0 === (++$i % 20)) {
342
                $documentManager->flush();
343
                $documentManager->clear();
344
            }
345
        }
346
347
        $documentManager->flush();
348
        $documentManager->clear();
349
    }
350
351
    /**
352
     * {@inheritdoc}
353
     */
354
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
355
    {
356
        $datagrid->buildPager();
357
        $query = $datagrid->getQuery();
358
359
        $query->setFirstResult($firstResult);
360
        $query->setMaxResults($maxResult);
361
362
        return new DoctrineODMQuerySourceIterator($query instanceof ProxyQuery ? $query->getQuery() : $query, $fields);
0 ignored issues
show
Bug introduced by
The method getQuery() does not exist on Sonata\DoctrineMongoDBAd...dle\Datagrid\ProxyQuery. Did you maybe mean getQueryBuilder()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    public function getExportFields($class)
369
    {
370
        $metadata = $this->getDocumentManager($class)->getClassMetadata($class);
371
372
        return $metadata->getFieldNames();
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function getModelInstance($class)
379
    {
380
        if (!class_exists($class)) {
381
            throw new \InvalidArgumentException(sprintf('Class "%s" not found', $class));
382
        }
383
384
        $r = new \ReflectionClass($class);
385
        if ($r->isAbstract()) {
386
            throw new \InvalidArgumentException(sprintf('Cannot initialize abstract class: %s', $class));
387
        }
388
389
        $constructor = $r->getConstructor();
390
391
        if (null !== $constructor && (!$constructor->isPublic() || $constructor->getNumberOfRequiredParameters() > 0)) {
392
            return $r->newInstanceWithoutConstructor();
393
        }
394
395
        return new $class();
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     */
401
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
402
    {
403
        $values = $datagrid->getValues();
404
405
        if ($this->isFieldAlreadySorted($fieldDescription, $datagrid)) {
406
            if ('ASC' === $values['_sort_order']) {
407
                $values['_sort_order'] = 'DESC';
408
            } else {
409
                $values['_sort_order'] = 'ASC';
410
            }
411
        } else {
412
            $values['_sort_order'] = 'ASC';
413
        }
414
415
        $values['_sort_by'] = \is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
416
417
        return ['filter' => $values];
418
    }
419
420
    /**
421
     * {@inheritdoc}
422
     */
423
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
424
    {
425
        $values = $datagrid->getValues();
426
427
        if (isset($values['_sort_by']) && $values['_sort_by'] instanceof FieldDescriptionInterface) {
428
            $values['_sort_by'] = $values['_sort_by']->getName();
429
        }
430
        $values['_page'] = $page;
431
432
        return ['filter' => $values];
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     */
438
    public function getDefaultSortValues($class)
439
    {
440
        return [
441
            '_sort_order' => 'ASC',
442
            '_sort_by' => $this->getModelIdentifier($class),
443
            '_page' => 1,
444
            '_per_page' => 25,
445
        ];
446
    }
447
448
    public function getDefaultPerPageOptions(string $class): array
0 ignored issues
show
Unused Code introduced by
The parameter $class is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
449
    {
450
        return [10, 25, 50, 100, 250];
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456
    public function modelTransform($class, $instance)
457
    {
458
        return $instance;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464
    public function modelReverseTransform($class, array $array = [])
465
    {
466
        $instance = $this->getModelInstance($class);
467
        $metadata = $this->getMetadata($class);
468
469
        foreach ($array as $name => $value) {
470
            $property = $this->getFieldName($metadata, $name);
471
472
            $this->propertyAccessor->setValue($instance, $property, $value);
473
        }
474
475
        return $instance;
0 ignored issues
show
Bug Compatibility introduced by
The expression return $instance; of type object|array is incompatible with the return type declared by the interface Sonata\AdminBundle\Model...::modelReverseTransform of type object as it can also be of type array which is not included in this return type.
Loading history...
476
    }
477
478
    /**
479
     * {@inheritdoc}
480
     */
481
    public function getModelCollectionInstance($class)
482
    {
483
        return new ArrayCollection();
484
    }
485
486
    /**
487
     * {@inheritdoc}
488
     */
489
    public function collectionClear(&$collection)
490
    {
491
        return $collection->clear();
0 ignored issues
show
Bug introduced by
The method clear 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...
492
    }
493
494
    /**
495
     * {@inheritdoc}
496
     */
497
    public function collectionHasElement(&$collection, &$element)
498
    {
499
        return $collection->contains($element);
0 ignored issues
show
Bug introduced by
The method contains 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...
500
    }
501
502
    /**
503
     * {@inheritdoc}
504
     */
505
    public function collectionAddElement(&$collection, &$element)
506
    {
507
        return $collection->add($element);
0 ignored issues
show
Bug introduced by
The method add 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...
508
    }
509
510
    /**
511
     * {@inheritdoc}
512
     */
513
    public function collectionRemoveElement(&$collection, &$element)
514
    {
515
        return $collection->removeElement($element);
0 ignored issues
show
Bug introduced by
The method removeElement 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...
516
    }
517
518
    /**
519
     * NEXT_MAJOR: Remove this method.
520
     *
521
     * @deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x, to be removed in 4.0.'.
522
     *
523
     * @param string $property
524
     *
525
     * @return mixed
526
     */
527
    protected function camelize($property)
528
    {
529
        @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
530
            'Method "%s()" is deprecated since sonata-project/doctrine-mongodb-admin-bundle 3.x and will be removed in version 4.0.',
531
            __METHOD__
532
        ), E_USER_DEPRECATED);
533
534
        return str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
535
    }
536
537
    /**
538
     * NEXT_MAJOR: Remove CommonClassMetadata when dropping doctrine/mongodb-odm 1.3.x
539
     *
540
     * @param ClassMetadata|CommonClassMetadata $metadata
541
     */
542
    private function getFieldName($metadata, string $name): string
543
    {
544
        if (\array_key_exists($name, $metadata->fieldMappings)) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
545
            return $metadata->fieldMappings[$name]['fieldName'];
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
546
        }
547
548
        if (\array_key_exists($name, $metadata->associationMappings)) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
549
            return $metadata->associationMappings[$name]['fieldName'];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
550
        }
551
552
        return $name;
553
    }
554
555
    private function isFieldAlreadySorted(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid): bool
556
    {
557
        $values = $datagrid->getValues();
558
559
        if (!isset($values['_sort_by']) || !$values['_sort_by'] instanceof FieldDescriptionInterface) {
560
            return false;
561
        }
562
563
        return $values['_sort_by']->getName() === $fieldDescription->getName()
564
            || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable');
565
    }
566
}
567