Completed
Pull Request — 3.x (#703)
by
unknown
01:53
created

ModelManager::getNormalizedIdentifier()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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