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