day22(から3日遅れ)の今日は論理削除のやり方について見ていきます。
論理削除とは、レコードを削除する操作を行ったとき、実際にはDBMS上からDELETEせずに削除フラグや削除日時をUPDATEで書き込むことで、「ゴミ箱に入れる」のように復活可能な削除を行うことです。
Doctrine
Enity/Book.php
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation\SoftDeleteable;
#[ORM\Entity]
#[ORM\Table(name: 'books')]
#[ORM\HasLifecycleCallbacks]
#[SoftDeleteable(fieldName: 'deletedAt')]
class Book
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private int $id;
#[ORM\Column(type: 'string', length: 255, nullable: false)]
private string $title;
#[ORM\Column(type: 'integer')]
private int $price;
#[ORM\ManyToOne(targetEntity: Author::class)]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
private Author $author;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description;
#[ORM\OneToMany(mappedBy: 'book', targetEntity: Reaction::class, cascade: ['persist'], orphanRemoval: true)]
private Collection $reactions;
#[ORM\Column(name: 'created_at', type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
#[ORM\Column(name: 'updated_at', type: 'datetime_immutable')]
private \DateTimeImmutable $updatedAt;
#[ORM\Column(name: 'deleted_at', type: 'datetime_immutable')]
private ?\DateTimeImmutable $deletedAt = null;
public function __construct()
{
$this->reactions = new ArrayCollection();
}
public function getId(): int
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public function getPrice(): int
{
return $this->price;
}
public function setPrice(int $price): void
{
$this->price = $price;
}
public function getAuthor(): Author
{
return $this->author;
}
public function setAuthor(Author $author): void
{
$this->author = $author;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): void
{
$this->description = $description;
}
/**
* @return Collection<Reaction>
*/
public function getReactions(): Collection
{
return $this->reactions;
}
public function setReactions(Collection $reactions): void
{
$this->reactions = $reactions;
}
public function addReaction(Reaction $reaction): void
{
if (!$this->reactions->contains($reaction)) {
$reaction->setBook($this);
$this->reactions->add($reaction);
}
}
public function removeReaction(Reaction $reaction): void
{
$reaction->setBook(null);
$this->reactions->removeElement($reaction);
}
#[ORM\PrePersist]
public function prePersist(): void
{
$this->createdAt = new \DateTimeImmutable();
$this->updatedAt = new \DateTimeImmutable();
}
#[ORM\PreUpdate]
public function preUpdate(): void
{
$this->updatedAt = new \DateTimeImmutable();
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeImmutable $createdAt): void
{
$this->createdAt = $createdAt;
}
public function getUpdatedAt(): \DateTimeImmutable
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeImmutable $updatedAt): void
{
$this->updatedAt = $updatedAt;
}
public function getDeletedAt(): ?\DateTimeImmutable
{
return $this->deletedAt;
}
public function setDeletedAt(?\DateTimeImmutable $deletedAt): void
{
$this->deletedAt = $deletedAt;
}
public function isDeleted(): bool
{
return $this->deletedAt !== null;
}
}
<?php
declare(strict_types=1);
use App\Entity\Author;
use App\Entity\Book;
use Doctrine\ORM\EntityManagerInterface;
require __DIR__.'/../vendor/autoload.php';
/** @var EntityManagerInterface $entityManager */
$entityManager = require __DIR__.'/bootstrap.php';
$book = $entityManager->find(Book::class, 11);
$entityManager->remove($book);
$entityManager->flush(); // DELETE FROM booksでなくUPDATE books set deleted_at = now() が発行される
- DoctrineExtensionsの
SoftDeleteable
を使っています。( https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/softdeleteable.md ) - SoftDeleteableのマッピングを付与し、
Book::$deletedAt
プロパティを追加しています。 - 通常の削除操作(
$entityManager->remove($book); $entityManager->flush();
)を行ったときに、DELETEでなくUPDATEでdeleted_atカラムに削除日時を書き込みます。SELECTのクエリには自動でdeleted_at IS NULL
の条件が追加され、削除されたレコードが除外された結果のみが出てきます。
Eloquent
Models/Book.php
<?php
declare(strict_types=1);
namespace App\Models;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\Book
*
* @property int $id
* @property string $title
* @property int $price
* @property Author $author
* @property string|null $description
* @property CarbonInterface $created_at
* @property CarbonInterface $updated_at
*/
class Book extends Model
{
use SoftDeletes;
public $fillable = [
'title',
'price',
'author_id',
'description',
];
public $timestamps = true;
public function author()
{
return $this->belongsTo(Author::class);
}
public function likes()
{
return $this->morphMany(Like::class, 'likable');
}
}
<?php
declare(strict_types=1);
use App\Models\Book;
require __DIR__.'/../vendor/autoload.php';
require __DIR__.'/bootstrap.php';
$book = Book::find(11);
$book->delete(); // DELETE FROM booksでなくUPDATE books set deleted_at = now() が発行される
- Modelに
SoftDeletes
トレイトをuseすることで論理削除が使えるようになります。 - 通常の削除操作(
$book->delete();
)を行ったときに、DELETEでなくUPDATEでdeleted_atカラムに削除日時を書き込みます。SELECTのクエリには自動でdeleted_at IS NULL
の条件が追加され、削除されたレコードが除外された結果のみが出てきます。