要望や症状
子カテゴリー一覧を表示する際に、商品が登録されていないカテゴリーも表示されてしまう問題が発生します。
現在のコード
現在のTwigテンプレートでは以下のようにカテゴリーを表示しています。
{% set Category = repository('Eccube\Entity\Category').find(11) %}
{% set cate_childs = Category.getDescendants %}
{% if cate_childs %}
{% set h = Category.hierarchy + 1 %}
<ul>
{% for cate in cate_childs %}
{% if h == cate.hierarchy %}
<li><a href="{{ url('product_list') }}?category_id={{ cate.id }}">{{ cate.name }}</a></li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
このコードでは商品の有無に関係なく、すべての子カテゴリーが表示されます。
理由や原因
現在のTwigテンプレートでは、カテゴリーの階層構造のみを参照しており、各カテゴリーに紐づく商品の存在確認を行っていないためです。
EC-CUBE標準では、カテゴリーと商品の関連はProductCategoryエンティティを介した多対多の関係で管理されており、商品の公開ステータスも考慮する必要があります。
解決策
商品が存在しないカテゴリーを非表示にする方法として、以下の2つのアプローチがあります。
方法1: リアルタイム判定(小規模サイト向け)
Twigテンプレートで商品の存在を動的に確認する方法です。
{% set Category = repository('Eccube\Entity\Category').find(11) %}
{% set cate_childs = Category.getDescendants %}
{% if cate_childs %}
{% set h = Category.hierarchy + 1 %}
<ul>
{% for cate in cate_childs %}
{% if h == cate.hierarchy %}
{% set product_count = repository('Eccube\Entity\Product').createQueryBuilder('p')
.select('COUNT(p.id)')
.innerJoin('p.ProductCategories', 'pc')
.where('pc.Category = :category')
.andWhere('p.Status = :status')
.setParameter('category', cate)
.setParameter('status', constant('Eccube\\Entity\\Master\\ProductStatus::DISPLAY_SHOW'))
.getQuery()
.getSingleScalarResult() %}
{% if product_count > 0 %}
<li><a href="{{ url('product_list') }}?category_id={{ cate.id }}">{{ cate.name }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
{% endif %}
方法2: 事前集計方式(大規模サイト向け)
商品数をカテゴリーテーブルに事前保存する方法です。まずマイグレーションを作成します。
// app/DoctrineMigrations/VersionXXXXXXXXXXXXXX.php
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class VersionXXXXXXXXXXXXXX extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE dtb_category ADD product_count INT DEFAULT 0');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE dtb_category DROP COLUMN product_count');
}
}
カテゴリーエンティティの拡張
// app/Customize/Entity/Category.php
namespace Customize\Entity;
use Doctrine\ORM\Mapping as ORM;
use Eccube\Entity\Category as BaseCategory;
if (!class_exists('\Customize\Entity\Category')) {
/**
* @ORM\Entity
* @ORM\Table(name="dtb_category")
*/
class Category extends BaseCategory
{
/**
* @var int
*
* @ORM\Column(name="product_count", type="integer", options={"default":0})
*/
private $product_count = 0;
public function getProductCount(): int
{
return $this->product_count;
}
public function setProductCount(int $product_count): self
{
$this->product_count = $product_count;
return $this;
}
}
}
商品数更新用コマンドの作成
// app/Customize/Command/UpdateCategoryProductCountCommand.php
namespace Customize\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Repository\CategoryRepository;
use Eccube\Repository\ProductRepository;
use Eccube\Entity\Master\ProductStatus;
class UpdateCategoryProductCountCommand extends Command
{
protected static $defaultName = 'app:update-category-product-count';
private $entityManager;
private $categoryRepository;
private $productRepository;
public function __construct(
EntityManagerInterface $entityManager,
CategoryRepository $categoryRepository,
ProductRepository $productRepository
) {
$this->entityManager = $entityManager;
$this->categoryRepository = $categoryRepository;
$this->productRepository = $productRepository;
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$categories = $this->categoryRepository->findAll();
foreach ($categories as $category) {
$qb = $this->productRepository->createQueryBuilder('p')
->select('COUNT(p.id)')
->innerJoin('p.ProductCategories', 'pc')
->where('pc.Category = :category')
->andWhere('p.Status = :status')
->setParameter('category', $category)
->setParameter('status', ProductStatus::DISPLAY_SHOW);
$count = $qb->getQuery()->getSingleScalarResult();
$category->setProductCount($count);
}
$this->entityManager->flush();
$output->writeln('Category product counts updated successfully.');
return Command::SUCCESS;
}
}
更新したTwigテンプレート
{% set Category = repository('Eccube\Entity\Category').find(11) %}
{% set cate_childs = Category.getDescendants %}
{% if cate_childs %}
{% set h = Category.hierarchy + 1 %}
<ul>
{% for cate in cate_childs %}
{% if h == cate.hierarchy and cate.product_count > 0 %}
<li><a href="{{ url('product_list') }}?category_id={{ cate.id }}">{{ cate.name }}</a></li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
定期実行の設定
cronを使用して定期的に商品数を更新します。
# 毎日深夜2時に実行
0 2 * * * cd /path/to/eccube && php bin/console app:update-category-product-count
注意事項
方法1はページ表示のたびにデータベースクエリが実行されるため、商品数やカテゴリー数が多い場合はパフォーマンスが大幅に低下する可能性があります。
方法2では商品の登録・削除・ステータス変更時にも商品数の更新が必要になる場合があります。
本体ファイルの直接変更は避け、Customizeディレクトリでの拡張を推奨します。