2011-05-04 06:48:40 +04:00
|
|
|
Persisting the Decorator Pattern
|
|
|
|
================================
|
|
|
|
|
|
|
|
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
|
|
|
|
|
2011-05-06 05:36:18 +04:00
|
|
|
This recipe will show you a simple example of how you can use
|
2012-12-02 21:58:15 +04:00
|
|
|
Doctrine 2 to persist an implementation of the
|
2011-05-06 05:36:18 +04:00
|
|
|
`Decorator Pattern <http://en.wikipedia.org/wiki/Decorator_pattern>`_
|
2011-05-04 06:48:40 +04:00
|
|
|
|
2011-05-04 07:04:41 +04:00
|
|
|
Component
|
|
|
|
---------
|
2011-05-04 06:48:40 +04:00
|
|
|
|
2011-05-06 05:36:18 +04:00
|
|
|
The ``Component`` class needs to be persisted, so it's going to
|
2011-05-06 05:16:40 +04:00
|
|
|
be an ``Entity``. As the top of the inheritance hierarchy, it's going
|
2011-05-04 06:48:40 +04:00
|
|
|
to have to define the persistent inheritance. For this example, we
|
2011-05-06 05:16:40 +04:00
|
|
|
will use Single Table Inheritance, but Class Table Inheritance
|
2011-05-04 07:04:41 +04:00
|
|
|
would work as well. In the discriminator map, we will define two
|
2011-05-06 05:16:40 +04:00
|
|
|
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Test;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Entity
|
|
|
|
* @InheritanceType("SINGLE_TABLE")
|
|
|
|
* @DiscriminatorColumn(name="discr", type="string")
|
2011-05-06 05:16:40 +04:00
|
|
|
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
|
|
|
|
"cd" = "Test\Decorator\ConcreteDecorator"})
|
2011-05-04 06:48:40 +04:00
|
|
|
*/
|
|
|
|
abstract class Component
|
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Id @Column(type="integer")
|
|
|
|
* @GeneratedValue(strategy="AUTO")
|
|
|
|
*/
|
|
|
|
protected $id;
|
|
|
|
|
2013-01-17 00:48:15 +04:00
|
|
|
/** @Column(type="string", nullable=true) */
|
2011-05-04 06:48:40 +04:00
|
|
|
protected $name;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get id
|
|
|
|
* @return integer $id
|
|
|
|
*/
|
|
|
|
public function getId()
|
|
|
|
{
|
|
|
|
return $this->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set name
|
|
|
|
* @param string $name
|
|
|
|
*/
|
|
|
|
public function setName($name)
|
|
|
|
{
|
|
|
|
$this->name = $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get name
|
|
|
|
* @return string $name
|
|
|
|
*/
|
|
|
|
public function getName()
|
|
|
|
{
|
|
|
|
return $this->name;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ConcreteComponent
|
|
|
|
-----------------
|
|
|
|
|
2011-05-06 05:16:40 +04:00
|
|
|
The ``ConcreteComponent`` class is pretty simple and doesn't do much
|
|
|
|
more than extend the abstract ``Component`` class (only for the
|
|
|
|
purpose of keeping this example simple).
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Test\Component;
|
|
|
|
|
|
|
|
use Test\Component;
|
|
|
|
|
|
|
|
/** @Entity */
|
|
|
|
class ConcreteComponent extends Component
|
|
|
|
{}
|
|
|
|
|
|
|
|
Decorator
|
|
|
|
---------
|
|
|
|
|
2011-05-06 05:16:40 +04:00
|
|
|
The ``Decorator`` class doesn't need to be persisted, but it does
|
|
|
|
need to define an association with a persisted ``Entity``. We can
|
|
|
|
use a ``MappedSuperclass`` for this.
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Test;
|
|
|
|
|
|
|
|
/** @MappedSuperclass */
|
|
|
|
abstract class Decorator extends Component
|
|
|
|
{
|
|
|
|
|
|
|
|
/**
|
2011-05-06 05:16:40 +04:00
|
|
|
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
|
2011-05-04 06:48:40 +04:00
|
|
|
* @JoinColumn(name="decorates", referencedColumnName="id")
|
|
|
|
*/
|
|
|
|
protected $decorates;
|
|
|
|
|
|
|
|
/**
|
2012-12-02 21:58:15 +04:00
|
|
|
* initialize the decorator
|
2011-05-04 06:48:40 +04:00
|
|
|
* @param Component $c
|
|
|
|
*/
|
|
|
|
public function __construct(Component $c)
|
|
|
|
{
|
2013-01-17 00:48:15 +04:00
|
|
|
$this->setDecorates($c);
|
2011-05-04 06:48:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (non-PHPdoc)
|
2011-05-06 05:16:40 +04:00
|
|
|
* @see Test.Component::getName()
|
2011-05-04 06:48:40 +04:00
|
|
|
*/
|
|
|
|
public function getName()
|
|
|
|
{
|
|
|
|
return 'Decorated ' . $this->getDecorates()->getName();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the component being decorated
|
|
|
|
* @return Component
|
|
|
|
*/
|
|
|
|
protected function getDecorates()
|
|
|
|
{
|
|
|
|
return $this->decorates;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sets the component being decorated
|
|
|
|
* @param Component $c
|
|
|
|
*/
|
|
|
|
protected function setDecorates(Component $c)
|
|
|
|
{
|
|
|
|
$this->decorates = $c;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2011-05-06 05:16:40 +04:00
|
|
|
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
|
|
|
|
cascade from the ``Decorator`` to the ``Component``. This means that
|
|
|
|
when we persist a ``Decorator``, Doctrine will take care of
|
|
|
|
persisting the chain of decorated objects for us. A ``Decorator`` can
|
|
|
|
be treated exactly as a ``Component`` when it comes time to
|
|
|
|
persisting it.
|
|
|
|
|
|
|
|
The ``Decorator's`` constructor accepts an instance of a
|
2011-05-06 05:23:10 +04:00
|
|
|
``Component``, as defined by the ``Decorator`` pattern. The
|
|
|
|
setDecorates/getDecorates methods have been defined as protected to
|
|
|
|
hide the fact that a ``Decorator`` is decorating a ``Component`` and
|
|
|
|
keeps the ``Component`` interface and the ``Decorator`` interface
|
|
|
|
identical.
|
|
|
|
|
|
|
|
To illustrate the intended result of the ``Decorator`` pattern, the
|
|
|
|
getName() method has been overridden to append a string to the
|
|
|
|
``Component's`` getName() method.
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
ConcreteDecorator
|
|
|
|
-----------------
|
|
|
|
|
2011-05-04 07:04:41 +04:00
|
|
|
The final class required to complete a simple implementation of the
|
2011-05-06 05:16:40 +04:00
|
|
|
Decorator pattern is the ``ConcreteDecorator``. In order to further
|
|
|
|
illustrate how the ``Decorator`` can alter data as it moves through
|
|
|
|
the chain of decoration, a new field, "special", has been added to
|
|
|
|
this class. The getName() has been overridden and appends the value
|
|
|
|
of the getSpecial() method to its return value.
|
2011-05-04 07:04:41 +04:00
|
|
|
|
2011-05-04 06:48:40 +04:00
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Test\Decorator;
|
|
|
|
|
|
|
|
use Test\Decorator;
|
|
|
|
|
|
|
|
/** @Entity */
|
|
|
|
class ConcreteDecorator extends Decorator
|
|
|
|
{
|
|
|
|
|
2013-01-17 00:48:15 +04:00
|
|
|
/** @Column(type="string", nullable=true) */
|
2011-05-04 06:48:40 +04:00
|
|
|
protected $special;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set special
|
|
|
|
* @param string $special
|
|
|
|
*/
|
|
|
|
public function setSpecial($special)
|
|
|
|
{
|
|
|
|
$this->special = $special;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get special
|
|
|
|
* @return string $special
|
|
|
|
*/
|
|
|
|
public function getSpecial()
|
|
|
|
{
|
|
|
|
return $this->special;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (non-PHPdoc)
|
2011-05-06 05:16:40 +04:00
|
|
|
* @see Test.Component::getName()
|
2011-05-04 06:48:40 +04:00
|
|
|
*/
|
|
|
|
public function getName()
|
|
|
|
{
|
|
|
|
return '[' . $this->getSpecial()
|
|
|
|
. '] ' . parent::getName();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2011-05-04 07:04:41 +04:00
|
|
|
Examples
|
|
|
|
--------
|
|
|
|
|
|
|
|
Here is an example of how to persist and retrieve your decorated
|
|
|
|
objects
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
2011-05-06 05:16:40 +04:00
|
|
|
use Test\Component\ConcreteComponent,
|
|
|
|
Test\Decorator\ConcreteDecorator;
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
// assumes Doctrine 2 is configured and an instance of
|
|
|
|
// an EntityManager is available as $em
|
|
|
|
|
|
|
|
// create a new concrete component
|
|
|
|
$c = new ConcreteComponent();
|
|
|
|
$c->setName('Test Component 1');
|
|
|
|
$em->persist($c); // assigned unique ID = 1
|
|
|
|
|
|
|
|
// create a new concrete decorator
|
|
|
|
$c = new ConcreteComponent();
|
|
|
|
$c->setName('Test Component 2');
|
|
|
|
|
|
|
|
$d = new ConcreteDecorator($c);
|
|
|
|
$d->setSpecial('Really');
|
|
|
|
$em->persist($d);
|
|
|
|
// assigns c as unique ID = 2, and d as unique ID = 3
|
|
|
|
|
|
|
|
$em->flush();
|
|
|
|
|
|
|
|
$c = $em->find('Test\Component', 1);
|
|
|
|
$d = $em->find('Test\Component', 3);
|
|
|
|
|
|
|
|
echo get_class($c);
|
2011-05-06 05:16:40 +04:00
|
|
|
// prints: Test\Component\ConcreteComponent
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
echo $c->getName();
|
|
|
|
// prints: Test Component 1
|
|
|
|
|
|
|
|
echo get_class($d)
|
2011-05-06 05:16:40 +04:00
|
|
|
// prints: Test\Component\ConcreteDecorator
|
2011-05-04 06:48:40 +04:00
|
|
|
|
|
|
|
echo $d->getName();
|
|
|
|
// prints: [Really] Decorated Test Component 2
|
|
|
|
|