はじめに
保守しているECサイトで、商品検索の際に全角カタカナで検索すると、半角カタカナの商品が引っかからないという要望があった。(その逆も然り)
ググってみると、参考になる記事があったためそちらを参考にChatGPTの力を借りて修正してみる。
参考サイト:https://dodosu.hatenablog.jp/entry/2021/01/26/125325
とりあえずやってみる
とりあえず、
SELECT * FROM skill WHERE(REPLACE(REPLACE(bigName, '゙', ''), '゚', '') collate utf8_unicode_ci LIKE REPLACE(REPLACE(?0, '゙', ''), '゚', ''))
のようなことは、できるのかと聞いてみるとDoctrine
では、REPLACE
は標準では使えないよう。
カスタムDQL関数
なるものを定義すればできるとのことで指示に従ってやってみる。
1. app/Customize/Doctrine/ORM/Query/Replace.php
を作成
namespace Customize\Doctrine\ORM\Query;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
class Replace extends FunctionNode
{
public $stringPrimary;
public $stringFrom;
public $stringTo;
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // REPLACE
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA); // ,
$this->stringFrom = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA); // ,
$this->stringTo = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // )
}
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'REPLACE(%s, %s, %s)',
$this->stringPrimary->dispatch($sqlWalker),
$this->stringFrom->dispatch($sqlWalker),
$this->stringTo->dispatch($sqlWalker)
);
}
}
ほぼ、ChatGPTが提案してくれたものそのまま。
2. doctrine.yaml
にカスタムDQL関数なるものを登録
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
dql:
string_functions:
NORMALIZE: Eccube\Doctrine\ORM\Query\Normalize
REPLACE: Customize\Doctrine\ORM\Query\Replace # ←ここ
numeric_functions:
EXTRACT: Eccube\Doctrine\ORM\Query\Extract
3. 該当箇所のコードを修正
src/Eccube/Repository/ProductRepository.php
を編集する。
↑ここに、商品検索のクエリを作成しているメソッドがある。
※ 本来は、app/Customize配下に継承するなりして作る。
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( // ↓ココ
'REPLACE(REPLACE(NORMALIZE(p.name), \'゙\', \'\'), \'゚\', \'\') LIKE REPLACE(REPLACE(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 . '%');
}
}