1
0
mirror of synced 2024-12-14 07:06:04 +03:00
doctrine2/en/cookbook/decorator-pattern.rst

275 lines
6.5 KiB
ReStructuredText
Raw Normal View History

2011-05-04 06:48:40 +04:00
Persisting the Decorator Pattern
================================
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
2011-05-04 07:04:41 +04:00
INTRO
2011-05-04 06:48:40 +04:00
Let's take a quick look at a visual representation of the Decorator
pattern
2011-05-04 07:04:41 +04:00
DECORATOR PATTERN IMAGE
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
Since the Component class needs to be persisted, it's going to be a
Doctrine Entity. As the top of the inheritance hierarchy, it's going
to have to define the persistent inheritance. For this example, we
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
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-04 07:04:41 +04:00
* @DiscriminatorMap({"cc" = "Test\Component\Concrete\Component",
"cd" = "Test\Decorator\Concrete\Decorator"})
2011-05-04 06:48:40 +04:00
*/
abstract class Component
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/** @Column(type="string", nullable="true") */
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
-----------------
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).
.. code-block:: php
<?php
namespace Test\Component;
use Test\Component;
/** @Entity */
class ConcreteComponent extends Component
{}
Decorator
---------
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.
.. code-block:: php
<?php
namespace Test;
/** @MappedSuperclass */
abstract class Decorator extends Component
{
/**
* @OneToOne(targetEntity="TestComponent", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
protected $decorates;
/**
* intialize the decorator
* @param Component $c
*/
public function __construct(Component $c)
{
$this->setDecorates($c);
}
/**
* (non-PHPdoc)
* @see ImedevacTest.Component::getName()
*/
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;
}
}
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 Component, as
defined by the Decorator pattern (using constructor injection). 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 purpose of the Decorator pattern, the getName()
method has been overridden to append a string to the Component's
getName() method.
ConcreteDecorator
-----------------
2011-05-04 07:04:41 +04:00
The final class required to complete a simple implementation of the
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 06:48:40 +04:00
.. code-block:: php
<?php
namespace Test\Decorator;
use Test\Decorator;
/** @Entity */
class ConcreteDecorator extends Decorator
{
/** @Column(type="string", nullable="true") */
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)
* @see ImedevacTest.Component::getName()
*/
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
use Test\Component\Concrete\Component,
Test\Decorator\Concrete\Decorator;
// 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);
// prints: Test\Component\Concrete\Component
echo $c->getName();
// prints: Test Component 1
echo get_class($d)
// prints: Test\Component\Concrete\Decorator
echo $d->getName();
// prints: [Really] Decorated Test Component 2