経緯
先日実装したUndo/Redo機能(元に戻す/やり直す)がそれなりの工数がかかったので、
今後のためにざっくり残します。
スタッフのシフトを管理するWebアプリケーションに実装しました。
本記事ではバックエンド側の実装のみ残します。
※以前の関連記事
Vue.js3で「ctrl+z」「ctrl+shift+z」の処理を実装する
用語定義
- 履歴
- 画面操作した時のテーブルのレコードを保存したもの
- 元に戻す(Undo)
- 操作を過去に戻す↩️ 一つ過去の履歴を適用する
- やり直す(Redo)
- 操作を先に進める↪️ 一つ先の履歴を適用する
動作のイメージ
まず初めにブラウザ更新をして履歴1が作成され、何かしらの操作を3回行ったとする。
そうすると履歴2〜4が作成され、履歴4が適用されている状況になる。
仮に「元に戻す」を3回実行すると、履歴が過去へ戻り、履歴1が適用されている状況になる。
更にここで「やり直す」を1回実行すると、履歴が先に進み、履歴2が適用されている状況になる。
この状況で新たに操作を実行すると、履歴3以降が削除されて、新たな履歴(履歴3')が適用された状態になる。
※ブラウザが更新されたタイミングで、履歴が全て削除されて、履歴1から始まる。
Undo/Redo機能概要
履歴をリセットする
- 保存してある履歴を全て削除、テーブルからdeleteする
- ブラウザ更新された際に実行する
履歴を保存する
- その時の状態を履歴として保存、テーブルにinsertする
- 画面上で操作が発生した際に実行する
操作を元に戻す・やり直す
- 保存された履歴、テーブルからselectして、過去へ戻す or 先へ進める
- Undo/Redoボタン押下時に実行する
やり直す対象の履歴をリセットする
-
やり直す対象の履歴を全て削除、テーブルからdeleteする
- 過去の履歴は残す
- 画面上で操作が発生した際に実行する
テーブル構成
既存テーブル
- users
| カラム | 型 | |
|---|---|---|
| id | int | PK |
| name | varchar |
- staffs
| カラム | 型 | |
|---|---|---|
| id | int | PK |
| name | varchar |
- shifts
| カラム | 型 | |
|---|---|---|
| id | int | PK |
| date | datetime |
- shift_staffs
| カラム | 型 | |
|---|---|---|
| id | int | PK |
| shift_id | int | FK |
| staff_id | int | FK |
| start_time | int | |
| end_time | int |
履歴テーブル ※今回実装
- histories
| カラム | 型 | |
|---|---|---|
| id | int | PK |
| user_id | int | FK |
| status | int |
- history_shift_staffs
| カラム | 型 | |
|---|---|---|
| id | int | PK |
| history_id | int | FK |
| shift_id | int | FK |
| staff_id | int | FK |
| start_time | int | |
| end_time | int |
対象のテーブルが増える場合は、history_*テーブルを作成して、history_idをFKにする
※今回実際はhistory_shift_staffsの子テーブルが存在していたので、その場合はhistory_idではなくhistory_shift_staff_idをFKにしました
実装
※いずれ時間を取れたらGithub整理したい…
Historyモデル
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class History extends Model
{
use HasFactory;
const CURRENT = 1;
const UNDO_TARGET = 2;
const REDO_TARGET = 3;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'user_id',
'status',
];
public function history_shift_staffs(): HasMany
{
return $this->hasMany(HistoryShiftStaff::class);
}
}
HistoryServiceクラス
コード量が比較的多いので、ここではpublicメソッドの説明だけ書きます。
-
create_history()- 履歴を作成するメソッドで、操作が発生するたびに呼び出される
-
copy_history_to_target()- 履歴からシフト・画面に反映させる処理で、いわゆるUndo/Redo処理を行う
-
exist_undo_target()- Undo対象の履歴が存在するか確認、Undoボタンの制御に使用
-
exist_redo_target()- Redo対象の履歴が存在するか確認、Redoボタンの制御に使用



