Doctrine 2实体中的常量

Gho*_*ola 7 php constants doctrine-orm

假设我有以下Doctrine 2实体:

/**
 * @ORM\Entity
 * @ORM\Table(name="users")
 */
class User {

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     *
     * @var int
     */
    protected $id;

    /**
     * @ORM\Column(length=100)
     *
     * @var string
     */
    protected $name;

    /**
     * @ORM\Column(type="integer")
     *
     * @var int
     */
    protected $status;
}
Run Code Online (Sandbox Code Playgroud)

用户可以有多种状态,例如:Pending,Active,Suspended.整个代码(服务,存储库等)以及UI层(用户编辑表单将在下拉列表中显示它们)需要这些状态.

为了避免在多个地方定义它们,我到目前为止所做的是使用一个类来保存它们(所有应用程序的常量),它看起来有点像这样:

class App_Constants extends Zrzr_Constants
{

    protected static $_constants = array( 
        'users' => array( 
            'status' => array( 
                0 => 'Pending', 
                1 => 'Active',
                2 => 'Suspended' ) ) );

}
Run Code Online (Sandbox Code Playgroud)

基类(Zrzr_Constants)将提供一些检索它们的方法,它看起来像这样:

class Zrzr_Constants
{
    protected static $_constants = array();

    public static function getConstantValues( $key, $subkey )
    {
        // ...
    }

    public static function getConstantByName( $name )
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

常见用法是:

// example of retrieval by constant name ... it would return an integer
$pendingStatus = App_Constants::getConstantByName( 'USERS.STATUS.PENDING' );

// example of retrieval for UI display purposes ... would return an array
$statuses = App_Constants::getConstantValues('users', 'status');
Run Code Online (Sandbox Code Playgroud)

当然这意味着存在一些限制,即常量标签不能包含点,但我可以忍受它.

然而,使用Doctrine 2并以DDD方式告诉我'status'字段实际上应该是'value object'(但是Doctrine 2还不支持value对象),或者至少我应该在其中定义常量实体(使用const).

我的问题是如何做到这一点,以避免不断重新定义UI层?我需要通过名称(在代码中)访问常量,并在UI下拉列表中具有此类字段的所有可能值(例如).

Vla*_*sny 6

我想,你可以这样做:

class User {
  const STATUS_PENDING = 'Pending';
  const STATUS_ACTIVE = 'Active';
  const STATUS_SUSPENDED = 'Suspended';

  public static function getStatusList() {
    return array(
                 self::STATUS_PENDING, 
                 self::STATUS_ACTIVE, 
                 self::STATUS_SUSPENDED
                );
  }

  public function getStatus() {...}

  public function setStatus($value) {...}

  public function isStatusPending() {...} //If you need it
}
Run Code Online (Sandbox Code Playgroud)

在UI层,您可以使用本地化服务获取状态的文本版本(如果状态常量是数字,UI层可以通过添加前缀将它们转换为字符串,例如user_status_0).在Symfony2视图中,您可以使用transTwig过滤器从user本地化域获取用户状态的文本版本.

如果您的网站只使用一种语言,那么我认为只需User :: STATUS_XXX即可.我认为你不应该通过创建一个新类来保存用户的状态来使问题复杂化.

如果你最终会有很多状态或其他相关的东西,我认为你必须为它们创建一个单独的实体.


Gho*_*ola 1

几年后,有了更多的经验,我认为正确的答案已经改变了。最初的问题是关于 UI 层中使用的域常量,但给定的示例和讨论实际上涉及以下概念:枚举、枚举映射和值对象。我当时没有这些概念,我的问题的答案也没有提供它们。

当您看到或想到像STATUS_PENDING,这样的常量时STATUS_ACTIVESTATUS_SUSPENDED您应该想到枚举。标准 PHP 枚举是不够的,所以我喜欢使用第三方库,例如marc-mabe/php-enum。它看起来是这样的:

use MabeEnum\Enum;

/**
 * @method static UserStatus PENDING()
 * @method static UserStatus ACTIVE()
 * @method static UserStatus SUSPENDED()
 */
class UserStatus extends Enum
{
    const PENDING = 0;
    const ACTIVE = 1;
    const SUSPENDED = 2;
}
Run Code Online (Sandbox Code Playgroud)

如果您需要向其添加功能,很容易将其转换为值对象(我建议通过组合而不是继承来完成)。回到User实体,使用上面的枚举,实体最终会像这样:

/**
 * @ORM\Entity
 * @ORM\Table(name="users")
 */
class User {

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     *
     * @var int
     */
    protected $id;

    /**
     * @ORM\Column(length=100)
     *
     * @var string
     */
    protected $name;

    /**
     * @ORM\Column(type="user_status")
     *
     * @var UserStatus
     */
    protected $status;
}
Run Code Online (Sandbox Code Playgroud)

请注意,列类型是“user_status”。要使其正常工作,您需要定义自定义 Doctrine 类型并将其注册到 Doctrine。这样的类型看起来像这样:

/**
 * Field type mapping for the Doctrine Database Abstraction Layer (DBAL).
 *
 * UserStatus fields will be stored as an integer in the database and converted back to
 * the UserStatus value object when querying.
 */
class UserStatusType extends Type
{
    /**
     * @var string
     */
    const NAME = 'user_status';

    /**
     * {@inheritdoc}
     *
     * @param array            $fieldDeclaration
     * @param AbstractPlatform $platform
     */
    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration);
    }

    /**
     * {@inheritdoc}
     *
     * @param string|null      $value
     * @param AbstractPlatform $platform
     */
    public function convertToPHPValue($value, AbstractPlatform $platform)
    {
        if (empty($value)) {
            return null;
        }

        if ($value instanceof UserStatus) {
            return $value;
        }

        try {
            $status = UserStatus::get((int)$value);
        } catch (InvalidArgumentException $e) {
            throw ConversionException::conversionFailed($value, self::NAME);
        }

        return $status;
    }

    /**
     * {@inheritdoc}
     *
     * @param UserStatus|null  $value
     * @param AbstractPlatform $platform
     */
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if (empty($value)) {
            return null;
        }

        if ($value instanceof UserStatus) {
            return $value->getValue();
        }

        throw ConversionException::conversionFailed($value, self::NAME);
    }

    /**
     * {@inheritdoc}
     *
     * @return string
     */
    public function getName()
    {
        return self::NAME;
    }

    /**
     * {@inheritdoc}
     *
     * @param AbstractPlatform $platform
     *
     * @return boolean
     */
    public function requiresSQLCommentHint(AbstractPlatform $platform)
    {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,当谈到满足用户界面的需求时,您最终可以使用枚举映射。请记住,UI 可能需要额外的功能,例如多语言支持,因此您不能将这些问题混杂到域中,从而进行分离:

use MabeEnum\EnumMap;

class UserStatusMap extends EnumMap
{
    public function __construct()
    {
        parent::__construct(UserStatus::class);

        $this[UserStatus::PENDING] = ['name' => 'Pending'];
        $this[UserStatus::ACTIVE] = ['name' => 'Active'];
        $this[UserStatus::SUSPENDED] = ['name' => 'Suspended'];
    }
}
Run Code Online (Sandbox Code Playgroud)

除了“名称”之外,您可以添加任意数量的键。在 UI 中,您可以使用这样的地图:

// if you want to display the name when you know the value
echo (new UserStatusMap ())[UserStatus::PENDING]['name'];
// or
echo (new UserStatusMap ())[UserStatus::PENDING()]['name'];

// if you want to build a list for a select (value => name)
$list = (new UserStatusMap ())->toArray('name');
Run Code Online (Sandbox Code Playgroud)

toArray函数在 MabeEnum\EnumMap 中不可用,但您可以创建自己的函数:

use MabeEnum\EnumMap as BaseEnumMap;

class EnumMap extends BaseEnumMap
{
    /**
     * @param string|null $metadataKey
     *
     * @return array
     */
    public function toArray($metadataKey = null)
    {
        $return = [];

        $flags = $this->getFlags();
        $this->setFlags(BaseEnumMap::KEY_AS_VALUE | BaseEnumMap::CURRENT_AS_DATA);

        if ($metadataKey) {
            foreach ($this as $key => $value) {
                $return[$key] = $value[$metadataKey];
            }
        } else {
            $return = iterator_to_array($this, true);
        }

        $this->setFlags($flags);

        return $return;
    }
}
Run Code Online (Sandbox Code Playgroud)

总结一下:

  1. 使用枚举来定义单个字段的替代值列表。
  2. 如果您想向此字段添加 VO 特定功能,请创建一个在构造函数中接收此枚举的值对象。
  3. 使用枚举映射来满足 UI 需求。