Completed
Push — master ( f92116...19d34a )
by Vincent
15s queued 12s
created

ModelManager::getFieldName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 3
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\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\OptimisticLockException;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\QueryBuilder;
25
use Doctrine\ORM\UnitOfWork;
26
use Doctrine\Persistence\ManagerRegistry;
27
use Doctrine\Persistence\Mapping\ClassMetadata;
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
    /**
62
     * NEXT_MAJOR: Make $propertyAccessor mandatory.
63
     */
64
    public function __construct(ManagerRegistry $registry, ?PropertyAccessorInterface $propertyAccessor = null)
65
    {
66
        $this->registry = $registry;
67
68
        // NEXT_MAJOR: Remove this block.
69
        if (null === $propertyAccessor) {
70
            @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...
71
                'Constructing "%s" without passing an instance of "%s" as second argument is deprecated since'
72
                .' sonata-project/doctrine-orm-admin-bundle 3.x and will be mandatory in 4.0',
73
                __CLASS__,
74
                PropertyAccessorInterface::class
75
            ), E_USER_DEPRECATED);
76
77
            $propertyAccessor = PropertyAccess::createPropertyAccessor();
78
        }
79
80
        $this->propertyAccessor = $propertyAccessor;
81
    }
82
83
    /**
84
     * @param string $class
85
     *
86
     * @return ClassMetadata
87
     */
88
    public function getMetadata($class)
89
    {
90
        return $this->getEntityManager($class)->getMetadataFactory()->getMetadataFor($class);
91
    }
92
93
    /**
94
     * Returns the model's metadata holding the fully qualified property, and the last
95
     * property name.
96
     *
97
     * @param string $baseClass        The base class of the model holding the fully qualified property
98
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
99
     *                                 property string)
100
     *
101
     * @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...
102
     *                \Doctrine\ORM\Mapping\ClassMetadata $parentMetadata,
103
     *                string $lastPropertyName,
104
     *                array $parentAssociationMappings
105
     *                )
106
     */
107
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
108
    {
109
        $nameElements = explode('.', $propertyFullName);
110
        $lastPropertyName = array_pop($nameElements);
111
        $class = $baseClass;
112
        $parentAssociationMappings = [];
113
114
        foreach ($nameElements as $nameElement) {
115
            $metadata = $this->getMetadata($class);
116
117
            if (isset($metadata->associationMappings[$nameElement])) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
118
                $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
119
                $class = $metadata->getAssociationTargetClass($nameElement);
120
121
                continue;
122
            }
123
124
            break;
125
        }
126
127
        $properties = \array_slice($nameElements, \count($parentAssociationMappings));
128
        $properties[] = $lastPropertyName;
129
130
        return [$this->getMetadata($class), implode('.', $properties), $parentAssociationMappings];
131
    }
132
133
    /**
134
     * @param string $class
135
     *
136
     * @return bool
137
     */
138
    public function hasMetadata($class)
139
    {
140
        return $this->getEntityManager($class)->getMetadataFactory()->hasMetadataFor($class);
141
    }
142
143
    public function getNewFieldDescriptionInstance($class, $name, array $options = [])
144
    {
145
        if (!\is_string($name)) {
146
            throw new \RuntimeException('The name argument must be a string');
147
        }
148
149
        if (!isset($options['route']['name'])) {
150
            $options['route']['name'] = 'edit';
151
        }
152
153
        if (!isset($options['route']['parameters'])) {
154
            $options['route']['parameters'] = [];
155
        }
156
157
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
158
159
        $fieldDescription = new FieldDescription();
160
        $fieldDescription->setName($name);
161
        $fieldDescription->setOptions($options);
162
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
163
164
        if (isset($metadata->associationMappings[$propertyName])) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
165
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
166
        }
167
168
        if (isset($metadata->fieldMappings[$propertyName])) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
169
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
170
        }
171
172
        return $fieldDescription;
173
    }
174
175
    public function create($object): void
176
    {
177
        try {
178
            $entityManager = $this->getEntityManager($object);
179
            $entityManager->persist($object);
180
            $entityManager->flush();
181
        } catch (\PDOException $e) {
182
            throw new ModelManagerException(
183
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
184
                $e->getCode(),
185
                $e
186
            );
187
        } catch (DBALException $e) {
188
            throw new ModelManagerException(
189
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
190
                $e->getCode(),
191
                $e
192
            );
193
        }
194
    }
195
196
    public function update($object): void
197
    {
198
        try {
199
            $entityManager = $this->getEntityManager($object);
200
            $entityManager->persist($object);
201
            $entityManager->flush();
202
        } catch (\PDOException $e) {
203
            throw new ModelManagerException(
204
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
205
                $e->getCode(),
206
                $e
207
            );
208
        } catch (DBALException $e) {
209
            throw new ModelManagerException(
210
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
211
                $e->getCode(),
212
                $e
213
            );
214
        }
215
    }
216
217
    public function delete($object): void
218
    {
219
        try {
220
            $entityManager = $this->getEntityManager($object);
221
            $entityManager->remove($object);
222
            $entityManager->flush();
223
        } catch (\PDOException $e) {
224
            throw new ModelManagerException(
225
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
226
                $e->getCode(),
227
                $e
228
            );
229
        } catch (DBALException $e) {
230
            throw new ModelManagerException(
231
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
232
                $e->getCode(),
233
                $e
234
            );
235
        }
236
    }
237
238
    public function getLockVersion($object)
239
    {
240
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
241
242
        if (!$metadata->isVersioned) {
0 ignored issues
show
Bug introduced by
Accessing isVersioned on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
243
            return null;
244
        }
245
246
        return $metadata->reflFields[$metadata->versionField]->getValue($object);
0 ignored issues
show
Bug introduced by
Accessing reflFields on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing versionField on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
247
    }
248
249
    public function lock($object, $expectedVersion): void
250
    {
251
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
252
253
        if (!$metadata->isVersioned) {
0 ignored issues
show
Bug introduced by
Accessing isVersioned on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
254
            return;
255
        }
256
257
        try {
258
            $entityManager = $this->getEntityManager($object);
259
            $entityManager->lock($object, LockMode::OPTIMISTIC, $expectedVersion);
260
        } catch (OptimisticLockException $e) {
261
            throw new LockException($e->getMessage(), $e->getCode(), $e);
262
        }
263
    }
264
265
    public function find($class, $id)
266
    {
267
        if (null === $id) {
268
            @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...
269
                '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.',
270
                __METHOD__
271
            ), E_USER_DEPRECATED);
272
273
            return null;
274
        }
275
276
        $values = array_combine($this->getIdentifierFieldNames($class), explode(self::ID_SEPARATOR, (string) $id));
277
278
        return $this->getEntityManager($class)->getRepository($class)->find($values);
279
    }
280
281
    public function findBy($class, array $criteria = [])
282
    {
283
        return $this->getEntityManager($class)->getRepository($class)->findBy($criteria);
284
    }
285
286
    public function findOneBy($class, array $criteria = [])
287
    {
288
        return $this->getEntityManager($class)->getRepository($class)->findOneBy($criteria);
289
    }
290
291
    /**
292
     * @param string|object $class
293
     *
294
     * @return EntityManager
295
     */
296
    public function getEntityManager($class)
297
    {
298
        if (\is_object($class)) {
299
            $class = \get_class($class);
300
        }
301
302
        if (!isset($this->cache[$class])) {
303
            $em = $this->registry->getManagerForClass($class);
304
305
            if (!$em) {
306
                throw new \RuntimeException(sprintf('No entity manager defined for class %s', $class));
307
            }
308
309
            $this->cache[$class] = $em;
310
        }
311
312
        return $this->cache[$class];
313
    }
314
315
    public function getParentFieldDescription($parentAssociationMapping, $class)
316
    {
317
        $fieldName = $parentAssociationMapping['fieldName'];
318
319
        $metadata = $this->getMetadata($class);
320
321
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
322
323
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
324
        $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...
325
        $fieldDescription->setAssociationMapping($associatingMapping);
326
327
        return $fieldDescription;
328
    }
329
330
    public function createQuery($class, $alias = 'o')
331
    {
332
        $repository = $this->getEntityManager($class)->getRepository($class);
333
334
        return new ProxyQuery($repository->createQueryBuilder($alias));
335
    }
336
337
    public function executeQuery($query)
338
    {
339
        if ($query instanceof QueryBuilder) {
340
            return $query->getQuery()->execute();
341
        }
342
343
        return $query->execute();
344
    }
345
346
    /**
347
     * NEXT_MAJOR: Remove this function.
348
     *
349
     * @deprecated since sonata-project/doctrine-orm-admin-bundle 3.18. To be removed in 4.0.
350
     */
351
    public function getModelIdentifier($class)
352
    {
353
        return $this->getMetadata($class)->identifier;
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
354
    }
355
356
    public function getIdentifierValues($entity)
357
    {
358
        // Fix code has an impact on performance, so disable it ...
359
        //$entityManager = $this->getEntityManager($entity);
360
        //if (!$entityManager->getUnitOfWork()->isInIdentityMap($entity)) {
361
        //    throw new \RuntimeException('Entities passed to the choice field must be managed');
362
        //}
363
364
        $class = ClassUtils::getClass($entity);
365
        $metadata = $this->getMetadata($class);
366
        $platform = $this->getEntityManager($class)->getConnection()->getDatabasePlatform();
367
368
        $identifiers = [];
369
370
        foreach ($metadata->getIdentifierValues($entity) as $name => $value) {
371
            if (!\is_object($value)) {
372
                $identifiers[] = $value;
373
374
                continue;
375
            }
376
377
            $fieldType = $metadata->getTypeOfField($name);
378
            $type = $fieldType && Type::hasType($fieldType) ? Type::getType($fieldType) : null;
379
            if ($type) {
380
                $identifiers[] = $this->getValueFromType($value, $type, $fieldType, $platform);
381
382
                continue;
383
            }
384
385
            $identifierMetadata = $this->getMetadata(ClassUtils::getClass($value));
386
387
            foreach ($identifierMetadata->getIdentifierValues($value) as $value) {
388
                $identifiers[] = $value;
389
            }
390
        }
391
392
        return $identifiers;
393
    }
394
395
    public function getIdentifierFieldNames($class)
396
    {
397
        return $this->getMetadata($class)->getIdentifierFieldNames();
398
    }
399
400
    public function getNormalizedIdentifier($entity)
401
    {
402
        // NEXT_MAJOR: Remove the following 2 checks and declare "object" as type for argument 1.
403
        if (null === $entity) {
404
            @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...
405
                '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.',
406
                __METHOD__
407
            ), E_USER_DEPRECATED);
408
409
            return null;
410
        }
411
412
        if (!\is_object($entity)) {
413
            throw new \RuntimeException('Invalid argument, object or null required');
414
        }
415
416
        if (\in_array($this->getEntityManager($entity)->getUnitOfWork()->getEntityState($entity), [
417
            UnitOfWork::STATE_NEW,
418
            UnitOfWork::STATE_REMOVED,
419
        ], true)) {
420
            // NEXT_MAJOR: Uncomment the following exception, remove the deprecation and the return statement inside this conditional block.
421
            // throw new \InvalidArgumentException(sprintf(
422
            //    'Can not get the normalized identifier for %s since it is in state %u.',
423
            //    ClassUtils::getClass($entity),
424
            //    $this->getEntityManager($entity)->getUnitOfWork()->getEntityState($entity)
425
            // ));
426
427
            @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...
428
                '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'
429
                .'and will be not allowed in version 4.0.',
430
                UnitOfWork::STATE_NEW,
431
                UnitOfWork::STATE_REMOVED,
432
                __METHOD__
433
            ), E_USER_DEPRECATED);
434
435
            return null;
436
        }
437
438
        $values = $this->getIdentifierValues($entity);
439
440
        if (0 === \count($values)) {
441
            return null;
442
        }
443
444
        return implode(self::ID_SEPARATOR, $values);
445
    }
446
447
    /**
448
     * {@inheritdoc}
449
     *
450
     * The ORM implementation does nothing special but you still should use
451
     * this method when using the id in a URL to allow for future improvements.
452
     */
453
    public function getUrlSafeIdentifier($entity)
454
    {
455
        // NEXT_MAJOR: Remove the following check and declare "object" as type for argument 1.
456
        if (!\is_object($entity)) {
457
            @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...
458
                '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.',
459
                __METHOD__
460
            ), E_USER_DEPRECATED);
461
462
            return null;
463
        }
464
465
        return $this->getNormalizedIdentifier($entity);
466
    }
467
468
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx): void
469
    {
470
        $fieldNames = $this->getIdentifierFieldNames($class);
471
        $qb = $queryProxy->getQueryBuilder();
472
473
        $prefix = uniqid();
474
        $sqls = [];
475
        foreach ($idx as $pos => $id) {
476
            $ids = explode(self::ID_SEPARATOR, $id);
477
478
            $ands = [];
479
            foreach ($fieldNames as $posName => $name) {
480
                $parameterName = sprintf('field_%s_%s_%d', $prefix, $name, $pos);
481
                $ands[] = sprintf('%s.%s = :%s', current($qb->getRootAliases()), $name, $parameterName);
482
                $qb->setParameter($parameterName, $ids[$posName]);
483
            }
484
485
            $sqls[] = implode(' AND ', $ands);
486
        }
487
488
        $qb->andWhere(sprintf('( %s )', implode(' OR ', $sqls)));
489
    }
490
491
    public function batchDelete($class, ProxyQueryInterface $queryProxy): void
492
    {
493
        $queryProxy->select('DISTINCT '.current($queryProxy->getRootAliases()));
494
495
        try {
496
            $entityManager = $this->getEntityManager($class);
497
498
            $i = 0;
499
            foreach ($queryProxy->getQuery()->iterate() as $pos => $object) {
500
                $entityManager->remove($object[0]);
501
502
                if (0 === (++$i % 20)) {
503
                    $entityManager->flush();
504
                    $entityManager->clear();
505
                }
506
            }
507
508
            $entityManager->flush();
509
            $entityManager->clear();
510
        } catch (\PDOException | DBALException $e) {
511
            throw new ModelManagerException('', 0, $e);
512
        }
513
    }
514
515
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
516
    {
517
        $datagrid->buildPager();
518
        $query = $datagrid->getQuery();
519
520
        $query->select('DISTINCT '.current($query->getRootAliases()));
521
        $query->setFirstResult($firstResult);
522
        $query->setMaxResults($maxResult);
523
524
        if ($query instanceof ProxyQueryInterface) {
525
            $sortBy = $query->getSortBy();
526
527
            if (!empty($sortBy)) {
528
                $query->addOrderBy($sortBy, $query->getSortOrder());
529
                $query = $query->getQuery();
530
                $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [OrderByToSelectWalker::class]);
531
            } else {
532
                $query = $query->getQuery();
533
            }
534
        }
535
536
        return new DoctrineORMQuerySourceIterator($query, $fields);
537
    }
538
539
    public function getExportFields($class)
540
    {
541
        $metadata = $this->getEntityManager($class)->getClassMetadata($class);
542
543
        return $metadata->getFieldNames();
544
    }
545
546
    public function getModelInstance($class)
547
    {
548
        $r = new \ReflectionClass($class);
549
        if ($r->isAbstract()) {
550
            throw new \RuntimeException(sprintf('Cannot initialize abstract class: %s', $class));
551
        }
552
553
        $constructor = $r->getConstructor();
554
555
        if (null !== $constructor && (!$constructor->isPublic() || $constructor->getNumberOfRequiredParameters() > 0)) {
556
            return $r->newInstanceWithoutConstructor();
557
        }
558
559
        return new $class();
560
    }
561
562
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
563
    {
564
        $values = $datagrid->getValues();
565
566
        if ($this->isFieldAlreadySorted($fieldDescription, $datagrid)) {
567
            if ('ASC' === $values['_sort_order']) {
568
                $values['_sort_order'] = 'DESC';
569
            } else {
570
                $values['_sort_order'] = 'ASC';
571
            }
572
        } else {
573
            $values['_sort_order'] = 'ASC';
574
        }
575
576
        $values['_sort_by'] = \is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
577
578
        return ['filter' => $values];
579
    }
580
581
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
582
    {
583
        $values = $datagrid->getValues();
584
585
        if (isset($values['_sort_by']) && $values['_sort_by'] instanceof FieldDescriptionInterface) {
586
            $values['_sort_by'] = $values['_sort_by']->getName();
587
        }
588
        $values['_page'] = $page;
589
590
        return ['filter' => $values];
591
    }
592
593
    public function getDefaultSortValues($class)
594
    {
595
        return [
596
            '_page' => 1,
597
            '_per_page' => 25,
598
        ];
599
    }
600
601
    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...
602
    {
603
        return [10, 25, 50, 100, 250];
604
    }
605
606
    public function modelTransform($class, $instance)
607
    {
608
        return $instance;
609
    }
610
611
    public function modelReverseTransform($class, array $array = [])
612
    {
613
        $instance = $this->getModelInstance($class);
614
        $metadata = $this->getMetadata($class);
615
616
        foreach ($array as $name => $value) {
617
            $property = $this->getFieldName($metadata, $name);
618
            $this->propertyAccessor->setValue($instance, $property, $value);
619
        }
620
621
        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...
622
    }
623
624
    public function getModelCollectionInstance($class)
625
    {
626
        return new \Doctrine\Common\Collections\ArrayCollection();
627
    }
628
629
    public function collectionClear(&$collection)
630
    {
631
        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...
632
    }
633
634
    public function collectionHasElement(&$collection, &$element)
635
    {
636
        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...
637
    }
638
639
    public function collectionAddElement(&$collection, &$element)
640
    {
641
        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...
642
    }
643
644
    public function collectionRemoveElement(&$collection, &$element)
645
    {
646
        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...
647
    }
648
649
    /**
650
     * NEXT_MAJOR: Remove this method.
651
     *
652
     * @param string $property
653
     *
654
     * @return mixed
655
     */
656
    protected function camelize($property)
657
    {
658
        @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...
659
            'Method %s() is deprecated since sonata-project/doctrine-orm-admin-bundle 3.x and will be removed in version 4.0.',
660
            __METHOD__
661
        ), E_USER_DEPRECATED);
662
663
        return str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
664
    }
665
666
    private function getFieldName(ClassMetadata $metadata, string $name): string
667
    {
668
        if (\array_key_exists($name, $metadata->fieldMappings)) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
669
            return $metadata->fieldMappings[$name]['fieldName'];
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
670
        }
671
672
        if (\array_key_exists($name, $metadata->associationMappings)) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
673
            return $metadata->associationMappings[$name]['fieldName'];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
674
        }
675
676
        return $name;
677
    }
678
679
    private function isFieldAlreadySorted(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid): bool
680
    {
681
        $values = $datagrid->getValues();
682
683
        if (!isset($values['_sort_by']) || !$values['_sort_by'] instanceof FieldDescriptionInterface) {
684
            return false;
685
        }
686
687
        return $values['_sort_by']->getName() === $fieldDescription->getName()
688
            || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable');
689
    }
690
691
    /**
692
     * @param mixed $value
693
     */
694
    private function getValueFromType($value, Type $type, string $fieldType, AbstractPlatform $platform): string
695
    {
696
        if ($platform->hasDoctrineTypeMappingFor($fieldType) &&
697
            'binary' === $platform->getDoctrineTypeMapping($fieldType)
698
        ) {
699
            return (string) $type->convertToPHPValue($value, $platform);
700
        }
701
702
        // some libraries may have `toString()` implementation
703
        if (\is_callable([$value, 'toString'])) {
704
            return $value->toString();
705
        }
706
707
        // final fallback to magic `__toString()` which may throw an exception in 7.4
708
        if (method_exists($value, '__toString')) {
709
            return $value->__toString();
710
        }
711
712
        return (string) $type->convertToDatabaseValue($value, $platform);
713
    }
714
}
715