Completed
Pull Request — master (#523)
by Jeroen
04:33
created

ModelManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Sonata 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 Exporter\Source\DoctrineORMQuerySourceIterator;
23
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
24
use Sonata\AdminBundle\Datagrid\DatagridInterface;
25
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
26
use Sonata\AdminBundle\Exception\LockException;
27
use Sonata\AdminBundle\Exception\ModelManagerException;
28
use Sonata\AdminBundle\Model\LockInterface;
29
use Sonata\AdminBundle\Model\ModelManagerInterface;
30
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
31
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
32
use Symfony\Bridge\Doctrine\RegistryInterface;
33
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
34
35
class ModelManager implements ModelManagerInterface, LockInterface
36
{
37
    /**
38
     * @var RegistryInterface
39
     */
40
    protected $registry;
41
42
    /**
43
     * @var EntityManager[]
44
     */
45
    protected $cache = array();
46
47
    const ID_SEPARATOR = '~';
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(sprintf('Failed to create object: %s', ClassUtils::getClass($object)), $e->getCode(), $e);
160
        } catch (DBALException $e) {
161
            throw new ModelManagerException(sprintf('Failed to create object: %s', ClassUtils::getClass($object)), $e->getCode(), $e);
162
        }
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function update($object)
169
    {
170
        try {
171
            $entityManager = $this->getEntityManager($object);
172
            $entityManager->persist($object);
173
            $entityManager->flush();
174
        } catch (\PDOException $e) {
175
            throw new ModelManagerException(sprintf('Failed to update object: %s', ClassUtils::getClass($object)), $e->getCode(), $e);
176
        } catch (DBALException $e) {
177
            throw new ModelManagerException(sprintf('Failed to update object: %s', ClassUtils::getClass($object)), $e->getCode(), $e);
178
        }
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function delete($object)
185
    {
186
        try {
187
            $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...
188
            $entityManager->remove($object);
189
            $entityManager->flush();
190
        } catch (\PDOException $e) {
191
            throw new ModelManagerException(sprintf('Failed to delete object: %s', ClassUtils::getClass($object)), $e->getCode(), $e);
192
        } catch (DBALException $e) {
193
            throw new ModelManagerException(sprintf('Failed to delete object: %s', ClassUtils::getClass($object)), $e->getCode(), $e);
194
        }
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function getLockVersion($object)
201
    {
202
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
203
204
        if (!$metadata->isVersioned) {
205
            return;
206
        }
207
208
        return $metadata->reflFields[$metadata->versionField]->getValue($object);
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function lock($object, $expectedVersion)
215
    {
216
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
217
218
        if (!$metadata->isVersioned) {
219
            return;
220
        }
221
222
        try {
223
            $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...
224
            $entityManager->lock($object, LockMode::OPTIMISTIC, $expectedVersion);
225
        } catch (OptimisticLockException $e) {
226
            throw new LockException($e->getMessage(), $e->getCode(), $e);
227
        }
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233
    public function find($class, $id)
234
    {
235
        if (!isset($id)) {
236
            return;
237
        }
238
239
        $values = array_combine($this->getIdentifierFieldNames($class), explode(self::ID_SEPARATOR, $id));
240
241
        return $this->getEntityManager($class)->getRepository($class)->find($values);
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247
    public function findBy($class, array $criteria = array())
248
    {
249
        return $this->getEntityManager($class)->getRepository($class)->findBy($criteria);
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function findOneBy($class, array $criteria = array())
256
    {
257
        return $this->getEntityManager($class)->getRepository($class)->findOneBy($criteria);
258
    }
259
260
    /**
261
     * @param string $class
262
     *
263
     * @return EntityManager
264
     */
265
    public function getEntityManager($class)
266
    {
267
        if (is_object($class)) {
268
            $class = get_class($class);
269
        }
270
271
        if (!isset($this->cache[$class])) {
272
            $em = $this->registry->getManagerForClass($class);
273
274
            if (!$em) {
275
                throw new \RuntimeException(sprintf('No entity manager defined for class %s', $class));
276
            }
277
278
            $this->cache[$class] = $em;
279
        }
280
281
        return $this->cache[$class];
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function getParentFieldDescription($parentAssociationMapping, $class)
288
    {
289
        $fieldName = $parentAssociationMapping['fieldName'];
290
291
        $metadata = $this->getMetadata($class);
292
293
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
294
295
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
296
        $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...
297
        $fieldDescription->setAssociationMapping($associatingMapping);
298
299
        return $fieldDescription;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function createQuery($class, $alias = 'o')
306
    {
307
        $repository = $this->getEntityManager($class)->getRepository($class);
308
309
        return new ProxyQuery($repository->createQueryBuilder($alias));
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315
    public function executeQuery($query)
316
    {
317
        if ($query instanceof QueryBuilder) {
318
            return $query->getQuery()->execute();
319
        }
320
321
        return $query->execute();
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327
    public function getModelIdentifier($class)
328
    {
329
        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...
330
    }
331
332
    /**
333
     * {@inheritdoc}
334
     */
335
    public function getIdentifierValues($entity)
336
    {
337
        // Fix code has an impact on performance, so disable it ...
338
        //$entityManager = $this->getEntityManager($entity);
339
        //if (!$entityManager->getUnitOfWork()->isInIdentityMap($entity)) {
340
        //    throw new \RuntimeException('Entities passed to the choice field must be managed');
341
        //}
342
343
        $class = ClassUtils::getClass($entity);
344
        $metadata = $this->getMetadata($class);
345
        $platform = $this->getEntityManager($class)->getConnection()->getDatabasePlatform();
346
347
        $identifiers = array();
348
349
        foreach ($metadata->getIdentifierValues($entity) as $name => $value) {
350
            if (!is_object($value)) {
351
                $identifiers[] = $value;
352
                continue;
353
            }
354
355
            $fieldType = $metadata->getTypeOfField($name);
356
            $type = Type::getType($fieldType);
1 ignored issue
show
Bug introduced by
It seems like $fieldType defined by $metadata->getTypeOfField($name) on line 355 can also be of type null or object<Doctrine\DBAL\Types\Type>; however, Doctrine\DBAL\Types\Type::getType() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
357
            if ($type) {
358
                $identifiers[] = $type->convertToDatabaseValue($value, $platform);
359
                continue;
360
            }
361
362
            $metadata = $this->getMetadata(ClassUtils::getClass($value));
363
364
            foreach ($metadata->getIdentifierValues($value) as $value) {
365
                $identifiers[] = $value;
366
            }
367
        }
368
369
        return $identifiers;
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375
    public function getIdentifierFieldNames($class)
376
    {
377
        return $this->getMetadata($class)->getIdentifierFieldNames();
378
    }
379
380
    /**
381
     * {@inheritdoc}
382
     */
383
    public function getNormalizedIdentifier($entity)
384
    {
385
        if (is_scalar($entity)) {
386
            throw new \RunTimeException('Invalid argument, object or null required');
387
        }
388
389
        // the entities is not managed
390
        if (!$entity || !$this->getEntityManager($entity)->getUnitOfWork()->isInIdentityMap($entity)) {
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...
391
            return;
392
        }
393
394
        $values = $this->getIdentifierValues($entity);
395
396
        if (count($values) === 0) {
397
            return;
398
        }
399
400
        return implode(self::ID_SEPARATOR, $values);
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     *
406
     * The ORM implementation does nothing special but you still should use
407
     * this method when using the id in a URL to allow for future improvements.
408
     */
409
    public function getUrlsafeIdentifier($entity)
410
    {
411
        return $this->getNormalizedIdentifier($entity);
412
    }
413
414
    /**
415
     * {@inheritdoc}
416
     */
417
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
418
    {
419
        $fieldNames = $this->getIdentifierFieldNames($class);
420
        $qb         = $queryProxy->getQueryBuilder();
421
422
        $prefix = uniqid();
423
        $sqls   = array();
424
        foreach ($idx as $pos => $id) {
425
            $ids = explode(self::ID_SEPARATOR, $id);
426
427
            $ands = array();
428
            foreach ($fieldNames as $posName => $name) {
429
                $parameterName = sprintf('field_%s_%s_%d', $prefix, $name, $pos);
430
                $ands[]        = sprintf('%s.%s = :%s', $qb->getRootAlias(), $name, $parameterName);
431
                $qb->setParameter($parameterName, $ids[$posName]);
432
            }
433
434
            $sqls[] = implode(' AND ', $ands);
435
        }
436
437
        $qb->andWhere(sprintf('( %s )', implode(' OR ', $sqls)));
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
444
    {
445
        $queryProxy->select('DISTINCT '.$queryProxy->getRootAlias());
446
447
        try {
448
            $entityManager = $this->getEntityManager($class);
449
450
            $i = 0;
451
            foreach ($queryProxy->getQuery()->iterate() as $pos => $object) {
452
                $entityManager->remove($object[0]);
453
454
                if ((++$i % 20) == 0) {
455
                    $entityManager->flush();
456
                    $entityManager->clear();
457
                }
458
            }
459
460
            $entityManager->flush();
461
            $entityManager->clear();
462
        } catch (\PDOException $e) {
463
            throw new ModelManagerException('', 0, $e);
464
        } catch (DBALException $e) {
465
            throw new ModelManagerException('', 0, $e);
466
        }
467
    }
468
469
    /**
470
     * {@inheritdoc}
471
     */
472
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
473
    {
474
        $datagrid->buildPager();
475
        $query = $datagrid->getQuery();
476
477
        $query->select('DISTINCT '.$query->getRootAlias());
478
        $query->setFirstResult($firstResult);
479
        $query->setMaxResults($maxResult);
480
481
        if ($query instanceof ProxyQueryInterface) {
482
            $query->addOrderBy($query->getSortBy(), $query->getSortOrder());
483
484
            $query = $query->getQuery();
485
        }
486
487
        return new DoctrineORMQuerySourceIterator($query, $fields);
488
    }
489
490
    /**
491
     * {@inheritdoc}
492
     */
493
    public function getExportFields($class)
494
    {
495
        $metadata = $this->getEntityManager($class)->getClassMetadata($class);
496
497
        return $metadata->getFieldNames();
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503
    public function getModelInstance($class)
504
    {
505
        $r = new \ReflectionClass($class);
506
        if ($r->isAbstract()) {
507
            throw new \RuntimeException(sprintf('Cannot initialize abstract class: %s', $class));
508
        }
509
510
        return new $class();
511
    }
512
513
    /**
514
     * {@inheritdoc}
515
     */
516
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
517
    {
518
        $values = $datagrid->getValues();
519
520
        if ($fieldDescription->getName() == $values['_sort_by']->getName() || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable')) {
521
            if ($values['_sort_order'] == 'ASC') {
522
                $values['_sort_order'] = 'DESC';
523
            } else {
524
                $values['_sort_order'] = 'ASC';
525
            }
526
        } else {
527
            $values['_sort_order'] = 'ASC';
528
        }
529
530
        $values['_sort_by'] = is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
531
532
        return array('filter' => $values);
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
539
    {
540
        $values = $datagrid->getValues();
541
542
        $values['_sort_by'] = $values['_sort_by']->getName();
543
        $values['_page']    = $page;
544
545
        return array('filter' => $values);
546
    }
547
548
    /**
549
     * {@inheritdoc}
550
     */
551
    public function getDefaultSortValues($class)
552
    {
553
        return array(
554
            '_sort_order' => 'ASC',
555
            '_sort_by'    => implode(',', $this->getModelIdentifier($class)),
556
            '_page'       => 1,
557
            '_per_page'   => 25,
558
        );
559
    }
560
561
    /**
562
     * {@inheritdoc}
563
     */
564
    public function modelTransform($class, $instance)
565
    {
566
        return $instance;
567
    }
568
569
    /**
570
     * {@inheritdoc}
571
     */
572
    public function modelReverseTransform($class, array $array = array())
573
    {
574
        $instance = $this->getModelInstance($class);
575
        $metadata = $this->getMetadata($class);
576
577
        $reflClass = $metadata->reflClass;
578
        foreach ($array as $name => $value) {
579
            $reflection_property = false;
580
            // property or association ?
581
            if (array_key_exists($name, $metadata->fieldMappings)) {
582
                $property            = $metadata->fieldMappings[$name]['fieldName'];
583
                $reflection_property = $metadata->reflFields[$name];
584
            } elseif (array_key_exists($name, $metadata->associationMappings)) {
585
                $property = $metadata->associationMappings[$name]['fieldName'];
586
            } else {
587
                $property = $name;
588
            }
589
590
            $setter = 'set'.$this->camelize($name);
591
592
            if ($reflClass->hasMethod($setter)) {
593
                if (!$reflClass->getMethod($setter)->isPublic()) {
594
                    throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
0 ignored issues
show
Bug introduced by
Consider using $reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
595
                }
596
597
                $instance->$setter($value);
598
            } elseif ($reflClass->hasMethod('__set')) {
599
                // needed to support magic method __set
600
                $instance->$property = $value;
601
            } elseif ($reflClass->hasProperty($property)) {
602
                if (!$reflClass->getProperty($property)->isPublic()) {
603
                    throw new PropertyAccessDeniedException(
604
                        sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?', $property, $reflClass->getName(), ucfirst($property))
0 ignored issues
show
Bug introduced by
Consider using $reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
605
                    );
606
                }
607
608
                $instance->$property = $value;
609
            } elseif ($reflection_property) {
610
                $reflection_property->setValue($instance, $value);
611
            }
612
        }
613
614
        return $instance;
615
    }
616
617
    /**
618
     * method taken from PropertyPath.
619
     *
620
     * @param string $property
621
     *
622
     * @return mixed
623
     */
624
    protected function camelize($property)
625
    {
626
        return preg_replace(array('/(^|_)+(.)/e', '/\.(.)/e'), array("strtoupper('\\2')", "'_'.strtoupper('\\1')"), $property);
627
    }
628
629
    /**
630
     * {@inheritdoc}
631
     */
632
    public function getModelCollectionInstance($class)
633
    {
634
        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...
635
    }
636
637
    /**
638
     * {@inheritdoc}
639
     */
640
    public function collectionClear(&$collection)
641
    {
642
        return $collection->clear();
643
    }
644
645
    /**
646
     * {@inheritdoc}
647
     */
648
    public function collectionHasElement(&$collection, &$element)
649
    {
650
        return $collection->contains($element);
651
    }
652
653
    /**
654
     * {@inheritdoc}
655
     */
656
    public function collectionAddElement(&$collection, &$element)
657
    {
658
        return $collection->add($element);
659
    }
660
661
    /**
662
     * {@inheritdoc}
663
     */
664
    public function collectionRemoveElement(&$collection, &$element)
665
    {
666
        return $collection->removeElement($element);
667
    }
668
}
669