Completed
Pull Request — 3.x (#696)
by
unknown
02:18
created

ModelManager::getNewFieldDescriptionInstance()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
rs 8.439
cc 6
eloc 17
nc 17
nop 3
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\DoctrineORMAdminBundle\Model;
13
14
use Doctrine\Common\Util\ClassUtils;
15
use Doctrine\DBAL\DBALException;
16
use Doctrine\DBAL\LockMode;
17
use Doctrine\DBAL\Types\Type;
18
use Doctrine\ORM\EntityManager;
19
use Doctrine\ORM\Mapping\ClassMetadata;
20
use Doctrine\ORM\OptimisticLockException;
21
use Doctrine\ORM\QueryBuilder;
22
use Doctrine\ORM\UnitOfWork;
23
use Exporter\Source\DoctrineORMQuerySourceIterator;
24
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
25
use Sonata\AdminBundle\Datagrid\DatagridInterface;
26
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
27
use Sonata\AdminBundle\Exception\LockException;
28
use Sonata\AdminBundle\Exception\ModelManagerException;
29
use Sonata\AdminBundle\Model\LockInterface;
30
use Sonata\AdminBundle\Model\ModelManagerInterface;
31
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
32
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
33
use Symfony\Bridge\Doctrine\RegistryInterface;
34
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
35
36
class ModelManager implements ModelManagerInterface, LockInterface
37
{
38
    const ID_SEPARATOR = '~';
39
    /**
40
     * @var RegistryInterface
41
     */
42
    protected $registry;
43
44
    /**
45
     * @var EntityManager[]
46
     */
47
    protected $cache = array();
48
49
    /**
50
     * @param RegistryInterface $registry
51
     */
52
    public function __construct(RegistryInterface $registry)
53
    {
54
        $this->registry = $registry;
55
    }
56
57
    /**
58
     * @param string $class
59
     *
60
     * @return ClassMetadata
61
     */
62
    public function getMetadata($class)
63
    {
64
        return $this->getEntityManager($class)->getMetadataFactory()->getMetadataFor($class);
65
    }
66
67
    /**
68
     * Returns the model's metadata holding the fully qualified property, and the last
69
     * property name.
70
     *
71
     * @param string $baseClass        The base class of the model holding the fully qualified property
72
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
73
     *                                 property string)
74
     *
75
     * @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...
76
     *                \Doctrine\ORM\Mapping\ClassMetadata $parentMetadata,
77
     *                string $lastPropertyName,
78
     *                array $parentAssociationMappings
79
     *                )
80
     */
81
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
82
    {
83
        $nameElements = explode('.', $propertyFullName);
84
        $lastPropertyName = array_pop($nameElements);
85
        $class = $baseClass;
86
        $parentAssociationMappings = array();
87
88
        foreach ($nameElements as $nameElement) {
89
            $metadata = $this->getMetadata($class);
90
91
            if (isset($metadata->associationMappings[$nameElement])) {
92
                $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
93
                $class = $metadata->getAssociationTargetClass($nameElement);
94
            } elseif (isset($metadata->embeddedClasses[$nameElement])) {
95
                $parentAssociationMappings = array();
96
                $lastPropertyName = $propertyFullName;
97
                $class = $baseClass;
98
            }
99
        }
100
101
        return array($this->getMetadata($class), $lastPropertyName, $parentAssociationMappings);
102
    }
103
104
    /**
105
     * @param string $class
106
     *
107
     * @return bool
108
     */
109
    public function hasMetadata($class)
110
    {
111
        return $this->getEntityManager($class)->getMetadataFactory()->hasMetadataFor($class);
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function getNewFieldDescriptionInstance($class, $name, array $options = array())
118
    {
119
        if (!is_string($name)) {
120
            throw new \RuntimeException('The name argument must be a string');
121
        }
122
123
        if (!isset($options['route']['name'])) {
124
            $options['route']['name'] = 'edit';
125
        }
126
127
        if (!isset($options['route']['parameters'])) {
128
            $options['route']['parameters'] = array();
129
        }
130
131
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
132
133
        $fieldDescription = new FieldDescription();
134
        $fieldDescription->setName($name);
135
        $fieldDescription->setOptions($options);
136
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
137
138
        if (isset($metadata->associationMappings[$propertyName])) {
139
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
140
        }
141
142
        if (isset($metadata->fieldMappings[$propertyName])) {
143
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
144
        }
145
146
        return $fieldDescription;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function create($object)
153
    {
154
        try {
155
            $entityManager = $this->getEntityManager($object);
156
            $entityManager->persist($object);
157
            $entityManager->flush();
158
        } catch (\PDOException $e) {
159
            throw new ModelManagerException(
160
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
161
                $e->getCode(),
162
                $e
163
            );
164
        } catch (DBALException $e) {
165
            throw new ModelManagerException(
166
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
167
                $e->getCode(),
168
                $e
169
            );
170
        }
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function update($object)
177
    {
178
        try {
179
            $entityManager = $this->getEntityManager($object);
180
            $entityManager->persist($object);
181
            $entityManager->flush();
182
        } catch (\PDOException $e) {
183
            throw new ModelManagerException(
184
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
185
                $e->getCode(),
186
                $e
187
            );
188
        } catch (DBALException $e) {
189
            throw new ModelManagerException(
190
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
191
                $e->getCode(),
192
                $e
193
            );
194
        }
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function delete($object)
201
    {
202
        try {
203
            $entityManager = $this->getEntityManager($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...
204
            $entityManager->remove($object);
205
            $entityManager->flush();
206
        } catch (\PDOException $e) {
207
            throw new ModelManagerException(
208
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
209
                $e->getCode(),
210
                $e
211
            );
212
        } catch (DBALException $e) {
213
            throw new ModelManagerException(
214
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
215
                $e->getCode(),
216
                $e
217
            );
218
        }
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224
    public function getLockVersion($object)
225
    {
226
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
227
228
        if (!$metadata->isVersioned) {
229
            return;
230
        }
231
232
        return $metadata->reflFields[$metadata->versionField]->getValue($object);
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function lock($object, $expectedVersion)
239
    {
240
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
241
242
        if (!$metadata->isVersioned) {
243
            return;
244
        }
245
246
        try {
247
            $entityManager = $this->getEntityManager($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...
248
            $entityManager->lock($object, LockMode::OPTIMISTIC, $expectedVersion);
249
        } catch (OptimisticLockException $e) {
250
            throw new LockException($e->getMessage(), $e->getCode(), $e);
251
        }
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257
    public function find($class, $id)
258
    {
259
        if (!isset($id)) {
260
            return;
261
        }
262
263
        $values = array_combine($this->getIdentifierFieldNames($class), explode(self::ID_SEPARATOR, $id));
264
265
        return $this->getEntityManager($class)->getRepository($class)->find($values);
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271
    public function findBy($class, array $criteria = array())
272
    {
273
        return $this->getEntityManager($class)->getRepository($class)->findBy($criteria);
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279
    public function findOneBy($class, array $criteria = array())
280
    {
281
        return $this->getEntityManager($class)->getRepository($class)->findOneBy($criteria);
282
    }
283
284
    /**
285
     * @param string $class
286
     *
287
     * @return EntityManager
288
     */
289
    public function getEntityManager($class)
290
    {
291
        if (is_object($class)) {
292
            $class = get_class($class);
293
        }
294
295
        if (!isset($this->cache[$class])) {
296
            $em = $this->registry->getManagerForClass($class);
297
298
            if (!$em) {
299
                throw new \RuntimeException(sprintf('No entity manager defined for class %s', $class));
300
            }
301
302
            $this->cache[$class] = $em;
303
        }
304
305
        return $this->cache[$class];
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311
    public function getParentFieldDescription($parentAssociationMapping, $class)
312
    {
313
        $fieldName = $parentAssociationMapping['fieldName'];
314
315
        $metadata = $this->getMetadata($class);
316
317
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
318
319
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
320
        $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...
321
        $fieldDescription->setAssociationMapping($associatingMapping);
322
323
        return $fieldDescription;
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329
    public function createQuery($class, $alias = 'o')
330
    {
331
        $repository = $this->getEntityManager($class)->getRepository($class);
332
333
        return new ProxyQuery($repository->createQueryBuilder($alias));
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    public function executeQuery($query)
340
    {
341
        if ($query instanceof QueryBuilder) {
342
            return $query->getQuery()->execute();
343
        }
344
345
        return $query->execute();
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function getModelIdentifier($class)
352
    {
353
        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...
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359
    public function getIdentifierValues($entity)
360
    {
361
        // Fix code has an impact on performance, so disable it ...
362
        //$entityManager = $this->getEntityManager($entity);
363
        //if (!$entityManager->getUnitOfWork()->isInIdentityMap($entity)) {
364
        //    throw new \RuntimeException('Entities passed to the choice field must be managed');
365
        //}
366
367
        $class = ClassUtils::getClass($entity);
368
        $metadata = $this->getMetadata($class);
369
        $platform = $this->getEntityManager($class)->getConnection()->getDatabasePlatform();
370
371
        $identifiers = array();
372
373
        foreach ($metadata->getIdentifierValues($entity) as $name => $value) {
374
            if (!is_object($value)) {
375
                $identifiers[] = $value;
376
                continue;
377
            }
378
379
            $fieldType = $metadata->getTypeOfField($name);
380
            $type = $fieldType && Type::hasType($fieldType) ? Type::getType($fieldType) : null;
381
            if ($type) {
382
                $identifiers[] = $type->convertToDatabaseValue($value, $platform);
383
                continue;
384
            }
385
386
            $metadata = $this->getMetadata(ClassUtils::getClass($value));
387
388
            foreach ($metadata->getIdentifierValues($value) as $value) {
389
                $identifiers[] = $value;
390
            }
391
        }
392
393
        return $identifiers;
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    public function getIdentifierFieldNames($class)
400
    {
401
        return $this->getMetadata($class)->getIdentifierFieldNames();
402
    }
403
404
    /**
405
     * {@inheritdoc}
406
     */
407
    public function getNormalizedIdentifier($entity)
408
    {
409
        if (is_scalar($entity)) {
410
            throw new \RuntimeException('Invalid argument, object or null required');
411
        }
412
413
        if (!$entity) {
414
            return;
415
        }
416
417
        if (in_array($this->getEntityManager($entity)->getUnitOfWork()->getEntityState($entity), array(
0 ignored issues
show
Documentation introduced by
$entity 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...
418
            UnitOfWork::STATE_NEW,
419
            UnitOfWork::STATE_REMOVED,
420
        ), true)) {
421
            return;
422
        }
423
424
        $values = $this->getIdentifierValues($entity);
425
426
        if (count($values) === 0) {
427
            return;
428
        }
429
430
        return implode(self::ID_SEPARATOR, $values);
431
    }
432
433
    /**
434
     * {@inheritdoc}
435
     *
436
     * The ORM implementation does nothing special but you still should use
437
     * this method when using the id in a URL to allow for future improvements.
438
     */
439
    public function getUrlsafeIdentifier($entity)
440
    {
441
        return $this->getNormalizedIdentifier($entity);
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
448
    {
449
        $fieldNames = $this->getIdentifierFieldNames($class);
450
        $qb = $queryProxy->getQueryBuilder();
451
452
        $prefix = uniqid();
453
        $sqls = array();
454
        foreach ($idx as $pos => $id) {
455
            $ids = explode(self::ID_SEPARATOR, $id);
456
457
            $ands = array();
458
            foreach ($fieldNames as $posName => $name) {
459
                $parameterName = sprintf('field_%s_%s_%d', $prefix, $name, $pos);
460
                $ands[] = sprintf('%s.%s = :%s', $qb->getRootAlias(), $name, $parameterName);
461
                $qb->setParameter($parameterName, $ids[$posName]);
462
            }
463
464
            $sqls[] = implode(' AND ', $ands);
465
        }
466
467
        $qb->andWhere(sprintf('( %s )', implode(' OR ', $sqls)));
468
    }
469
470
    /**
471
     * {@inheritdoc}
472
     */
473
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
474
    {
475
        $queryProxy->select('DISTINCT '.$queryProxy->getRootAlias());
476
477
        try {
478
            $entityManager = $this->getEntityManager($class);
479
480
            $i = 0;
481
            foreach ($queryProxy->getQuery()->iterate() as $pos => $object) {
482
                $entityManager->remove($object[0]);
483
484
                if ((++$i % 20) == 0) {
485
                    $entityManager->flush();
486
                    $entityManager->clear();
487
                }
488
            }
489
490
            $entityManager->flush();
491
            $entityManager->clear();
492
        } catch (\PDOException $e) {
493
            throw new ModelManagerException('', 0, $e);
494
        } catch (DBALException $e) {
495
            throw new ModelManagerException('', 0, $e);
496
        }
497
    }
498
499
    /**
500
     * {@inheritdoc}
501
     */
502
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
503
    {
504
        $datagrid->buildPager();
505
        $query = $datagrid->getQuery();
506
507
        $query->select('DISTINCT '.$query->getRootAlias());
508
        $query->setFirstResult($firstResult);
509
        $query->setMaxResults($maxResult);
510
511
        if ($query instanceof ProxyQueryInterface) {
512
            $sortBy = $query->getSortBy();
513
            $sortOrder = $query->getSortOrder();
514
515
            if (!empty($sortBy) && !empty($sortOrder)) {
516
                $query->addOrderBy($query->getSortBy(), $query->getSortOrder());
517
            }
518
519
            $query = $query->getQuery();
520
        }
521
522
        return new DoctrineORMQuerySourceIterator($query, $fields);
523
    }
524
525
    /**
526
     * {@inheritdoc}
527
     */
528
    public function getExportFields($class)
529
    {
530
        $metadata = $this->getEntityManager($class)->getClassMetadata($class);
531
532
        return $metadata->getFieldNames();
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538
    public function getModelInstance($class)
539
    {
540
        $r = new \ReflectionClass($class);
541
        if ($r->isAbstract()) {
542
            throw new \RuntimeException(sprintf('Cannot initialize abstract class: %s', $class));
543
        }
544
545
        return new $class();
546
    }
547
548
    /**
549
     * {@inheritdoc}
550
     */
551
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
552
    {
553
        $values = $datagrid->getValues();
554
555
        if ($fieldDescription->getName() == $values['_sort_by']->getName() || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable')) {
556
            if ($values['_sort_order'] == 'ASC') {
557
                $values['_sort_order'] = 'DESC';
558
            } else {
559
                $values['_sort_order'] = 'ASC';
560
            }
561
        } else {
562
            $values['_sort_order'] = 'ASC';
563
        }
564
565
        $values['_sort_by'] = is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
566
567
        return array('filter' => $values);
568
    }
569
570
    /**
571
     * {@inheritdoc}
572
     */
573
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
574
    {
575
        $values = $datagrid->getValues();
576
577
        $values['_sort_by'] = $values['_sort_by']->getName();
578
        $values['_page'] = $page;
579
580
        return array('filter' => $values);
581
    }
582
583
    /**
584
     * {@inheritdoc}
585
     */
586
    public function getDefaultSortValues($class)
587
    {
588
        return array(
589
            '_sort_order' => 'ASC',
590
            '_sort_by' => implode(',', $this->getModelIdentifier($class)),
591
            '_page' => 1,
592
            '_per_page' => 25,
593
        );
594
    }
595
596
    /**
597
     * {@inheritdoc}
598
     */
599
    public function modelTransform($class, $instance)
600
    {
601
        return $instance;
602
    }
603
604
    /**
605
     * {@inheritdoc}
606
     */
607
    public function modelReverseTransform($class, array $array = array())
608
    {
609
        $instance = $this->getModelInstance($class);
610
        $metadata = $this->getMetadata($class);
611
612
        $reflClass = $metadata->reflClass;
613
        foreach ($array as $name => $value) {
614
            $reflection_property = false;
615
            // property or association ?
616
            if (array_key_exists($name, $metadata->fieldMappings)) {
617
                $property = $metadata->fieldMappings[$name]['fieldName'];
618
                $reflection_property = $metadata->reflFields[$name];
619
            } elseif (array_key_exists($name, $metadata->associationMappings)) {
620
                $property = $metadata->associationMappings[$name]['fieldName'];
621
            } else {
622
                $property = $name;
623
            }
624
625
            $setter = 'set'.$this->camelize($name);
626
627
            if ($reflClass->hasMethod($setter)) {
628
                if (!$reflClass->getMethod($setter)->isPublic()) {
629
                    throw new PropertyAccessDeniedException(sprintf(
630
                        'Method "%s()" is not public in class "%s"',
631
                        $setter,
632
                        $reflClass->getName()
633
                    ));
634
                }
635
636
                $instance->$setter($value);
637
            } elseif ($reflClass->hasMethod('__set')) {
638
                // needed to support magic method __set
639
                $instance->$property = $value;
640
            } elseif ($reflClass->hasProperty($property)) {
641
                if (!$reflClass->getProperty($property)->isPublic()) {
642
                    throw new PropertyAccessDeniedException(sprintf(
643
                        'Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?',
644
                            $property,
645
                            $reflClass->getName(),
646
                            ucfirst($property)
647
                    ));
648
                }
649
650
                $instance->$property = $value;
651
            } elseif ($reflection_property) {
652
                $reflection_property->setValue($instance, $value);
653
            }
654
        }
655
656
        return $instance;
657
    }
658
659
    /**
660
     * {@inheritdoc}
661
     */
662
    public function getModelCollectionInstance($class)
663
    {
664
        return new \Doctrine\Common\Collections\ArrayCollection();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Doctrine\Com...ions\ArrayCollection(); (Doctrine\Common\Collections\ArrayCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Model...ModelCollectionInstance of type array.

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...
665
    }
666
667
    /**
668
     * {@inheritdoc}
669
     */
670
    public function collectionClear(&$collection)
671
    {
672
        return $collection->clear();
673
    }
674
675
    /**
676
     * {@inheritdoc}
677
     */
678
    public function collectionHasElement(&$collection, &$element)
679
    {
680
        return $collection->contains($element);
681
    }
682
683
    /**
684
     * {@inheritdoc}
685
     */
686
    public function collectionAddElement(&$collection, &$element)
687
    {
688
        return $collection->add($element);
689
    }
690
691
    /**
692
     * {@inheritdoc}
693
     */
694
    public function collectionRemoveElement(&$collection, &$element)
695
    {
696
        return $collection->removeElement($element);
697
    }
698
699
    /**
700
     * method taken from PropertyPath.
701
     *
702
     * @param string $property
703
     *
704
     * @return mixed
705
     */
706
    protected function camelize($property)
707
    {
708
        return preg_replace(
709
            array('/(^|_)+(.)/e', '/\.(.)/e'),
710
            array("strtoupper('\\2')", "'_'.strtoupper('\\1')"),
711
            $property
712
        );
713
    }
714
}
715