【EC-CUBE 4】リポジトリで独自の商品検索メソッドを作成する(expr)

findBy()より複雑な条件で、商品を検索するには?

Repositoryに独自のメソッドを追加し、expr()メソッドを使ってレコードを取り出してみよう!

データベースから欲しい情報(レコード)を取り出すRepositoryには、find()findBy()といったメソッドが備わっています。以下記事で紹介したとおり、特にfindBy()は使い勝手がよく、これで十分な場合も多々あります。

ただ、検索条件を複数設定したり、あいまい検索(キーワードを含むものすべてを検索)したりというような場合には、さすがにfindBy()だけでは使い勝手が悪くなってしまいます。

そこで本記事では、Repositoryを拡張(オリジナルメソッドを作成)し、より細かな条件でレコードを取り出す方法を紹介します。

開発前にデバッグモードの設定をお薦めします

デバッグモードを設定しておくと、エラーが起きたときに詳細情報が表示されるようになります。
エラー箇所を探しやすくなるので、開発前に設定しておくのをオススメします。
デバッグモードの設定方法については 以下記事 で解説しています。

カスタマイズ後は、デバッグモードの解除を忘れないように。

【動作環境】
EC CUBEのバージョン:4.3.0
サーバー:XServer

目次

まずはRepositoryを新規に作成する

EC-CUBE(Symfony)でデータベースから特定のレコードを取り出すには、取り出したいレコードが保存されているテーブルに対応したRepositoryを使います。

商品テーブルのようなデフォルトで用意されているRepositoryは「src/Eccube/Repository」下にまとめて保存されていますが(ProductRepository.phpなど)、src下のファイルには直接修正等加えない方がよいので、「app/Customize/Repository」下に以下のようなRepositoryファイルを新規作成します。

EC-CUBEのバージョンによってコードが異なります。(4.2以降ではResistoryInterfaceが使えず、ManagerRegistryを使います。)

【EC-CUBE 4.0 / 4.1】のRepositoryファイルはこちら
<?php

namespace Customize\Repository;

use Eccube\Entity\Product;
use Eccube\Repository\AbstractRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;

/**
 * CustomProductRepository
 */
class CustomProductRepository extends AbstractRepository
{
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, Product::class);
    }

}
【EC-CUBE 4.2以降】のRepositoryファイルはこちら
<?php

namespace Customize\Repository;

use Eccube\Entity\Product;
use Eccube\Repository\AbstractRepository;
use Doctrine\Persistence\ManagerRegistry as RegistryInterface;

/**
 * CustomProductRepository
 */
class CustomProductRepository extends AbstractRepository
{
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, Product::class);
    }

}

ベースとなるシンプルなRepositoryファイルです。(こちらは商品テーブルに紐づくRepositoryです。必要に応じてEntityなどを変更してください。)

ここで作成したCustomProductRepositoryクラスに、オリジナルの検索条件をもつ新しいメソッドを追加していきます。

Exprクラスを使って複雑な検索条件を設定する

Exprクラスは、QueryBuilderで条件式を作成する際に使用され、SQLの演算子をクエリに組み込むために便利なメソッド:eq(等価)、neq(非等価)、lt(小なり)、gt(大なり)などの比較演算子を、簡単にクエリに組み込むことができます。

「SQLのWHERE条件を作る仕組み」と考えればわかりやすいかも。

Exprクラスの基本的な使い方

下記コードは、引数に「商品名」を指定するとそれに合致したレコードを返してくれるメソッドです。

public function findByName($value)
{
    // QueryBuilderインスタンスの作成
    $builder = $this->createQueryBuilder('p');
    
    // where条件の指定
    return $builder
        ->where($builder->expr()->eq('p.name', '?1'))   // eqを使って比較式を作成
        ->setParameter(1, $value)                       // プレースホルダーに値をセット
        ->getQuery()                                    // クエリを取得
        ->getResult();                                  // 実行して結果を取得
}
$builder = $this->createQueryBuilder('p');

まずcreateQueryBuilder()メソッドを使用して、クエリを組み立てるためのオブジェクトQueryBuilderを作成します。'p'はProductエンティティの別名(エイリアス)で、以降のクエリの中でpを使ってフィールド名を指定できるようになります。

->where($builder->expr()->eq('p.name', '?1'))

次に、where()で検索条件を指定します。ここではexpr()メソッドを使い、Exprクラスのインスタンスを取得します。これにより比較演算子が使えるようになるので、eq(等価を意味する演算子)を使ってnameフィールドが?1と等しいという条件eq('p.name', '?1')を作成します。これにより、以下のSQLと同じ動作をすることになります。

SELECT * FROM product p WHERE p.name = ?
->setParameter(1, $value)

?1$value をセットします。(SQLでいうプリペアドステートメントと同じ仕組み)

?1 ではなく、以下のように名前付きプレースホルダー(:name) を使うこともできます。

->where($builder->expr()->eq('p.name', ':name'))
->setParameter('name', $value)

「直接値を埋め込まず、プレースホルダーを使う」 のが大事なポイントで、SQLインジェクション対策となりセキュリティ面で推奨されています。

->getQuery()->getResult();

最後に、getQuery()で最終的なクエリオブジェクトを取得し、getResult()でクエリを実行 → 結果を取得します。

慣れないとちょっとややこしいかもしれませんが、この型さえ掴めれば、あとは検索したい条件に応じたexprのメソッドを呼び出すだけです。(Exprクラスがもつ代表的なメソッドは次項の通りです。)

Exprに用意されている主なメソッド一覧

前項の->where($builder->expr()に続くメソッドを以下の通り変えることで、色々な条件で検索できるようになります。

値が等しい

eq( フィールド名, 値 )

値が異なる

neq( フィールド名, 値 )

値より小さい

lt( フィールド名, 値)

値以下

lte( フィールド名, 値)

値より大きい

gt( フィールド名, 値)

値以上

gte( フィールド名, 値)

あいまい検索

like( フィールド名, 値)

複数検索

in( フィールド名, 配列)

あいまい検索

like()メソッドを使うことで、「ある文字列の一部を含むデータ」を検索できます。

public function findByLikeName($value)
{
    $builder = $this->createQueryBuilder('p');
    
    return $builder
        ->where($builder->expr()->like('p.name', ':name'))  // LIKE検索条件
        ->setParameter('name', '%' . $value . '%')          // ワイルドカードを含める
        ->getQuery()
        ->getResult();
}

%はワイルドカードと呼ばれ、ここにはどのような文字(1字でも複数でも可)も当てはめることができます。この例では %$value% となり、メソッドの引数に指定した値が含まれるすべてのレコードを検索し、取得できます。

ワイルドカード % の例
'%りんご%'

「りんご」を含む(前後に何かがあってもOK)

'りんご%'

「りんご」で始まるもの

'%りんご'

「りんご」で終わるもの

複数検索

IN() は SQL の IN 句と同じ動作をし、指定した複数の値のどれか1つでも一致すれば検索結果に含まれます。

public function findByNames(array $values){
    $builder = $this->createQueryBuilder('p');
    
    return $builder
        ->where($builder->expr()->in('p.name', ':names'))  // `in()` で複数検索
        ->setParameter('names', $values)  // プレースホルダーを使って配列を渡す
        ->getQuery()
        ->getResult();
}

例えば $values = ['りんご', 'バナナ', 'みかん'] の場合、次のような SQL が実行されます。

SELECT * FROM product p WHERE p.name IN ('りんご', 'バナナ', 'みかん')

引数には「配列」を指定する点に注意してください。

条件を複数設定する(AND / OR)

「andWhere」または「orWhere」を用いることで、条件を複数設定できます。

andWhere(AND検索)

設定した条件すべてに当てはまるもののみを検索します(AND検索)。

where( 条件, 値 ) -> andWhere( 条件, 値 )

whereに続けてandWhereで条件を追加します。andWhereは複数追加してもOKです。

以下は、「第一引数以上のID かつ 第二引数以下のID をもつ商品レコード」を検索するメソッドです。

public function findByIDs($value1, $value2)
{
    $builder = $this->createQueryBuilder('p');
    return $builder
        ->where($builder->expr()->gte('p.id', ':id_min'))
        ->andWhere($builder->expr()->lte('p.id', ':id_max'))
        ->setParameters([
            'id_min' => $value1,
            'id_max' => $value2
        ])
        ->getQuery()
        ->getResult();
}

値を複数セットする場合はsetParameter’s’を用い、配列で値を指定します。

orWhere(OR検索)

設定した条件のいずれかに当てはまるものを検索します(OR検索)。

orWhere( 条件, 値 ) -> orWhere( 条件, 値 )

orWhereを複数設定することで、検索条件を追加できます。

以下は、「第一引数の値に合致するID または 第二引数の値に合致する名前 をもつ商品レコードを検索する」メソッドです。

public function findByIDOrName($value1, $value2)
{
    $builder = $this->createQueryBuilder('p');
    return $builder
        ->orWhere($builder->expr()->eq('p.id', ':id'))
        ->orWhere($builder->expr()->eq('p.name', ':name'))
        ->setParameters([
            'id' => $value1,
            'name' => $value2
        ])
        ->getQuery()
        ->getResult();
}

値を複数セットする場合はsetParameter’s’を用い、配列で値を指定します。

レコードの取得順を変える(orderBy)

「orderBy」を用いることで、レコードの並び順を昇順(ASC)か降順(DESC)に指定できます。

orderBy( フィールド名, 並び順 ) -> addOrderBy( フィールド名, 並び順 )

第一引数に基準となるフィールド名、第二引数に並び順(ASC / DESC)を指定します。
また、並び順を追加したい場合はaddOrderByを使います。

以下は、「商品レコードをすべて取得し、IDの大きい順(降順)で並べ替える」というメソッドです。

public function findAllSorted()
{
    $builder = $this->createQueryBuilder('p');
    return $builder
        ->orderBy('p.price', 'ASC')    // 価格の昇順
        ->addOrderBy('p.id', 'DESC')   // 同じ価格ならIDの降順
        ->getQuery()
        ->getResult();
}

レコードの取得範囲を変える(setFirst / setMax)

「setFirstResult」でレコードの取得開始位置を、「setMaxResult」で取得する最大レコード数を指定できます。

setFirstResult( 整数 ) -> setMaxResult( 整数 )

setFirstResultの第一引数に0を指定すると1番目、1を指定すると2番目のレコードから取得されます。
setMaxResultは、指定された値の数だけレコードを取得します。(足りない場合はその数まで。)

以下は、「商品レコードをすべて取得し、2番目から4番目のレコード(計3つ)を検索する」メソッドです。

public function findSome()
{
    $builder = $this->createQueryBuilder('p');
    return $builder
        ->orderBy('p.id', 'ASC')  // 取得順を指定
        ->setFirstResult(1)       // 2番目から取得(0が1番目)
        ->setMaxResults(3)        // 計3件取得
        ->getQuery()
        ->getResult();
}

取得するレコードの数が膨大になる場合、負荷を低減するためにこのメソッドを使うと良いでしょう。

まとめ

以上、データベースからレコードを取り出すための条件設定について、Exprの使い方を中心に解説しました。

ちなみに、Expr を使わずに where('p.id >= :id') のように直接条件式をテキストで記述する方法や、DQL(Doctrine Query Language)を利用する方法もあります。ただ、SQL に慣れていない方には少し難しく感じるかもしれません。まずは EC-CUBE(Symfony)独特の機能である Expr を活用 して、クエリビルダーの使い方に慣れてみてください!


よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次