Несколько лет спустя и еще немного опыта, который я считаю правильным ответом, изменился.Первоначальный вопрос касается доменных констант, используемых на уровне пользовательского интерфейса, но данный пример и обсуждения фактически относятся к следующим понятиям: перечисления, перечисления и объекты значений.У меня тогда не было этих понятий, и ответы на мой вопрос не давали их.
Когда вы видите или думаете о таких константах, как STATUS_PENDING
, STATUS_ACTIVE
, STATUS_SUSPENDED
, вы должны думать оENUM.Стандартного перечисления 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;
}
}
Наконец, когда дело доходит до удовлетворения потребностей пользовательского интерфейса, вы можете использовать enum map.Помните, что для пользовательского интерфейса могут потребоваться дополнительные функции, такие как поддержка нескольких языков, поэтому вы не можете смешивать такие проблемы в домене, отсюда и разделение:
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;
}
}
Подводя итог:
- Используйте Enum для определения списка альтернативных значений для одного поля.
- Создайте объект значения, который получает этот Enum в конструкторе, если вы хотите добавитьОсобые функции VO для этого поля.
- Используйте карту Enum для удовлетворения потребностей пользовательского интерфейса.