Skip to content

[refactor] メールテンプレートをEC-CUBE標準のdtb_mail_templateに移行#2

Merged
dotani1111 merged 12 commits intomainfrom
feature/use-eccube-mail-template
Mar 22, 2026
Merged

[refactor] メールテンプレートをEC-CUBE標準のdtb_mail_templateに移行#2
dotani1111 merged 12 commits intomainfrom
feature/use-eccube-mail-template

Conversation

@dotani1111
Copy link
Owner

@dotani1111 dotani1111 commented Mar 21, 2026

Summary

  • PluginManager: enable() 時に dtb_mail_template レコード作成 + twigファイルをテーマディレクトリへコピー。uninstall() 時にDBレコードとtwigファイルを削除
  • メールテンプレート管理: プラグイン独自の編集画面を廃止し、EC-CUBE標準のメール設定画面(/admin/setting/shop/mail)で件名・本文を編集できるように移行
  • StockAlertMailBuilder: MailTemplateRepository 経由で件名・本文を取得、テンプレート未設定時はプラグイン内蔵の @StockAlertMail/Mail/stock_alert.twig にfallback
  • StockAlertConfig: mail_subject / mail_body カラムを削除。マイグレーション Version20260321000000 で既存DBから DROP COLUMN
  • テストメール送信: 設定画面にテストメール送信ボタンを追加(CSRF保護付き)、削除したテンプレート編集画面から移動
  • 削除: StockAlertMailTemplateController, StockAlertMailTemplateType, mail_template.twig(カスタム編集画面)

Test plan

  • プラグイン有効化時に dtb_mail_template にレコードが作成されること
  • app/template/default/Mail/stock_alert.twig がコピーされること
  • EC-CUBE管理画面 → システム設定 → メール設定 に「在庫アラートメール」が表示されること
  • メール設定から件名・本文を編集し、cronコマンド実行後に変更が反映されること
  • 設定画面のテストメール送信ボタンでメールが届くこと
  • プラグインアンインストール時にMailTemplateレコードとtwigファイルが削除されること
  • bin/phpunit app/Plugin/StockAlertMail/ がパスすること

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能

    • 設定画面に「テストメール送信」ボタンを追加(確認ダイアログ・送信結果の成功/失敗表示・設定未検出時のエラー対応)。
  • 変更

    • メールの件名/本文管理をプラグイン設定からEC‑CUBE標準のメールテンプレートへ移行。
    • メール本文を商品ごとにグループ化、表示フォーマットを整理し商品IDを表示。
  • 削除

    • プラグイン専用のテンプレート編集画面と該当ナビ項目を削除。

- PluginManager: enable時にMailTemplateレコード作成+twigファイルコピー、uninstall時に削除
- StockAlertMailBuilder: MailTemplateRepositoryを使って件名・本文を取得、fallback付き
- StockAlertConfig: mail_subject / mail_body カラムを削除
- Version20260321000000: 既存インストール向け DROP COLUMN マイグレーション追加
- StockAlertConfigController: テストメール送信アクションを追加(CSRF保護付き)
- config.twig: メールテンプレート案内カード+テストメール送信ボタンを追加
- カスタムメールテンプレート編集画面(Controller/Form/twig)を削除

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

StockAlertConfigから件名/本文を削除し、MailTemplateを参照してメールを生成するよう移行。管理画面に「テスト送信」POSTアクションとボタンを追加。テンプレート編集UI/フォーム/コントローラを削除、マイグレーションでカラム除去を扱い、PluginManagerでTwigテンプレートを配置/削除。

Changes

Cohort / File(s) Summary
メール送信ロジック
Command/StockAlertCommand.php, Service/StockAlertMailBuilder.php
MailBuilderがMailTemplate参照へ移行(buildMailSubject()/buildMailBody()シグネチャ変更、Twigレンダリング、LoaderErrorフォールバック)。コマンドは設定をfind(1)で取得、メール準備をtry内へ移動、例外捕捉を\Throwableへ拡大。
管理画面設定とテスト送信
Controller/Admin/StockAlertConfigController.php, Resource/template/admin/config.twig
コントローラにStockAlertMailBuilderMailerInterfaceを注入し、sendTest POSTアクションを追加。設定画面に「テスト送信」ボタンと隠しPOSTフォーム(CSRF)を実装。
テンプレート編集UIの削除
Controller/Admin/StockAlertMailTemplateController.php, Form/Type/Admin/StockAlertMailTemplateType.php, Resource/template/admin/mail_template.twig, Nav.php
独自テンプレート編集コントローラ・フォーム・ビュー・ナビ項目を削除(件名/本文管理をMailTemplateへ移行)。
エンティティ & マイグレーション
Entity/StockAlertConfig.php, DoctrineMigrations/Version20260320000000.php, DoctrineMigrations/Version20260321000000.php
StockAlertConfig.idを固定(1)化、mailSubject/mailBodyフィールドを削除。既存マイグレーションからカラム除去、条件付きでカラム削除/復元する新マイグレーションを追加。
プラグインライフサイクル
PluginManager.php
enable()MailTemplateレコード作成とTwigテンプレートをフロントテーマへコピー、uninstall()でレコードとファイルを削除。MAIL_TEMPLATE_FILE_NAME等定数追加。初期設定チェックをfind(1)へ変更。
ロケール・テンプレート更新
Resource/locale/messages.ja.yaml, Resource/locale/messages.en.yaml, Resource/template/Mail/stock_alert.twig
テスト送信文言を追加、テンプレート編集関連文言を削除。メール本文Twigを製品ごとにグルーピングしてproduct_id表示を追加。
テスト修正
Tests/StockAlertCommandTest.php, Tests/PluginManagerTest.php
テストをMailTemplate移行とID固定に合わせ更新。リポジトリ検索をfind(1)へ置換、MailTemplate削除処理とentityManager->clear()追加。カスタム件名/本文テストを削除/差替え。
CI設定
.github/workflows/ci.yml
CIからMailHogを削除し、MAILER_DSNnull://nullへ変更。

Sequence Diagram(s)

sequenceDiagram
  participant Admin as "Admin UI"
  participant Controller as "StockAlertConfigController"
  participant Builder as "StockAlertMailBuilder"
  participant Repo as "MailTemplateRepository"
  participant Mailer as "MailerInterface"

  Admin->>Controller: POST /plugin/stock-alert/config/send-test (CSRF)
  Controller->>Controller: validate CSRF
  Controller->>Controller: repo->find(1) (StockAlertConfig)
  Controller->>Builder: buildBaseInfo(), resolveRecipients(), generateDummyItems()
  Controller->>Builder: buildMailSubject()
  Builder->>Repo: findMailTemplate() by `PluginManager::MAIL_TEMPLATE_FILE_NAME`
  alt MailTemplate found
    Repo-->>Builder: MailTemplate (subject/body)
    Builder->>Builder: render body via Twig (MailTemplate.getFileName())
  else fallback
    Builder->>Builder: render built-in template `@StockAlertMail/Mail/stock_alert.twig`
  end
  Controller->>Mailer: create Email(from,to,subject,body) and send()
  Mailer-->>Controller: success / throws
  Controller-->>Admin: redirect with success|failure flash
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 テンプレは標準へぴょんとお引越し
件名と本文、もうMailTemplateにお任せだよ
設定画面に「テスト送信」ぽんと追加して
有効化でテンプレ作って、解除でさよなら〜
ふわふわ確認、届きますように ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、メールテンプレート管理をEC-CUBE標準のdtb_mail_templateに移行するというリファクタリングの主要な変更を明確に要約しており、変更セット全体の主要な目的を適切に反映している。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/use-eccube-mail-template

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
Resource/template/admin/config.twig (1)

78-81: インラインイベントハンドラの改善を検討してください。

onclick 属性内の JavaScript は動作しますが、翻訳文字列に引用符が含まれる場合に問題が発生する可能性があります。

♻️ 改善案: data 属性とイベントリスナーを使用
-                        <button type="button" class="btn btn-outline-primary"
-                                onclick="if(confirm('{{ 'stock_alert_mail.admin.config.send_test.confirm'|trans }}')){document.getElementById('form-send-test').submit();}">
+                        <button type="button" class="btn btn-outline-primary"
+                                id="btn-send-test"
+                                data-confirm="{{ 'stock_alert_mail.admin.config.send_test.confirm'|trans|e('html_attr') }}">

そして {% block javascript %} 内で:

document.getElementById('btn-send-test').addEventListener('click', function() {
    if (confirm(this.dataset.confirm)) {
        document.getElementById('form-send-test').submit();
    }
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resource/template/admin/config.twig` around lines 78 - 81, Replace the inline
onclick handler on the button with a data-confirm attribute and a stable id (use
id="btn-send-test") and keep the form id "form-send-test"; then add a JS event
listener in the template's {% block javascript %} that reads dataset.confirm,
calls confirm(...) and submits the form if true — this avoids quoting issues
with translated strings and centralizes behavior in a safe event handler tied to
btn-send-test and form-send-test.
Controller/Admin/StockAlertConfigController.php (2)

93-101: メール送信ロジックの重複を検討してください。

StockAlertCommand と同様のメール構築・送信ロジックがあります。将来的にメール送信処理をサービスクラスに抽出することで、DRY 原則に沿った設計になります。

現時点では許容範囲ですが、今後の拡張時に検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Controller/Admin/StockAlertConfigController.php` around lines 93 - 101, The
controller duplicates the email construction/sending logic already present in
StockAlertCommand; extract that logic into a reusable service (e.g., a
NotificationMailer or StockAlertMailer) and have both StockAlertConfigController
and StockAlertCommand call a single method like sendStockAlert(subject, body,
fromAddress, toEmails) to build the Email, add recipients, and dispatch it;
update StockAlertConfigController (the block that builds $message with Email,
Address, ->text and foreach addTo) to call the new service instead, and refactor
StockAlertCommand to use the same service so email behavior is centralized and
DRY.

105-107: addSuccess/addError での翻訳処理の一貫性を確認してください。

Line 59 では addSuccess('stock_alert_mail.admin.config.save_success', 'admin') と翻訳キーを直接渡していますが、Lines 105, 107 では $this->translator->trans() を事前に呼び出しています。

EC-CUBE の AbstractController::addSuccess/addError は内部で翻訳を行いますが、パラメータ付きの翻訳(%emails% など)の場合は事前に trans() を呼ぶ必要があります。現在の実装は正しいですが、コメントがあると保守性が向上します。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Controller/Admin/StockAlertConfigController.php` around lines 105 - 107, The
translation handling is inconsistent: when passing a translation key directly to
addSuccess/addError (e.g.
addSuccess('stock_alert_mail.admin.config.save_success', 'admin')) EC-CUBE will
translate internally, but when the message needs runtime parameters you must
call $this->translator->trans(...) beforehand (as done around
$this->translator->trans('stock_alert_mail.admin.config.send_test.success',
['%emails%' => implode(', ', $toEmails)]) and the corresponding catch block).
Add a short inline comment near the addSuccess/addError calls (and in the catch
block) explaining this rule (use direct key for simple messages, pre-call
$this->translator->trans when injecting parameters) so future maintainers know
why trans() is used before addSuccess/addError; reference the methods
addSuccess, addError and $this->translator->trans in the comment.
PluginManager.php (1)

116-127: ファイル操作のエラーハンドリングが不足しています。

copy() の戻り値を確認せず、コピー失敗時も処理が続行します。プラグイン有効化時にテンプレートコピーが失敗すると、メール送信時にフォールバックテンプレートが使用されますが、これが意図通りか不明確です。

また、$sourcePath が存在しない場合に静かにリターンするのは、デバッグを困難にする可能性があります。

♻️ エラーハンドリングの改善案
         $sourcePath = __DIR__.'/Resource/template/Mail/stock_alert.twig';

         if (!file_exists($sourcePath)) {
-            return;
+            throw new \RuntimeException('Source template not found: '.$sourcePath);
         }

         $targetDir = dirname($targetPath);
         if (!is_dir($targetDir)) {
-            mkdir($targetDir, 0755, true);
+            if (!mkdir($targetDir, 0755, true)) {
+                throw new \RuntimeException('Failed to create directory: '.$targetDir);
+            }
         }

-        copy($sourcePath, $targetPath);
+        if (!copy($sourcePath, $targetPath)) {
+            throw new \RuntimeException('Failed to copy template to: '.$targetPath);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PluginManager.php` around lines 116 - 127, sourcePath の存在チェックが無条件に静かに return
し、また copy() の戻り値や mkdir の失敗を無視しているためファイル操作の失敗が見えなくなっています。PluginManager.php
の該当ブロックで file_exists($sourcePath) が false の場合は単に return するのではなく
processLogger(または例外)で明確にエラー/警告を出し、mkdir の戻り値と copy($sourcePath, $targetPath)
の戻り値を確認して失敗時は適切にログ出力して処理を中断(または例外を投げる)するように修正してください。エラーメッセージには $sourcePath と
$targetPath を含め、必要ならパーミッションや親ディレクトリ作成の失敗理由も含めて記録するようにしてください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Controller/Admin/StockAlertConfigController.php`:
- Around line 93-101: The controller duplicates the email construction/sending
logic already present in StockAlertCommand; extract that logic into a reusable
service (e.g., a NotificationMailer or StockAlertMailer) and have both
StockAlertConfigController and StockAlertCommand call a single method like
sendStockAlert(subject, body, fromAddress, toEmails) to build the Email, add
recipients, and dispatch it; update StockAlertConfigController (the block that
builds $message with Email, Address, ->text and foreach addTo) to call the new
service instead, and refactor StockAlertCommand to use the same service so email
behavior is centralized and DRY.
- Around line 105-107: The translation handling is inconsistent: when passing a
translation key directly to addSuccess/addError (e.g.
addSuccess('stock_alert_mail.admin.config.save_success', 'admin')) EC-CUBE will
translate internally, but when the message needs runtime parameters you must
call $this->translator->trans(...) beforehand (as done around
$this->translator->trans('stock_alert_mail.admin.config.send_test.success',
['%emails%' => implode(', ', $toEmails)]) and the corresponding catch block).
Add a short inline comment near the addSuccess/addError calls (and in the catch
block) explaining this rule (use direct key for simple messages, pre-call
$this->translator->trans when injecting parameters) so future maintainers know
why trans() is used before addSuccess/addError; reference the methods
addSuccess, addError and $this->translator->trans in the comment.

In `@PluginManager.php`:
- Around line 116-127: sourcePath の存在チェックが無条件に静かに return し、また copy() の戻り値や mkdir
の失敗を無視しているためファイル操作の失敗が見えなくなっています。PluginManager.php の該当ブロックで
file_exists($sourcePath) が false の場合は単に return するのではなく
processLogger(または例外)で明確にエラー/警告を出し、mkdir の戻り値と copy($sourcePath, $targetPath)
の戻り値を確認して失敗時は適切にログ出力して処理を中断(または例外を投げる)するように修正してください。エラーメッセージには $sourcePath と
$targetPath を含め、必要ならパーミッションや親ディレクトリ作成の失敗理由も含めて記録するようにしてください。

In `@Resource/template/admin/config.twig`:
- Around line 78-81: Replace the inline onclick handler on the button with a
data-confirm attribute and a stable id (use id="btn-send-test") and keep the
form id "form-send-test"; then add a JS event listener in the template's {%
block javascript %} that reads dataset.confirm, calls confirm(...) and submits
the form if true — this avoids quoting issues with translated strings and
centralizes behavior in a safe event handler tied to btn-send-test and
form-send-test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abf78334-2694-4bee-b2d5-cc2b48828aa5

📥 Commits

Reviewing files that changed from the base of the PR and between 23d45cf and c8f3022.

📒 Files selected for processing (15)
  • Command/StockAlertCommand.php
  • Controller/Admin/StockAlertConfigController.php
  • Controller/Admin/StockAlertMailTemplateController.php
  • DoctrineMigrations/Version20260320000000.php
  • DoctrineMigrations/Version20260321000000.php
  • Entity/StockAlertConfig.php
  • Form/Type/Admin/StockAlertMailTemplateType.php
  • Nav.php
  • PluginManager.php
  • Resource/locale/messages.en.yaml
  • Resource/locale/messages.ja.yaml
  • Resource/template/admin/config.twig
  • Resource/template/admin/mail_template.twig
  • Service/StockAlertMailBuilder.php
  • Tests/StockAlertCommandTest.php
💤 Files with no reviewable changes (6)
  • Nav.php
  • DoctrineMigrations/Version20260320000000.php
  • Resource/template/admin/mail_template.twig
  • Entity/StockAlertConfig.php
  • Form/Type/Admin/StockAlertMailTemplateType.php
  • Controller/Admin/StockAlertMailTemplateController.php

dotani1111 and others added 2 commits March 21, 2026 16:50
- StockAlertConfig: GeneratedValue を NONE に変更し id=1 固定
- findOneBy([]) を find(1) に統一(Command・Controller・PluginManager・Tests)
- メールテンプレート: 商品IDを表示し同一商品の規格をグルーピング
- コマンドクエリに orderBy('p.id') を追加(グルーピング前提のソート)
- 翻訳キー stock_alert_mail.mail.product_id を追加

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StockAlertCommandTest: DQL DELETE後にentityManager->clear()を追加し、
  テスト間でアイデンティティマップに残存するエンティティを除去する
- StockAlertCommand: catch(\Exception)をcatch(\Throwable)に変更し、
  PHPの\Error(TypeError等)も確実に捕捉する

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Tests/PluginManagerTest.php (1)

90-97: ⚠️ Potential issue | 🟠 Major

新しい MailTemplate / Twig のライフサイクルをこのテスト群が素通りしています。

enable() / uninstall() は今や dtb_mail_template 行とテーマ側 Mail/stock_alert.twig も触りますが、ここでは設定やプラグインテーブルしか見ていません。さらに setUp() / tearDown()StockAlertConfig しか初期化していないため、前テストのテンプレート行・配置ファイルが残ると createMailTemplate() / deleteMailTemplate() が実際には何もしていなくても通ります。file_name = Mail/stock_alert.twigMailTemplate 行と配置先ファイルも各ケースで seed / assert / cleanup して、この PR の主経路を直接検証したいです。

Also applies to: 121-144, 189-211

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/PluginManagerTest.php` around lines 90 - 97, テストが MailTemplate/Twig
ライフサイクルを検証していないため、testEnable()(および同様の箇所:lines ~121-144, 189-211)で
pluginManager->enable() / uninstall() の実動作を保証するために、setUp() で dtb_mail_template に
file_name = 'Mail/stock_alert.twig' の行と実際の配備先テンプレートファイルをシードし、test 内で
enable()/uninstall() 実行後に createMailTemplate()/deleteMailTemplate()
の期待効果(テーブルの追加/削除とファイルの存在/不在)を明示的にアサートし、tearDown()
でそのテンプレート行とファイルを確実にクリーンアップするように修正してください(StockAlertConfig 初期化だけでなく MailTemplate
行とファイルの seed/assert/cleanup を追加すること)。
🧹 Nitpick comments (2)
Tests/PluginManagerTest.php (1)

199-204: testUninstallThenReinstall() が旧スキーマを復元しています。

recreateTablesIfNeeded()mail_subject / mail_body をまだ作っているので、PR で削除したカラムへの再依存をこの再インストールテストが見逃します。手組み DDL は現行スキーマに合わせるか、可能なら実マイグレーション相当の経路を使った方が安全です。

♻️ 例
             $table->addColumn('threshold', 'integer', ['default' => 5]);
             $table->addColumn('alert_emails', 'string', ['length' => 1000, 'notnull' => false]);
-            $table->addColumn('mail_subject', 'string', ['length' => 500, 'notnull' => false]);
-            $table->addColumn('mail_body', 'text', ['notnull' => false]);
             $table->addColumn('create_date', 'datetimetz');
             $table->addColumn('update_date', 'datetimetz');

Also applies to: 223-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/PluginManagerTest.php` around lines 199 - 204, The test
testUninstallThenReinstall is accidentally recreating removed columns because
recreateTablesIfNeeded still builds mail_subject/mail_body; update the test
setup so recreateTablesIfNeeded aligns with the current schema (remove creation
of those deleted columns) or, preferably, replace the manual DDL with running
the real migration path (use the same migration runner the app uses) so
resyncTransactionState($conn) runs against the up-to-date schema and the
re-install sequence in enable()/flush() is validated against the current
migrations.
Resource/template/Mail/stock_alert.twig (1)

10-15: 商品グルーピングが入力順に依存しています。

現状は StockAlertCommandp.id で並べているので成立しますが、このロジック自体は同一 Product.id が連続している前提です。StockAlertMailBuilder::buildMailBody() は配列をそのまま渡すだけなので、別経路から未ソート配列が来ると同じ商品の見出しが分裂します。グルーピングはサービス側で確定してからテンプレートへ渡す方が保守しやすいです。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resource/template/Mail/stock_alert.twig` around lines 10 - 15, The template
groups lowStockItems by relying on consecutive items with the same Product.id
which is brittle; update the service before rendering (e.g. in
StockAlertMailBuilder::buildMailBody()) to either sort the lowStockItems array
by Product.id or, preferably, transform it into an explicit grouped structure
(map keyed by Product.id with name and its items) and pass that to the template
so the template no longer assumes input order; adjust callers such as
StockAlertCommand to stop relying on ordering if you choose grouping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@PluginManager.php`:
- Around line 76-83: The current flow can create a DB-only MailTemplate when
copyTwigTemplate() fails silently; change PluginManager so filesystem operations
are guaranteed before committing DB rows: make copyTwigTemplate() throw on
mkdir()/copy() failure (do not swallow errors) and/or run the template copy
before inserting/returning when checking $repository->findOneBy(['file_name' =>
self::MAIL_TEMPLATE_FILE_NAME]); after copying verify the target file (e.g.
Mail/stock_alert.twig) exists and throw an exception to abort registration if
not; apply the same fix to the other block referenced (lines ~118-127) so both
code paths validate file presence and propagate errors instead of leaving a
DB-only MailTemplate that breaks StockAlertMailBuilder::getFileName().

In `@Resource/template/Mail/stock_alert.twig`:
- Line 14: 現在のテンプレート embeds 全角括弧 around the translated product id so those
brackets appear even in English; update the template so the parentheses are
handled by the translation key or made locale‑neutral: move the brackets into
the translation resource key 'stock_alert_mail.mail.product_id' (so translators
can supply locale-appropriate surrounding punctuation and wording like "(Product
ID: ...)" vs "(商品ID:...)"), or change the template to use a locale‑neutral
delimiter (e.g. " - " or ":" placed outside the translation) and reference
item.Product.id via the same 'stock_alert_mail.mail.product_id' key; adjust the
corresponding translation entries to include the surrounding text accordingly.

---

Outside diff comments:
In `@Tests/PluginManagerTest.php`:
- Around line 90-97: テストが MailTemplate/Twig
ライフサイクルを検証していないため、testEnable()(および同様の箇所:lines ~121-144, 189-211)で
pluginManager->enable() / uninstall() の実動作を保証するために、setUp() で dtb_mail_template に
file_name = 'Mail/stock_alert.twig' の行と実際の配備先テンプレートファイルをシードし、test 内で
enable()/uninstall() 実行後に createMailTemplate()/deleteMailTemplate()
の期待効果(テーブルの追加/削除とファイルの存在/不在)を明示的にアサートし、tearDown()
でそのテンプレート行とファイルを確実にクリーンアップするように修正してください(StockAlertConfig 初期化だけでなく MailTemplate
行とファイルの seed/assert/cleanup を追加すること)。

---

Nitpick comments:
In `@Resource/template/Mail/stock_alert.twig`:
- Around line 10-15: The template groups lowStockItems by relying on consecutive
items with the same Product.id which is brittle; update the service before
rendering (e.g. in StockAlertMailBuilder::buildMailBody()) to either sort the
lowStockItems array by Product.id or, preferably, transform it into an explicit
grouped structure (map keyed by Product.id with name and its items) and pass
that to the template so the template no longer assumes input order; adjust
callers such as StockAlertCommand to stop relying on ordering if you choose
grouping.

In `@Tests/PluginManagerTest.php`:
- Around line 199-204: The test testUninstallThenReinstall is accidentally
recreating removed columns because recreateTablesIfNeeded still builds
mail_subject/mail_body; update the test setup so recreateTablesIfNeeded aligns
with the current schema (remove creation of those deleted columns) or,
preferably, replace the manual DDL with running the real migration path (use the
same migration runner the app uses) so resyncTransactionState($conn) runs
against the up-to-date schema and the re-install sequence in enable()/flush() is
validated against the current migrations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 007b009d-86d6-461e-b62e-44f0dd05f610

📥 Commits

Reviewing files that changed from the base of the PR and between c8f3022 and 9d027ef.

📒 Files selected for processing (9)
  • Command/StockAlertCommand.php
  • Controller/Admin/StockAlertConfigController.php
  • Entity/StockAlertConfig.php
  • PluginManager.php
  • Resource/locale/messages.en.yaml
  • Resource/locale/messages.ja.yaml
  • Resource/template/Mail/stock_alert.twig
  • Tests/PluginManagerTest.php
  • Tests/StockAlertCommandTest.php
🚧 Files skipped from review as they are similar to previous changes (6)
  • Command/StockAlertCommand.php
  • Entity/StockAlertConfig.php
  • Resource/locale/messages.en.yaml
  • Controller/Admin/StockAlertConfigController.php
  • Resource/locale/messages.ja.yaml
  • Tests/StockAlertCommandTest.php

SMTP接続失敗によるテスト失敗を解消。MailerAssertionsTraitは
nullトランスポートでも正常に動作する。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

142-150: MAILER_DSN をセットアップ工程でも統一してください。

Line 165 は null://null になっていますが、Line 149 が SMTP のままです。MailHog サービスなし構成なので、セットアップ中にメール送信が発生した場合の不安定要因になります。Setup database schema 側も同じ DSN に揃えるのが安全です。

差分案
-          MAILER_DSN: 'smtp://127.0.0.1:1025'
+          MAILER_DSN: 'null://null'

Also applies to: 165-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 142 - 150, In the "Setup database
schema" GitHub Actions step, the MAILER_DSN environment variable is still set to
the SMTP DSN while a later step uses null://null; change the MAILER_DSN in that
step's env block to null://null so the setup run uses the same no-mail transport
as the later step (update the MAILER_DSN entry in the step named "Setup database
schema" to match the null://null value).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 142-150: In the "Setup database schema" GitHub Actions step, the
MAILER_DSN environment variable is still set to the SMTP DSN while a later step
uses null://null; change the MAILER_DSN in that step's env block to null://null
so the setup run uses the same no-mail transport as the later step (update the
MAILER_DSN entry in the step named "Setup database schema" to match the
null://null value).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bc596466-9aef-44d3-84c3-3c81bda22f8f

📥 Commits

Reviewing files that changed from the base of the PR and between 9d027ef and 03a155f.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • Tests/StockAlertCommandTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests/StockAlertCommandTest.php

CIではeccube:plugin:enableでコピーされるテーマ側テンプレート
(app/template/default/Mail/)が存在しない場合があるため、
テストではMailTemplateレコードを削除して@StockAlertMailネームスペースの
テンプレートにフォールバックさせる。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
Tests/StockAlertCommandTest.php (2)

86-88: find(1) 固定はテストの将来的な不安定要因になりえます。

setUpで作成した StockAlertConfig の実IDを保持して参照すると、DBシーケンス状態に依存せず堅牢です。

💡 例: setUpで作成したIDを使う差分
 class StockAlertCommandTest extends EccubeTestCase
 {
     use MailerAssertionsTrait;

@@
     /** `@var` CommandTester */
     private $commandTester;
+    /** `@var` int */
+    private $configId;

@@
         $this->entityManager->persist($config);
         $this->entityManager->flush();
+        $this->configId = $config->getId();
@@
-        $config = $this->configRepository->find(1);
+        $config = $this->configRepository->find($this->configId);
         $config->setThreshold(-1);
@@
-        $config = $this->configRepository->find(1);
+        $config = $this->configRepository->find($this->configId);
         $config->setThreshold(-1);
@@
-        $config = $this->configRepository->find(1);
+        $config = $this->configRepository->find($this->configId);
         $config->setThreshold(-1);

Also applies to: 128-130, 159-161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/StockAlertCommandTest.php` around lines 86 - 88, The tests use a
hardcoded find(1) which makes them brittle; instead capture and store the real
ID of the StockAlertConfig created in setUp (e.g. save the entity's getId() into
a property like $this->configId) and update all usages of
$this->configRepository->find(1) (including the occurrences around the other
blocks referenced) to $this->configRepository->find($this->configId) so the
tests do not depend on DB sequence values and will reliably reference the config
created in setUp.

198-200: 本文アサート前に null チェックを追加してください。

getTextBody() が null の場合に、失敗理由をより明確にできます。

💡 例: null 明示チェックを追加
         $body = $message->getTextBody();
+        $this->assertNotNull($body, 'Text body should not be null.');
         $this->assertStringContainsString('管理者様', $body);
         $this->assertStringContainsString('在庫', $body);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/StockAlertCommandTest.php` around lines 198 - 200, Add an explicit null
check for the email body before asserting its contents: after assigning $body =
$message->getTextBody() in Tests/StockAlertCommandTest.php, assertNotNull($body,
'Expected text body to be present') (or an equivalent null check) and
return/fail early if null so the subsequent assertions on $body and the calls to
assertStringContainsString('管理者様', $body) and assertStringContainsString('在庫',
$body) don’t throw unclear errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Tests/StockAlertCommandTest.php`:
- Around line 86-88: The tests use a hardcoded find(1) which makes them brittle;
instead capture and store the real ID of the StockAlertConfig created in setUp
(e.g. save the entity's getId() into a property like $this->configId) and update
all usages of $this->configRepository->find(1) (including the occurrences around
the other blocks referenced) to $this->configRepository->find($this->configId)
so the tests do not depend on DB sequence values and will reliably reference the
config created in setUp.
- Around line 198-200: Add an explicit null check for the email body before
asserting its contents: after assigning $body = $message->getTextBody() in
Tests/StockAlertCommandTest.php, assertNotNull($body, 'Expected text body to be
present') (or an equivalent null check) and return/fail early if null so the
subsequent assertions on $body and the calls to
assertStringContainsString('管理者様', $body) and assertStringContainsString('在庫',
$body) don’t throw unclear errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b63e15aa-91f6-45ef-b5f8-4aa8e066f54b

📥 Commits

Reviewing files that changed from the base of the PR and between 03a155f and a59ac41.

📒 Files selected for processing (1)
  • Tests/StockAlertCommandTest.php

- PluginManager: copyTwigTemplateのソース欠落・mkdir・copy失敗を
  例外化し、DB登録前にファイル存在を保証
- StockAlertMailBuilder: MailTemplateのファイルが見つからない場合に
  @StockAlertMailネームスペースのテンプレートへフォールバック

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
PluginManager.php (1)

76-83: ⚠️ Potential issue | 🟠 Major

テンプレート欠落時の自己修復がまだ効きません。

Line 77 の早期 return により、dtb_mail_template レコードだけ残って Twig ファイルが消えているケースで copyTwigTemplate() が実行されません。ファイル存在保証を先に行ってから DB 存在チェックする順序にしてください。

🔧 修正案
-        // 既に登録済みの場合はスキップ
-        if ($repository->findOneBy(['file_name' => self::MAIL_TEMPLATE_FILE_NAME]) !== null) {
-            return;
-        }
-
-        // プラグインのデフォルトテンプレートを app/template/default/Mail/ にコピー
-        $this->copyTwigTemplate($container);
+        // まずテンプレート実体を保証(欠落時の自己修復)
+        $this->copyTwigTemplate($container);
+
+        // 既に登録済みの場合はDB登録のみスキップ
+        if ($repository->findOneBy(['file_name' => self::MAIL_TEMPLATE_FILE_NAME]) !== null) {
+            return;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PluginManager.php` around lines 76 - 83, The early return after
repository->findOneBy(['file_name' => self::MAIL_TEMPLATE_FILE_NAME]) prevents
copyTwigTemplate($container) from running when the DB row exists but the Twig
file is missing; move the file-existence/self-heal step before the DB existence
check (i.e., call copyTwigTemplate($container) or a new
ensureTwigTemplatesExist() helper first), then perform the
repository->findOneBy(...) check and return only after templates are guaranteed
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Service/StockAlertMailBuilder.php`:
- Around line 66-70: The subject fallback currently only checks for null on
$mailTemplate and can still produce an empty subject when getMailSubject()
returns an empty string or whitespace; update the logic in
Service\StockAlertMailBuilder (the $subjectSuffix assignment that reads
$mailTemplate->getMailSubject()) to treat null or empty/whitespace-only values
as missing by using trim() or an empty() check and fall back to the default
'在庫アラート通知', then build $subject as before using $this->BaseInfo->getShopName().

---

Duplicate comments:
In `@PluginManager.php`:
- Around line 76-83: The early return after repository->findOneBy(['file_name'
=> self::MAIL_TEMPLATE_FILE_NAME]) prevents copyTwigTemplate($container) from
running when the DB row exists but the Twig file is missing; move the
file-existence/self-heal step before the DB existence check (i.e., call
copyTwigTemplate($container) or a new ensureTwigTemplatesExist() helper first),
then perform the repository->findOneBy(...) check and return only after
templates are guaranteed present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8894730f-5442-418c-bb8e-9be09ca4430d

📥 Commits

Reviewing files that changed from the base of the PR and between a59ac41 and fea0ece.

📒 Files selected for processing (2)
  • PluginManager.php
  • Service/StockAlertMailBuilder.php

dotani1111 and others added 6 commits March 22, 2026 10:12
- 空文字/空白のみの件名もデフォルト件名にフォールバック
- テンプレートの全角括弧を翻訳キーに移動し多言語対応

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DB シーケンス値に依存しないよう $this->configId を使用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ファイルコピーをDB存在チェックの前に移動し、DB行だけ残る半壊状態を修復できるようにした

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- testMailSubjectFallbackWhenEmpty: 空白のみ件名がデフォルトにフォールバックされること
- testEnableRestoresMissingTwigTemplate: DB行あり・Twigファイル欠損時の自己修復

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dotani1111 dotani1111 merged commit e51f85e into main Mar 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant