Completed
Pull Request — 3.x (#1078)
by Vincent
01:27
created

ModelManager::createQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
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\DoctrineORMAdminBundle\Model;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Doctrine\DBAL\DBALException;
18
use Doctrine\DBAL\LockMode;
19
use Doctrine\DBAL\Platforms\AbstractPlatform;
20
use Doctrine\DBAL\Types\Type;
21
use Doctrine\ORM\EntityManager;
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\OptimisticLockException;
24
use Doctrine\ORM\Query;
25
use Doctrine\ORM\QueryBuilder;
26
use Doctrine\ORM\UnitOfWork;
27
use Doctrine\Persistence\ManagerRegistry;
28
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
29
use Sonata\AdminBundle\Datagrid\DatagridInterface;
30
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
31
use Sonata\AdminBundle\Exception\LockException;
32
use Sonata\AdminBundle\Exception\ModelManagerException;
33
use Sonata\AdminBundle\Model\LockInterface;
34
use Sonata\AdminBundle\Model\ModelManagerInterface;
35
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
36
use Sonata\DoctrineORMAdminBundle\Datagrid\OrderByToSelectWalker;
37
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
38
use Sonata\Exporter\Source\DoctrineORMQuerySourceIterator;
39
use Symfony\Component\PropertyAccess\PropertyAccess;
40
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
41
42
class ModelManager implements ModelManagerInterface, LockInterface
43
{
44
    public const ID_SEPARATOR = '~';
45
46
    /**
47
     * @var ManagerRegistry
48
     */
49
    protected $registry;
50
51
    /**
52
     * @var PropertyAccessorInterface
53
     */
54
    protected $propertyAccessor;
55
56
    /**
57
     * @var EntityManager[]
58
     */
59
    protected $cache = [];
60
61
    public function __construct(ManagerRegistry $registry, ?PropertyAccessorInterface $propertyAccessor = null)
62
    {
63
        $this->registry = $registry;
64
        $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
65
    }
66
67
    /**
68
     * @param string $class
69
     *
70
     * @return ClassMetadata
71
     */
72
    public function getMetadata($class)
73
    {
74
        return $this->getEntityManager($class)->getMetadataFactory()->getMetadataFor($class);
75
    }
76
77
    /**
78
     * Returns the model's metadata holding the fully qualified property, and the last
79
     * property name.
80
     *
81
     * @param string $baseClass        The base class of the model holding the fully qualified property
82
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
83
     *                                 property string)
84
     *
85
     * @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...
86
     *                \Doctrine\ORM\Mapping\ClassMetadata $parentMetadata,
87
     *                string $lastPropertyName,
88
     *                array $parentAssociationMappings
89
     *                )
90
     */
91
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
92
    {
93
        $nameElements = explode('.', $propertyFullName);
94
        $lastPropertyName = array_pop($nameElements);
95
        $class = $baseClass;
96
        $parentAssociationMappings = [];
97
98
        foreach ($nameElements as $nameElement) {
99
            $metadata = $this->getMetadata($class);
100
101
            if (isset($metadata->associationMappings[$nameElement])) {
102
                $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
103
                $class = $metadata->getAssociationTargetClass($nameElement);
104
105
                continue;
106
            }
107
108
            break;
109
        }
110
111
        $properties = \array_slice($nameElements, \count($parentAssociationMappings));
112
        $properties[] = $lastPropertyName;
113
114
        return [$this->getMetadata($class), implode('.', $properties), $parentAssociationMappings];
115
    }
116
117
    /**
118
     * @param string $class
119
     *
120
     * @return bool
121
     */
122
    public function hasMetadata($class)
123
    {
124
        return $this->getEntityManager($class)->getMetadataFactory()->hasMetadataFor($class);
125
    }
126
127
    public function getNewFieldDescriptionInstance($class, $name, array $options = [])
128
    {
129
        if (!\is_string($name)) {
130
            throw new \RuntimeException('The name argument must be a string');
131
        }
132
133
        if (!isset($options['route']['name'])) {
134
            $options['route']['name'] = 'edit';
135
        }
136
137
        if (!isset($options['route']['parameters'])) {
138
            $options['route']['parameters'] = [];
139
        }
140
141
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
142
143
        $fieldDescription = new FieldDescription();
144
        $fieldDescription->setName($name);
145
        $fieldDescription->setOptions($options);
146
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
147
148
        if (isset($metadata->associationMappings[$propertyName])) {
149
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
150
        }
151
152
        if (isset($metadata->fieldMappings[$propertyName])) {
153
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
154
        }
155
156
        return $fieldDescription;
157
    }
158
159
    public function create($object)
160
    {
161
        try {
162
            $entityManager = $this->getEntityManager($object);
163
            $entityManager->persist($object);
164
            $entityManager->flush();
165
        } catch (\PDOException $e) {
166
            throw new ModelManagerException(
167
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
168
                $e->getCode(),
169
                $e
170
            );
171
        } catch (DBALException $e) {
172
            throw new ModelManagerException(
173
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
174
                $e->getCode(),
175
                $e
176
            );
177
        }
178
    }
179
180
    public function update($object)
181
    {
182
        try {
183
            $entityManager = $this->getEntityManager($object);
184
            $entityManager->persist($object);
185
            $entityManager->flush();
186
        } catch (\PDOException $e) {
187
            throw new ModelManagerException(
188
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
189
                $e->getCode(),
190
                $e
191
            );
192
        } catch (DBALException $e) {
193
            throw new ModelManagerException(
194
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
195
                $e->getCode(),
196
                $e
197
            );
198
        }
199
    }
200
201
    public function delete($object)
202
    {
203
        try {
204
            $entityManager = $this->getEntityManager($object);
205
            $entityManager->remove($object);
206
            $entityManager->flush();
207
        } catch (\PDOException $e) {
208
            throw new ModelManagerException(
209
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
210
                $e->getCode(),
211
                $e
212
            );
213
        } catch (DBALException $e) {
214
            throw new ModelManagerException(
215
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
216
                $e->getCode(),
217
                $e
218
            );
219
        }
220
    }
221
222
    public function getLockVersion($object)
223
    {
224
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
225
226
        if (!$metadata->isVersioned) {
227
            return null;
228
        }
229
230
        return $metadata->reflFields[$metadata->versionField]->getValue($object);
231
    }
232
233
    public function lock($object, $expectedVersion)
234
    {
235
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
236
237
        if (!$metadata->isVersioned) {
238
            return;
239
        }
240
241
        try {
242
            $entityManager = $this->getEntityManager($object);
243
            $entityManager->lock($object, LockMode::OPTIMISTIC, $expectedVersion);
244
        } catch (OptimisticLockException $e) {
245
            throw new LockException($e->getMessage(), $e->getCode(), $e);
246
        }
247
    }
248
249
    public function find($class, $id)
250
    {
251
        if (null === $id) {
252
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
253
                'Passing null as argument 1 for %s() is deprecated since sonata-project/doctrine-orm-admin-bundle 3.20 and will be not allowed in version 4.0.',
254
                __METHOD__
255
            ), E_USER_DEPRECATED);
256
257
            return null;
258
        }
259
260
        $values = array_combine($this->getIdentifierFieldNames($class), explode(self::ID_SEPARATOR, (string) $id));
261
262
        return $this->getEntityManager($class)->getRepository($class)->find($values);
263
    }
264
265
    public function findBy($class, array $criteria = [])
266
    {
267
        return $this->getEntityManager($class)->getRepository($class)->findBy($criteria);
268
    }
269
270
    public function findOneBy($class, array $criteria = [])
271
    {
272
        return $this->getEntityManager($class)->getRepository($class)->findOneBy($criteria);
273
    }
274
275
    /**
276
     * @param string|object $class
277
     *
278
     * @return EntityManager
279
     */
280
    public function getEntityManager($class)
281
    {
282
        if (\is_object($class)) {
283
            $class = \get_class($class);
284
        }
285
286
        if (!isset($this->cache[$class])) {
287
            $em = $this->registry->getManagerForClass($class);
288
289
            if (!$em) {
290
                throw new \RuntimeException(sprintf('No entity manager defined for class %s', $class));
291
            }
292
293
            $this->cache[$class] = $em;
294
        }
295
296
        return $this->cache[$class];
297
    }
298
299
    public function getParentFieldDescription($parentAssociationMapping, $class)
300
    {
301
        $fieldName = $parentAssociationMapping['fieldName'];
302
303
        $metadata = $this->getMetadata($class);
304
305
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
306
307
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
308
        $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...
309
        $fieldDescription->setAssociationMapping($associatingMapping);
310
311
        return $fieldDescription;
312
    }
313
314
    public function createQuery($class, $alias = 'o')
315
    {
316
        $repository = $this->getEntityManager($class)->getRepository($class);
317
318
        return new ProxyQuery($repository->createQueryBuilder($alias));
319
    }
320
321
    public function executeQuery($query)
322
    {
323
        if ($query instanceof QueryBuilder) {
324
            return $query->getQuery()->execute();
325
        }
326
327
        return $query->execute();
328
    }
329
330
    /**
331
     * NEXT_MAJOR: Remove this function.
332
     *
333
     * @deprecated since sonata-project/doctrine-orm-admin-bundle 3.18. To be removed in 4.0.
334
     */
335
    public function getModelIdentifier($class)
336
    {
337
        return $this->getMetadata($class)->identifier;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getMetadata($class)->identifier; (array) is incompatible with the return type declared by the interface Sonata\AdminBundle\Model...ace::getModelIdentifier of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
338
    }
339
340
    public function getIdentifierValues($entity)
341
    {
342
        // Fix code has an impact on performance, so disable it ...
343
        //$entityManager = $this->getEntityManager($entity);
344
        //if (!$entityManager->getUnitOfWork()->isInIdentityMap($entity)) {
345
        //    throw new \RuntimeException('Entities passed to the choice field must be managed');
346
        //}
347
348
        $class = ClassUtils::getClass($entity);
349
        $metadata = $this->getMetadata($class);
350
        $platform = $this->getEntityManager($class)->getConnection()->getDatabasePlatform();
351
352
        $identifiers = [];
353
354
        foreach ($metadata->getIdentifierValues($entity) as $name => $value) {
355
            if (!\is_object($value)) {
356
                $identifiers[] = $value;
357
358
                continue;
359
            }
360
361
            $fieldType = $metadata->getTypeOfField($name);
362
            $type = $fieldType && Type::hasType($fieldType) ? Type::getType($fieldType) : null;
363
            if ($type) {
364
                $identifiers[] = $this->getValueFromType($value, $type, $fieldType, $platform);
365
366
                continue;
367
            }
368
369
            $identifierMetadata = $this->getMetadata(ClassUtils::getClass($value));
370
371
            foreach ($identifierMetadata->getIdentifierValues($value) as $value) {
372
                $identifiers[] = $value;
373
            }
374
        }
375
376
        return $identifiers;
377
    }
378
379
    public function getIdentifierFieldNames($class)
380
    {
381
        return $this->getMetadata($class)->getIdentifierFieldNames();
382
    }
383
384
    public function getNormalizedIdentifier($entity)
385
    {
386
        // NEXT_MAJOR: Remove the following 2 checks and declare "object" as type for argument 1.
387
        if (null === $entity) {
388
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
389
                'Passing null as argument 1 for %s() is deprecated since sonata-project/doctrine-orm-admin-bundle 3.20 and will be not allowed in version 4.0.',
390
                __METHOD__
391
            ), E_USER_DEPRECATED);
392
393
            return null;
394
        }
395
396
        if (!\is_object($entity)) {
397
            throw new \RuntimeException('Invalid argument, object or null required');
398
        }
399
400
        if (\in_array($this->getEntityManager($entity)->getUnitOfWork()->getEntityState($entity), [
401
            UnitOfWork::STATE_NEW,
402
            UnitOfWork::STATE_REMOVED,
403
        ], true)) {
404
            // NEXT_MAJOR: Uncomment the following exception, remove the deprecation and the return statement inside this conditional block.
405
            // throw new \InvalidArgumentException(sprintf(
406
            //    'Can not get the normalized identifier for %s since it is in state %u.',
407
            //    ClassUtils::getClass($entity),
408
            //    $this->getEntityManager($entity)->getUnitOfWork()->getEntityState($entity)
409
            // ));
410
411
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
412
                'Passing an object which is in state %u (new) or %u (removed) as argument 1 for %s() is deprecated since sonata-project/doctrine-orm-admin-bundle 3.20'
413
                .'and will be not allowed in version 4.0.',
414
                UnitOfWork::STATE_NEW,
415
                UnitOfWork::STATE_REMOVED,
416
                __METHOD__
417
            ), E_USER_DEPRECATED);
418
419
            return null;
420
        }
421
422
        $values = $this->getIdentifierValues($entity);
423
424
        if (0 === \count($values)) {
425
            return null;
426
        }
427
428
        return implode(self::ID_SEPARATOR, $values);
429
    }
430
431
    /**
432
     * {@inheritdoc}
433
     *
434
     * The ORM implementation does nothing special but you still should use
435
     * this method when using the id in a URL to allow for future improvements.
436
     */
437
    public function getUrlSafeIdentifier($entity)
438
    {
439
        // NEXT_MAJOR: Remove the following check and declare "object" as type for argument 1.
440
        if (!\is_object($entity)) {
441
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
442
                'Passing other type than object for argument 1 for %s() is deprecated since sonata-project/doctrine-orm-admin-bundle 3.20 and will be not allowed in version 4.0.',
443
                __METHOD__
444
            ), E_USER_DEPRECATED);
445
446
            return null;
447
        }
448
449
        return $this->getNormalizedIdentifier($entity);
450
    }
451
452
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
453
    {
454
        $fieldNames = $this->getIdentifierFieldNames($class);
455
        $qb = $queryProxy->getQueryBuilder();
456
457
        $prefix = uniqid();
458
        $sqls = [];
459
        foreach ($idx as $pos => $id) {
460
            $ids = explode(self::ID_SEPARATOR, $id);
461
462
            $ands = [];
463
            foreach ($fieldNames as $posName => $name) {
464
                $parameterName = sprintf('field_%s_%s_%d', $prefix, $name, $pos);
465
                $ands[] = sprintf('%s.%s = :%s', current($qb->getRootAliases()), $name, $parameterName);
466
                $qb->setParameter($parameterName, $ids[$posName]);
467
            }
468
469
            $sqls[] = implode(' AND ', $ands);
470
        }
471
472
        $qb->andWhere(sprintf('( %s )', implode(' OR ', $sqls)));
473
    }
474
475
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
476
    {
477
        $queryProxy->select('DISTINCT '.current($queryProxy->getRootAliases()));
478
479
        try {
480
            $entityManager = $this->getEntityManager($class);
481
482
            $i = 0;
483
            foreach ($queryProxy->getQuery()->iterate() as $pos => $object) {
484
                $entityManager->remove($object[0]);
485
486
                if (0 === (++$i % 20)) {
487
                    $entityManager->flush();
488
                    $entityManager->clear();
489
                }
490
            }
491
492
            $entityManager->flush();
493
            $entityManager->clear();
494
        } catch (\PDOException | DBALException $e) {
495
            throw new ModelManagerException('', 0, $e);
496
        }
497
    }
498
499
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
500
    {
501
        $datagrid->buildPager();
502
        $query = $datagrid->getQuery();
503
504
        $query->select('DISTINCT '.current($query->getRootAliases()));
505
        $query->setFirstResult($firstResult);
506
        $query->setMaxResults($maxResult);
507
508
        if ($query instanceof ProxyQueryInterface) {
509
            $sortBy = $query->getSortBy();
510
511
            if (!empty($sortBy)) {
512
                $query->addOrderBy($sortBy, $query->getSortOrder());
513
                $query = $query->getQuery();
514
                $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [OrderByToSelectWalker::class]);
515
            } else {
516
                $query = $query->getQuery();
517
            }
518
        }
519
520
        return new DoctrineORMQuerySourceIterator($query, $fields);
521
    }
522
523
    public function getExportFields($class)
524
    {
525
        $metadata = $this->getEntityManager($class)->getClassMetadata($class);
526
527
        return $metadata->getFieldNames();
528
    }
529
530
    public function getModelInstance($class)
531
    {
532
        $r = new \ReflectionClass($class);
533
        if ($r->isAbstract()) {
534
            throw new \RuntimeException(sprintf('Cannot initialize abstract class: %s', $class));
535
        }
536
537
        $constructor = $r->getConstructor();
538
539
        if (null !== $constructor && (!$constructor->isPublic() || $constructor->getNumberOfRequiredParameters() > 0)) {
540
            return $r->newInstanceWithoutConstructor();
541
        }
542
543
        return new $class();
544
    }
545
546
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
547
    {
548
        $values = $datagrid->getValues();
549
550
        if ($this->isFieldAlreadySorted($fieldDescription, $datagrid)) {
551
            if ('ASC' === $values['_sort_order']) {
552
                $values['_sort_order'] = 'DESC';
553
            } else {
554
                $values['_sort_order'] = 'ASC';
555
            }
556
        } else {
557
            $values['_sort_order'] = 'ASC';
558
        }
559
560
        $values['_sort_by'] = \is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
561
562
        return ['filter' => $values];
563
    }
564
565
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
566
    {
567
        $values = $datagrid->getValues();
568
569
        if (isset($values['_sort_by']) && $values['_sort_by'] instanceof FieldDescriptionInterface) {
570
            $values['_sort_by'] = $values['_sort_by']->getName();
571
        }
572
        $values['_page'] = $page;
573
574
        return ['filter' => $values];
575
    }
576
577
    public function getDefaultSortValues($class)
578
    {
579
        return [
580
            '_sort_order' => 'ASC',
581
            '_sort_by' => implode(',', $this->getModelIdentifier($class)),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\DoctrineORMAdminB...r::getModelIdentifier() has been deprecated with message: since sonata-project/doctrine-orm-admin-bundle 3.18. To be removed in 4.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
582
            '_page' => 1,
583
            '_per_page' => 25,
584
        ];
585
    }
586
587
    public function getDefaultPerPageOptions(string $class): array
0 ignored issues
show
Unused Code introduced by
The parameter $class is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
588
    {
589
        return [10, 25, 50, 100, 250];
590
    }
591
592
    public function modelTransform($class, $instance)
593
    {
594
        return $instance;
595
    }
596
597
    public function modelReverseTransform($class, array $array = [])
598
    {
599
        $instance = $this->getModelInstance($class);
600
        $metadata = $this->getMetadata($class);
601
602
        foreach ($array as $name => $value) {
603
            // property or association ?
604
            if (\array_key_exists($name, $metadata->fieldMappings)) {
605
                $property = $metadata->fieldMappings[$name]['fieldName'];
606
            } elseif (\array_key_exists($name, $metadata->associationMappings)) {
607
                $property = $metadata->associationMappings[$name]['fieldName'];
608
            } else {
609
                $property = $name;
610
            }
611
612
            $this->propertyAccessor->setValue($instance, $property, $value);
613
        }
614
615
        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...
616
    }
617
618
    public function getModelCollectionInstance($class)
619
    {
620
        return new \Doctrine\Common\Collections\ArrayCollection();
621
    }
622
623
    public function collectionClear(&$collection)
624
    {
625
        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...
626
    }
627
628
    public function collectionHasElement(&$collection, &$element)
629
    {
630
        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...
631
    }
632
633
    public function collectionAddElement(&$collection, &$element)
634
    {
635
        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...
636
    }
637
638
    public function collectionRemoveElement(&$collection, &$element)
639
    {
640
        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...
641
    }
642
643
    /**
644
     * NEXT_MAJOR: Remove this method.
645
     *
646
     * @param string $property
647
     *
648
     * @return mixed
649
     */
650
    protected function camelize($property)
651
    {
652
        @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
653
            'Method %s() is deprecated since sonata-project/doctrine-orm-admin-bundle 3.x and will be removed in version 4.0.',
654
            __METHOD__
655
        ), E_USER_DEPRECATED);
656
657
        return str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
658
    }
659
660
    private function isFieldAlreadySorted(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid): bool
661
    {
662
        $values = $datagrid->getValues();
663
664
        if (!isset($values['_sort_by']) || !$values['_sort_by'] instanceof FieldDescriptionInterface) {
665
            return false;
666
        }
667
668
        return $values['_sort_by']->getName() === $fieldDescription->getName()
669
            || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable');
670
    }
671
672
    /**
673
     * @param mixed $value
674
     */
675
    private function getValueFromType($value, Type $type, string $fieldType, AbstractPlatform $platform): string
676
    {
677
        if ($platform->hasDoctrineTypeMappingFor($fieldType) &&
678
            'binary' === $platform->getDoctrineTypeMapping($fieldType)
679
        ) {
680
            return (string) $type->convertToPHPValue($value, $platform);
681
        }
682
683
        // some libraries may have `toString()` implementation
684
        if (\is_callable([$value, 'toString'])) {
685
            return $value->toString();
686
        }
687
688
        // final fallback to magic `__toString()` which may throw an exception in 7.4
689
        if (method_exists($value, '__toString')) {
690
            return $value->__toString();
691
        }
692
693
        return (string) $type->convertToDatabaseValue($value, $platform);
694
    }
695
}
696