refactoring of filters

This commit is contained in:
2024-08-24 17:22:40 +03:00
parent 97ad9de3d2
commit d9e2c4954a
57 changed files with 1175 additions and 349 deletions
+16 -139
View File
@@ -5,19 +5,26 @@ namespace Lucent\Query;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Lucent\Query\BuilderConverter\BuilderConverter;
use Lucent\Query\Filter\AndFilter;
use Lucent\Query\Filter\Argument;
use Lucent\Query\Filter\Filter;
use Lucent\Query\Filter\OrFilter;
use Lucent\Query\Operator\In;
use Lucent\Query\Operator\OperatorDetector;
use function explode;
final class FilterParser
{
public function __construct(public Application $app)
public function __construct(
public Application $app,
public BuilderConverter $builderConverter,
public OperatorDetector $operatorDetector,
)
{
}
/**
* @param array $arguments
* @return array<Argument>
@@ -25,105 +32,11 @@ final class FilterParser
private function formatArguments(array $arguments): array
{
return collect($arguments)->reduce(function ($c, $v, $k) {
$c[] = $this->formatArgument($v, $k);
$c[] = $this->operatorDetector->detect($v, $k);
return $c;
}, []);
}
private function formatArgument(mixed $value, string $filter): Argument
{
$operator = $this->detectOperator($filter);
$field = $this->detectField($filter, $operator);
$formattedValue = match ($operator) {
"eq" => $this->formatText($value),
"ne" => $this->formatText($value),
"eqnum" => $this->formatNumber($value),
"nenum" => $this->formatNumber($value),
"object" => $this->formatText($value),
"in" => $this->formatListString($value),
"nin" => $this->formatListString($value),
"innum" => $this->formatListNum($value),
"ninnum" => $this->formatListNum($value),
"eqtrue" => true,
"eqfalse" => false,
"netrue" => true,
"nefalse" => false,
"regex" => "%" . strtolower($value) . "%",
"gt" => is_numeric($value) ? floatval($value) : $value,
"gte" => is_numeric($value) ? floatval($value) : $value,
"lt" => is_numeric($value) ? floatval($value) : $value,
"lte" => is_numeric($value) ? floatval($value) : $value,
"null" => null,
"nnull" => null,
"exists" => true,
"nexists" => false,
default => $value,
};
$matchedOperator = Operator::list()[$operator];
return new Argument(
field: str_replace(".", "->", $field),
operator: $matchedOperator->db,
value: $formattedValue
);
}
private function formatText(string $value): string
{
return trim($value);
}
private function formatNumber(string $value): float
{
return floatval($value);
}
private function formatListString(mixed $value): array
{
if (is_string($value)) {
$value = explode(",", $value);
}
return array_map(fn($v) => $this->formatText($v), $value);
}
private function formatListNum(mixed $value): array
{
if (\is_string($value)) {
$value = explode(",", $value);
}
return \array_map(fn($v) => $this->formatNumber($v), $value);
}
private function detectOperator(string $filter): string
{
$exploded = \explode("_", $filter);
$candidate = end($exploded);
$operatorsListNames = collect(Operator::list())->map(fn($o) => $o->name)->toArray();
if (\in_array($candidate, $operatorsListNames)) {
return $candidate;
}
return 'eq';
}
private function detectField(string $filter, string $operator): string
{
$exploded = explode("_", $filter);
$candidate = array_pop($exploded);
if ($candidate === $operator) {
return implode("_", $exploded);
}
return $filter;
}
private function formatReferences(array $referenceArguments): Argument
{
$subqueries = collect($referenceArguments)->reduce(function ($c, $v, $k) {
@@ -151,7 +64,7 @@ final class FilterParser
return new Argument(
field: "id",
operator: "in",
operator: In::make(),
value: $sourceIds
);
@@ -174,7 +87,6 @@ final class FilterParser
private function parseArguments(Filter $arguments): array
{
[$normalArguments, $referenceArguments] = $this->separateMainFromReferenceArguments($arguments);
$formattedArguments = $this->formatArguments($normalArguments);
if (!empty($referenceArguments)) {
$formattedArguments[] = $this->formatReferences($referenceArguments);
@@ -198,26 +110,8 @@ final class FilterParser
*/
private function parseAnd(Builder $builder, array $arguments): Builder
{
foreach ($arguments as $argument) {
if ($argument->operator == "in") {
$builder->whereIn($argument->field, $argument->value);
} else if ($argument->operator == "nin") {
$builder->whereNotIn($argument->field, $argument->value);
} else if ($argument->operator == "exists") {
$builder->where($argument->field, "!=", "");
$builder->where($argument->field, "!=", null);
} elseif ($argument->operator == "filter") {
$builder->whereJsonContains($argument->field, [$argument->value]);
// target result
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
// $query->whereJsonContains($filter["field"], [$objectFilters]);
} else {
$builder->where($argument->field, $argument->operator, $argument->value);
}
}
return $builder;
return collect($arguments)
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toAndQueryBuilder($cBuilder), $builder);
}
/**
@@ -225,27 +119,10 @@ final class FilterParser
*/
private function parseOr(Builder $builder, array $arguments): Builder
{
$builder->where(function (Builder $orBuilder) use ($arguments) {
foreach ($arguments as $argument) {
if ($argument->operator == "in") {
$orBuilder->orWhereIn($argument->field, $argument->value);
} else if ($argument->operator == "nin") {
$orBuilder->orWhereNotIn($argument->field, $argument->value);
} else if ($argument->operator == "exists") {
$orBuilder->where($argument->field, "!=", "");
$orBuilder->where($argument->field, "!=", null);
} elseif ($argument->operator == "filter") {
$orBuilder->whereJsonContains($argument->field, [$argument->value]);
// target result
// filter[data.previousNames_object]=previousNames&filter[previousNames.name_eq]=alpha&filter[previousNames.id_eqnum]=24
// $query->whereJsonContains("data->previousNames", [["name" => "alpha", "id" => 24]]);
// $query->whereJsonContains($filter["field"], [$objectFilters]);
} else {
$orBuilder->orWhere($argument->field, $argument->operator, $argument->value);
}
}
return $builder->where(function (Builder $orBuilder) use ($arguments) {
return collect($arguments)
->reduce(fn($cBuilder, Argument $arg) => $this->builderConverter->for($arg)->toOrQueryBuilder($cBuilder), $orBuilder);
});
return $builder;
}