Skip to content

[ページ管理] ページ削除時に Nested Set の整合性が保たれず、子ページの parent_id が不整合になる #2481

Description

@masaton0216

概要

ページ削除処理が Eloquent モデルを経由しない生の DELETE 文で実装されているため、削除された親ページを指したままの parent_id を持つ子ページが残ります。

app/Plugins/Manage/PageManage/PageManage.php

public function destroy($request, $page_id)
{
    // Log::debug($id);
    DB::table('pages')->where('id', '=', $page_id)->delete();

    // ページ管理画面に戻る
    return redirect("/manage/page");
}

DB::table()(クエリビルダ)による削除のため、NodeTrait(kalnoy/nestedset)の削除フックが動作しません。本来 Page::find($page_id)->delete() であれば行われる以下の処理がすべてスキップされます。

  • 子孫ページの連鎖削除
  • 残ノードの _lft/_rgt の詰め直し
  • 子ノードの parent_id の付け替え

結果として起きること

項目 削除後の状態
子ページの parent_id 削除された親のIDを指したまま残る(不整合)
子ページの _lft/_rgt 番号に穴が空くのみ。祖先判定は消えた親を飛ばして解決されるため実用上は正しく動作する
子ページ自体 削除されず残り、実質トップレベル扱いになる

ancestorsAndSelf()_lft/_rgt の範囲比較で祖先を引くため、行ごと消えた親は自然に候補から外れます。そのため _lft/_rgt 由来の情報は正しく、parent_id だけが取り残されるという非対称な状態になります。

この不整合自体は長らく無害でしたが、parent_id を参照する機能が追加されたことで顕在化しました。

現状の把握

  • この destroy() は initial commit から変更されておらず、初期から存在する挙動です
  • ある顧客環境では系統の異なる5件の不整合が確認されており、単発ではなく繰り返し発生しています
  • 子ページを持つページも警告なく削除できます(page_edit.blade.php の削除フォームに子の有無チェックはなく、確認ダイアログのみ)
  • pages テーブルを生クエリで削除・更新している箇所は、この1箇所のみです

実行環境

OS: 影響なし(サーバサイドの実装によるもののため全環境で再現)
ブラウザ: 影響なし
Connect-CMS: 全バージョン(initial commit から現在まで)

再現手順

  1. 親ページAの配下に子ページBを作成する
  2. ページ管理から親ページAを削除する
  3. DBを確認すると、子ページBの parent_id が削除済みの親ページAのIDを指したまま残っている
  4. 子ページB自体は削除されず、ページ一覧上はトップレベルのページとして扱われる

対応候補

方針は未定です。以下は候補の列挙にとどめます。

  1. destroy()Page::find($page_id)->delete() に変更する
    • ただし子孫ページが連鎖削除される挙動変更になります。現状の「子が残る」挙動に依存した運用がある可能性があり、要件確認が必要です
  2. 子ページを持つページの削除時に、警告表示またはブロックを行う
  3. 既存データの parent_id_lft/_rgt から再同期するメンテナンスコマンドを提供する

注意

Page::fixTree()parent_id を正として _lft/_rgt を再構築するメソッドです。今回の壊れ方(parent_id のみが不正)に対して実行すると、正しい _lft/_rgt を不正な parent_id で上書きするため状況が悪化します。復旧には逆方向(_lft/_rgtparent_id)の処理が必要です。


本Issueが原因となって顕在化した不具合: #2480

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions