From 81b0ce4de264970b7d620b2c11d7b84f81eeeff2 Mon Sep 17 00:00:00 2001 From: kiy0taka Date: Wed, 19 Apr 2017 13:54:28 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=83=9D=E3=82=B8=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=81=A7=E7=B5=84=E3=81=BF=E7=AB=8B=E3=81=A6=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8BQueryBuilder=E3=82=92=E3=82=AB=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=82=BA=E3=81=99=E3=82=8B=E6=A9=9F=E6=A7=8B?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/AdminProductListCustomizer.php | 28 ++ src/Eccube/Annotation/QueryExtension.php | 18 ++ src/Eccube/Doctrine/Query/JoinClause.php | 188 +++++++++++ src/Eccube/Doctrine/Query/JoinCustomizer.php | 60 ++++ src/Eccube/Doctrine/Query/OrderByClause.php | 64 ++++ .../Doctrine/Query/OrderByCustomizer.php | 62 ++++ src/Eccube/Doctrine/Query/Queries.php | 42 +++ src/Eccube/Doctrine/Query/QueryCustomizer.php | 45 +++ src/Eccube/Doctrine/Query/WhereClause.php | 305 ++++++++++++++++++ src/Eccube/Doctrine/Query/WhereCustomizer.php | 30 ++ src/Eccube/Repository/CustomerRepository.php | 5 +- src/Eccube/Repository/OrderRepository.php | 10 +- src/Eccube/Repository/ProductRepository.php | 10 +- .../ServiceProvider/EccubeServiceProvider.php | 9 +- .../Tests/Doctrine/Query/JoinClauseTest.php | 78 +++++ .../Doctrine/Query/JoinCustomizerTest.php | 72 +++++ .../Doctrine/Query/OrderByCustomizerTest.php | 93 ++++++ .../Tests/Doctrine/Query/QueriesTest.php | 75 +++++ .../Tests/Doctrine/Query/WhereClauseTest.php | 178 ++++++++++ .../Doctrine/Query/WhereCustomizerTest.php | 76 +++++ 20 files changed, 1440 insertions(+), 8 deletions(-) create mode 100644 app/Acme/Entity/AdminProductListCustomizer.php create mode 100644 src/Eccube/Annotation/QueryExtension.php create mode 100644 src/Eccube/Doctrine/Query/JoinClause.php create mode 100644 src/Eccube/Doctrine/Query/JoinCustomizer.php create mode 100644 src/Eccube/Doctrine/Query/OrderByClause.php create mode 100644 src/Eccube/Doctrine/Query/OrderByCustomizer.php create mode 100644 src/Eccube/Doctrine/Query/Queries.php create mode 100644 src/Eccube/Doctrine/Query/QueryCustomizer.php create mode 100644 src/Eccube/Doctrine/Query/WhereClause.php create mode 100644 src/Eccube/Doctrine/Query/WhereCustomizer.php create mode 100644 tests/Eccube/Tests/Doctrine/Query/JoinClauseTest.php create mode 100644 tests/Eccube/Tests/Doctrine/Query/JoinCustomizerTest.php create mode 100644 tests/Eccube/Tests/Doctrine/Query/OrderByCustomizerTest.php create mode 100644 tests/Eccube/Tests/Doctrine/Query/QueriesTest.php create mode 100644 tests/Eccube/Tests/Doctrine/Query/WhereClauseTest.php create mode 100644 tests/Eccube/Tests/Doctrine/Query/WhereCustomizerTest.php diff --git a/app/Acme/Entity/AdminProductListCustomizer.php b/app/Acme/Entity/AdminProductListCustomizer.php new file mode 100644 index 00000000000..2a00ebc9198 --- /dev/null +++ b/app/Acme/Entity/AdminProductListCustomizer.php @@ -0,0 +1,28 @@ +leftJoin = $leftJoin; + $this->join = $join; + $this->alias = $alias; + $this->conditionType = $conditionType; + $this->condition = $condition; + $this->indexBy = $indexBy; + $this->whereCustomizer = new JoinClauseWhereCustomizer(); + $this->orderByCustomizer = new JoinClauseOrderByCustomizer(); + } + + /** + * INNER JOIN用のファクトリメソッド。 + * + * @see QueryBuilder::innerJoin() + * @param $join + * @param $alias + * @param $conditionType + * @param $condition + * @param $indexBy + * @return JoinClause + */ + public static function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) + { + return new JoinClause(false, $join, $alias, $conditionType, $condition, $indexBy); + } + + /** + * LEFT JOIN用のファクトリメソッド。 + * + * @see QueryBuilder::leftJoin() + * @param $join + * @param $alias + * @param $conditionType + * @param $condition + * @param $indexBy + * @return JoinClause + */ + public static function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null) + { + return new JoinClause(true, $join, $alias, $conditionType, $condition, $indexBy); + } + + /** + * WHERE句を追加します。 + * + * @param WhereClause $whereClause + * @return $this + */ + public function addWhere(WhereClause $whereClause) + { + $this->whereCustomizer->add($whereClause); + return $this; + } + + /** + * ORDER BY句を追加します。 + * @param OrderByClause $orderByClause + * @return $this + */ + public function addOrderBy(OrderByClause $orderByClause) + { + $this->orderByCustomizer->add($orderByClause); + return $this; + } + + public function build(QueryBuilder $builder) { + if ($this->leftJoin) { + $builder->leftJoin($this->join, $this->alias, $this->conditionType, $this->condition, $this->indexBy); + } else { + $builder->innerJoin($this->join, $this->alias, $this->conditionType, $this->condition, $this->indexBy); + } + $this->whereCustomizer->customize($builder, null, ''); + $this->orderByCustomizer->customize($builder, null, ''); + } +} + +class JoinClauseWhereCustomizer extends WhereCustomizer +{ + /** + * @var WhereClause[] + */ + private $whereClauses = []; + + public function add(WhereClause $whereClause) + { + $this->whereClauses[] = $whereClause; + } + + /** + * @param array $params + * @param $queryKey + * @return WhereClause[] + */ + protected function createStatements($params, $queryKey) + { + return $this->whereClauses; + } +} + +class JoinClauseOrderByCustomizer extends OrderByCustomizer +{ + /** + * @var OrderByClause[] + */ + private $orderByClauses = []; + + public function add(OrderByClause $orderByClause) + { + $this->orderByClauses[] = $orderByClause; + } + + /** + * @param array $params + * @param $queryKey + * @return OrderByClause[] + */ + protected function createStatements($params, $queryKey) + { + return $this->orderByClauses; + } +} \ No newline at end of file diff --git a/src/Eccube/Doctrine/Query/JoinCustomizer.php b/src/Eccube/Doctrine/Query/JoinCustomizer.php new file mode 100644 index 00000000000..ad397154883 --- /dev/null +++ b/src/Eccube/Doctrine/Query/JoinCustomizer.php @@ -0,0 +1,60 @@ +createStatements($params, $queryKey) as $joinClause) { + $joinClause->build($builder); + } + } + + /** + * 追加するJOIN句を組み立てます。 + * このメソッドの戻り値が、元のクエリのJOIN句に追加されます。 + * + * @param array $params + * @param $queryKey + * @return JoinClause[] + */ + public abstract function createStatements($params, $queryKey); + +} \ No newline at end of file diff --git a/src/Eccube/Doctrine/Query/OrderByClause.php b/src/Eccube/Doctrine/Query/OrderByClause.php new file mode 100644 index 00000000000..8fbb68d8265 --- /dev/null +++ b/src/Eccube/Doctrine/Query/OrderByClause.php @@ -0,0 +1,64 @@ +sort = $sort; + $this->order = $order; + } + + /** + * @return string + */ + public function getSort() + { + return $this->sort; + } + + /** + * @return string + */ + public function getOrder() + { + return $this->order; + } +} \ No newline at end of file diff --git a/src/Eccube/Doctrine/Query/OrderByCustomizer.php b/src/Eccube/Doctrine/Query/OrderByCustomizer.php new file mode 100644 index 00000000000..c93f61f714a --- /dev/null +++ b/src/Eccube/Doctrine/Query/OrderByCustomizer.php @@ -0,0 +1,62 @@ +createStatements($params, $queryKey) as $index=>$orderByClause) { + if ($index === 0) { + $builder->orderBy($orderByClause->getSort(), $orderByClause->getOrder()); + } else { + $builder->addOrderBy($orderByClause->getSort(), $orderByClause->getOrder()); + } + } + } + + /** + * 変更するORDER BY句を組み立てます。 + * このメソッドの戻り値で、元のクエリのORDER BY句が上書きされます。 + * + * @param array $params + * @param $queryKey + * @return OrderByClause[] + */ + protected abstract function createStatements($params, $queryKey); +} \ No newline at end of file diff --git a/src/Eccube/Doctrine/Query/Queries.php b/src/Eccube/Doctrine/Query/Queries.php new file mode 100644 index 00000000000..b8b0e456ced --- /dev/null +++ b/src/Eccube/Doctrine/Query/Queries.php @@ -0,0 +1,42 @@ +getClassAnnotation($rc, QueryExtension::class); + if (!$anno) { + throw new InvalidArgumentException(get_class($customizer).' doesn\'t have any '.QueryExtension::class.' annotation.'); + } + foreach ($anno->value as $queryKey) { + $this->customizers[$queryKey][] = $customizer; + } + } + + public function customize($queryKey, QueryBuilder $builder, $params) + { + if (isset($this->customizers[$queryKey])) { + /* @var QueryCustomizer $customizer */ + foreach ($this->customizers[$queryKey] as $customizer) { + $customizer->customize($builder, $params, $queryKey); + } + } + return $builder; + } +} \ No newline at end of file diff --git a/src/Eccube/Doctrine/Query/QueryCustomizer.php b/src/Eccube/Doctrine/Query/QueryCustomizer.php new file mode 100644 index 00000000000..056fa8220aa --- /dev/null +++ b/src/Eccube/Doctrine/Query/QueryCustomizer.php @@ -0,0 +1,45 @@ +expr = $expr; + $this->params = $params; + } + + private static function newWhereClause($expr, $x, $y) + { + if ($y) { + if (is_array($y)) { + return new WhereClause($expr, $y); + } else { + return new WhereClause($expr, [$x => $y]); + } + } + return new WhereClause($expr); + } + + /** + * =条件式のファクトリメソッド。 + * + * Example: + * WhereClause::eq('name', ':Name', 'hoge') + * WhereClause::eq('name', ':Name', ['Name' => 'hoge']) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function eq($x, $y, $param) + { + return self::newWhereClause(self::expr()->eq($x, $y), $y, $param); + } + + /** + * <>条件式のファクトリメソッド。 + * + * Example: + * WhereClause::neq('name', ':Name', 'hoge') + * WhereClause::neq('name', ':Name', ['Name' => 'hoge']) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function neq($x, $y, $param) + { + return self::newWhereClause(self::expr()->neq($x, $y), $y, $param); + } + + /** + * IS NULL条件式のファクトリメソッド。 + * + * Example: + * WhereClause::isNull('name') + * + * @param $x + * @return WhereClause + */ + public static function isNull($x) + { + return new WhereClause(self::expr()->isNull($x)); + } + + /** + * IS NOT NULL条件式のファクトリメソッド。 + * + * Example: + * WhereClause::isNotNull('name') + * + * @param $x + * @return WhereClause + */ + public static function isNotNull($x) + { + return new WhereClause(self::expr()->isNotNull($x)); + } + + /** + * LIKE演算子のファクトリメソッド。 + * + * Example: + * WhereClause::like('name', ':Name', '%hoge') + * WhereClause::like('name', ':Name', ['Name' => '%hoge']) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function like($x, $y, $param) + { + return self::newWhereClause(self::expr()->like($x, $y), $y, $param); + } + + /** + * NOT LIKE演算子のファクトリメソッド。 + * + * Example: + * WhereClause::notLike('name', ':Name', '%hoge') + * WhereClause::notLike('name', ':Name', ['Name' => '%hoge']) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function notLike($x, $y, $param) + { + return self::newWhereClause(self::expr()->notLike($x, $y), $y, $param); + } + + /** + * IN演算子のファクトリメソッド。 + * + * Example: + * WhereClause::in('name', ':Names', ['foo', 'bar']) + * WhereClause::in('name', ':Names', ['Names' => ['foo', 'bar']]) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function in($x, $y, $param) + { + return new WhereClause(self::expr()->in($x, $y), self::isMap($param) ? $param : [$y => $param]); + } + + private static function isMap($arrayOrMap) + { + return array_values($arrayOrMap) !== $arrayOrMap; + } + + /** + * NOT IN演算子のファクトリメソッド。 + * + * Example: + * WhereClause::notIn('name', ':Names', ['foo', 'bar']) + * WhereClause::notIn('name', ':Names', ['Names' => ['foo', 'bar']]) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function notIn($x, $y, $param) + { + return new WhereClause(self::expr()->notIn($x, $y), self::isMap($param) ? $param : [$y => $param]); + } + + /** + * BETWEEN演算子のファクトリメソッド。 + * + * Example: + * WhereClause::between('price', ':PriceMin', ':PriceMax', [1000, 2000]) + * WhereClause::between('price', ':PriceMin', ':PriceMax', ['PriceMin' => 1000, 'PriceMax' => 2000]) + * + * @param $var + * @param $x + * @param $y + * @param $params + * @return WhereClause + */ + public static function between($var, $x, $y, $params) + { + return new WhereClause(self::expr()->between($var, $x, $y), self::isMap($params) ? $params : [$x => $params[0], $y => $params[1]]); + } + + /** + * >演算子のファクトリメソッド。 + * + * Example: + * WhereClause::gt('price', ':Price', 1000) + * WhereClause::gt('price', ':Price', ['Price' => 1000]) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function gt($x, $y, $param) + { + return self::newWhereClause(self::expr()->gt($x, $y), $y, $param); + } + + /** + * >=演算子のファクトリメソッド。 + * + * Example: + * WhereClause::gte('price', ':Price', 1000) + * WhereClause::gte('price', ':Price', ['Price' => 1000]) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function gte($x, $y, $param) + { + return self::newWhereClause(self::expr()->gte($x, $y), $y, $param); + } + + /** + * <演算子のファクトリメソッド。 + * + * Example: + * WhereClause::lt('price', ':Price', 1000) + * WhereClause::lt('price', ':Price', ['Price' => 1000]) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function lt($x, $y, $param) + { + return self::newWhereClause(self::expr()->lt($x, $y), $y, $param); + } + + /** + * <=演算子のファクトリメソッド。 + * + * Example: + * WhereClause::lte('price', ':Price', 1000) + * WhereClause::lte('price', ':Price', ['Price' => 1000]) + * + * @param $x + * @param $y + * @param $param + * @return WhereClause + */ + public static function lte($x, $y, $param) + { + return self::newWhereClause(self::expr()->lte($x, $y), $y, $param); + } + + /** + * @return Expr + */ + private static function expr() + { + return new Expr(); + } + + public function build(QueryBuilder $builder) { + $builder->andWhere($this->expr); + if ($this->params) { + foreach ($this->params as $key=>$param) { + $builder->setParameter($key, $param); + } + } + } +} \ No newline at end of file diff --git a/src/Eccube/Doctrine/Query/WhereCustomizer.php b/src/Eccube/Doctrine/Query/WhereCustomizer.php new file mode 100644 index 00000000000..589499584c1 --- /dev/null +++ b/src/Eccube/Doctrine/Query/WhereCustomizer.php @@ -0,0 +1,30 @@ +createStatements($params, $queryKey) as $whereClause) { + $whereClause->build($builder); + } + } + + /** + * @param array $params + * @param $queryKey + * @return WhereClause[] + */ + protected abstract function createStatements($params, $queryKey); +} \ No newline at end of file diff --git a/src/Eccube/Repository/CustomerRepository.php b/src/Eccube/Repository/CustomerRepository.php index 1534a95fa09..ffe1b587e5a 100644 --- a/src/Eccube/Repository/CustomerRepository.php +++ b/src/Eccube/Repository/CustomerRepository.php @@ -42,6 +42,9 @@ */ class CustomerRepository extends EntityRepository implements UserProviderInterface { + + const QUERY_KEY_SEARCH = '\Eccube\Repository\CustomerRepository_SEARCH'; + protected $app; public function setApplication($app) @@ -288,7 +291,7 @@ public function getQueryBuilderBySearchData($searchData) // Order By $qb->addOrderBy('c.update_date', 'DESC'); - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_SEARCH, $qb, $searchData); } /** diff --git a/src/Eccube/Repository/OrderRepository.php b/src/Eccube/Repository/OrderRepository.php index c958864942a..c587d22e506 100644 --- a/src/Eccube/Repository/OrderRepository.php +++ b/src/Eccube/Repository/OrderRepository.php @@ -36,6 +36,10 @@ */ class OrderRepository extends EntityRepository { + const QUERY_KEY_SEARCH = '\Eccube\Repository\OrderRepository_SEARCH'; + const QUERY_KEY_SEARCH_ADMIN = '\Eccube\Repository\OrderRepository_SEARCH_ADMIN'; + const QUERY_KEY_SEARCH_BY_CUSTOMER = '\Eccube\Repository\OrderRepository_SEARCH_BY_CUSTOMER'; + protected $app; public function setApplication($app) @@ -244,7 +248,7 @@ public function getQueryBuilderBySearchData($searchData) // Order By $qb->addOrderBy('o.update_date', 'DESC'); - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_SEARCH, $qb, $searchData); } @@ -439,7 +443,7 @@ public function getQueryBuilderBySearchDataForAdmin($searchData) $qb->orderBy('o.update_date', 'DESC'); $qb->addorderBy('o.id', 'DESC'); - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_SEARCH_ADMIN, $qb, $searchData); } @@ -456,7 +460,7 @@ public function getQueryBuilderByCustomer(\Eccube\Entity\Customer $Customer) // Order By $qb->addOrderBy('o.id', 'DESC'); - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_SEARCH_BY_CUSTOMER, $qb, ['customer' => $Customer]); } /** diff --git a/src/Eccube/Repository/ProductRepository.php b/src/Eccube/Repository/ProductRepository.php index 7967b758045..0b09bc20494 100644 --- a/src/Eccube/Repository/ProductRepository.php +++ b/src/Eccube/Repository/ProductRepository.php @@ -39,6 +39,10 @@ class ProductRepository extends EntityRepository { + const QUERY_KEY_SEARCH = '\Eccube\Repository\ProductRepository_SEARCH'; + const QUERY_KEY_SEARCH_ADMIN = '\Eccube\Repository\ProductRepository_SEARCH_ADMIN'; + const QUERY_KEY_GET_FAVORITE = '\Eccube\Repository\ProductRepository_GET_FAVORITE'; + /** * @var \Eccube\Application */ @@ -156,7 +160,7 @@ public function getQueryBuilderBySearchData($searchData) ->addOrderBy('p.id', 'DESC'); } - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_SEARCH, $qb, $searchData); } /** @@ -270,7 +274,7 @@ public function getQueryBuilderBySearchDataForAdmin($searchData) $qb ->orderBy('p.update_date', 'DESC'); - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_SEARCH_ADMIN, $qb, $searchData); } /** @@ -292,6 +296,6 @@ public function getFavoriteProductQueryBuilderByCustomer($Customer) // XXX Paginater を使用した場合に PostgreSQL で正しくソートできない $qb->addOrderBy('cfp.create_date', 'DESC'); - return $qb; + return $this->app['eccube.queries']->customize(self::QUERY_KEY_GET_FAVORITE, $qb, ['customer' => $Customer]); } } diff --git a/src/Eccube/ServiceProvider/EccubeServiceProvider.php b/src/Eccube/ServiceProvider/EccubeServiceProvider.php index 3dbf04f7608..ba2b21dfa2f 100644 --- a/src/Eccube/ServiceProvider/EccubeServiceProvider.php +++ b/src/Eccube/ServiceProvider/EccubeServiceProvider.php @@ -204,7 +204,9 @@ public function register(Container $app) return $CategoryRepository; }; $app['eccube.repository.customer'] = function () use ($app) { - return $app['orm.em']->getRepository('Eccube\Entity\Customer'); + $customerRepository = $app['orm.em']->getRepository('Eccube\Entity\Customer'); + $customerRepository->setApplication($app); + return $customerRepository; }; $app['eccube.repository.news'] = function () use ($app) { return $app['orm.em']->getRepository('Eccube\Entity\News'); @@ -478,6 +480,11 @@ public function register(Container $app) return $types; }); $app['eccube.entity.event.dispatcher']->addEventListener(new \Acme\Entity\SoldOutEventListener()); + $app['eccube.queries'] = function () { + return new \Eccube\Doctrine\Query\Queries(); + }; + // TODO QueryCustomizerの追加方法は要検討 + $app['eccube.queries']->addCustomizer(new \Acme\Entity\AdminProductListCustomizer()); } public function subscribe(Container $app, EventDispatcherInterface $dispatcher) diff --git a/tests/Eccube/Tests/Doctrine/Query/JoinClauseTest.php b/tests/Eccube/Tests/Doctrine/Query/JoinClauseTest.php new file mode 100644 index 00000000000..768fac4c3f4 --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/Query/JoinClauseTest.php @@ -0,0 +1,78 @@ +asString($clause)); + } + + public function testLeftJoin() + { + $clause = JoinClause::leftJoin('p.ProductCategories', 'pct'); + self::assertEquals('LEFT JOIN p.ProductCategories pct', $this->asString($clause)); + } + + public function testInnerJoinFull() + { + $clause = JoinClause::innerJoin('p.ProductCategories', 'pct', 'ON', 'pct.rank = 1', 'categoryId'); + self::assertEquals('INNER JOIN p.ProductCategories pct INDEX BY categoryId ON pct.rank = 1', $this->asString($clause)); + } + + public function testLeftJoinFull() + { + $clause = JoinClause::leftJoin('p.ProductCategories', 'pct', 'ON', 'pct.rank = 1', 'categoryId'); + self::assertEquals('LEFT JOIN p.ProductCategories pct INDEX BY categoryId ON pct.rank = 1', $this->asString($clause)); + } + + public function testWithWhere() + { + $clause = JoinClause::leftJoin('p.ProductCategories', 'pct') + ->addWhere(WhereClause::eq('p.name', ':Name', 'hoge')) + ->addWhere(WhereClause::eq('pct.rank', ':Rank', 1)); + self::assertEquals('LEFT JOIN p.ProductCategories pct WHERE p.name = :Name AND pct.rank = :Rank', $this->asString($clause)); + self::assertEquals([new Parameter('Name', 'hoge'), new Parameter('Rank', 1)], $this->getParams($clause)); + } + + public function testWithOrderBy() + { + $clause = JoinClause::leftJoin('p.ProductCategories', 'pct') + ->addOrderBy(new OrderByClause('pct.rank', 'desc')) + ->addOrderBy(new OrderByClause('pct.categoryId')); + self::assertEquals('LEFT JOIN p.ProductCategories pct ORDER BY pct.rank desc, pct.categoryId asc', $this->asString($clause)); + } + + private function asString(JoinClause $clause) + { + $builder = $this->queryBuilder(); + $clause->build($builder); + return preg_replace('/^SELECT p FROM Product p /', '', $builder->getDQL()); + } + + private function getParams(JoinClause $clause) + { + $builder = $this->queryBuilder(); + $clause->build($builder); + return $builder->getParameters()->toArray(); + } + + /** + * @return QueryBuilder + */ + private function queryBuilder() + { + return $this->app['orm.em']->createQueryBuilder() + ->select('p')->from('Product', 'p'); + } + +} diff --git a/tests/Eccube/Tests/Doctrine/Query/JoinCustomizerTest.php b/tests/Eccube/Tests/Doctrine/Query/JoinCustomizerTest.php new file mode 100644 index 00000000000..3908c6ccb96 --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/Query/JoinCustomizerTest.php @@ -0,0 +1,72 @@ +createQueryBuilder(); + $customizer = new JoinCustomizerTest_Customizer(function() { return []; }); + $customizer->customize($builder, null, ''); + self::assertEquals($builder->getDQL(), 'SELECT p FROM Product p'); + } + + public function testCustomizeInnerJoin() + { + $builder = $this->createQueryBuilder(); + $customizer = new JoinCustomizerTest_Customizer(function() { return [ + JoinClause::innerJoin('p.ProductCategories', 'pct') + ]; }); + $customizer->customize($builder, null, ''); + self::assertEquals($builder->getDQL(), 'SELECT p FROM Product p INNER JOIN p.ProductCategories pct'); + } + + public function testCustomizeMultiInnerJoin() + { + $builder = $this->createQueryBuilder(); + $customizer = new JoinCustomizerTest_Customizer(function() { return [ + JoinClause::innerJoin('p.ProductCategories', 'pct'), + JoinClause::innerJoin('pct.Category', 'c') + ]; }); + $customizer->customize($builder, null, ''); + self::assertEquals($builder->getDQL(), 'SELECT p FROM Product p INNER JOIN p.ProductCategories pct INNER JOIN pct.Category c'); + } + + /** + * @return QueryBuilder + */ + private function createQueryBuilder() + { + return $this->app['orm.em']->createQueryBuilder() + ->select('p') + ->from('Product', 'p'); + } +} + +class JoinCustomizerTest_Customizer extends JoinCustomizer +{ + + private $callback; + + function __construct($callback) + { + $this->callback = $callback; + } + + /** + * @param array $params + * @param $queryKey + * @return JoinClause[] + */ + public function createStatements($params, $queryKey) + { + $callback = $this->callback; + return $callback($params); + } +} diff --git a/tests/Eccube/Tests/Doctrine/Query/OrderByCustomizerTest.php b/tests/Eccube/Tests/Doctrine/Query/OrderByCustomizerTest.php new file mode 100644 index 00000000000..ba8be8518f1 --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/Query/OrderByCustomizerTest.php @@ -0,0 +1,93 @@ +createQueryBuilder(); + $customizer = new OrderByCustomizerTest_Customizer(function() { return []; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p', $builder->getDQL()); + } + + public function testCustomizeNop_Should_not_Override() + { + $builder = $this->createQueryBuilder() + ->orderBy('name', 'desc'); + $customizer = new OrderByCustomizerTest_Customizer(function() { return []; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p ORDER BY name desc', $builder->getDQL()); + } + + public function testCustomize_Override() + { + $builder = $this->createQueryBuilder() + ->orderBy('name', 'desc'); + $customizer = new OrderByCustomizerTest_Customizer(function() { return [ + new OrderByClause('productId') + ]; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p ORDER BY productId asc', $builder->getDQL()); + } + + public function testCustomize_Override_with_multi_clause() + { + $builder = $this->createQueryBuilder() + ->orderBy('name', 'desc'); + $customizer = new OrderByCustomizerTest_Customizer(function() { return [ + new OrderByClause('productId'), + new OrderByClause('name', 'desc') + ]; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p ORDER BY productId asc, name desc', $builder->getDQL()); + } + + /** + * @return QueryBuilder + */ + private function createQueryBuilder() + { + return $this->app['orm.em']->createQueryBuilder() + ->select('p') + ->from('Product', 'p'); + } +} + +class OrderByCustomizerTest_Customizer extends OrderByCustomizer +{ + + /** + * @var callable $closure + */ + private $closure; + + function __construct($closure) + { + $this->closure = $closure; + } + + /** + * @param array $params + * @param $queryKey + * @return OrderByClause[] + */ + public function createStatements($params, $queryKey) + { + $callback = $this->closure; + return $callback($params); + } +} \ No newline at end of file diff --git a/tests/Eccube/Tests/Doctrine/Query/QueriesTest.php b/tests/Eccube/Tests/Doctrine/Query/QueriesTest.php new file mode 100644 index 00000000000..3b87ec37f2d --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/Query/QueriesTest.php @@ -0,0 +1,75 @@ +addCustomizer($customizer); + + $queries->customize(QueriesTest::class, $this->queryBuilder(), null); + + self::assertTrue($customizer->customized); + } + + public function testCustomizerShouldNotBeCalled() + { + $customizer = new QueriesTest_Customizer(); + $queries = new Queries(); + $queries->addCustomizer($customizer); + + $queries->customize('Dummy', $this->queryBuilder(), null); + + self::assertFalse($customizer->customized); + } + + public function testAddCustomizerWithoutAnnotation() + { + $customizer = new QueriesTest_CustomizerWithoutAnnotation(); + $queries = new Queries(); + + $this->setExpectedException(InvalidArgumentException::class); + + $queries->addCustomizer($customizer); + } + + /** + * @return QueryBuilder + */ + private function queryBuilder() + { + return $this->app['orm.em']->createQueryBuilder() + ->select('p')->from('Product', 'p'); + } + +} + +/** + * @QueryExtension(QueriesTest::class) + */ +class QueriesTest_Customizer implements QueryCustomizer +{ + + public $customized = false; + + public function customize(QueryBuilder $builder, $params, $queryKey) + { + $this->customized = true; + } +} + +class QueriesTest_CustomizerWithoutAnnotation implements QueryCustomizer +{ + public function customize(QueryBuilder $builder, $params, $queryKey) {} +} \ No newline at end of file diff --git a/tests/Eccube/Tests/Doctrine/Query/WhereClauseTest.php b/tests/Eccube/Tests/Doctrine/Query/WhereClauseTest.php new file mode 100644 index 00000000000..586bd8a9a9c --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/Query/WhereClauseTest.php @@ -0,0 +1,178 @@ +asString($actual)); + self::assertEquals([new Parameter('Name', 'foo')], $this->getParams($actual)); + } + + public function testEqWithMapParam() + { + $actual = WhereClause::eq('name', ':Name', ['Name' => 'foo']); + self::assertEquals('name = :Name', $this->asString($actual)); + self::assertEquals([new Parameter('Name', 'foo')], $this->getParams($actual)); + } + + public function testNeq() + { + $actual = WhereClause::neq('name', ':Name', 'foo'); + self::assertEquals('name <> :Name', $this->asString($actual)); + self::assertEquals([new Parameter('Name', 'foo')], $this->getParams($actual)); + } + + public function testIsNull() + { + $actual = WhereClause::isNull('name'); + self::assertEquals('name IS NULL', $this->asString($actual)); + self::assertEquals([], $this->getParams($actual)); + } + + public function testIsNotNull() + { + $actual = WhereClause::isNotNull('name'); + self::assertEquals('name IS NOT NULL', $this->asString($actual)); + self::assertEquals([], $this->getParams($actual)); + } + + public function testLike() + { + $actual = WhereClause::like('name', ':Name', '%hoge'); + self::assertEquals('name LIKE :Name', $this->asString($actual)); + self::assertEquals([new Parameter('Name', '%hoge')], $this->getParams($actual)); + } + + public function testNotLike() + { + $actual = WhereClause::notLike('name', ':Name', '%hoge'); + self::assertEquals('name NOT LIKE :Name', $this->asString($actual)); + self::assertEquals([new Parameter('Name', '%hoge')], $this->getParams($actual)); + } + + public function testIn() + { + $actual = WhereClause::in('name', ':Names', ['foo', 'bar']); + self::assertEquals('name IN(:Names)', $this->asString($actual)); + self::assertEquals([new Parameter('Names', ['foo', 'bar'])], $this->getParams($actual)); + } + + public function testInWithMap() + { + $actual = WhereClause::in('name', ':Names', ['Names' => ['foo', 'bar']]); + self::assertEquals('name IN(:Names)', $this->asString($actual)); + self::assertEquals([new Parameter('Names', ['foo', 'bar'])], $this->getParams($actual)); + } + + public function testNotIn() + { + $actual = WhereClause::notIn('name', ':Names', ['foo', 'bar']); + self::assertEquals('name NOT IN(:Names)', $this->asString($actual)); + self::assertEquals([new Parameter('Names', ['foo', 'bar'])], $this->getParams($actual)); + } + + public function testNotInWithMap() + { + $actual = WhereClause::notIn('name', ':Names', ['Names' => ['foo', 'bar']]); + self::assertEquals('name NOT IN(:Names)', $this->asString($actual)); + self::assertEquals([new Parameter('Names', ['foo', 'bar'])], $this->getParams($actual)); + } + + public function testBetween() + { + $actual = WhereClause::between('price', ':Min', ':Max', [1000, 2000]); + self::assertEquals('price BETWEEN :Min AND :Max', $this->asString($actual)); + self::assertEquals([new Parameter('Min', 1000), new Parameter('Max', 2000)], $this->getParams($actual)); + } + + public function testBetweenWithMap() + { + $actual = WhereClause::between('price', ':Min', ':Max', ['Min' => 1000, 'Max' => 2000]); + self::assertEquals('price BETWEEN :Min AND :Max', $this->asString($actual)); + self::assertEquals([new Parameter('Min', 1000), new Parameter('Max', 2000)], $this->getParams($actual)); + } + + public function testGt() + { + $actual = WhereClause::gt('price', ':Price', 1000); + self::assertEquals('price > :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testGtWithMap() + { + $actual = WhereClause::gt('price', ':Price', ['Price' => 1000]); + self::assertEquals('price > :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testGte() + { + $actual = WhereClause::gte('price', ':Price', 1000); + self::assertEquals('price >= :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testGteWithMap() + { + $actual = WhereClause::gte('price', ':Price', ['Price' => 1000]); + self::assertEquals('price >= :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testLt() + { + $actual = WhereClause::lt('price', ':Price', 1000); + self::assertEquals('price < :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testLtWithMap() + { + $actual = WhereClause::lt('price', ':Price', ['Price' => 1000]); + self::assertEquals('price < :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testLte() + { + $actual = WhereClause::lte('price', ':Price', 1000); + self::assertEquals('price <= :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + public function testLteWithMap() + { + $actual = WhereClause::lte('price', ':Price', ['Price' => 1000]); + self::assertEquals('price <= :Price', $this->asString($actual)); + self::assertEquals([new Parameter('Price', 1000)], $this->getParams($actual)); + } + + /* + * Helper methods. + */ + + private function asString(WhereClause $clause) + { + /** @var QueryBuilder $builder */ + $builder = $this->app['orm.em']->createQueryBuilder(); + $clause->build($builder); + return preg_replace('/^SELECT WHERE /', '', $builder->getDQL()); + } + + private function getParams(WhereClause $clause) + { + /** @var QueryBuilder $builder */ + $builder = $this->app['orm.em']->createQueryBuilder(); + $clause->build($builder); + return $builder->getParameters()->toArray(); + } +} diff --git a/tests/Eccube/Tests/Doctrine/Query/WhereCustomizerTest.php b/tests/Eccube/Tests/Doctrine/Query/WhereCustomizerTest.php new file mode 100644 index 00000000000..d2fff09e3bf --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/Query/WhereCustomizerTest.php @@ -0,0 +1,76 @@ +createQueryBuilder(); + $customizer = new WhereCustomizerTest_Customizer(function() { return []; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p', $builder->getDQL()); + } + + public function testCustomizeAddWhereClause() + { + $builder = $this->createQueryBuilder(); + $customizer = new WhereCustomizerTest_Customizer(function() { return [WhereClause::eq('name', ':Name', 'hoge')]; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p WHERE name = :Name', $builder->getDQL()); + } + + public function testCustomizeAddMultipleWhereClause() + { + $builder = $this->createQueryBuilder(); + $customizer = new WhereCustomizerTest_Customizer(function() { return [ + WhereClause::eq('name', ':Name', 'hoge'), + WhereClause::eq('delFlg', ':DelFlg', 0) + ]; }); + $customizer->customize($builder, null, ''); + + self::assertEquals('SELECT p FROM Product p WHERE name = :Name AND delFlg = :DelFlg', $builder->getDQL()); + } + + /** + * @return QueryBuilder + */ + private function createQueryBuilder() + { + return $this->app['orm.em']->createQueryBuilder() + ->select('p') + ->from('Product', 'p'); + } +} + +class WhereCustomizerTest_Customizer extends WhereCustomizer +{ + /** + * @var callable $callback + */ + private $callback; + + function __construct($callback) + { + $this->callback = $callback; + } + + /** + * @param array $params + * @param $queryKey + * @return WhereClause[] + */ + protected function createStatements($params, $queryKey) + { + $callback = $this->callback; + return $callback($params); + } +} \ No newline at end of file