Completed
Push — 3.x ( da643f...3e3cbd )
by
unknown
9s
created

ModelManager::modelReverseTransform()   C

Complexity

Conditions 10
Paths 22

Size

Total Lines 51
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 51
rs 6
c 0
b 0
f 0
cc 10
eloc 34
nc 22
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
            $query->addOrderBy($query->getSortBy(), $query->getSortOrder());
513
514
            $query = $query->getQuery();
515
        }
516
517
        return new DoctrineORMQuerySourceIterator($query, $fields);
518
    }
519
520
    /**
521
     * {@inheritdoc}
522
     */
523
    public function getExportFields($class)
524
    {
525
        $metadata = $this->getEntityManager($class)->getClassMetadata($class);
526
527
        return $metadata->getFieldNames();
528
    }
529
530
    /**
531
     * {@inheritdoc}
532
     */
533
    public function getModelInstance($class)
534
    {
535
        $r = new \ReflectionClass($class);
536
        if ($r->isAbstract()) {
537
            throw new \RuntimeException(sprintf('Cannot initialize abstract class: %s', $class));
538
        }
539
540
        return new $class();
541
    }
542
543
    /**
544
     * {@inheritdoc}
545
     */
546
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
547
    {
548
        $values = $datagrid->getValues();
549
550
        if ($fieldDescription->getName() == $values['_sort_by']->getName() || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable')) {
551
            if ($values['_sort_order'] == 'ASC') {
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 array('filter' => $values);
563
    }
564
565
    /**
566
     * {@inheritdoc}
567
     */
568
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
569
    {
570
        $values = $datagrid->getValues();
571
572
        $values['_sort_by'] = $values['_sort_by']->getName();
573
        $values['_page'] = $page;
574
575
        return array('filter' => $values);
576
    }
577
578
    /**
579
     * {@inheritdoc}
580
     */
581
    public function getDefaultSortValues($class)
582
    {
583
        return array(
584
            '_sort_order' => 'ASC',
585
            '_sort_by' => implode(',', $this->getModelIdentifier($class)),
586
            '_page' => 1,
587
            '_per_page' => 25,
588
        );
589
    }
590
591
    /**
592
     * {@inheritdoc}
593
     */
594
    public function modelTransform($class, $instance)
595
    {
596
        return $instance;
597
    }
598
599
    /**
600
     * {@inheritdoc}
601
     */
602
    public function modelReverseTransform($class, array $array = array())
603
    {
604
        $instance = $this->getModelInstance($class);
605
        $metadata = $this->getMetadata($class);
606
607
        $reflClass = $metadata->reflClass;
608
        foreach ($array as $name => $value) {
609
            $reflection_property = false;
610
            // property or association ?
611
            if (array_key_exists($name, $metadata->fieldMappings)) {
612
                $property = $metadata->fieldMappings[$name]['fieldName'];
613
                $reflection_property = $metadata->reflFields[$name];
614
            } elseif (array_key_exists($name, $metadata->associationMappings)) {
615
                $property = $metadata->associationMappings[$name]['fieldName'];
616
            } else {
617
                $property = $name;
618
            }
619
620
            $setter = 'set'.$this->camelize($name);
621
622
            if ($reflClass->hasMethod($setter)) {
623
                if (!$reflClass->getMethod($setter)->isPublic()) {
624
                    throw new PropertyAccessDeniedException(sprintf(
625
                        'Method "%s()" is not public in class "%s"',
626
                        $setter,
627
                        $reflClass->getName()
628
                    ));
629
                }
630
631
                $instance->$setter($value);
632
            } elseif ($reflClass->hasMethod('__set')) {
633
                // needed to support magic method __set
634
                $instance->$property = $value;
635
            } elseif ($reflClass->hasProperty($property)) {
636
                if (!$reflClass->getProperty($property)->isPublic()) {
637
                    throw new PropertyAccessDeniedException(sprintf(
638
                        'Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?',
639
                            $property,
640
                            $reflClass->getName(),
641
                            ucfirst($property)
642
                    ));
643
                }
644
645
                $instance->$property = $value;
646
            } elseif ($reflection_property) {
647
                $reflection_property->setValue($instance, $value);
648
            }
649
        }
650
651
        return $instance;
652
    }
653
654
    /**
655
     * {@inheritdoc}
656
     */
657
    public function getModelCollectionInstance($class)
658
    {
659
        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...
660
    }
661
662
    /**
663
     * {@inheritdoc}
664
     */
665
    public function collectionClear(&$collection)
666
    {
667
        return $collection->clear();
668
    }
669
670
    /**
671
     * {@inheritdoc}
672
     */
673
    public function collectionHasElement(&$collection, &$element)
674
    {
675
        return $collection->contains($element);
676
    }
677
678
    /**
679
     * {@inheritdoc}
680
     */
681
    public function collectionAddElement(&$collection, &$element)
682
    {
683
        return $collection->add($element);
684
    }
685
686
    /**
687
     * {@inheritdoc}
688
     */
689
    public function collectionRemoveElement(&$collection, &$element)
690
    {
691
        return $collection->removeElement($element);
692
    }
693
694
    /**
695
     * method taken from PropertyPath.
696
     *
697
     * @param string $property
698
     *
699
     * @return mixed
700
     */
701
    protected function camelize($property)
702
    {
703
        return preg_replace(
704
            array('/(^|_)+(.)/e', '/\.(.)/e'),
705
            array("strtoupper('\\2')", "'_'.strtoupper('\\1')"),
706
            $property
707
        );
708
    }
709
}
710