app/Customize/Repository/ProductRepository.php line 151

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of EC-CUBE
  4.  *
  5.  * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6.  *
  7.  * http://www.ec-cube.co.jp/
  8.  *
  9.  * For the full copyright and license information, please view the LICENSE
  10.  * file that was distributed with this source code.
  11.  */
  12. namespace Customize\Repository;
  13. use Doctrine\Common\Collections\ArrayCollection;
  14. use Doctrine\Persistence\ManagerRegistry as RegistryInterface;
  15. use Eccube\Repository\AbstractRepository;
  16. use Eccube\Common\EccubeConfig;
  17. use Eccube\Doctrine\Query\Queries;
  18. use Eccube\Entity\Category;
  19. use Eccube\Entity\Master\ProductListMax;
  20. use Eccube\Entity\Master\ProductListOrderBy;
  21. use Eccube\Entity\Master\ProductStatus;
  22. use Eccube\Entity\Product;
  23. use Eccube\Entity\ProductStock;
  24. use Eccube\Entity\Tag;
  25. use Eccube\Util\StringUtil;
  26. use Eccube\Repository\QueryKey;
  27. /**
  28.  * ProductRepository
  29.  *
  30.  * This class was generated by the Doctrine ORM. Add your own custom
  31.  * repository methods below.
  32.  */
  33. class ProductRepository extends AbstractRepository
  34. {
  35.     /**
  36.      * @var Queries
  37.      */
  38.     protected $queries;
  39.     /**
  40.      * @var EccubeConfig
  41.      */
  42.     protected $eccubeConfig;
  43.     public const COLUMNS = [
  44.         'product_id' => 'p.id''name' => 'p.name''product_code' => 'pc.code''stock' => 'pc.stock''status' => 'p.Status''create_date' => 'p.create_date''update_date' => 'p.update_date',
  45.         'series' => 'pfc_sort.meta_content',
  46.     ];
  47.     /**
  48.      * ProductRepository constructor.
  49.      *
  50.      * @param RegistryInterface $registry
  51.      * @param Queries $queries
  52.      * @param EccubeConfig $eccubeConfig
  53.      */
  54.     public function __construct(
  55.         RegistryInterface $registry,
  56.         Queries $queries,
  57.         EccubeConfig $eccubeConfig
  58.     ) {
  59.         parent::__construct($registryProduct::class);
  60.         $this->queries $queries;
  61.         $this->eccubeConfig $eccubeConfig;
  62.     }
  63.     /**
  64.      * Find the Product with sorted ClassCategories.
  65.      *
  66.      * @param integer $productId
  67.      *
  68.      * @return Product
  69.      */
  70.     public function findWithSortedClassCategories($productId)
  71.     {
  72.         $qb $this->createQueryBuilder('p');
  73.         $qb->addSelect(['pc''cc1''cc2''pi''pt'])
  74.             ->innerJoin('p.ProductClasses''pc')
  75.             ->leftJoin('pc.ClassCategory1''cc1')
  76.             ->leftJoin('pc.ClassCategory2''cc2')
  77.             ->leftJoin('p.ProductImage''pi')
  78.             ->leftJoin('p.ProductTag''pt')
  79.             ->where('p.id = :id')
  80.             ->andWhere('pc.visible = :visible')
  81.             ->setParameter('id'$productId)
  82.             ->setParameter('visible'true)
  83.             ->orderBy('cc1.sort_no''DESC')
  84.             ->addOrderBy('cc2.sort_no''DESC');
  85.         $product $qb
  86.             ->getQuery()
  87.             ->getSingleResult();
  88.         return $product;
  89.     }
  90.     /**
  91.      * Find the Products with sorted ClassCategories.
  92.      *
  93.      * @param array $ids Product in ids
  94.      * @param string $indexBy The index for the from.
  95.      *
  96.      * @return ArrayCollection|array
  97.      */
  98.     public function findProductsWithSortedClassCategories(array $ids$indexBy null)
  99.     {
  100.         if (count($ids) < 1) {
  101.             return [];
  102.         }
  103.         $qb $this->createQueryBuilder('p'$indexBy);
  104.         $qb->addSelect(['pc''cc1''cc2''pi''pt''tr''ps'])
  105.             ->innerJoin('p.ProductClasses''pc')
  106.             // XXX Joined 'TaxRule' and 'ProductStock' to prevent lazy loading
  107.             ->leftJoin('pc.TaxRule''tr')
  108.             ->innerJoin('pc.ProductStock''ps')
  109.             ->leftJoin('pc.ClassCategory1''cc1')
  110.             ->leftJoin('pc.ClassCategory2''cc2')
  111.             ->leftJoin('p.ProductImage''pi')
  112.             ->leftJoin('p.ProductTag''pt')
  113.             ->where($qb->expr()->in('p.id'$ids))
  114.             ->andWhere('pc.visible = :visible')
  115.             ->setParameter('visible'true)
  116.             ->orderBy('cc1.sort_no''DESC')
  117.             ->addOrderBy('cc2.sort_no''DESC');
  118.         $products $qb
  119.             ->getQuery()
  120.             ->useResultCache(true$this->eccubeConfig['eccube_result_cache_lifetime_short'])
  121.             ->getResult();
  122.         return $products;
  123.     }
  124.     /**
  125.      * get query builder.
  126.      *
  127.      * @param array{
  128.      *         category_id?:Category,
  129.      *         name?:string,
  130.      *         pageno?:string,
  131.      *         disp_number?:ProductListMax,
  132.      *         orderby?:ProductListOrderBy
  133.      *     } $searchData
  134.      *
  135.      * @return \Doctrine\ORM\QueryBuilder
  136.      */
  137.     public function getQueryBuilderBySearchData($searchData)
  138.     {
  139.         $productFieldContentClass \Plugin\ProductField\Entity\ProductFieldContent::class;
  140.         $productClassClass \Eccube\Entity\ProductClass::class;
  141.         // ===== Phase 1: カテゴリ・名前・メーカーに合致する公開商品IDを収集 =====
  142.         $preQb $this->createQueryBuilder('p_pre')
  143.             ->select('DISTINCT p_pre.id')
  144.             ->andWhere('p_pre.Status = 1');
  145.         if (!empty($searchData['category_id']) && $searchData['category_id']) {
  146.             $Categories $searchData['category_id']->getSelfAndDescendants();
  147.             if ($Categories) {
  148.                 $preQb
  149.                     ->innerJoin('p_pre.ProductCategories''pct_pre')
  150.                     ->andWhere($preQb->expr()->in('pct_pre.Category'':cat_pre'))
  151.                     ->setParameter('cat_pre'$Categories);
  152.             }
  153.         }
  154.         if (!empty($searchData['maker_id']) && $searchData['maker_id']) {
  155.             $preQb
  156.                 ->innerJoin('p_pre.Maker''pm_pre')
  157.                 ->andWhere('pm_pre.id = :maker_id_pre')
  158.                 ->setParameter('maker_id_pre'$searchData['maker_id']);
  159.         }
  160.         if (isset($searchData['name']) && StringUtil::isNotBlank($searchData['name'])) {
  161.             $keywords preg_split('/[\s ]+/u'str_replace(['%''_'], ['\\%''\\_'], $searchData['name']), -1PREG_SPLIT_NO_EMPTY);
  162.             foreach ($keywords as $index => $keyword) {
  163.                 $key sprintf('kw_pre_%s'$index);
  164.                 $preQb
  165.                     ->andWhere(sprintf('NORMALIZE(p_pre.name) LIKE NORMALIZE(:%s) OR NORMALIZE(p_pre.search_word) LIKE NORMALIZE(:%s) OR EXISTS (SELECT wpc_pre%d FROM \Eccube\Entity\ProductClass wpc_pre%d WHERE p_pre = wpc_pre%d.Product AND NORMALIZE(wpc_pre%d.code) LIKE NORMALIZE(:%s))',
  166.                         $key$key$index$index$index$index$key))
  167.                     ->setParameter($key'%'.$keyword.'%');
  168.             }
  169.         }
  170.         $preRows $preQb->getQuery()->getArrayResult();
  171.         $matchingIds array_column($preRows'id');
  172.         if (empty($matchingIds)) {
  173.             $qb $this->createQueryBuilder('p')
  174.                 ->andWhere('1 = 0');
  175.             return $this->queries->customize(QueryKey::PRODUCT_SEARCH$qb$searchData);
  176.         }
  177.         // ===== Phase 2: matchingIds の中で related_keyword グループの代表を選ぶ =====
  178.         // matchingIds 内の各商品の related_keyword を取得
  179.         $kwQb $this->getEntityManager()->createQueryBuilder();
  180.         $kwQb
  181.             ->select('pfc_m.product_id, pfc_m.meta_content AS keyword')
  182.             ->from($productFieldContentClass'pfc_m')
  183.             ->where('pfc_m.meta_key = :mk_m')
  184.             ->andWhere($kwQb->expr()->in('pfc_m.product_id'':matching_ids'))
  185.             ->setParameter('mk_m''related_keyword')
  186.             ->setParameter('matching_ids'$matchingIds);
  187.         $kwRows $kwQb->getQuery()->getArrayResult();
  188.         // product_id => keyword のマップ
  189.         $productKeywordMap = [];
  190.         foreach ($kwRows as $row) {
  191.             $productKeywordMap[$row['product_id']] = $row['keyword'];
  192.         }
  193.         $idsWithKeyword array_keys($productKeywordMap);
  194.         $idsWithoutKeyword array_values(array_diff($matchingIds$idsWithKeyword));
  195.         // keyword => [product_ids] のグループ
  196.         $keywordGroups = [];
  197.         foreach ($productKeywordMap as $pid => $kw) {
  198.             $keywordGroups[$kw][] = $pid;
  199.         }
  200.         // 各グループから最安値・最小IDの代表を選ぶ
  201.         $representativeIds = [];
  202.         if (!empty($idsWithKeyword)) {
  203.             $priceQb $this->getEntityManager()->createQueryBuilder();
  204.             $priceQb
  205.                 ->select('IDENTITY(pc_p.Product) as product_id, MIN(pc_p.price02) as min_price')
  206.                 ->from($productClassClass'pc_p')
  207.                 ->where($priceQb->expr()->in('pc_p.Product'':ids_kw'))
  208.                 ->andWhere('pc_p.visible = true')
  209.                 ->groupBy('pc_p.Product')
  210.                 ->setParameter('ids_kw'$idsWithKeyword);
  211.             $priceRows $priceQb->getQuery()->getArrayResult();
  212.             $productPriceMap = [];
  213.             foreach ($priceRows as $row) {
  214.                 $productPriceMap[$row['product_id']] = (int) $row['min_price'];
  215.             }
  216.             foreach ($keywordGroups as $kw => $pids) {
  217.                 $minPrice PHP_INT_MAX;
  218.                 $repId null;
  219.                 foreach ($pids as $pid) {
  220.                     $price $productPriceMap[$pid] ?? PHP_INT_MAX;
  221.                     if ($price $minPrice || ($price === $minPrice && ($repId === null || $pid $repId))) {
  222.                         $minPrice $price;
  223.                         $repId $pid;
  224.                     }
  225.                 }
  226.                 if ($repId !== null) {
  227.                     $representativeIds[] = $repId;
  228.                 }
  229.             }
  230.         }
  231.         // グループ代表 + グループ未所属 = 表示対象
  232.         $visibleIds array_merge($representativeIds$idsWithoutKeyword);
  233.         if (empty($visibleIds)) {
  234.             $qb $this->createQueryBuilder('p')
  235.                 ->andWhere('1 = 0');
  236.             return $this->queries->customize(QueryKey::PRODUCT_SEARCH$qb$searchData);
  237.         }
  238.         // ===== Phase 3: 最終クエリを構築(visibleIds + 並び順 + 価格帯) =====
  239.         $qb $this->createQueryBuilder('p')
  240.             ->andWhere('p.Status = 1');
  241.         $qb->andWhere($qb->expr()->in('p.id'':visible_ids'))
  242.            ->setParameter('visible_ids'$visibleIds);
  243.         // category(並び順のJOIN用)
  244.         $categoryJoin false;
  245.         if (!empty($searchData['category_id']) && $searchData['category_id']) {
  246.             $Categories $searchData['category_id']->getSelfAndDescendants();
  247.             if ($Categories) {
  248.                 $qb
  249.                     ->innerJoin('p.ProductCategories''pct')
  250.                     ->innerJoin('pct.Category''c')
  251.                     ->andWhere($qb->expr()->in('pct.Category'':Categories'))
  252.                     ->setParameter('Categories'$Categories);
  253.                 $categoryJoin true;
  254.             }
  255.         }
  256.         // maker
  257.         if (!empty($searchData['maker_id']) && $searchData['maker_id']) {
  258.             $qb
  259.                 ->innerJoin('p.Maker''pm')
  260.                 ->andWhere('pm.id = :maker_id')
  261.                 ->setParameter('maker_id'$searchData['maker_id']);
  262.         }
  263.         // name
  264.         if (isset($searchData['name']) && StringUtil::isNotBlank($searchData['name'])) {
  265.             $keywords preg_split('/[\s ]+/u'str_replace(['%''_'], ['\\%''\\_'], $searchData['name']), -1PREG_SPLIT_NO_EMPTY);
  266.             foreach ($keywords as $index => $keyword) {
  267.                 $key sprintf('keyword%s'$index);
  268.                 $qb
  269.                     ->andWhere(sprintf('NORMALIZE(p.name) LIKE NORMALIZE(:%s) OR
  270.                         NORMALIZE(p.search_word) LIKE NORMALIZE(:%s) OR
  271.                         EXISTS (SELECT wpc%d FROM \Eccube\Entity\ProductClass wpc%d WHERE p = wpc%d.Product AND NORMALIZE(wpc%d.code) LIKE NORMALIZE(:%s))',
  272.                         $key$key$index$index$index$index$key))
  273.                     ->setParameter($key'%'.$keyword.'%');
  274.             }
  275.         }
  276.         // 価格低い順
  277.         $config $this->eccubeConfig;
  278.         if (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_price_lower']) {
  279.             // @see http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html
  280.             $qb->addSelect('MIN(pc.price02) as HIDDEN price02_min');
  281.             $qb->innerJoin('p.ProductClasses''pc');
  282.             $qb->andWhere('pc.visible = true');
  283.             $qb->groupBy('p.id');
  284.             $qb->orderBy('price02_min''ASC');
  285.             $qb->addOrderBy('p.id''DESC');
  286.         // 価格高い順
  287.         } elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_price_higher']) {
  288.             $qb->addSelect('MAX(pc.price02) as HIDDEN price02_max');
  289.             $qb->innerJoin('p.ProductClasses''pc');
  290.             $qb->andWhere('pc.visible = true');
  291.             $qb->groupBy('p.id');
  292.             $qb->orderBy('price02_max''DESC');
  293.             $qb->addOrderBy('p.id''DESC');
  294.         // 新着順
  295.         } elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_newer']) {
  296.             // 在庫切れ商品非表示の設定が有効時対応
  297.             // @see https://github.com/EC-CUBE/ec-cube/issues/1998
  298.             if ($this->getEntityManager()->getFilters()->isEnabled('option_nostock_hidden') == true) {
  299.                 $qb->innerJoin('p.ProductClasses''pc');
  300.                 $qb->andWhere('pc.visible = true');
  301.             }
  302.             $qb->orderBy('p.create_date''DESC');
  303.             $qb->addOrderBy('p.id''DESC');
  304.         } else {
  305.             if ($categoryJoin === false) {
  306.                 $qb
  307.                     ->leftJoin('p.ProductCategories''pct')
  308.                     ->leftJoin('pct.Category''c');
  309.             }
  310.             $qb
  311.                 ->addOrderBy('p.id''ASC');
  312.         }
  313.         // 価格帯の絞り込み
  314.         // price_min / price_max はコントローラから $searchData に追加される
  315.         // pc(ProductClasses)がまだJOINされていない場合はここでJOINする
  316.         $pcJoined false;
  317.         foreach ($qb->getDQLPart('join') as $joins) {
  318.             foreach ($joins as $join) {
  319.                 if ($join->getAlias() === 'pc') {
  320.                     $pcJoined true;
  321.                     break 2;
  322.                 }
  323.             }
  324.         }
  325.         if (!empty($searchData['price_min']) || !empty($searchData['price_max'])) {
  326.             if (!$pcJoined) {
  327.                 $qb->innerJoin('p.ProductClasses''pc')
  328.                    ->andWhere('pc.visible = true');
  329.             }
  330.             // price02 は税抜価格カラム(DBの実カラム名)
  331.             // 税込換算: price02 * (1 + tax_rate/100)
  332.             // ただし税率はTaxRuleに依存するため、サイトの消費税率(10%)を固定で使用
  333.             // 税込入力値 → 税抜換算して比較(÷1.1)
  334.             if (!empty($searchData['price_min'])) {
  335.                 $priceMinExTax = (int) round((int) $searchData['price_min'] / 1.1);
  336.                 $qb->andWhere('pc.price02 >= :price_min')
  337.                    ->setParameter('price_min'$priceMinExTax);
  338.             }
  339.             if (!empty($searchData['price_max'])) {
  340.                 $priceMaxExTax = (int) round((int) $searchData['price_max'] / 1.1);
  341.                 $qb->andWhere('pc.price02 <= :price_max')
  342.                    ->setParameter('price_max'$priceMaxExTax);
  343.             }
  344.             // GROUP BYが未設定の場合は追加
  345.             $groupBy $qb->getDQLPart('groupBy');
  346.             if (empty($groupBy)) {
  347.                 $qb->groupBy('p.id');
  348.             }
  349.         }
  350.         return $this->queries->customize(QueryKey::PRODUCT_SEARCH$qb$searchData);
  351.     }
  352.     /**
  353.      * get query builder.
  354.      *
  355.      * @param array{
  356.      *         id?:string|int|null,
  357.      *         category_id?:Category,
  358.      *         status?:ProductStatus[],
  359.      *         link_status?:ProductStatus[],
  360.      *         stock_status?:int,
  361.      *         stock?:ProductStock::IN_STOCK|ProductStock::OUT_OF_STOCK,
  362.      *         tag_id?:Tag,
  363.      *         create_datetime_start?:\DateTime,
  364.      *         create_datetime_end?:\DateTime,
  365.      *         create_date_start?:\DateTime,
  366.      *         create_date_end?:\DateTime,
  367.      *         update_datetime_start?:\DateTime,
  368.      *         update_datetime_end?:\DateTime,
  369.      *         update_date_start?:\DateTime,
  370.      *         update_date_end?:\DateTime,
  371.      *         sortkey?:string,
  372.      *         sorttype?:string
  373.      *     } $searchData
  374.      *
  375.      * @return \Doctrine\ORM\QueryBuilder
  376.      */
  377.     public function getQueryBuilderBySearchDataForAdmin($searchData)
  378.     {
  379.         $qb $this->createQueryBuilder('p')
  380.             ->addSelect('pc''pi''tr''ps')
  381.             ->innerJoin('p.ProductClasses''pc')
  382.             ->leftJoin('p.ProductImage''pi')
  383.             ->leftJoin('pc.TaxRule''tr')
  384.             ->leftJoin('pc.ProductStock''ps')
  385.             ->andWhere('pc.visible = :visible')
  386.             ->setParameter('visible'true);
  387.         // id
  388.         if (isset($searchData['id']) && StringUtil::isNotBlank($searchData['id'])) {
  389.             $id preg_match('/^\d{0,10}$/'$searchData['id']) ? $searchData['id'] : null;
  390.             if ($id && $id '2147483647' && $this->isPostgreSQL()) {
  391.                 $id null;
  392.             }
  393.             $qb
  394.                 ->andWhere('p.id = :id OR p.name LIKE :likeid OR p.note LIKE :likeid OR pc.code LIKE :likeid')
  395.                 ->setParameter('id'$id)
  396.                 ->setParameter('likeid''%'.str_replace(['%''_'], ['\\%''\\_'], $searchData['id']).'%');
  397.         }
  398.         // code
  399.         /*
  400.         if (!empty($searchData['code']) && $searchData['code']) {
  401.             $qb
  402.                 ->innerJoin('p.ProductClasses', 'pc')
  403.                 ->andWhere('pc.code LIKE :code')
  404.                 ->setParameter('code', '%' . $searchData['code'] . '%');
  405.         }
  406.         // name
  407.         if (!empty($searchData['name']) && $searchData['name']) {
  408.             $keywords = preg_split('/[\s ]+/u', $searchData['name'], -1, PREG_SPLIT_NO_EMPTY);
  409.             foreach ($keywords as $keyword) {
  410.                 $qb
  411.                     ->andWhere('p.name LIKE :name')
  412.                     ->setParameter('name', '%' . $keyword . '%');
  413.             }
  414.         }
  415.        */
  416.         // category
  417.         if (!empty($searchData['category_id']) && $searchData['category_id']) {
  418.             $Categories $searchData['category_id']->getSelfAndDescendants();
  419.             if ($Categories) {
  420.                 $qb
  421.                     ->innerJoin('p.ProductCategories''pct')
  422.                     ->innerJoin('pct.Category''c')
  423.                     ->andWhere($qb->expr()->in('pct.Category'':Categories'))
  424.                     ->setParameter('Categories'$Categories);
  425.             }
  426.         }
  427.         // status
  428.         if (!empty($searchData['status']) && $searchData['status']) {
  429.             $qb
  430.                 ->andWhere($qb->expr()->in('p.Status'':Status'))
  431.                 ->setParameter('Status'$searchData['status']);
  432.         }
  433.         // link_status
  434.         if (isset($searchData['link_status']) && !empty($searchData['link_status'])) {
  435.             $qb
  436.                 ->andWhere($qb->expr()->in('p.Status'':Status'))
  437.                 ->setParameter('Status'$searchData['link_status']);
  438.         }
  439.         // stock status
  440.         if (isset($searchData['stock_status'])) {
  441.             $qb
  442.                 ->andWhere('pc.stock_unlimited = :StockUnlimited AND pc.stock = 0')
  443.                 ->setParameter('StockUnlimited'$searchData['stock_status']);
  444.         }
  445.         // stock status
  446.         if (isset($searchData['stock']) && !empty($searchData['stock'])) {
  447.             switch ($searchData['stock']) {
  448.                 case [ProductStock::IN_STOCK]:
  449.                     $qb->andWhere('pc.stock_unlimited = true OR pc.stock > 0');
  450.                     break;
  451.                 case [ProductStock::OUT_OF_STOCK]:
  452.                     $qb->andWhere('pc.stock_unlimited = false AND pc.stock <= 0');
  453.                     break;
  454.                 default:
  455.                     // 共に選択された場合は全権該当するので検索条件に含めない
  456.             }
  457.         }
  458.         // tag
  459.         if (!empty($searchData['tag_id']) && $searchData['tag_id']) {
  460.             $qb
  461.                 ->innerJoin('p.ProductTag''pt')
  462.                 ->andWhere('pt.Tag = :tag_id')
  463.                 ->setParameter('tag_id'$searchData['tag_id']);
  464.         }
  465.         // maker
  466.         if (!empty($searchData['maker_id']) && $searchData['maker_id']) {
  467.             $qb
  468.                 ->innerJoin('p.Maker''pm')
  469.                 ->andWhere('pm.id = :maker_id')
  470.                 ->setParameter('maker_id'$searchData['maker_id']);
  471.         }
  472.         // group (plg_product_field_content meta_key='related_keyword' partial match)
  473.         if (isset($searchData['group_keyword']) && StringUtil::isNotBlank($searchData['group_keyword'])) {
  474.             $qb
  475.                 ->andWhere('EXISTS (SELECT pfc FROM \Plugin\ProductField\Entity\ProductFieldContent pfc '
  476.                     .'WHERE pfc.product_id = p.id '
  477.                     .'AND pfc.meta_key = :group_meta_key '
  478.                     .'AND pfc.meta_content LIKE :group_keyword)')
  479.                 ->setParameter('group_meta_key''related_keyword')
  480.                 ->setParameter('group_keyword''%'.str_replace(['%''_'], ['\\%''\\_'], $searchData['group_keyword']).'%');
  481.         }
  482.         // crate_date
  483.         if (!empty($searchData['create_datetime_start']) && $searchData['create_datetime_start']) {
  484.             $date $searchData['create_datetime_start'];
  485.             $qb
  486.                 ->andWhere('p.create_date >= :create_date_start')
  487.                 ->setParameter('create_date_start'$date);
  488.         } elseif (!empty($searchData['create_date_start']) && $searchData['create_date_start']) {
  489.             $date $searchData['create_date_start'];
  490.             $qb
  491.                 ->andWhere('p.create_date >= :create_date_start')
  492.                 ->setParameter('create_date_start'$date);
  493.         }
  494.         if (!empty($searchData['create_datetime_end']) && $searchData['create_datetime_end']) {
  495.             $date $searchData['create_datetime_end'];
  496.             $qb
  497.                 ->andWhere('p.create_date < :create_date_end')
  498.                 ->setParameter('create_date_end'$date);
  499.         } elseif (!empty($searchData['create_date_end']) && $searchData['create_date_end']) {
  500.             $date = clone $searchData['create_date_end'];
  501.             $date $date
  502.                 ->modify('+1 days');
  503.             $qb
  504.                 ->andWhere('p.create_date < :create_date_end')
  505.                 ->setParameter('create_date_end'$date);
  506.         }
  507.         // update_date
  508.         if (!empty($searchData['update_datetime_start']) && $searchData['update_datetime_start']) {
  509.             $date $searchData['update_datetime_start'];
  510.             $qb
  511.                 ->andWhere('p.update_date >= :update_date_start')
  512.                 ->setParameter('update_date_start'$date);
  513.         } elseif (!empty($searchData['update_date_start']) && $searchData['update_date_start']) {
  514.             $date $searchData['update_date_start'];
  515.             $qb
  516.                 ->andWhere('p.update_date >= :update_date_start')
  517.                 ->setParameter('update_date_start'$date);
  518.         }
  519.         if (!empty($searchData['update_datetime_end']) && $searchData['update_datetime_end']) {
  520.             $date $searchData['update_datetime_end'];
  521.             $qb
  522.                 ->andWhere('p.update_date < :update_date_end')
  523.                 ->setParameter('update_date_end'$date);
  524.         } elseif (!empty($searchData['update_date_end']) && $searchData['update_date_end']) {
  525.             $date = clone $searchData['update_date_end'];
  526.             $date $date
  527.                 ->modify('+1 days');
  528.             $qb
  529.                 ->andWhere('p.update_date < :update_date_end')
  530.                 ->setParameter('update_date_end'$date);
  531.         }
  532.         // Order By
  533.         if (isset($searchData['sortkey']) && !empty($searchData['sortkey'])) {
  534.             $sortOrder = (isset($searchData['sorttype']) && $searchData['sorttype'] == 'a') ? 'ASC' 'DESC';
  535.             // Series sort needs a LEFT JOIN to plg_product_field_content
  536.             if ($searchData['sortkey'] === 'series') {
  537.                 $qb->leftJoin(
  538.                     \Plugin\ProductField\Entity\ProductFieldContent::class,
  539.                     'pfc_sort',
  540.                     'WITH',
  541.                     'pfc_sort.product_id = p.id AND pfc_sort.meta_key = :pfc_sort_key'
  542.                 )->setParameter('pfc_sort_key''related_keyword');
  543.             }
  544.             $qb->orderBy(self::COLUMNS[$searchData['sortkey']], $sortOrder);
  545.             $qb->addOrderBy('p.update_date''DESC');
  546.             $qb->addOrderBy('p.id''DESC');
  547.         } else {
  548.             $qb->orderBy('p.update_date''DESC');
  549.             $qb->addOrderBy('p.id''DESC');
  550.         }
  551.         return $this->queries->customize(QueryKey::PRODUCT_SEARCH_ADMIN$qb$searchData);
  552.     }
  553.     public function findByLikeName($value$categories)
  554.     {
  555.         $builder $this->createQueryBuilder('p');
  556.         
  557.         return $builder
  558.             ->innerJoin('p.ProductCategories''pct')
  559.             ->innerJoin('pct.Category''c')
  560.             ->where($builder->expr()->like('p.name'':name'))  // LIKE検索条件
  561.             ->andWhere($builder->expr()->in('pct.Category'':Categories'))
  562.             ->setParameter('name''%' $value '%')          // ワイルドカードを含める
  563.             ->setParameter('Categories'$categories)
  564.             ->getQuery()
  565.             ->getResult();
  566.     }
  567. }