Completed
Push — 3.x ( f08830...c43f1e )
by Christian
01:30
created

ModelManager::find()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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