はじめに
最近、Qiitaでもよく見かけるようになった Claude Codeくん。「そろそろ自分も使ってみようかな」と思い、せっかくなら普段の業務に近いものを作ってみようと思いました。
「Claude Code × EC-CUBE」 を調べてみたら、この組み合わせの記事がほとんどなかったのも後押しになりました。
「何を作るか」もClaude Codeに相談しました。シンプルに作れそうなもので、有用そうなプラグインというお題から、お気に入り登録数表示プラグイン を選択しました。
4.0系にはありますが、4.2/4.3向けは公式ストアに見当たりません。機能もシンプルなので記事にもしやすそうです。
というわけで、Claude Codeと一緒に「お気に入り登録数表示プラグイン」を作った記録を残していきます。
成果物
環境
- EC-CUBE: 4.2 / 4.3系
- PHP: 8.1以上
- Claude Code: Sonnet 4.6
完成イメージ
商品詳細・一覧ページのカートボタン上にこんな感じで表示されます。
♡ 3人がお気に入り登録
EC-CUBEのお気に入り機能を理解する
まず「EC-CUBEのお気に入り機能がどう実装されているか」をClaude Codeに調べてもらいました。
関連するファイルはこの2つです。
-
src/Eccube/Entity/CustomerFavoriteProduct.php-
dtb_customer_favorite_productテーブルに対応するエンティティ -
Customer(会員)とProduct(商品)の多対多を管理しています
-
-
src/Eccube/Repository/CustomerFavoriteProductRepository.php-
addFavorite()/isFavorite()/delete()などのメソッドを持っています
-
つまり、dtb_customer_favorite_product テーブルの product_id で件数をカウントすれば登録数が取れますね。
プラグインの設計方針
EC-CUBE 4系でテンプレートを変更するプラグインを作るには TemplateEvent を使います。
流れはこんな感じです。
-
Product/detail.twig/Product/list.twigの描画タイミングでイベントが発火する - イベントリスナーでお気に入り件数をDBから取得する
- テンプレートのソースにカウント表示を差し込む
差し込み位置の目印には、カートボタンの add-cart クラスを使います。str_replace で特定の文字列を検索する方法もありますが、インデントが一致しないとマッチしないようです。preg_replace で add-cart クラス名を目印にすることで、テンプレートをカスタマイズしている環境でも安定して動作するはずです。
ディレクトリ構成
app/Plugin/FavoriteCount/
├── composer.json
├── Event/
│ └── ProductDetailEventSubscriber.php
└── Resource/
└── template/
├── favorite_count.twig # 商品詳細用
└── favorite_count_list.twig # 商品一覧用
Entity も Migration も不要です。なかなかシンプルにいけそうです。
実装
composer.json
{
"name": "ec-cube/favorite-count",
"version": "1.0.0",
"description": "商品詳細・一覧ページにお気に入り登録数を表示するプラグイン",
"type": "eccube-plugin",
"require": {
"ec-cube/plugin-installer": "~0.0.6 || ^2.0"
},
"extra": {
"code": "FavoriteCount",
"supported-versions": ["4.2", "4.3"]
}
}
extra.code がプラグインコードです。app/Plugin/{code}/ のディレクトリ名と一致させます。
イベントサブスクライバ
簡単にポイントをまとめるとこんな感じです。
-
EventSubscriberInterfaceを実装するだけでEC-CUBEがサービスとして自動認識してくれます -
getSubscribedEvents()のキーがテンプレートパス(= イベント名)になります - 商品詳細は1件だけカウント、商品一覧は全商品を1クエリで一括取得してパフォーマンスを確保しています
-
preg_replaceでadd-cartクラスを目印にすることで、インデントや構造の違いに依存しないよう設定
// app/Plugin/FavoriteCount/Event/ProductDetailEventSubscriber.php
namespace Plugin\FavoriteCount\Event;
use Eccube\Event\TemplateEvent;
use Eccube\Repository\CustomerFavoriteProductRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProductDetailEventSubscriber implements EventSubscriberInterface
{
private CustomerFavoriteProductRepository $favoriteRepository;
public function __construct(CustomerFavoriteProductRepository $favoriteRepository)
{
$this->favoriteRepository = $favoriteRepository;
}
public static function getSubscribedEvents(): array
{
return [
'Product/detail.twig' => 'onProductDetail',
'Product/list.twig' => 'onProductList',
];
}
public function onProductDetail(TemplateEvent $event): void
{
$Product = $event->getParameter('Product');
if ($Product === null) {
return;
}
$count = (int) $this->favoriteRepository
->createQueryBuilder('cfp')
->select('COUNT(cfp.id)')
->where('cfp.Product = :Product')
->setParameter('Product', $Product)
->getQuery()
->getSingleScalarResult();
$event->setParameter('favorite_count', $count);
$source = preg_replace(
'/(<button[^>]+add-cart)/',
"{{ include('@FavoriteCount/favorite_count.twig') }}\n\$1",
$event->getSource()
);
$event->setSource($source);
}
public function onProductList(TemplateEvent $event): void
{
$Products = $event->getParameter('pagination');
if (empty($Products)) {
return;
}
// 全商品のお気に入り件数を一括取得して product_id => count の辞書に
$rows = $this->favoriteRepository
->createQueryBuilder('cfp')
->select('IDENTITY(cfp.Product) AS product_id, COUNT(cfp.id) AS cnt')
->groupBy('cfp.Product')
->getQuery()
->getArrayResult();
$favoriteCounts = [];
foreach ($rows as $row) {
$favoriteCounts[(int) $row['product_id']] = (int) $row['cnt'];
}
$event->setParameter('favorite_counts', $favoriteCounts);
$source = preg_replace(
'/(<button[^>]+add-cart)/',
"{{ include('@FavoriteCount/favorite_count_list.twig') }}\n\$1",
$event->getSource()
);
$event->setSource($source);
}
}
テンプレート
表示するHTMLはTwigファイルに切り出しています。Resource/template/ 配下に置くことで、管理画面の コンテンツ管理 → テンプレート管理 からデザインを変更できるようにしました。
細かいですが、それぞれ実装が異なります。理由を聞いたところ「一覧で同じ方式(1商品ずつクエリ)にするとN+1問題が起きるので、あえて実装を分けています。」とのこと。
これは実装する時にハマる点ですので、そこまで考慮できるのはすごいですね。
商品詳細用(favorite_count.twig)
{% if favorite_count is defined and favorite_count > 0 %}
<div style="margin:8px 0;">
<span style="display:inline-block;color:#e05c80;font-size:0.9em;padding:4px 12px;border:1px solid #e05c80;border-radius:20px;">
♡ {{ favorite_count }}人がお気に入り登録
</span>
</div>
{% endif %}
商品一覧用(favorite_count_list.twig)
{% set count = favorite_counts[Product.id] ?? 0 %}
{% if count > 0 %}
<div style="margin:4px 0;">
<span style="display:inline-block;color:#e05c80;font-size:0.85em;padding:3px 10px;border:1px solid #e05c80;border-radius:20px;">
♡ {{ count }}人がお気に入り登録
</span>
</div>
{% endif %}
プラグインのインストール・有効化
bin/console eccube:plugin:install --code=FavoriteCount
bin/console eccube:plugin:enable --code=FavoriteCount
動作確認
テストデータの投入
次に、お気に入りデータを登録するFixtureを作ります。
こちらもClaude Codeくんに作成してもらいました。
本題からは逸れるため、気になる方はapp/Plugin/FavoriteCount/DataFixtures/LoadFavoriteData.phpをご参照ください。
ブラウザで確認
商品詳細と商品一覧にて確認を行います。
手順はこんな感じです。
- 会員でログインして商品詳細ページでお気に入り登録
- 商品詳細・一覧ページのカートボタン上に
♡ 1人がお気に入り登録と表示される
まとめ
-
TemplateEventを使えば、既存テンプレートを直接書き換えずにHTMLを差し込めます -
preg_replace+ クラス名を目印にすることで、カスタマイズされたテンプレートでも安定動作します - テンプレートを
Resource/template/に分離しておくと、管理画面からデザイン変更できて便利です
感想
Claude Codeにコードを読ませることで、EC-CUBEの構造把握がかなり楽になりました。
実装より改善に注力できるのは非常に良い点です。
今回はシンプルな構成で作りましたが、管理画面に設定項目を追加したり、サイズの変更と応用の余地はまだありそうです。

