Doctrine 2实体中的常量

时间:2011-08-01 12:41:41

标签: 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;
}

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

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

class App_Constants extends Zrzr_Constants
{

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

}

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

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

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

    public static function getConstantByName( $name )
    {
        // ...
    }
}

常用用法是:

// 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');

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

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

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

3 个答案:

答案 0 :(得分: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
}

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

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

如果您最终会有很多状态或其他相关内容,我认为您必须为他们创建一个单独的实体。

答案 1 :(得分:2)

您可以按以下示例

定义您的课程
class ContactResource
{
   const TYPE_PHONE = 1;
   const TYPE_EMAIL = 2;
   const TYPE_BIRTDAY = 3;
   const TYPE_ADDRESS = 4;
   const TYPE_OTHER = 5;
   const TYPE_SKYPE = 6;
   const TYPE_LINKEDIN = 7;
   const TYPE_MEETUP = 8;
   const TYPE_TELEGRAM = 9;
   const TYPE_INSTAGRAM = 10;
   const TYPE_TWITTER = 11;

   public static $resourceType = array(
       ContactResource::TYPE_PHONE => "Phone",
       ContactResource::TYPE_EMAIL => "Email",
       ContactResource::TYPE_BIRTDAY => "Birtday",
       ContactResource::TYPE_ADDRESS => "Address",
       ContactResource::TYPE_OTHER => "Other",
       ContactResource::TYPE_SKYPE => "Skype",
       ContactResource::TYPE_LINKEDIN => "LinkedIn",
       ContactResource::TYPE_MEETUP => "Meetup",
       ContactResource::TYPE_TELEGRAM => "Telegram",
       ContactResource::TYPE_INSTAGRAM => "Instagram",
       ContactResource::TYPE_TWITTER => "Twitter",
   );

   /**
   * @var integer
   *
   * @ORM\Column(type="integer", length=2)
   *
   */
   private $type;


   public function __toString()
   {
      return (string)$this->getType();
   }

   public function getType()
   {
      if (!is_null($this->type)) {
          return self::$resourceType[$this->type];
      } else {
          return null;
      }
   }

   public static function getTypeList() {
      return self::$resourceType;
   }

}

如果你需要在Twig中输入类型

{{ entity.type }}

选择列表

ContactResource::getTypeList()
希望对你有用!

答案 2 :(得分:0)

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

当您看到或想到诸如STATUS_PENDINGSTATUS_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;
}

如果您需要向其添加功能,则很容易将其转换为值对象(我建议通过合成而不是继承来实现)。回到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;
}

请注意,列类型为“ 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;
    }
}

最后,要满足用户界面的需求,您最终可以使用枚举映射。请记住,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'];
    }
}

您可以在“名称”旁边添加任意数量的键。在用户界面中,您可以使用这样的地图:

// 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');

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;
    }
}

总结:

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