Константы в Учении 2 сущности - PullRequest
7 голосов
/ 01 августа 2011

Предположим, у меня есть следующая сущность 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;
}

Пользователь может иметь несколько статусов, например: Ожидание, Активно, Приостановлено.Эти статусы необходимы по всему коду (сервисы, репозитории и т. Д.), А также на уровне пользовательского интерфейса (форма редактирования пользователя будет отображать их в раскрывающемся списке).

Чтобы избежать их определения в нескольких местах, я до сих пор использовал класс для хранения их всех (всех констант приложения), и это выглядит примерно так:

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' должно быть вФактически «объект-значение» (но Doctrine 2 пока не поддерживает объекты-значения), или, по крайней мере, я должен иметь константы, определенные внутри объекта (используя const).

Мой вопрос: как мне это сделать, чтобы избежать постоянного переопределения для уровня пользовательского интерфейса?Мне нужно иметь доступ к константе по имени (в коде) и иметь все возможные значения для такого поля в случае раскрывающегося списка пользовательского интерфейса (например).

Ответы [ 3 ]

6 голосов
/ 18 ноября 2011

Я думаю, вы можете сделать это следующим образом:

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
}

На уровне пользовательского интерфейса вы можете получать текстовые версии своих статусов, используя службу локализации (если константы состояния являются числами, слой пользовательского интерфейса может преобразовать ихв строки, добавив префикс, например, user_status_0).В представлениях Symfony2 вы можете использовать фильтр trans Twig для этого, чтобы получить текстовую версию статуса пользователя из user домена локализации.

Если ваш сайт только на одном языке, тогда просто User :: STATUS_XXX будет делатьхорошо, я думаю.Я не думаю, что вам следует усложнять ситуацию, создав новый класс для хранения статусов пользователя.

Если у вас будет много статусов или других связанных вещей, я думаю, вам придется создатьотдельная сущность для них.

2 голосов
/ 23 января 2017

вы можете определить свой класс как в следующем примере

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()

Надежда работает на тебя!

0 голосов
/ 25 сентября 2018

Несколько лет спустя и еще немного опыта, который я считаю правильным ответом, изменился.Первоначальный вопрос касается доменных констант, используемых на уровне пользовательского интерфейса, но данный пример и обсуждения фактически относятся к следующим понятиям: перечисления, перечисления и объекты значений.У меня тогда не было этих понятий, и ответы на мой вопрос не давали их.

Когда вы видите или думаете о таких константах, как 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;
    }
}

Подводя итог:

  1. Используйте Enum для определения списка альтернативных значений для одного поля.
  2. Создайте объект значения, который получает этот Enum в конструкторе, если вы хотите добавитьОсобые функции VO для этого поля.
  3. Используйте карту Enum для удовлетворения потребностей пользовательского интерфейса.
...