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

src/Model/ModelManager.php (1 issue)

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();
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