baserCMS プラグイン バージョン移行ガイド(5.2 → 5.3)
baserCMS プラグインを 5.2系 から 5.3系へ移行する際に遭遇した baserCMS 固有の問題と修正レシピ集。バージョン別に整理して育てる。
推奨: 着手前に一度
basercms5-claude-workflow-setup(環境セットアップ)を参照し、進め方の環境(設計=superpowers brainstorming/権限整理=permissions-audit/その上での Auto mode/spec・plan の Markdown プレビュー)を整える。提案ベースで、整っていればスキップ。
収録: 5.2 → 5.3。5.3系は PHP 8.5 / CakePHP 5.2.x ベース(執筆時点で開発中)。 役割分担:
- PHP 本体起因(暗黙nullable・動的プロパティ・非正規キャスト・setAccessible・null オフセット等)→
php-migrationスキル- CakePHP 本体/関連パッケージ起因(AbstractMigration→BaseMigration・イベント戻り値・PluginCollection・TreeBehavior・ResultSet・order()/group() 等)→
cakephp-migrationスキル- ユニットテストの実行・集計・切り分け手順 →
basercms-unittestスキル- 本書は上記に当てはまらない baserCMS 固有 + プラグインのテスト基盤 に絞る。
大原則
- 「致命的エラー」と「非推奨警告」を区別する。
logs/debug.logのdebug:やlogs/error.logのerror:で…は非推奨ですと出るものは警告(動作継続)。Fatal / Exception が本当のエラー。 - テスト大量失敗は根本原因単位で分類する。数百件でも原因は十数種。例外メッセージを集計して systemic な原因(vendor 混在・プラグイン未ロード・フィクスチャ不正)から潰す。
- 依存の更新は
composer requireを使い、composer updateで依存全体を動かさない(cakephp-migration大原則と同じ)。
5.2 → 5.3 の変更
0. 依存バージョンの更新(composer.json)
プラグインの require-dev(または require)の baser-core を 5.3 系に上げる。
"require-dev": {
"baserproject/baser-core": "5.3.x"
}
- 上げた後は
composer update baserproject/baser-core --with-dependencies(プラグインの vendor を持つ場合)で 5.3 系 + CakePHP 5.2.x を取り込む。 - PHP 要件は 8.1 互換を維持するなら
"php": ">=8.1"のまま(8.5 の非推奨対応はphp-migrationの ❌ 印=据え置き方針に従う)。
テスト基盤(standalone プラグイン test harness)
自前
vendor/とtests/TestApp/を持つ「単体実行型」のプラグインを 5.3 で動かすと、ここが最も詰まる。コアプラグイン(親ディレクトリのアプリ経由でテスト)の場合はアプリ側tests/bootstrap.phpが同等の処理をしているので、本節はアプリ側を参照する。
T-1. CakePHP の二重ロード(vendor 混在)で validateUnique(): Argument #2 ($options) not passed
composer test が ルートの phpunit(../../vendor/bin/phpunit)を起動する一方、プラグインの tests/bootstrap.php が ROOT/vendor/autoload.php(=プラグイン自前 vendor)を読み込むと、2つの異なる CakePHP バージョンが同時にロードされる。Composer のオートローダは register(prepend=true) で後勝ちになるため、起動時に読まれたクラス(例 Cake\ORM\Table 5.0系)と実行時に初めて読まれるクラス(例 Cake\Validation\Validator 5.2系)が混在し、validateUnique() 等でシグネチャ不一致の ArgumentCountError が出る。
- 判別: スタックトレースに
…/vendor/cakephp/…(ルート)と…/plugins/<plugin>/vendor/cakephp/…(自前)の 両方が現れる。 - 修正(standalone): テストはプラグイン自前の
vendor/bin/phpunitで実行する。bootstrap が読むROOT/vendorと phpunit bin の autoload が同一 vendor になり、CakePHP が単一バージョンに揃う。
実行(Docker 経由・basercms-unittest 参照):// plugins/<plugin>/composer.json "scripts": { "test": ["Composer\\Config::disableProcessTimeout", "vendor/bin/phpunit --colors=always"] }docker compose exec <container> sh -c "cd /var/www/html/plugins/<plugin> && vendor/bin/phpunit"
T-2. plugins.php の null → [](CakePHP 5.2 の PluginCollection::create が array 必須)
tests/TestApp/config/plugins.php の値が null だと、BaseApplication::bootstrap() → PluginCollection::addFromConfig() → create($name, null) で TypeError: …create(): Argument #2 ($config) must be of type array, null given になる(5.1 までは許容)。
// Before → After
return ['BaserCore' => null, ...]; → return ['BaserCore' => []];
- 関連:
cakephp-migration1-4 にも同記述あり。
T-3. プラグインの読み込みは BcApp.testAppPluginsToLoad を使う(plugins.php は BaserCore のみ)
baser-core 5.2+ の BcTestCase::loadTestAppPlugins() は Configure::read('BcApp.testAppPluginsToLoad') を読み、未 bootstrap が無くなるまでスナップショット単位で(ガード付きで)各プラグインを bootstrap する。
- plugins.php には
BaserCoreのみを記載し、コンテンツ系プラグイン(BcBlog / BcCustomContent / BcSearchIndex 等)と対象プラグイン自身はtestAppPluginsToLoadに列挙する。本体アプリ(config/plugins.phpは BaserCore + 基盤のみ、tests/bootstrap.phpでPlugin::loaded()から testAppPluginsToLoad を生成)と同方式。// plugins/<plugin>/tests/bootstrap.php(Migrator の後など) Configure::write('BcApp.testAppPluginsToLoad', [ 'BcMcp', // 対象プラグイン自身 'BcSearchIndex', 'BcBlog', 'BcCustomContent', // 依存コンテンツプラグイン ]);
T-4. 統合テストで Plugin named 'X' is already loaded(二重ロード例外)
T-3 と必ずセットで守る。プラグインを plugins.php と testAppPluginsToLoad の両方に書くと、統合テストがリクエスト毎に生成する per-request app が plugins.php を再び addFromConfig し、既に読み込み済みのプラグインを再 add して CakeException: Plugin named 'X' is already loaded(CakePHP 5.2、cakephp-migration 2-7)になる。500 → さらに後述 T-6 でマスクされ「原因不明の 500」に見える。
- 修正:
BaserCoreは plugins.php、その他は testAppPluginsToLoad に分離して重複させない。
T-5. 同梱サブプラグインのクラスが not found(例 Class "BcCcFile\Utility\BcCcFileUtil" not found)
BcCcFile は bc-custom-content に同梱されたサブプラグイン(vendor/baserproject/bc-custom-content/plugins/BcCcFile/)で、BcCustomContentPlugin::bootstrap() → loadPlugin() が App.paths.plugins を追加し BcUtil::includePluginClass() で動的に PSR-4 登録する。composer の autoload には現れないため、親プラグインが bootstrap されないとクラス解決できない。
- 修正: 親プラグイン(
BcCustomContent)を T-3 のtestAppPluginsToLoadに入れて bootstrap させる。サブプラグインの namespace を composer.json に手書き登録する必要はない。
T-6. テストの真因が MissingTemplateException: Error/error500.php でマスクされる
standalone の test harness には templates/Error/error500.php が無いため、統合テストで発生した 500 が「エラーページのレンダリング失敗」に化けて本当の例外が見えない(debug の有無に関わらず baserCMS はエラーテンプレートを描画しようとする)。
- 調査法(一時措置):
plugins/<plugin>/templates/Error/error500.phpを一時的に置き、テンプレートに渡る$error(Throwable)を捕捉してファイルへ書き出す → テスト実行 → 真因を確認 → テンプレートは削除する。<?php // TEMP: 調査用。確認後に削除 $e = $error ?? null; if ($e) { file_put_contents('/tmp/real_error.txt', get_class($e).': '.$e->getMessage()."\n".$e->getTraceAsString()); } echo 'ERR500'; - エラーレンダラーが探すのは プラグインの
templates/Error/(tests/TestApp/templates/ではない)点に注意。
baserCMS 固有の非推奨
B-1. BcFile のコンストラクタ/delete() のパス指定
logs/error.log に下記が出る(6.0.0 で必須化=今は警告)。
BcFile では、コンストラクタでパスの指定をしないのは非推奨です。パスの指定行うようにしてください。この要件はバージョン 6.0.0 で必須となります。
BcFile::delete() では、第一引数にパスを指定するのは非推奨です。パスの指定はコンストラクタで行ってください。この要件はバージョン 6.0.0 で必須となります。
- 修正: パスはコンストラクタで渡し、
delete()は引数なしにする。// Before → After $file = new BcFile(); $file = new BcFile($path); $file->delete($path); $file->delete(); - 自前プラグインのコードで
BcFileを使っている箇所のみ対象(vendor 内の発生は baser-core 側の対応待ちで、プラグイン移行としては無視してよい)。
TreeBehavior + Content フィクスチャ
C-1. Cannot set a node's parent as itself.(Content 保存テスト)
CakePHP 5.2 の TreeBehavior は「親に自分自身」を保存時に例外化する(cakephp-migration 2-11 と同系の厳格化)。baserCMS のテストシナリオで顕在化しやすい典型:
BlogContentScenario等は$parentId = $args[2] ?? 1のように?? 1でデフォルトを与えるため、テストがnullを渡しても parent_id=1 になる。id=1のコンテンツだと parent_id == id(自己親) の不正データになる。- 読み取りテストでは無害だが、Content を保存するテスト(
editXxxで name/title/site_id 等を更新し Content 関連が save される)でのみ例外化する。 - 修正(テスト側): シナリオ読込後に Content を正当なルートへ補正してツリーを再構築する。
$contentsTable = TableRegistry::getTableLocator()->get('BaserCore.Contents'); $contentsTable->updateAll(['parent_id' => null], ['id' => 1]); // behavior を介さず is-a-root に $contentsTable->recover(); // lft/rght 再構築 - プラグイン本体コードが
parent_idをルートに対して送っていないか(cakephp-migration2-11)も併せて確認する。送っていなければアプリ修正は不要で、上記フィクスチャ補正で足りる。
日付・時刻依存テスト
D-1. アップロードパス等の年月ハードコード
ファイルアップロード先は実行時の年月(date('Y/m'))でディレクトリが切られるため、'2025/09/...' のように固定値でアサートしていると月替わりで必ず失敗する。
// Before → After
$this->assertEquals('2025/09/00000001_image_field.png', $result['image_field'] ?? '');
$this->assertEquals(date('Y/m') . '/00000001_image_field.png', $result['image_field'] ?? '');
- 時刻比較全般の注意は
cakephp-migration3-4(実time()を使わない)も参照。
調査の進め方(チェックリスト)
- テスト実行・全失敗の集計は
basercms-unittestスキル。まず vendor 混在(T-1) を疑い、トレースに 2 つの cakephp パスが無いか確認。 PluginCollection::create(): … null given→ T-2、Plugin named 'X' is already loaded→ T-3/T-4。Class "…\…" not found(サブプラグイン)→ T-5。- 統合テストの原因不明 500 → T-6 で真因を可視化してから分類。
- フレームワーク/PHP 起因は
cakephp-migration/php-migrationの該当節へ。baserCMS 固有非推奨は B-1、TreeBehavior は C-1、日付依存は D-1。 - 修正のたびに
php -l→--filter単体確認 → 仕上げに全テスト再実行で件数改善と新規回帰を確認。
メモ: 新しくスキルファイルを
.claude/skills/に追加・更新した直後は、現在の Claude Code セッションには反映されない(セッション起動時に discover される)。Claude Code を再起動すると/basercms-plugin-5x-updateとして呼べるようになる。