ModelManager   F
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 492
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 67
lcom 1
cbo 15
dl 0
loc 492
rs 3.04
c 0
b 0
f 0

36 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getMetadata() 0 4 1
A getParentMetadataForProperty() 0 15 2
A hasMetadata() 0 4 1
B getNewFieldDescriptionInstance() 0 32 6
A create() 0 6 1
A update() 0 6 1
A delete() 0 6 1
A find() 0 18 4
A findBy() 0 4 1
A findOneBy() 0 4 1
A getDocumentManager() 0 14 3
A getParentFieldDescription() 0 14 1
A createQuery() 0 6 1
A executeQuery() 0 8 2
A getModelIdentifier() 0 4 1
A getIdentifierValues() 0 4 1
A getIdentifierFieldNames() 0 4 1
A getNormalizedIdentifier() 0 15 4
A getUrlsafeIdentifier() 0 4 1
A addIdentifiersToQuery() 0 5 1
A batchDelete() 0 20 3
A getDataSourceIterator() 0 10 2
A getExportFields() 0 6 1
A getModelInstance() 0 4 1
A getSortParameters() 0 18 5
A getPaginationParameters() 0 9 1
A getDefaultSortValues() 0 9 1
A modelTransform() 0 4 1
B modelReverseTransform() 0 42 10
A getModelCollectionInstance() 0 4 1
A collectionClear() 0 4 1
A collectionHasElement() 0 4 1
A collectionAddElement() 0 4 1
A collectionRemoveElement() 0 4 1
A camelize() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ModelManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModelManager, and based on these observations, apply Extract Interface, too.

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\ODM\MongoDB\Query\Builder;
17
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
18
use Sonata\AdminBundle\Datagrid\DatagridInterface;
19
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
20
use Sonata\AdminBundle\Model\ModelManagerInterface;
21
use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription;
22
use Sonata\DoctrineMongoDBAdminBundle\Datagrid\ProxyQuery;
23
use Sonata\Exporter\Source\DoctrineODMQuerySourceIterator;
24
use Symfony\Bridge\Doctrine\ManagerRegistry;
25
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
26
27
class ModelManager implements ModelManagerInterface
28
{
29
    public const ID_SEPARATOR = '-';
30
    protected $registry;
31
32
    public function __construct(ManagerRegistry $registry)
33
    {
34
        $this->registry = $registry;
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function getMetadata($class)
41
    {
42
        return $this->getDocumentManager($class)->getMetadataFactory()->getMetadataFor($class);
43
    }
44
45
    /**
46
     * Returns the model's metadata holding the fully qualified property, and the last
47
     * property name.
48
     *
49
     * @param string $baseClass        The base class of the model holding the fully qualified property
50
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
51
     *                                 property string)
52
     *
53
     * @return array(
0 ignored issues
show
Documentation introduced by Simon Chrzanowski
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...
54
     *                \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $parentMetadata,
55
     *                string $lastPropertyName,
56
     *                array $parentAssociationMappings
57
     *                )
58
     */
59
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
60
    {
61
        $nameElements = explode('.', $propertyFullName);
62
        $lastPropertyName = array_pop($nameElements);
63
        $class = $baseClass;
64
        $parentAssociationMappings = [];
65
66
        foreach ($nameElements as $nameElement) {
67
            $metadata = $this->getMetadata($class);
68
            $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
69
            $class = $metadata->getAssociationTargetClass($nameElement);
70
        }
71
72
        return [$this->getMetadata($class), $lastPropertyName, $parentAssociationMappings];
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function hasMetadata($class)
79
    {
80
        return $this->getDocumentManager($class)->getMetadataFactory()->hasMetadataFor($class);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function getNewFieldDescriptionInstance($class, $name, array $options = [])
87
    {
88
        if (!\is_string($name)) {
89
            throw new \RunTimeException('The name argument must be a string');
90
        }
91
92
        if (!isset($options['route']['name'])) {
93
            $options['route']['name'] = 'edit';
94
        }
95
96
        if (!isset($options['route']['parameters'])) {
97
            $options['route']['parameters'] = [];
98
        }
99
100
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
101
102
        $fieldDescription = new FieldDescription();
103
        $fieldDescription->setName($name);
104
        $fieldDescription->setOptions($options);
105
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
106
107
        /* @var ClassMetadata */
108
        if (isset($metadata->associationMappings[$propertyName])) {
109
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
110
        }
111
112
        if (isset($metadata->fieldMappings[$propertyName])) {
113
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
114
        }
115
116
        return $fieldDescription;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function create($object): void
123
    {
124
        $documentManager = $this->getDocumentManager($object);
0 ignored issues
show
Documentation introduced by Emmanuel Vella
$object is of type object, 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...
125
        $documentManager->persist($object);
126
        $documentManager->flush();
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function update($object): void
133
    {
134
        $documentManager = $this->getDocumentManager($object);
0 ignored issues
show
Documentation introduced by Emmanuel Vella
$object is of type object, 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...
135
        $documentManager->persist($object);
136
        $documentManager->flush();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function delete($object): void
143
    {
144
        $documentManager = $this->getDocumentManager($object);
0 ignored issues
show
Documentation introduced by Emmanuel Vella
$object is of type object, 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...
145
        $documentManager->remove($object);
146
        $documentManager->flush();
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function find($class, $id)
153
    {
154
        if (!isset($id)) {
155
            return;
156
        }
157
158
        $documentManager = $this->getDocumentManager($class);
159
160
        if (is_numeric($id)) {
161
            $value = $documentManager->getRepository($class)->find((int) $id);
162
163
            if (!empty($value)) {
164
                return $value;
165
            }
166
        }
167
168
        return $documentManager->getRepository($class)->find($id);
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function findBy($class, array $criteria = [])
175
    {
176
        return $this->getDocumentManager($class)->getRepository($class)->findBy($criteria);
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182
    public function findOneBy($class, array $criteria = [])
183
    {
184
        return $this->getDocumentManager($class)->getRepository($class)->findOneBy($criteria);
185
    }
186
187
    /**
188
     * @param string $class
189
     *
190
     * @throw \RuntimeException
191
     *
192
     * @return \Doctrine\ODM\MongoDB\DocumentManager
193
     */
194
    public function getDocumentManager($class)
195
    {
196
        if (\is_object($class)) {
197
            $class = \get_class($class);
198
        }
199
200
        $dm = $this->registry->getManagerForClass($class);
201
202
        if (!$dm) {
203
            throw new \RuntimeException(sprintf('No document manager defined for class %s', $class));
204
        }
205
206
        return $dm;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function getParentFieldDescription($parentAssociationMapping, $class)
213
    {
214
        $fieldName = $parentAssociationMapping['fieldName'];
215
216
        $metadata = $this->getMetadata($class);
217
218
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
219
220
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
221
        $fieldDescription->setName($parentAssociationMapping);
0 ignored issues
show
Documentation introduced by Thomas Rabaix
$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...
222
        $fieldDescription->setAssociationMapping($associatingMapping);
223
224
        return $fieldDescription;
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function createQuery($class, $alias = 'o')
231
    {
232
        $repository = $this->getDocumentManager($class)->getRepository($class);
233
234
        return new ProxyQuery($repository->createQueryBuilder());
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function executeQuery($query)
241
    {
242
        if ($query instanceof Builder) {
243
            return $query->getQuery()->execute();
244
        }
245
246
        return $query->execute();
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function getModelIdentifier($class)
253
    {
254
        return $this->getMetadata($class)->identifier;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260
    public function getIdentifierValues($document)
261
    {
262
        return [$this->getDocumentManager($document)->getUnitOfWork()->getDocumentIdentifier($document)];
0 ignored issues
show
Documentation introduced by Emmanuel Vella
$document is of type object, 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...
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268
    public function getIdentifierFieldNames($class)
269
    {
270
        return [$this->getMetadata($class)->getIdentifier()];
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function getNormalizedIdentifier($document)
277
    {
278
        if (is_scalar($document)) {
279
            throw new \RunTimeException('Invalid argument, object or null required');
280
        }
281
282
        // the entities is not managed
283
        if (!$document || !$this->getDocumentManager($document)->getUnitOfWork()->isInIdentityMap($document)) {
0 ignored issues
show
Documentation introduced by Emmanuel Vella
$document is of type object, 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...
284
            return;
285
        }
286
287
        $values = $this->getIdentifierValues($document);
288
289
        return implode(self::ID_SEPARATOR, $values);
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295
    public function getUrlsafeIdentifier($entity)
296
    {
297
        return $this->getNormalizedIdentifier($entity);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx): void
304
    {
305
        $queryBuilder = $queryProxy->getQueryBuilder();
306
        $queryBuilder->field('_id')->in($idx);
307
    }
308
309
    /**
310
     * {@inheritdoc}
311
     */
312
    public function batchDelete($class, ProxyQueryInterface $queryProxy): void
313
    {
314
        /** @var Query $queryBuilder */
315
        $queryBuilder = $queryProxy->getQuery();
316
317
        $documentManager = $this->getDocumentManager($class);
318
319
        $i = 0;
320
        foreach ($queryBuilder->execute() as $object) {
321
            $documentManager->remove($object);
322
323
            if (0 === (++$i % 20)) {
324
                $documentManager->flush();
325
                $documentManager->clear();
326
            }
327
        }
328
329
        $documentManager->flush();
330
        $documentManager->clear();
331
    }
332
333
    /**
334
     * {@inheritdoc}
335
     */
336
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
337
    {
338
        $datagrid->buildPager();
339
        $query = $datagrid->getQuery();
340
341
        $query->setFirstResult($firstResult);
342
        $query->setMaxResults($maxResult);
343
344
        return new DoctrineODMQuerySourceIterator($query instanceof ProxyQuery ? $query->getQuery() : $query, $fields);
0 ignored issues
show
Bug introduced by Dylan Oliver
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...
345
    }
346
347
    /**
348
     * {@inheritdoc}
349
     */
350
    public function getExportFields($class)
351
    {
352
        $metadata = $this->getDocumentManager($class)->getClassMetadata($class);
353
354
        return $metadata->getFieldNames();
355
    }
356
357
    /**
358
     * {@inheritdoc}
359
     */
360
    public function getModelInstance($class)
361
    {
362
        return new $class();
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
369
    {
370
        $values = $datagrid->getValues();
371
372
        if ($fieldDescription->getName() === $values['_sort_by']->getName() || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable')) {
373
            if ('ASC' === $values['_sort_order']) {
374
                $values['_sort_order'] = 'DESC';
375
            } else {
376
                $values['_sort_order'] = 'ASC';
377
            }
378
        } else {
379
            $values['_sort_order'] = 'ASC';
380
        }
381
382
        $values['_sort_by'] = \is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
383
384
        return ['filter' => $values];
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
391
    {
392
        $values = $datagrid->getValues();
393
394
        $values['_sort_by'] = $values['_sort_by']->getName();
395
        $values['_page'] = $page;
396
397
        return ['filter' => $values];
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403
    public function getDefaultSortValues($class)
404
    {
405
        return [
406
            '_sort_order' => 'ASC',
407
            '_sort_by' => $this->getModelIdentifier($class),
408
            '_page' => 1,
409
            '_per_page' => 25,
410
        ];
411
    }
412
413
    /**
414
     * {@inheritdoc}
415
     */
416
    public function modelTransform($class, $instance)
417
    {
418
        return $instance;
419
    }
420
421
    /**
422
     * {@inheritdoc}
423
     */
424
    public function modelReverseTransform($class, array $array = [])
425
    {
426
        $instance = $this->getModelInstance($class);
427
        $metadata = $this->getMetadata($class);
428
429
        $reflClass = $metadata->reflClass;
430
        foreach ($array as $name => $value) {
431
            $reflection_property = false;
432
            // property or association ?
433
            if (\array_key_exists($name, $metadata->fieldMappings)) {
434
                $property = $metadata->fieldMappings[$name]['fieldName'];
435
                $reflection_property = $metadata->reflFields[$name];
436
            } elseif (\array_key_exists($name, $metadata->associationMappings)) {
437
                $property = $metadata->associationMappings[$name]['fieldName'];
438
            } else {
439
                $property = $name;
440
            }
441
442
            $setter = 'set'.$this->camelize($name);
443
444
            if ($reflClass->hasMethod($setter)) {
445
                if (!$reflClass->getMethod($setter)->isPublic()) {
446
                    throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
447
                }
448
449
                $instance->$setter($value);
450
            } elseif ($reflClass->hasMethod('__set')) {
451
                // needed to support magic method __set
452
                $instance->$property = $value;
453
            } elseif ($reflClass->hasProperty($property)) {
454
                if (!$reflClass->getProperty($property)->isPublic()) {
455
                    throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?', $property, $reflClass->getName(), ucfirst($property)));
456
                }
457
458
                $instance->$property = $value;
459
            } elseif ($reflection_property) {
460
                $reflection_property->setValue($instance, $value);
461
            }
462
        }
463
464
        return $instance;
465
    }
466
467
    /**
468
     * {@inheritdoc}
469
     */
470
    public function getModelCollectionInstance($class)
471
    {
472
        return new \Doctrine\Common\Collections\ArrayCollection();
473
    }
474
475
    /**
476
     * {@inheritdoc}
477
     */
478
    public function collectionClear(&$collection)
479
    {
480
        return $collection->clear();
0 ignored issues
show
Bug introduced by Kévin Dunglas
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...
481
    }
482
483
    /**
484
     * {@inheritdoc}
485
     */
486
    public function collectionHasElement(&$collection, &$element)
487
    {
488
        return $collection->contains($element);
0 ignored issues
show
Bug introduced by Kévin Dunglas
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...
489
    }
490
491
    /**
492
     * {@inheritdoc}
493
     */
494
    public function collectionAddElement(&$collection, &$element)
495
    {
496
        return $collection->add($element);
0 ignored issues
show
Bug introduced by Kévin Dunglas
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...
497
    }
498
499
    /**
500
     * {@inheritdoc}
501
     */
502
    public function collectionRemoveElement(&$collection, &$element)
503
    {
504
        return $collection->removeElement($element);
0 ignored issues
show
Bug introduced by Dylan Oliver
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...
505
    }
506
507
    /**
508
     * method taken from PropertyPath.
509
     *
510
     * @param string $property
511
     *
512
     * @return mixed
513
     */
514
    protected function camelize($property)
515
    {
516
        return preg_replace(['/(^|_)+(.)/e', '/\.(.)/e'], ["strtoupper('\\2')", "'_'.strtoupper('\\1')"], $property);
517
    }
518
}
519