使用Doctrine ORM实现状态模式

时间:2015-07-14 15:46:34

标签: php doctrine-orm doctrine-odm

我有一个使用State模式的类。这是一个简单的例子

/**
 * @Enitity
 **/
class Door
{
  protected $id;
  protected $state;
  public function __construct($id, DoorState $state)
  public function setState(DoorState $state)
  {
    $this->state = $state;
  }
  public function close()
  {
    $this->setState($this->state->close())
  }
  ...
}

interface DoorState
{
  public function close;
  public function open;
  public function lock;
  public function unlock;
}

class DoorAction implements DoorState
{
  public function close()
  {
     throw new DoorError();
  }
  ...
}

然后是几个定义状态中适当行为的类

class OpenedDoor extends DoorAction
{
  public function close()
  {
      return new ClosedDoor();
  }
}

所以我会有一些像

这样的东西
$door = new Door('1', new OpenedDoor());
DoctrineDoorRepository::save($door);
$door->close();
DoctrineDoorRepository::save($door);

如何在Doctrine中实现映射,以便我可以坚持下去? 我挂了$ state属性。我想保存整个基于DoorAction的对象但是我必须在地图上使用DoorAction超类还是每个子类?

我已经考虑过使用EmbeddableSuperMapping来实现它,但每个问题都会遇到问题。

2 个答案:

答案 0 :(得分:2)

Doctrine2 DBAL在文档中有一个允许ENUM

的功能

http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/cookbook/mysql-enums.html

当我们将解决方案2:定义类型作为基础时,可以创建一个自己的类型,例如名为doorstatetype或类似的代表打开/关闭状态。例如:

<?php
namespace Acme\Model\Door;

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class DoorStateType extends Type
{
    const ENUM_DOORSTATE = 'enumdoorstate';
    const STATE_OPEN = 'open';
    const STATE_CLOSED = 'closed';

    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return "ENUM('" . self::STATE_OPEN . "', '" . self::STATE_CLOSED . "') COMMENT '(DC2Type:" . ENUM_DOORSTATE  . ")'";
    }

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        return $value;
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if (!in_array($value, array(self::STATE_OPEN, self::STATE_CLOSED))) {
            throw new \InvalidArgumentException("Invalid state");
        }

        return $value;
    }

    public function getName()
    {
        return self::ENUM_DOORSTATE;
    }
}

然后像这样使用它:

<?php

namespace Acme\Model\Door;

/** @Entity */
class Door
{
    /** @Column(type="enumdoorstate") */
    private $state;

    public function open()
    {
        if (!DoorStateType::STATE_OPEN === $this->state) {
            throw new \LogicException('Cannot open an already open door');
        }

        $this->state = DoorStateType::STATE_OPEN;
    }

    public function close()
    {
        if (!DoorStateType::STATE_CLOSED === $this->state) {
            throw new \LogicException('Cannot close an already closed door');
        }

        $this->state = DoorStateType::STATE_CLOSED;
    }
}

这允许搜索状态:

$openDoors = $repository->findBy(array('state' => DoorStateType::STATE_OPEN));

基本上你可以让convertToPHPValue方法创建允许某些逻辑的所需状态的对象,比如检查是否可以锁定打开的门或类似的。

如果状态必须是包含逻辑的类,您可以像这样实现它:

首先我们定义一个可以继承的正常状态:

<?php
namespace Acme\Model\Door;

abstract class DoorState
{
    // Those methods define default behaviour for when something isn't possible
    public function open()
    {
        throw new \LogicException('Cannot open door');
    }

    public function close()
    {
        throw new \LogicException('Cannot close door');
    }


    abstract public function getStateName();
}

然后是OpenState:

<?php
namespace Acme\Model\Door;

class OpenState extends DoorState
{
    const STATE = 'open';

    public function close()
    {
        return new ClosedState();
    }

    public function getStateName()
    {
        return self::STATE;
    }

    // More logic
}

最后是ClosedState:

<?php
namespace Acme\Model\Door;

class ClosedState extends DoorState
{
    const STATE = 'open';

    public function open()
    {
        return new OpenState();
    }

    public function getStateName()
    {
        return self::STATE;
    }

    // More logic
}

然后,对于持久性,我们可以简单地使用不同的转换方法:

<?php
namespace Acme\Model\Door;

use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class DoorStateType extends Type
{
    // SQL declarations etc.

    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        if ($value === OpenState::STATE) {
            return new OpenState();
        }

        if ($value === ClosedState::STATE) {
            return new ClosedState();
        }

        throw new \Exception(sprintf('Unknown state "%s", expected one of "%s"', $value, implode('", "', [OpenState::STATE, ClosedState::STATE])));
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        return $value->getStateName();
    }
}

答案 1 :(得分:0)

如果您将state映射为字符串然后:

,该怎么办?
public function setState(DoorState $state)
{
  $this->state = serialize($state);
}

  private function state() 
  {
    return unserialize($this->state);
  }

  public function close()
  {
    $this->setState($this->state()->close())
  }