Persisting the Decorator Pattern

This recipe will show you a simple example of how you can use Doctrine ORM to persist an implementation of the Decorator Pattern

このレシピは、Doctrine ORM を使用してデコレータ パターンの実装を永続化する方法の簡単な例を示します。

Component

The Component class needs to be persisted, so it’s going to be an 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 would work as well. In the discriminator map, we will define two concrete subclasses, ConcreteComponent and ConcreteDecorator.

Component クラスは永続化する必要があるため、Entity になります。継承階層の最上位として、永続的な継承を定義する必要があります。この例では、単一テーブルの継承を使用しますが、クラス テーブルの継承も同様に機能します。 Discriminator マップでは、ConcreteComponent と ConcreteDecorator の 2 つの具象サブクラスを定義します。

<?php

namespace Test;

#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
    'cd' => Decorator\ConcreteDecorator::class])]
abstract class Component
{

    #[Id, Column]
    #[GeneratedValue(strategy: 'AUTO')]
    protected int|null $id = null;

    #[Column(type: 'string', nullable: true)]
    protected $name;

    public function getId(): int|null
    {
        return $this->id;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        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).

ConcreteComponent クラスは非常に単純で、抽象的な Component クラスを拡張する以上のことはしません (この例を単純にするためだけに)。

<?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.

Decorator クラスは永続化する必要はありませんが、永続化されたエンティティとの関連付けを定義する必要はありません。これには MappedSuperclass を使用できます。

<?php

namespace Test;

#[MappedSuperclass]
abstract class Decorator extends Component
{
    #[OneToOne(targetEntity: Component::class, cascade: ['all'])]
    #[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
    protected $decorates;

    /**
     * initialize the decorator
     * @param Component $c
     */
    public function __construct(Component $c)
    {
        $this->setDecorates($c);
    }

    /**
     * (non-PHPdoc)
     * @see Test.Component::getName()
     */
    public function getName(): string
    {
        return 'Decorated ' . $this->getDecorates()->getName();
    }

    /** the component being decorated */
    protected function getDecorates(): Component
    {
        return $this->decorates;
    }

    /** sets the component being decorated */
    protected function setDecorates(Component $c): void
    {
        $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.

デコレーターに対するすべての操作 (永続化、削除など) は、デコレーターからコンポーネントにカスケードされます。これは、Decorator を永続化すると、Doctrine が装飾されたオブジェクトのチェーンを永続化してくれることを意味します。デコレータは、それを永続化するときにコンポーネントとして正確に扱うことができます。

The Decorator's constructor accepts an instance of a 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.

Decorator のコンストラクターは、Decorator パターンで定義されているように、aComponent のインスタンスを受け入れます。これらの setDecorates/getDecorates メソッドは、Decorator が Component を装飾しているという事実を隠し、Component インターフェイスと Decorator インターフェイスを同一に保つために保護されていると定義されています。

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.

Decorator パターンの意図した結果を示すために、getName() メソッドをオーバーライドして、コンポーネントの getName() メソッドに文字列を追加しています。

ConcreteDecorator

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.

Decorator パターンの単純な実装を完了するために必要な最後のクラスは、ConcreteDecorator です。デコレーターが装飾のチェーンを移動するときにデータを変更する方法をさらに説明するために、新しいフィールド「special」がこのクラスに追加されました。 getName() はオーバーライドされ、getSpecial() メソッドの値が戻り値に追加されます。

<?php

namespace Test\Decorator;

use Test\Decorator;

#[Entity]
class ConcreteDecorator extends Decorator
{

    #[Column(type: 'string', nullable: true)]
    protected string|null $special = null;

    public function setSpecial(string|null $special): void
    {
        $this->special = $special;
    }

    public function getSpecial(): string|null
    {
        return $this->special;
    }

    /**
     * (non-PHPdoc)
     * @see Test.Component::getName()
     */
    public function getName(): string
    {
        return '[' . $this->getSpecial()
            . '] ' . parent::getName();
    }

}

Examples

Here is an example of how to persist and retrieve your decorated objects

装飾されたオブジェクトを永続化して取得する方法の例を次に示します

<?php

use Test\Component\ConcreteComponent,
    Test\Decorator\ConcreteDecorator;

// assumes Doctrine ORM 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\ConcreteComponent

echo $c->getName();
// prints: Test Component 1

echo get_class($d)
// prints: Test\Component\ConcreteDecorator

echo $d->getName();
// prints: [Really] Decorated Test Component 2

Table Of Contents

Previous topic

Custom Mapping Types

カスタム マッピング タイプ

Next topic

Extending DQL in Doctrine ORM: Custom AST Walkers

Doctrine ORM での DQL の拡張: カスタム AST ウォーカー

This Page

Fork me on GitHub