Skip to content

Commit af56583

Browse files
committed
Overhaul Laravel filters
1 parent 4af3e35 commit af56583

12 files changed

+258
-111
lines changed

src/Laravel/Filter/EloquentFilter.php renamed to src/Laravel/Filter/ColumnFilter.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@
22

33
namespace Tobyz\JsonApiServer\Laravel\Filter;
44

5+
use Illuminate\Contracts\Database\Query\Expression;
56
use Illuminate\Support\Str;
67
use Tobyz\JsonApiServer\Schema\Filter;
78

8-
abstract class EloquentFilter extends Filter
9+
abstract class ColumnFilter extends Filter
910
{
10-
protected ?string $column = null;
11+
protected string|Expression|null $column = null;
1112

1213
public static function make(string $name): static
1314
{
1415
return new static($name);
1516
}
1617

17-
public function column(?string $column): static
18+
public function column(string|Expression|null $column): static
1819
{
1920
$this->column = $column;
2021

2122
return $this;
2223
}
2324

24-
protected function getColumn(): string
25+
protected function getColumn(): string|Expression
2526
{
2627
return $this->column ?: Str::snake($this->name);
2728
}

src/Laravel/Filter/Has.php

Lines changed: 0 additions & 41 deletions
This file was deleted.

src/Laravel/Filter/Scope.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@
88

99
class Scope extends Filter
1010
{
11+
use SupportsOperators;
12+
13+
public const SUPPORTED_OPERATORS = ['eq', 'ne'];
14+
1115
protected null|string|Closure $scope = null;
1216
protected bool $asBoolean = false;
17+
protected bool $commaSeparated = false;
1318

1419
public static function make(string $name): static
1520
{
@@ -30,6 +35,13 @@ public function asBoolean(bool $asBoolean = true): static
3035
return $this;
3136
}
3237

38+
public function commaSeparated(): static
39+
{
40+
$this->commaSeparated = true;
41+
42+
return $this;
43+
}
44+
3345
public function apply(object $query, array|string $value, Context $context): void
3446
{
3547
$scope = $this->scope ?: $this->name;
@@ -46,8 +58,26 @@ public function apply(object $query, array|string $value, Context $context): voi
4658
} else {
4759
$query->whereNot(fn($query) => $scope($query));
4860
}
49-
} else {
50-
$scope($query, $value);
61+
return;
62+
}
63+
64+
foreach ($this->resolveOperators($value) as $operator => $val) {
65+
$val = $this->splitCommaSeparated($val);
66+
67+
if ($operator === 'ne') {
68+
$query->whereNot(fn($query) => $scope($query, $val));
69+
} else {
70+
$scope($query, $val);
71+
}
5172
}
5273
}
74+
75+
private function splitCommaSeparated(array|string $value): array|string
76+
{
77+
if ($this->commaSeparated && is_string($value)) {
78+
return explode(',', $value);
79+
}
80+
81+
return $value;
82+
}
5383
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Laravel\Filter;
4+
5+
use InvalidArgumentException;
6+
7+
trait SupportsOperators
8+
{
9+
protected array $operators = self::SUPPORTED_OPERATORS;
10+
11+
public function operators(array $only = null): static
12+
{
13+
$supported = static::SUPPORTED_OPERATORS;
14+
15+
if ($only === null) {
16+
return $supported;
17+
}
18+
19+
$invalid = array_diff($only, $supported);
20+
21+
if (!empty($invalid)) {
22+
throw new InvalidArgumentException(
23+
'Unsupported operators requested: ' . implode(', ', $invalid),
24+
);
25+
}
26+
27+
$this->operators = $only;
28+
29+
return $this;
30+
}
31+
32+
protected function resolveOperators(array|string $value): array
33+
{
34+
$default = $this->operators[0];
35+
36+
if (is_string($value) || array_is_list($value)) {
37+
return [$default => $value];
38+
}
39+
40+
$result = [];
41+
42+
foreach ($value as $key => $val) {
43+
if (in_array($key, $this->operators)) {
44+
$result[$key] = $val;
45+
} elseif (is_array($result[$default] ?? [])) {
46+
$result[$default][$key] = $val;
47+
} else {
48+
$result[$default] = [$result[$default], $val];
49+
}
50+
}
51+
52+
return $result;
53+
}
54+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Laravel\Filter;
4+
5+
use Closure;
6+
7+
trait UsesRelationship
8+
{
9+
public ?string $relationship = null;
10+
public ?Closure $scope = null;
11+
12+
public function relationship(?string $relationship): static
13+
{
14+
$this->relationship = $relationship;
15+
16+
return $this;
17+
}
18+
19+
public function scope(?Closure $scope): static
20+
{
21+
$this->scope = $scope;
22+
23+
return $this;
24+
}
25+
}

src/Laravel/Filter/Where.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,25 @@
55
use Tobyz\JsonApiServer\Context;
66
use Tobyz\JsonApiServer\Exception\BadRequestException;
77

8-
class Where extends EloquentFilter
8+
class Where extends ColumnFilter
99
{
10+
use SupportsOperators;
11+
12+
public const SUPPORTED_OPERATORS = [
13+
'eq',
14+
'ne',
15+
'in',
16+
'notin',
17+
'lt',
18+
'lte',
19+
'gt',
20+
'gte',
21+
'like',
22+
'notlike',
23+
'null',
24+
'notnull',
25+
];
26+
1027
protected bool $asBoolean = false;
1128
protected bool $commaSeparated = false;
1229

@@ -36,10 +53,7 @@ public function apply(object $query, array|string $value, Context $context): voi
3653
return;
3754
}
3855

39-
if (is_string($value) || array_is_list($value)) {
40-
$this->applyEquals($query, $value);
41-
return;
42-
}
56+
$value = $this->resolveOperators($value);
4357

4458
foreach ($value as $operator => $v) {
4559
switch ($operator) {
@@ -70,7 +84,10 @@ public function apply(object $query, array|string $value, Context $context): voi
7084

7185
case 'null':
7286
case 'notnull':
73-
$this->applyNull($query, $operator === 'null' ? (bool) $v : !$v);
87+
$this->applyNull(
88+
$query,
89+
$operator === 'null' xor !filter_var($value, FILTER_VALIDATE_BOOLEAN),
90+
);
7491
break;
7592

7693
default:

src/Laravel/Filter/WhereBelongsTo.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
namespace Tobyz\JsonApiServer\Laravel\Filter;
44

55
use Tobyz\JsonApiServer\Context;
6-
use Tobyz\JsonApiServer\Exception\BadRequestException;
76
use Tobyz\JsonApiServer\Schema\Filter;
87

98
class WhereBelongsTo extends Filter
109
{
10+
use SupportsOperators;
11+
12+
public const SUPPORTED_OPERATORS = ['eq', 'in', 'ne', 'notin', 'null', 'notnull'];
13+
1114
protected ?string $relationship = null;
1215

1316
public static function make(string $name): static
@@ -25,16 +28,12 @@ public function relationship(?string $relationship): static
2528
public function apply(object $query, array|string $value, Context $context): void
2629
{
2730
$relationship = $query->getModel()->{$this->relationship ?: $this->name}();
31+
$column = $relationship->getQualifiedForeignKeyName();
2832

29-
if (!array_is_list($values = (array) $value)) {
30-
throw (new BadRequestException('filter value must be list'))->setSource([
31-
'parameter' => "[$this->name]",
32-
]);
33-
}
34-
35-
$query->whereIn(
36-
$relationship->getQualifiedForeignKeyName(),
37-
array_merge(...array_map(fn($v) => explode(',', $v), $values)),
38-
);
33+
Where::make($this->name)
34+
->column($column)
35+
->operators($this->operators)
36+
->commaSeparated()
37+
->apply($query, $value, $context);
3938
}
4039
}

src/Laravel/Filter/WhereCount.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Laravel\Filter;
4+
5+
use Tobyz\JsonApiServer\Context;
6+
use Tobyz\JsonApiServer\Schema\Filter;
7+
8+
class WhereCount extends Filter
9+
{
10+
use UsesRelationship;
11+
use SupportsOperators;
12+
13+
public const SUPPORTED_OPERATORS = ['eq', 'ne', 'gt', 'lt', 'lte', 'gte'];
14+
15+
private const OPERATOR_MAP = [
16+
'eq' => '=',
17+
'ne' => '!=',
18+
'gt' => '>',
19+
'lt' => '<',
20+
'lte' => '<=',
21+
'gte' => '>=',
22+
];
23+
24+
public static function make(string $name): static
25+
{
26+
return new static($name);
27+
}
28+
29+
public function apply(object $query, array|string $value, Context $context): void
30+
{
31+
foreach ($this->resolveOperators($value) as $operator => $val) {
32+
$query->whereHas(
33+
$this->relationship ?: $this->name,
34+
$this->scope ? fn($query) => ($this->scope)($query, $context) : null,
35+
static::OPERATOR_MAP[$operator],
36+
$val,
37+
);
38+
}
39+
}
40+
}

src/Laravel/Filter/WhereExists.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Tobyz\JsonApiServer\Laravel\Filter;
4+
5+
use Tobyz\JsonApiServer\Context;
6+
use Tobyz\JsonApiServer\Schema\Filter;
7+
8+
class WhereExists extends Filter
9+
{
10+
use UsesRelationship;
11+
12+
public static function make(string $name): static
13+
{
14+
return new static($name);
15+
}
16+
17+
public function apply(object $query, array|string $value, Context $context): void
18+
{
19+
$method = filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'whereHas' : 'whereDoesntHave';
20+
21+
$query->{$method}(
22+
$this->relationship ?: $this->name,
23+
$this->scope ? fn($query) => ($this->scope)($query, $context) : null,
24+
);
25+
}
26+
}

0 commit comments

Comments
 (0)