Completed
Push — 3.x ( 3a82e5...0238da )
by Grégoire
01:26
created

ModelManager::getUrlSafeIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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
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)
123
    {
124
        $documentManager = $this->getDocumentManager($object);
125
        $documentManager->persist($object);
126
        $documentManager->flush();
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    public function update($object)
133
    {
134
        $documentManager = $this->getDocumentManager($object);
135
        $documentManager->persist($object);
136
        $documentManager->flush();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function delete($object)
143
    {
144
        $documentManager = $this->getDocumentManager($object);
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 null;
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 object|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
$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)];
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 (null === $document) {
279
            return null;
280
        }
281
282
        if (!\is_object($document)) {
283
            throw new \RunTimeException('Invalid argument, object or null required');
284
        }
285
286
        // the document is not managed
287
        if (!$this->getDocumentManager($document)->getUnitOfWork()->isInIdentityMap($document)) {
288
            return null;
289
        }
290
291
        $values = $this->getIdentifierValues($document);
292
293
        return implode(self::ID_SEPARATOR, $values);
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public function getUrlSafeIdentifier($document)
300
    {
301
        return $this->getNormalizedIdentifier($document);
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
308
    {
309
        $queryBuilder = $queryProxy->getQueryBuilder();
310
        $queryBuilder->field('_id')->in($idx);
311
    }
312
313
    /**
314
     * {@inheritdoc}
315
     */
316
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
317
    {
318
        /** @var Query $queryBuilder */
319
        $queryBuilder = $queryProxy->getQuery();
320
321
        $documentManager = $this->getDocumentManager($class);
322
323
        $i = 0;
324
        foreach ($queryBuilder->execute() as $object) {
325
            $documentManager->remove($object);
326
327
            if (0 === (++$i % 20)) {
328
                $documentManager->flush();
329
                $documentManager->clear();
330
            }
331
        }
332
333
        $documentManager->flush();
334
        $documentManager->clear();
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     */
340
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
341
    {
342
        $datagrid->buildPager();
343
        $query = $datagrid->getQuery();
344
345
        $query->setFirstResult($firstResult);
346
        $query->setMaxResults($maxResult);
347
348
        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...
349
    }
350
351
    /**
352
     * {@inheritdoc}
353
     */
354
    public function getExportFields($class)
355
    {
356
        $metadata = $this->getDocumentManager($class)->getClassMetadata($class);
357
358
        return $metadata->getFieldNames();
359
    }
360
361
    /**
362
     * {@inheritdoc}
363
     */
364
    public function getModelInstance($class)
365
    {
366
        return new $class();
367
    }
368
369
    /**
370
     * {@inheritdoc}
371
     */
372
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
373
    {
374
        $values = $datagrid->getValues();
375
376
        if ($this->isFieldAlreadySorted($fieldDescription, $datagrid)) {
377
            if ('ASC' === $values['_sort_order']) {
378
                $values['_sort_order'] = 'DESC';
379
            } else {
380
                $values['_sort_order'] = 'ASC';
381
            }
382
        } else {
383
            $values['_sort_order'] = 'ASC';
384
        }
385
386
        $values['_sort_by'] = \is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
387
388
        return ['filter' => $values];
389
    }
390
391
    /**
392
     * {@inheritdoc}
393
     */
394
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
395
    {
396
        $values = $datagrid->getValues();
397
398
        if (isset($values['_sort_by']) && $values['_sort_by'] instanceof FieldDescriptionInterface) {
399
            $values['_sort_by'] = $values['_sort_by']->getName();
400
        }
401
        $values['_page'] = $page;
402
403
        return ['filter' => $values];
404
    }
405
406
    /**
407
     * {@inheritdoc}
408
     */
409
    public function getDefaultSortValues($class)
410
    {
411
        return [
412
            '_sort_order' => 'ASC',
413
            '_sort_by' => $this->getModelIdentifier($class),
414
            '_page' => 1,
415
            '_per_page' => 25,
416
        ];
417
    }
418
419
    /**
420
     * {@inheritdoc}
421
     */
422
    public function modelTransform($class, $instance)
423
    {
424
        return $instance;
425
    }
426
427
    /**
428
     * {@inheritdoc}
429
     */
430
    public function modelReverseTransform($class, array $array = [])
431
    {
432
        $instance = $this->getModelInstance($class);
433
        $metadata = $this->getMetadata($class);
434
435
        $reflClass = $metadata->reflClass;
436
        foreach ($array as $name => $value) {
437
            $reflection_property = false;
438
            // property or association ?
439
            if (\array_key_exists($name, $metadata->fieldMappings)) {
440
                $property = $metadata->fieldMappings[$name]['fieldName'];
441
                $reflection_property = $metadata->reflFields[$name];
442
            } elseif (\array_key_exists($name, $metadata->associationMappings)) {
443
                $property = $metadata->associationMappings[$name]['fieldName'];
444
            } else {
445
                $property = $name;
446
            }
447
448
            $setter = 'set'.$this->camelize($name);
449
450
            if ($reflClass->hasMethod($setter)) {
451
                if (!$reflClass->getMethod($setter)->isPublic()) {
452
                    throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
453
                }
454
455
                $instance->$setter($value);
456
            } elseif ($reflClass->hasMethod('__set')) {
457
                // needed to support magic method __set
458
                $instance->$property = $value;
459
            } elseif ($reflClass->hasProperty($property)) {
460
                if (!$reflClass->getProperty($property)->isPublic()) {
461
                    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)));
462
                }
463
464
                $instance->$property = $value;
465
            } elseif ($reflection_property) {
466
                $reflection_property->setValue($instance, $value);
467
            }
468
        }
469
470
        return $instance;
471
    }
472
473
    /**
474
     * {@inheritdoc}
475
     */
476
    public function getModelCollectionInstance($class)
477
    {
478
        return new \Doctrine\Common\Collections\ArrayCollection();
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484
    public function collectionClear(&$collection)
485
    {
486
        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...
487
    }
488
489
    /**
490
     * {@inheritdoc}
491
     */
492
    public function collectionHasElement(&$collection, &$element)
493
    {
494
        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...
495
    }
496
497
    /**
498
     * {@inheritdoc}
499
     */
500
    public function collectionAddElement(&$collection, &$element)
501
    {
502
        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...
503
    }
504
505
    /**
506
     * {@inheritdoc}
507
     */
508
    public function collectionRemoveElement(&$collection, &$element)
509
    {
510
        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...
511
    }
512
513
    /**
514
     * method taken from PropertyPath.
515
     *
516
     * @param string $property
517
     *
518
     * @return mixed
519
     */
520
    protected function camelize($property)
521
    {
522
        return preg_replace(['/(^|_)+(.)/e', '/\.(.)/e'], ["strtoupper('\\2')", "'_'.strtoupper('\\1')"], $property);
523
    }
524
525
    private function isFieldAlreadySorted(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid): bool
526
    {
527
        $values = $datagrid->getValues();
528
529
        if (!isset($values['_sort_by']) || !$values['_sort_by'] instanceof FieldDescriptionInterface) {
530
            return false;
531
        }
532
533
        return $values['_sort_by']->getName() === $fieldDescription->getName()
534
            || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable');
535
    }
536
}
537