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
の条件が追加され、削除されたレコードが除外された結果のみが出てきます。