Отметка времени в первый раз, когда поле было установлено

У меня есть свойство state (значение конечного автомата) для объекта, и я устанавливаю поля datetime в зависимости от того, когда объект переходит в заданное состояние.

Состояния задачи: (упрощенное)

  • новый
  • в ходе выполнения
  • полный

Задача может перейти обратно в in_progress после того, как она будет установлена ​​на complete, но я хочу использовать метку времени для установки свойства $dateStarted только при первом переходе на in_progress.

Этот код всегда устанавливает $dateStarted на последний раз, когда $state переходит на in_progress.

Есть ли способ настроить его так, чтобы $dateStarted устанавливался только при первом переходе задачи на in_progress?

/**
 * @var \DateTime
 *
 * @ORM\Column(type = "datetime", nullable=true)
 * @Gedmo\Timestampable(on = "change", field = "state", value = "in_progress")
 */
private $dateStarted;
Timestampable не может использовать выражения или обратные вызовы, вместо этого используйте Обратный вызов жизненного цикла preUpdate. Затем вы можете добавить условное событие для обновления поля по желанию. if ($this->state === 'in_progress' && null === $this->dateStarted) { $this->dateStarted = new \DateTime(); }
Will B. 29.07.2018 03:45

@fyrye Ocramius из команды Doctrine дал советы, как избегать обратных вызовов жизненного цикла (ocramius.github.io/doctrine-best-practices/#/58ocramius.github.io/doctrine-best-practices/#/59)

Franck Gamess 29.07.2018 12:51

@Franckgamess Timestampable - это обратный вызов жизненного цикла, он использует onFlush .. Однако я согласен, это делает код менее управляемым, добавляет ненужные накладные расходы, и то же самое можно сделать в методе setState.

Will B. 29.07.2018 14:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
0
3
159
2

Ответы 2

Как говорит Фрай, вы можете использовать обратный вызов жизненного цикла с функцией, чтобы установить дату только один раз. Я использую это так:

/**
 * @ORM\PrePersist
 * @ORM\PreUpdate
 */
public function setTheStartDate()
{
    if ($this->getState() === 'in_progress' && $this->getDateStarted === null) {
        $this->setDateStarted(new \DateTime('now'));
    }
}

Также убедитесь, что у вас есть следующая аннотация для вашего класса, чтобы включить обратные вызовы жизненного цикла:

* @ORM\HasLifecycleCallbacks()

Вы должны полагаться на операторов строгого сравнения === null или !== null, чтобы избежать возможных ошибок при слабом сравнении. Например, 0 == null, '' == null, false == null, array() == null https://3v4l.org/p5FYo

Will B. 29.07.2018 15:55

Как я упоминал в комментарии, Timestampable не может использовать выражения или метод обратного вызова для оценки измененного значения. Хотя вы можете использовать обратный вызов жизненного цикла для достижения желаемого результата. Обратные вызовы жизненного цикла действительно добавляют дополнительный уровень накладных расходов и сложности для вашей сущности, что затрудняет отслеживание возникающих из-за них проблем, а расширение кода в будущем более утомительно. Один пример использования обратных вызовов Timestampable или Lifecycle; dateStarted не будет установлен до тех пор, пока ПОСЛЕ вы не сбросите свои изменения в базу данных. Это приводит к тому, что ссылка на значение dateStarted в другой бизнес-логике становится невозможной, пока вы не отправите их в базу данных.

Поэтому рекомендуется вместо этого поместить вашу бизнес-логику в метод setState. Это приведет к принудительному применению желаемых правил назначения dateStarted. Это также позволяет вам использовать дополнительную бизнес-логику, например, гарантировать, что состояние не перескакивает с new на complete, без предварительной установки на in_progress и т. д.

Пример: https://3v4l.org/s9PJu

class MyEntity
{

    const STATE_NEW = 'new';
    const STATE_IN_PROGRESS = 'in_progress';
    const STATE_COMPLETE = 'complete';
    const CONFIG_STATES = array(
        self::STATE_NEW,
        self::STATE_IN_PROGRESS,
        self::STATE_COMPLETE
    );

    /**
     * @var \DateTime|null
     * @ORM\Column(type = "datetime", nullable=true)
     */
    private $dateStarted;

    /**
     * @var string|null
     * @ORM\Column(type = "string", nullable=true)
     */
    private $state = self::STATE_NEW;

    /**
     * @var string|null $state
     */
    public function setState($state = null)
    {
        $this->validateState($state);
        $this->updateStateDate($state);
        $this->state = $state;

        return $this;
    }

    public function getState()
    {
        return $this->state;
    }

    public function getDateStarted()
    {
        return $this->dateStarted;
    }

    /**
     * used to update the started date when the state changes to in_progress
     * @var string $state
     */
    protected function updateStateDate($state) 
    {
        if ($this->state !== $state && $state === self::STATE_IN_PROGRESS && $this->dateStarted === null) {
            $this->dateStarted = new \DateTime();
        }

        return $this;
    }

    protected function validateState($state)
    {
       if (!in_array($state, self::CONFIG_STATES, true)) {
            throw new \InvalidArgumentException(
                sprintf('Unknown state "%s". Expected one of: "%s"', 
                    $state, 
                    implode('", "', self::CONFIG_STATES)
                )
            );
        }
        if ($this->state === self::STATE_NEW && $state !== self::STATE_IN_PROGRESS) {
            throw new \InvalidArgumentException(
                sprintf('Invalid state "%s" specified, Expected: %s', 
                    $state, 
                    self::STATE_IN_PROGRESS
                )
            );
        }

        return $this;
    }
}

Применение:

//default state = "new", dateStarted = null
$entity = new \MyEntity;

//state = "in_progress",  dateStarted = \DateTime('now')
$entity->setState(MyEntity::STATE_IN_PROGRESS); 
//NOTE - $entity->getDateStarted()  will return a value now without first using $em->flush();

//only changes state to "complete", leaves the previously assigned date
$entity->setState(MyEntity::STATE_COMPLETE);

//only changes state to "in_progress", leaves the previously assigned date
$entity->setState(MyEntity::STATE_IN_PROGRESS);

Я также предлагаю, если это еще не сделано, преобразовать ваше поле state из строки в связанный объект. Затем вы можете удалить константы STATE и проверить в validateState, поскольку ограничение внешнего ключа будет обеспечивать правильное присвоение состояния.


В заключение, поскольку вы используете фреймворк Symfony, вы можете альтернативно использовать Ограничения проверки формы и переместить свою бизнес-логику в эти ограничения / обратные вызовы, которые будут проверяться при отправке форм и изменят вашу сущность и соответственно отобразят дружественные сообщения.

Другие вопросы по теме