Completed
Push — 3.x ( 064670...3a8e9d )
by Vincent
01:24
created

src/Model/ModelManager.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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