更新:
原文参考我的personal blog
SoftDeletes 特征
在 laravel 中,我们通过扩展 Illuminate\Database\Eloquent\Model 来定义自己的模型。要软删除模型实例,我们应该在模型中使用Illuminate\Database\Eloquent\SoftDeletes trait。 runSoftDelete() 是SoftDeletes trait 构建sql查询的关键函数,获取用于标记记录是否已被删除的列,然后使用当前时间戳更新该列。
protected function runSoftDelete()
{
$query = $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey());
$this->{$this->getDeletedAtColumn()} = $time = $this->freshTimestamp();
$query->update([$this->getDeletedAtColumn() => $this->fromDateTime($time)]);
}
Delete() 的过程
当我们在模型上调用delete() 函数时会发生什么?
由于我们自己的模型扩展了Illuminate\Database\Eloquent\Model,我们来看看它。这是delete() 函数:
public function delete()
{
if (is_null($this->getKeyName())) {
throw new Exception('No primary key defined on model.');
}
if ($this->exists) {
if ($this->fireModelEvent('deleting') === false) {
return false;
}
// Here, we'll touch the owning models, verifying these timestamps get updated
// for the models. This will allow any caching to get broken on the parents
// by the timestamp. Then we will go ahead and delete the model instance.
$this->touchOwners();
$this->performDeleteOnModel();
$this->exists = false;
// Once the model has been deleted, we will fire off the deleted event so that
// the developers may hook into post-delete operations. We will then return
// a boolean true as the delete is presumably successful on the database.
$this->fireModelEvent('deleted', false);
return true;
}
}
代码很清楚。它确保模型具有primaryKey,并且实例首先存在于数据库中。然后调用performDeleteOnModel()函数进行删除操作。一定要注意!
这里我们应该知道:
从基类继承的成员被由 Trait 插入的成员覆盖。优先顺序是当前类中的成员覆盖 Trait 方法,而 Trait 方法又会覆盖继承的方法。
因此,当调用performDeleteOnModel() 是SoftDeletes trait 中的同名函数而不是Model 类中的函数时,执行的确切函数。现在我们回到 trait:
protected function performDeleteOnModel()
{
if ($this->forceDeleting) {
return $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey())->forceDelete();
}
return $this->runSoftDelete();
}
嗯,它调用runSoftDelete(),我们一开始就谈到了。这就是软检测的过程。
获取问题
提问者希望在删除时使用不同的DELETED_AT 列。仅通过覆盖getDeletedAtColumn() 来保持软删除机制正常工作还有很多不足之处。为什么被软删除的模型仍然在结果中?
当Model 类被构造时,它将通过调用他们的boot[TraitName] 方法来引导特征。因此这里是bootSoftDelete() 方法。
protected static function bootTraits()
{
foreach (class_uses_recursive(get_called_class()) as $trait) {
if (method_exists(get_called_class(), $method = 'boot'.class_basename($trait))) {
forward_static_call([get_called_class(), $method]);
}
}
}
现在让我们再次关注SoftDeletes trait。
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingScope);
}
这里的 trait 通过调用 static::addGlobalScope() 注册了一个具有 apply() 方法的 SoftDeletingScope 类。位于Model 类中的方法将其存储到$globalScopes 数组中。
public static function addGlobalScope(ScopeInterface $scope)
{
static::$globalScopes[get_called_class()][get_class($scope)] = $scope;
}
在模型上构建查询时,会自动调用applyGlobalScopes()方法,逐一访问$globalScopes数组中的实例并调用它们的apply()方法。
public function applyGlobalScopes($builder)
{
foreach ($this->getGlobalScopes() as $scope) {
$scope->apply($builder, $this);
}
return $builder;
}
我们现在将揭开问题的面纱。在SoftDeletingScope类中:
public function apply(Builder $builder, Model $model)
{
$builder->whereNull($model->getQualifiedDeletedAtColumn());
$this->extend($builder);
}
它将在每个查询上添加一个约束,以选择那些DELETED_AT 列为空的记录。这就是SoftDeletes 的秘密。
动态 DELETED_AT 列
首先,我需要重申,我不推荐这种使用动态DELETED_AT 列的行为。
为了解决提问者动态DELETED_AT列的问题,你需要实现自己的SoftDeletingScope类,有这样的apply()函数:
public function apply(Builder $builder, Model $model)
{
$builder->where(function ($query){
$query->where('DELETED_AT_COLUMN_1',null)->orWhere('DELETED_AT_COLUMN_2',null);
});
$this->extend($builder);
}
然后用它覆盖bootSoftDeletes()
public static function bootSoftDeletes()
{
static::addGlobalScope(new YourOwnSoftDeletingScope);
}
原答案:
您无法在运行时更改const 变量的值。所以需要手动赋值CREATED_AT。