*/ private function formatArguments(array $arguments): array { return collect($arguments)->reduce(function ($c, $v, $k) { $c[] = $this->formatArgument($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) { $keyWithoutRef = str_replace("children.", "", $k); [$field] = explode(".", $keyWithoutRef); $referenceField = str_replace($field . ".", "", $keyWithoutRef); $c[$field][$referenceField] = $v; return $c; }, []); $sourceIds = collect($subqueries)->reduce(function ($c, $subquery, $k) { $query = $this->app->make(Query::class); $graph = $query->filter($subquery)->run(); if (!$graph->hasResults()) { return $c; } $targetIds = collect($graph->records)->pluck("id"); $sourceIds = DB::table("edges")->whereIn("target", $targetIds)->where("field", $k)->get()->pluck("source"); return array_merge($c, $sourceIds->toArray()); }, []); return new Argument( field: "id", operator: "in", value: $sourceIds ); } private function separateMainFromReferenceArguments(Filter $arguments): array { return collect($arguments->toArray())->partition(function ($v, $k) { if (!str_starts_with($k, "children.")) { return true; } return false; })->toArray(); } /** * @return array */ private function parseArguments(Filter $arguments): array { [$normalArguments, $referenceArguments] = $this->separateMainFromReferenceArguments($arguments); $formattedArguments = $this->formatArguments($normalArguments); if (!empty($referenceArguments)) { $formattedArguments[] = $this->formatReferences($referenceArguments); } return $formattedArguments; } public function parse(Builder $builder, Filter $filter): Builder { $arguments = $this->parseArguments($filter); return match (get_class($filter)) { AndFilter::class => $this->parseAnd($builder, $arguments), OrFilter::class => $this->parseOr($builder, $arguments), }; } /** * @param array $arguments */ 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; } /** * @param array $arguments */ 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; } }