ec-cube4 タグ 検索 – ECCUBE4の商品検索で商品タグも条件に追加するANDじゃなくてORで – Customizer(Where| Query)だと出来ない?

  • このエントリーをはてなブックマークに追加

EC-Cube4で商品検索でタグも検索条件に追加したいときどうすれば出来るのかいろいろ試してみました。

そもそも商品検索のクエリにdtb_product_tagが含まれていないので、WhereCustomizerではなくQueryCustomizerで下記のように作りました。

namespace Customize\Repository;

use Doctrine\ORM\QueryBuilder;
use Eccube\Doctrine\Query\QueryCustomizer;
use Eccube\Repository\QueryKey;

class TagSearchQueryCustomizer implements QueryCustomizer {

    protected $tag;

    public function __construct(TagRepository $tagRepository){
        $this->tag = $tagRepository;
    }

    public function getQueryKey() {
        return QueryKey::PRODUCT_SEARCH;
    }

    public function customize(QueryBuilder $builder, $params, $queryKey){
        if ($params['name']) {

            $builder->innerJoin("p.ProductTag", "pt");

            $tags = $this->tag->createQueryBuilder('t')
                    ->where("NORMALIZE( t.name ) LIKE NORMALIZE( :name )")
                    ->setParameter("name", '%' . $params['name'] . '%')
                    ->getQuery()->getResult();

            foreach ($tags as $tag) {
                $builder->orWhere("pt.Tag = :TagId");
                $builder->setParameter("TagId", $tag->getId());
            }
        }
    }
}

タグレポジトリからLIKE検索した結果のタグIDをキーに検索してくれるのですが、これだと下記のようなクエリになってしまい、表示属性や廃止属性を無視してしまいます。

FROM
    dtb_product d0_
    INNER JOIN
        dtb_product_class d1_
    ON  d0_.id = d1_.product_id
    AND d1_.discriminator_type IN('productclass')
    INNER JOIN
        dtb_product_tag d2_
    ON  d0_.id = d2_.product_id
    AND d2_.discriminator_type IN('producttag')
WHERE
    (
        (
            d0_.product_status_id = 1
        AND (
                CONVERT(d0_.name USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
            OR  CONVERT(d0_.search_word USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
            OR  EXISTS(
                    SELECT
                        d3_.id
                    FROM
                        dtb_product_class d3_
                    WHERE
                        (
                            d0_.id = d3_.product_id
                        AND CONVERT(d3_.product_code USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
                        )
                    AND d3_.discriminator_type IN('productclass')
                )
            )
        AND d1_.visible = 1
        )
    OR  d2_.tag_id = ?
    )
AND d0_.discriminator_type IN('product')

search_wordやnameをみているグルーピングの中にORでタグ条件を入れたいのですが、QueryCustomizerだと入れ込めませんでした。。

次にWhereCustomizerならsearch_wordやnameの部分に入れ込めるか試してみました。

<?php
namespace Customize\Repository;

use Eccube\Doctrine\Query\WhereClause;
use Eccube\Doctrine\Query\WhereCustomizer;
use Eccube\Repository\QueryKey;
use Eccube\Repository\TagRepository;

class TagSearchWhereCustomizer extends WhereCustomizer {

    protected $tag;

    public function __construct(TagRepository $tagRepository) {
        $this->tag = $tagRepository;
    }

    public function getQueryKey() {
        return QueryKey::PRODUCT_SEARCH;
    }

    protected function createStatements($params, $queryKey) {

        $whereCluse = [];

        if ($params['name']) {

            $tags = $this->tag->createQueryBuilder('t')
                ->where("NORMALIZE( t.name ) LIKE NORMALIZE( :name )")
                ->setParameter("name", '%' . $params['name'] . '%')
                ->getQuery()->getResult();

            // foreach ($tags as $tag) {
            //  // $whereCluse[] = WhereClause::eq("pt.Tag", ':Tag', $tag->getId());
            // }
        }

        return [
            WhereClause::isNull('p.note'),
        ];

        // return $whereCluse;
    }

}

一旦テストとして、dtb_productのノート属性を検索条件にしてcreateStatementsの戻り値がクエリのどこに追加されるのか確認してみました。

 INNER JOIN
        dtb_product_tag d2_
    ON  d0_.id = d2_.product_id
    AND d2_.discriminator_type IN('producttag')
WHERE
    (
        d0_.product_status_id = 1
    AND (
            CONVERT(d0_.name USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
        OR  CONVERT(d0_.search_word USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
        OR  EXISTS(
                SELECT
                    d3_.id
                FROM
                    dtb_product_class d3_
                WHERE
                    (
                        d0_.id = d3_.product_id
                    AND CONVERT(d3_.product_code USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
                    )
                AND d3_.discriminator_type IN('productclass')
            )
        )
    AND d1_.visible = 1
    AND d0_.note IS NULL

WhereCustomizerでも思った部分にはクエリは追加されませんでした。QueryCustomizerでinnerJoinして、WhereCustomizerで条件追加という作戦は失敗のようです。

ここに来てやっとProductRepositoryのソースをみてみると、getQueryBuilderBySearchDataの中でandWhereで追加したいグループが追加された後、customizeが呼ばれているので、自分が入れ込みたい場所にいれるにはProductRepositoryをいじる必要があるのかもしれない。。

    public function getQueryBuilderBySearchData($searchData)
    {
        $qb = $this->createQueryBuilder('p')
            ->andWhere('p.Status = 1');

        // category
        $categoryJoin = false;
        if (!empty($searchData['category_id']) && $searchData['category_id']) {
            $Categories = $searchData['category_id']->getSelfAndDescendants();
            if ($Categories) {
                $qb
                    ->innerJoin('p.ProductCategories', 'pct')
                    ->innerJoin('pct.Category', 'c')
                    ->andWhere($qb->expr()->in('pct.Category', ':Categories'))
                    ->setParameter('Categories', $Categories);
                $categoryJoin = true;
            }
        }

        // name
        if (isset($searchData['name']) && StringUtil::isNotBlank($searchData['name'])) {
            $keywords = preg_split('/[\s ]+/u', str_replace(['%', '_'], ['\\%', '\\_'], $searchData['name']), -1, PREG_SPLIT_NO_EMPTY);

            foreach ($keywords as $index => $keyword) {
                $key = sprintf('keyword%s', $index);
                $qb
                    ->andWhere(sprintf('NORMALIZE(p.name) LIKE NORMALIZE(:%s) OR 
                        NORMALIZE(p.search_word) LIKE NORMALIZE(:%s) OR 
                        EXISTS (SELECT wpc%d FROM \Eccube\Entity\ProductClass wpc%d WHERE p = wpc%d.Product AND NORMALIZE(wpc%d.code) LIKE NORMALIZE(:%s))',
                        $key, $key, $index, $index, $index, $index, $key))
                    ->setParameter($key, '%'.$keyword.'%');
            }
        }

並び替えなど。。。

 return $this->queries->customize(QueryKey::PRODUCT_SEARCH, $qb, $searchData);
    }

ECCUBEの作法というかいろいろわかってないけど、ProductRepository直接編集ってNGな気がするけど、どうするのが正しいんだろうか。。

もしProductRepositoryを修正していいならこんな感じでできたけど、proxyとかでやるのかな?

foreach ($keywords as $index => $keyword) {
                $key = sprintf('keyword%s', $index);
                $qb
                    ->andWhere(sprintf('NORMALIZE(p.name) LIKE NORMALIZE(:%s) OR 
                        NORMALIZE(p.search_word) LIKE NORMALIZE(:%s) OR 
                        EXISTS (SELECT wpc%d FROM \Eccube\Entity\ProductClass wpc%d WHERE p = wpc%d.Product AND NORMALIZE(wpc%d.code) LIKE NORMALIZE(:%s)) OR
                        EXISTS (
                            SELECT eept%d FROM \Eccube\Entity\ProductTag eept%d
                            WHERE p = eept%d.Product
                            AND eept%d.Tag IN (
                                SELECT eet%d FROM \Eccube\Entity\Tag eet%d
                                WHERE NORMALIZE( eet%d.name ) LIKE NORMALIZE( :%s )
                            )
                        )',
						$key, $key, $index, $index, $index, $index, $key, 
						$index, $index, $index, $index, $index, $index, $index, $key))
                    ->setParameter($key, '%'.$keyword.'%');

traitの例はカラム追加とかが多くて、メソッド書き換の方法を調べてみます。

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。