前提
特定製品専用のEC-CUBEプラグイン(一般配布しない)を開発したときの備忘録です。
プラグインとして開発するので、付け外ししやすいように実装していたつもりが、Customize と Plugin の境界があいまいになりやすく、いつの間にか依存関係が入り組んでしまうことがありました。
気をつけておけばよかったと感じた項目を整理しておきます。
疎結合をとにかく意識する
開発を進めていると、
- プラグイン → 別プラグイン
- Customize → プラグイン
といった意図しない依存関係をいつの間にか作ってしまうことがあります。
EC-CUBEでは、以下のような対応で疎結合を保ちながらプラグインで機能追加していくことができます。
イベント駆動で処理をフックする
最もシンプルで安全な拡張方法は「イベント駆動」です。EC-CUBE/Symfonyのイベント機構を使えば、既存コードを汚さずに動作を差し込めます。
namespace Plugin\SamplePlugin\Event;
use Eccube\Event\EccubeEvents;
use Eccube\Event\EventArgs;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class OrderEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
EccubeEvents::FRONT_SHOPPING_COMPLETE => 'onOrderComplete',
];
}
public function onOrderComplete(EventArgs $event): void
{
$Order = $event->getArgument('Order');
// 外部APIに通知など
}
}
各Processorで処理を挿し込む
もう一つの定番手法が、Processorに独自の処理を組み込む方法です。
前段/後段の Processor(priority で制御)や Validator、Decoratorなどを使うことで、EC-CUBEがコアファイルで持っている購入フローに関与することができます。
namespace Plugin\SamplePlugin\Service\PurchaseFlow;
use Eccube\Entity\Order;
use Eccube\Service\PurchaseFlow\PurchaseContext;
use Eccube\Service\PurchaseFlow\PurchaseProcessorInterface;
class StockValidationProcessor implements PurchaseProcessorInterface
{
public function process(Order $Order, PurchaseContext $context): void
{
foreach ($Order->getOrderDetails() as $Detail) {
// 在庫チェック
}
}
}
このようにProcessorを活用すれば、Customize に依存せず、安全に独自ロジックを差し込むことが可能です。使う際は、プラグイン内のResource/config/services.yamlに以下のように定義します。
services:
Plugin\SamplePlugin\Service\PurchaseFlow\StockValidationProcessor:
tags:
- { name: eccube.purchase.processor, flow_type: 'shopping', priority: 150 }
定義されているflow_typeのタイミングで、priorityの数値が大きい順に処理が実行されます。
テンプレートの追加は、3段階で
公式ドキュメントにあるプラグインサンプルが参考になります。
https://github.com/EC-CUBE/ProductReview-plugin/
- PluginManagerで、ページやCSVタイプの追加(必要に応じて)
- EventSubscriberで、テンプレートの読み込み($event->addSnippet)
- 差し込む箇所は、JSで動的に
※1. のページ追加は、ページ管理に追加しない(UIで編集させない)のであれば、特になくてもいいかなと。今回は既存テンプレートへの部分的な差し込みだったので、省略しました。
Customize配下の独自Entityはプラグインから拡張できない
Customize配下で独自に定義したEntityを、プラグイン側からtrait拡張しようとすると、生成されるproxyで名前空間が競合する次のようなエラーが出ます。
Fatal error: Cannot declare class Eccube\Entity\BaseInfo, because the name is already in use in app\proxy\entity\BaseInfo.php
EC-CUBEの仕様上、プラグインが特定のCustomize構造に依存することを想定していないことが原因なのかなと思いました。
なので、今回は別Entityを新しくプラグイン内で作って、リレーションで繋ぎました。
💡 CustomizeのEntityを直接変更するのは簡単ですが、プラグインを外した時に追加したフィールドが残ってしまいます...
Plugin配下の services.yaml は扱いに注意
Customize配下のServiceを「ちょっとだけ上書きしたい…」場合に、以下のようにデコレーターを定義することがあると思います。
Plugin\SamplePlugin\Service\Decorator\CustomServiceDecorator:
decorates: 'Customize\Service\SomeService'
arguments:
$inner: '@.inner'
ただ、落とし穴なのが、プラグインが無効化されていても services.yaml が常に読み込まれるという点です。
これは、src/Eccube/Kernel.php の configureContainer メソッドで
全プラグインの services.yaml がブート時に、一括ロードされる仕組みになっているためです。
// プラグインのservices.phpをロードする.
$dir = dirname(__DIR__).'/../app/Plugin/*/Resource/config';
$loader->load($dir.'/services'.self::CONFIG_EXTS, 'glob');
$loader->load($dir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob');
そのため、「Customize前提のプラグイン」を作ってしまうと、有効化していない状態でもPlugin配下にファイルがあれば、実装した内容の影響が及んでしまうことになります。(アンインストールすればOK)
なので、基本的には前述の疎結合な機能追加のやり方を採用し、直接特定クラスを上書きすることは避けた方が無難だと思います。
アンインストール時のファイル削除に注意
これは実際にやらかしたやつです。
EC-CUBEでは、プラグインをアンインストールすると Plugin 配下のファイルがすべて吹っ飛びます。
最終的にはファイル配布をするにしても、開発中はGit等でコード管理することをオススメします!