更新附加/分离 Eloquent 关系的时间戳

Posted

技术标签:

【中文标题】更新附加/分离 Eloquent 关系的时间戳【英文标题】:Updating timestamps on attaching/detaching Eloquent relations 【发布时间】:2015-01-03 02:08:35 【问题描述】:

我正在使用 Laravel 4,并且有 2 个模型:

class Asset extends \Eloquent 
    public function products() 
        return $this->belongsToMany('Product');
    


class Product extends \Eloquent 
    public function assets() 
        return $this->belongsToMany('Asset');
    

Product 上面有标准时间戳created_atupdated_at),我想在附加/分离Asset 时更新Productupdated_at 字段。

我在Asset 模型上试过这个:

class Asset extends \Eloquent 
    public function products() 
        return $this->belongsToMany('Product')->withTimestamps();
    

...但这根本没有做任何事情(显然)。 编辑:显然这是为了更新数据透视表上的时间戳,而不是在关系自己的表上更新它们(即更新assets_products.updated_at,而不是products.updated_at)。

然后我在Asset 模型上尝试了这个:

class Asset extends \Eloquent 
    protected $touches = [ 'products' ];

    public function products() 
        return $this->belongsToMany('Product');
    

...这行得通,但随后打破了我调用Asset::create([ ... ]); 的种子,因为显然Laravel 试图在关系上调用->touchOwners() 而没有检查它是否是null

php 致命错误:在第 1583 行的 /projectdir/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php 中调用未定义的方法 Illuminate\Database\Eloquent\Collection::touchOwners()

我用来添加/删除Assets 的代码是这样的:

Product::find( $validId )->assets()->attach( $anotherValidId );
Product::find( $validId )->assets()->detach( $anotherValidId );

我哪里错了?

【问题讨论】:

我想你必须在assets() 方法上添加withTimestamps()withTimestamps() 应该可以在这里找到:github.com/laravel/framework/issues/109 @arjan 我希望更新的时间戳在Product 上,而不是在数据透视表上 你用的是哪个版本的 laravel 4? 【参考方案1】:

您可以使用touch 方法手动完成:

$product = Product::find($validId);
$product->assets()->attach($anotherValidId);
$product->touch();

但如果您不想每次都手动执行此操作,您可以通过这种方式在 Product 模型中简化此创建方法:

public function attachAsset($id)

    $this->assets()->attach($id);
    $this->touch();

现在你可以这样使用它了:

Product::find($validId)->attachAsset($anotherValidId);

您当然可以对分离操作执行相同的操作。

我注意到你有一个关系 belongsToMany 和另一个 hasMany - 两者都应该是 belongsToMany 因为这是多对多的关系

编辑

如果您想在多个模型中使用它,您可以创建 trait 或创建另一个扩展 Eloquent 的基类,方法如下:

public function attach($id, $relationship =  null)

    $relationship = $relationship ?: $this->relationship;
    $this->$relationship()->attach($id);
    $this->touch();

现在,如果您需要此功能,您只需从另一个基类扩展(或使用 trait),现在您可以向您的 Product 类添加一个额外属性:

private $relationship = 'assets';

现在你可以使用:

Product::find($validId)->attach($anotherValidId);

Product::find($validId)->attach($anotherValidId, 'assets');

如果您需要通过更新updated_at 字段来附加数据。当然,你需要重复分离。

【讨论】:

HasMany 上的好地方 - 这是一个错字 :-) 如果可能的话,我想避免创建辅助方法,因为我可能会对其他一些模型做类似的事情,尽管如果没有其他方法,那么这是一个有效的解决方法 @Joe 我不知道是否可以通过其他方式完成,似乎无法使用事件侦听器跟踪附加/分离操作,但也许我错了,或者其他人会找到更好的解决方案 @Joe 我已经用可能的解决方案更新了我的答案,以便在许多模型中使用它【参考方案2】:

从code source开始,创建相关模型的新实例时需要将$touch设置为false:

Asset::create(array(),array(),false);

或使用:

$asset = new Asset;
// ... 
$asset->setTouchedRelations([]);
$asset->save();

【讨论】:

看来::create() 方法在BelongsToMany 关系上,而不是在Model 上,因此您不能只将额外参数传递给Asset::create()。第二个选项可能有效,但如果可能的话,我宁愿继续使用::create() 抱歉,我没有正确理解您的问题,显然@Marcin Nabiałek 的回答是一个很好的解决方法【参考方案3】:

解决方案:

创建一个扩展 Eloquent 的 BaseModel,对 create 方法进行简单调整:

BaseModel.php

class BaseModel extends Eloquent 
    /**
     * Save a new model and return the instance, passing along the
     * $options array to specify the behavior of 'timestamps' and 'touch'
     *
     * @param  array  $attributes
     * @param  array  $options
     * @return static
     */
    public static function create(array $attributes, array $options = array())
    
        $model = new static($attributes);
        $model->save($options);
        return $model;
    

让您的 AssetProduct 模型(以及其他模型,如果需要)扩展 BaseModel 而不是 Eloquent,并设置 $touches 属性:

Asset.php(和其他模型):

class Asset extends BaseModel 
    protected $touches = [ 'products' ];
    ...

在播种机中,将create 的第二个参数设置为将'touch' 指定为false 的数组:

Asset::create([...],['touch' => false])

解释:

Eloquent 的 save() method 接受一个(可选的)选项数组,您可以在其中指定两个标志:'timestamps''touch'。如果 touch 设置为 false,那么 Eloquent 将不会触及相关模型,无论您在模型上指定了任何 $touches 属性。这是 Eloquent 的 save() 方法的所有内置行为。

问题是 Eloquent 的 create() method 不接受任何传递给 save() 的选项。通过扩展 Eloquent(带有 BaseModel)以接受 $options 数组作为第二个属性,并将其传递给 save(),您现在可以在所有扩展模型上调用 create() 时使用这两个选项BaseModel

请注意,$options 数组是可选的,因此这样做不会破坏代码中可能对create() 的任何其他调用。

【讨论】:

这个问题在 Laravel/Framework 版本 4.2.6 上不存在,但在 4.2.11 上确实存在。在我看来,Laravel 应该能够自动检测到这一点,对吧?也就是说,这不就是个bug吗?

以上是关于更新附加/分离 Eloquent 关系的时间戳的主要内容,如果未能解决你的问题,请参考以下文章

Laravel Eloquent 在结果中附加一个非关系数据

如何更新 Laravel Eloquent 管理的时间戳(created_at 和 updated_at)的时区?

向 eloquent 资源添加过滤器以有条件地附加关系

在数据透视表中附加数据时时间戳不更新

Laravel Eloquent - 附加与 SyncWithoutDetaching

使用 Eloquent 更新行时数据不持久