我在实体上具有state
(状态机值)属性,并根据该实体何时转换为给定状态来设置日期时间字段。
任务状态:(简体)
在将任务设置为in_progress
之后,它可以转换回complete
,但是我想使用时间戳记来设置$dateStarted
属性,仅当它第一次转换为{{1 }}。
此代码将始终将in_progress
设置为上一次$dateStarted
过渡到$state
的时间。
是否可以配置它,以便仅在任务第一次转换为in_progress
时设置$dateStarted
?
in_progress
答案 0 :(得分:2)
像Fyrye所说,您可以使用带有功能的生命周期回调仅将日期设置一次。我这样使用它:
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function setTheStartDate()
{
if ($this->getState() === 'in_progress' && $this->getDateStarted === null) {
$this->setDateStarted(new \DateTime('now'));
}
}
还要确保在类上具有以下注释以启用生命周期回调:
* @ORM\HasLifecycleCallbacks()
答案 1 :(得分:1)
正如我在评论中提到的,Timestampable
无法使用表达式或回调方法来评估更改后的值。虽然您可以使用生命周期回调来完成所需的结果。生命周期回调确实为您的实体增加了额外的开销和复杂性,这很难跟踪由它们引起的问题,并且在以后的代码扩展中变得更加乏味。
使用Timestampable或Lifecycle回调的一个示例;在您之后将对数据库的更改刷新到数据库之前,dateStarted
才会设置。这将导致无法将其他业务逻辑中的dateStarted
值引用到数据库中。
因此,建议您将业务逻辑放入setState
方法中。这样做将强制执行所需的dateStarted
分配规则。这也使您可以使用其他业务逻辑,例如确保状态不从new
跳到complete
,而无需先设置为in_progress
等。
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框架,因此您可以选择使用Form validation constraints并将您的业务逻辑移动到那些约束/回调中,这些约束/回调将在提交表单并更改您的实体和显示相应的友好消息。