Completed
Push — 3.x ( 582d0d...34b967 )
by Vincent
01:25
created

ModelManager::isFieldAlreadySorted()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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