ThinkPHP一对一关联表数据软删除后恢复的思路和解决方案

发布时间:2022-12-08浏览次数:886 次
关于一对一关联的数据删除较容易,但对于软删除之后的关联数据如何恢复就稍微有点难度了,这一部分在官方文档中是没有涉及的,也是我们本文要将的重点。恢复软删除的数据这

有一个很常见的需求,但不知道为什么好像很少有人关注,而且在官方文档里也没有涉及。使用最常见的CMS文章管理系统来举例,文章概要信息和文章内容信息一般都是放在两张表中的,因为在大多数情况下我们只需要查看文章标题,不需要文章内容,基本上大家应该都是这么实现的。但是涉及到回收站等文章删除的时候,如果使用软删除,被关联的表也定义use SoftDelete,软删除可以实现,但是如果要将文章恢复出来,即恢复已经被软删除了的数据时,就不好办了。

官方文章中有详细讲解软删除,也有详细讲解一对一关联,但是这两者结合的时候,还是有一些问题的,本文就将我们的实现思路分享给大家。

首先,为了完整起见,我们从一对一关联的CURD都过一遍,为了演示方便,我们定义两个模型:Article及ArticleDetail。数据库假设主表为:article,包含字段:id,title,delete_time。关联文章详情表:article_detail,字段为:id,aid,content,delete_time。

一、一对一关联的定义

class Article extends Model
{
    public function detail(){
        return $this->hasOne(ArticleDetail::class,'aid','id');
    }
}

至于ArticleDetail模型,大家自行定义,可以是空模型;

二、一对一关联模型的新增

将关联表的数据,添加到ArticleDetail的实例上,再将该实例添加至Article的模型实例上的代码演示可参考官方文档中演示。在一对一数据新增中,新增方式主要分为:同步新增和分别新增两种情况。

2.1 同步新增

因为在实际中,我们获取数据时,大都是统一获取的,如果需要在数据获取到之后,再手动去区分,分别赋值,则较为麻烦,最好是将全部数据统一交给data()或者save()方法,由框架自行决定。ThinkPHP提供了together方法,可用来实现这一需求,代码如下:

$article = new Article();
$data = ['title'=>"文章标题1",'content'=>'文章内容1'];
$article->data($data)->together(['detail'=>['content']])->save(); // true

这里需要注意的是,data()方法的调用需要在together()方法之前,也就是需要在together()之前就将数据交给Article的模型实例。也可以用官方提供的示例代码:

$article = new Article();

$article->title = '标题2';
$article->content = '内容2';
$article->together(['detail'=>['content']])->save(); // true

2.2 分步新增

一般而言,在一对一的数据库设计中,大多数情况下都是同时将数据写入到主表和关联表中的,但是也存在一种情况就是先有了主表数据,随后再根据情况添加关联表的数据,如先有了用户基本数据,随后再有用户详细数据,这样的设计对性能可能会有所提升,因为关联表中不会存在大量空数据。

在这种情况下,我们给关联表新增数据的方法如下:

$article = Article::find(1);
$res = $article->detail()->save(['content'=>'文章1的内容']);

三、一对一关联的数据修改

常见会犯的错误如下:

$article = Article::find(1);
$data = ['title' => '新标题', 'content' => '新内容'];
$article->data($data)->together(['detail'=>['content']])->save(); // true

虽然也会返回true,而且也使用了together(),但其实只更新了主表,关联表是未被更新到的。正确的方法是在获取Article模型实例时,使用with()方法,提前将关联表的数据取出,如:

$article = Article::with('detail')->find(1);

只有这样才会连同关联表一起被更新。

另外,在一对一关联中,如果只存在对应的主表数据,但对应的关联表数据不存在时,这里的save()方法只会更新到主表,并不会新增关联数据(如需单独新增关联表数据,参考新增部分)。所以,最好是在新增一条数据时,同时写入主表和关联表的数据。

四、一对一关联的删除

关于删除这一块,官方文档其实已经很清楚了,这里稍微补充一下。

$article = Article::find(1);
$article->together(['detail'])->delete(); // true

返回true,但实际上只会删除主表数据,关联表的数据不会被删除。正确的方法和修改时一样,需要使用with()方法,即:

$article = Article::with('detail')->find(1);

如果在使用软删除的情况下,即给Article及ArticleDetail模型都定义了“use SoftDelete”时,关联表也会一并进行软删除。

在定义了软删除之后,如需真实删除数据,则需使用:

$article->together(['detail'])->force()->delete();

即使用force()方法,可以完成真实删除。

即:在关联修改和删除中,查询对象模型时均需使用到with()方法,进行预载入查询。

五、关联数据被软删除后如何恢复

关于删除其实很容易,稍微有点难度的是,对于软删除之后的数据,如何恢复,这一部分在官方文档中是没有涉及的,也是我们本文要将的重点。恢复软删除的数据这个场景其实也很常见,比如CMS系统里的文章回收站。我们知道,ThinkPHP里关于软删除提供了:withTrashed()包含软删除数据,onlyTrashed()仅查询软删除数据,restore()恢复软删除数据三个方法,但是在一对一关联中,这些方法的使用,还需注意一些事项。

如下代码是一个错误示例:

$article = Article::withTrashed()->with('detail')->find(8);
$res = $article->restore(); // true
$res = $article->together(['detail'])->restore(); // true

这里需要注意在查找模型实例前,需要使用了withTrashed()方法,因为数据已经被软删除了,不使用withTrashed()则会报错。这里restore()方法虽然返回了true,但是实际上也是只恢复了主表数据,关联表未被恢复(即便使用了together()方法,也不会恢复)。其实这样对实际的项目影响不是很大,因为大多数的场景,我们都是通过主表操作关联表。但是为了业务更加严谨,有必要探究下。如果实现主表和关联表的软删除同时恢复。

目前我们的解决方案是需要定义一个专门用于restore的关联方法,然后再分两步分别进行恢复,如下,在Article模型中,定义一个detailRestore方法,即Article模型的定义如下:

class Article extends Model
{
    use SoftDelete;

    public function detail(){
        return $this->hasOne(ArticleDetail::class,'aid','id');
    }
    // 用于恢复关联表的软删除数据
    public function detailRestore(){
        return $this->detail()->removeOption('soft_delete');
    }
}

恢复关联数据软删除的代码如下:

$article = Article::withTrashed()->find(8); // 此处不必再使用with()方法进行关联查询
$res = $article->restore();
$res = $article->detailRestore->restore();

如上代码可完美的恢复一对一的模型关联中主表和关联表中被软删除的数据。但是确实存在略显繁琐,且代码不够优雅的问题,欢迎大家积极讨论,如有更好的方法,我们会继续追加更新。

另外,需要注意的是,该思路不适用于一对多模型关联。

扫一扫,在手机上查看