first commit
This commit is contained in:
333
class/Query/Operator.php
Normal file
333
class/Query/Operator.php
Normal file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
namespace Query;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use KupShop\KupShopBundle\Exception\ForceEmptyResultException;
|
||||
|
||||
class Operator
|
||||
{
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public static function andX($operand1, $operand2 = null)
|
||||
{
|
||||
if (func_num_args() === 1 && is_array($operand1)) {
|
||||
$operands = $operand1;
|
||||
} else {
|
||||
$operands = func_get_args();
|
||||
}
|
||||
|
||||
$operands = array_filter($operands);
|
||||
|
||||
if (!$operands) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return function (QueryBuilder $qb) use ($operands) {
|
||||
$evaluatedOperands = array_filter($qb->evaluateClosures($operands));
|
||||
|
||||
if (!$evaluatedOperands) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func_array([$qb->expr(), 'andX'], array_values($evaluatedOperands));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public static function orX($operand1, $operand2 = null)
|
||||
{
|
||||
if (func_num_args() === 1 && is_array($operand1)) {
|
||||
$operands = $operand1;
|
||||
} else {
|
||||
$operands = func_get_args();
|
||||
}
|
||||
|
||||
$operands = array_filter($operands);
|
||||
|
||||
if (!$operands) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return function (QueryBuilder $qb) use ($operands) {
|
||||
return call_user_func_array([$qb->expr(), 'orX'], $qb->evaluateClosures($operands));
|
||||
};
|
||||
}
|
||||
|
||||
public static function between($field, \Range $range)
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($field, $range, &$counter) {
|
||||
$e = $qb->expr();
|
||||
$andXArgs = [];
|
||||
|
||||
if (($range->min() ?? '') !== '') {
|
||||
$minParam = ':between_min_'.$counter;
|
||||
$qb->setParameter($minParam, $range->min());
|
||||
$andXArgs[] = $e->gte($field, $minParam);
|
||||
}
|
||||
|
||||
if (($range->max() ?? '') !== '') {
|
||||
$maxParam = ':between_max_'.$counter;
|
||||
$qb->setParameter($maxParam, $range->max());
|
||||
$andXArgs[] = $e->lte($field, $maxParam);
|
||||
}
|
||||
|
||||
$counter++;
|
||||
|
||||
return static::andX($andXArgs);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $set
|
||||
* @param string $operator AND/OR
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function findInSet(array $needles, $set, $operator = 'AND')
|
||||
{
|
||||
if (!$needles) {
|
||||
throw new \InvalidArgumentException('Array $needles must not be empty');
|
||||
}
|
||||
|
||||
if (!$set) {
|
||||
throw new \InvalidArgumentException('Parameter $set must not be empty');
|
||||
}
|
||||
|
||||
if (!in_array($operator, ['AND', 'OR'])) {
|
||||
throw new \InvalidArgumentException('Parameter $operator must be AND or OR');
|
||||
}
|
||||
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($needles, $set, $operator, &$counter) {
|
||||
$expressions = [];
|
||||
foreach ($needles as $needle) {
|
||||
$paramName = ':needle_'.$counter++;
|
||||
$quotedSet = $qb->getConnection()->quoteIdentifier($set);
|
||||
$expressions[] = "FIND_IN_SET({$paramName}, {$quotedSet})";
|
||||
$qb->setParameter($paramName, $needle);
|
||||
}
|
||||
|
||||
return call_user_func_array([$qb->expr(), $operator.'X'], $expressions);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids array of integers to search for in $field
|
||||
* @param string $field field name to search
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function inIntArray(array $ids, $field)
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($ids, $field, &$counter) {
|
||||
if (!$ids) {
|
||||
return 'FALSE';
|
||||
}
|
||||
$paramName = 'inIntArray_'.$counter++;
|
||||
$qb->setParameter($paramName, $ids, Connection::PARAM_INT_ARRAY);
|
||||
|
||||
return "{$field} IN (:{$paramName})";
|
||||
};
|
||||
}
|
||||
|
||||
public static function inSubQuery(string $field, QueryBuilder $subQuery): \Closure
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($subQuery, $field) {
|
||||
$qb->addParameters($subQuery->getParameters(), $subQuery->getParameterTypes());
|
||||
|
||||
return " {$field} IN ({$subQuery->getSQL()})";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids array of integers to search for in $field
|
||||
* @param string $field field name to search
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function inStringArray(array $ids, $field)
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($ids, $field, &$counter) {
|
||||
$paramName = 'inStringArray_'.$counter++;
|
||||
$qb->setParameter($paramName, $ids, Connection::PARAM_STR_ARRAY);
|
||||
|
||||
return "{$field} IN (:{$paramName})";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $mapping field => value mapping
|
||||
* @param string $operator operator used for joining
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function equals($mapping, $operator = 'AND')
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($mapping, $operator, &$counter) {
|
||||
$parts = [];
|
||||
foreach ($mapping as $field => $value) {
|
||||
$paramName = 'equals_'.$counter++;
|
||||
$qb->setParameter($paramName, $value);
|
||||
$parts[] = " {$field} = :{$paramName} ";
|
||||
}
|
||||
|
||||
return join($operator, $parts);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $mapping field => value mapping
|
||||
* @param string $operator operator used for joining
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function like($mapping, $operator = 'AND')
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($mapping, $operator, &$counter) {
|
||||
$parts = [];
|
||||
foreach ($mapping as $field => $value) {
|
||||
$paramName = 'like_'.$counter++;
|
||||
$qb->setParameter($paramName, $value);
|
||||
$parts[] = " {$field} LIKE :{$paramName} ";
|
||||
}
|
||||
|
||||
return join($operator, $parts);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $mapping field => value mapping
|
||||
* @param string $operator operator used for joining
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function equalsNullable($mapping, $operator = 'AND')
|
||||
{
|
||||
static $counter = 0;
|
||||
|
||||
return function (QueryBuilder $qb) use ($mapping, $operator, &$counter) {
|
||||
$parts = [];
|
||||
foreach ($mapping as $field => $value) {
|
||||
$paramName = 'equalsNullable_'.$counter++;
|
||||
if (is_null($value)) {
|
||||
$parts[] = " {$field} IS NULL ";
|
||||
} else {
|
||||
$qb->setParameter($paramName, $value);
|
||||
$parts[] = " {$field} = :{$paramName} ";
|
||||
}
|
||||
}
|
||||
|
||||
return join($operator, $parts);
|
||||
};
|
||||
}
|
||||
|
||||
public static function not($operand)
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($operand) {
|
||||
$expression = $qb->evaluateClosures([$operand])[0];
|
||||
|
||||
if (is_null($expression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "NOT ({$expression})";
|
||||
};
|
||||
}
|
||||
|
||||
public static function isNull($field)
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($field) {
|
||||
return $qb->expr()->isNull($field);
|
||||
};
|
||||
}
|
||||
|
||||
public static function isNotNull($field)
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($field) {
|
||||
return $qb->expr()->isNotNull($field);
|
||||
};
|
||||
}
|
||||
|
||||
public static function notOrNull($operand, $nullableField)
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($operand, $nullableField) {
|
||||
return $qb->evaluateClosures([Operator::not($operand)])[0].' OR '.$nullableField.' IS NULL';
|
||||
};
|
||||
}
|
||||
|
||||
public static function equalsToOrNullable($fieldName1, $fieldName2)
|
||||
{
|
||||
return "({$fieldName1} = {$fieldName2} OR ({$fieldName1} IS NULL AND {$fieldName2} IS NULL))";
|
||||
}
|
||||
|
||||
public static function coalesce($fieldName1, $fieldName2 = null)
|
||||
{
|
||||
$columns = array_filter(func_get_args());
|
||||
|
||||
// nothing to coalesce, return column without coalesce
|
||||
if (count($columns) === 1) {
|
||||
return reset($columns);
|
||||
}
|
||||
|
||||
$columns = join(',', $columns);
|
||||
|
||||
return "COALESCE({$columns})";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryBuilder[] $queryBuilders
|
||||
*/
|
||||
public static function union(array $queryBuilders): callable
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($queryBuilders) {
|
||||
$closures = [];
|
||||
foreach ($queryBuilders as $subQb) {
|
||||
$closures[] = Operator::subquery($subQb);
|
||||
}
|
||||
|
||||
return '('.implode(' UNION ', $qb->evaluateClosures($closures)).')';
|
||||
};
|
||||
}
|
||||
|
||||
public static function subquery(QueryBuilder $subQb): callable
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($subQb) {
|
||||
$qb->addQueryBuilderParameters($subQb);
|
||||
|
||||
return '('.$subQb->getSQL().')';
|
||||
};
|
||||
}
|
||||
|
||||
public static function exists(QueryBuilder $qbToTest)
|
||||
{
|
||||
return function (QueryBuilder $qb) use ($qbToTest) {
|
||||
$qb->addParameters($qbToTest->getParameters(), $qbToTest->getParameterTypes());
|
||||
|
||||
return "EXISTS ({$qbToTest->getSQL()})";
|
||||
};
|
||||
}
|
||||
|
||||
// Zbpůsobí, aby se query vůbec nevykonala. Je třeba zajistit, že to parent handluje.
|
||||
// Nebylo by lepší to vyřešit memberem na QB, který by vracel prázdný statement?
|
||||
public static function forceEmptyResult()
|
||||
{
|
||||
return function (QueryBuilder $qb) {
|
||||
throw new ForceEmptyResultException();
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user