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(); }; } }