1+ <?php
2+
3+ namespace Ark4ne \JsonApi \Filters ;
4+
5+ use Ark4ne \JsonApi \Support \Values ;
6+ use Closure ;
7+ use Illuminate \Contracts \Auth \Access \Gate ;
8+ use Illuminate \Http \Request ;
9+ use Illuminate \Http \Resources \MissingValue ;
10+ use Illuminate \Http \Resources \PotentiallyMissing ;
11+ use Illuminate \Support \Collection ;
12+
13+ /**
14+ * @template Resource
15+ */
16+ class Filters
17+ {
18+ /** @var array<FilterRule<Resource>> */
19+ protected array $ rules = [];
20+
21+ /**
22+ * Add a policy-based filter
23+ *
24+ * @param iterable<string>|string $abilities Abilities to check
25+ * @param array<mixed> $arguments Arguments to pass to the policy method, the model is always the first argument
26+ * @param string $gateClass Gate class to use, defaults to the default Gate implementation
27+ * @param string|null $guard Guard to use, defaults to the default guard
28+ * @return static
29+ */
30+ public function can (iterable |string $ abilities , array $ arguments = [], string $ gateClass = Gate::class, ?string $ guard = null ): static
31+ {
32+ $ this ->rules [] = new PolicyFilterRule ($ abilities , $ arguments , $ gateClass , $ guard );
33+ return $ this ;
34+ }
35+
36+ /**
37+ * Add a custom filter rule
38+ *
39+ * @param Closure(Request, Resource): bool $callback Callback that receives (Request $request, Model $model) and returns bool
40+ * @return static
41+ */
42+ public function when (Closure $ callback ): static
43+ {
44+ $ this ->rules [] = new CallbackFilterRule ($ callback );
45+ return $ this ;
46+ }
47+
48+ /**
49+ * Apply all filters to the given data
50+ *
51+ * @param Request $request
52+ * @param null|PotentiallyMissing|Resource|iterable<array-key, Resource> $data
53+ * @return mixed
54+ */
55+ public function apply (Request $ request , mixed $ data ): mixed
56+ {
57+ if ($ data === null ) {
58+ return $ data ;
59+ }
60+ if (Values::isMissing ($ data )) {
61+ return $ data ;
62+ }
63+
64+ // If it's a collection/array, filter each item
65+ if (is_iterable ($ data )) {
66+ $ filtered = (new Collection ($ data ))
67+ ->filter (fn ($ item ) => $ this ->shouldInclude ($ request , $ item ));
68+
69+ // Preserve the original collection type
70+ if ($ data instanceof Collection) {
71+ return $ filtered ;
72+ }
73+
74+ return $ filtered ->all ();
75+ }
76+
77+ // Single model - check if it should be included
78+ return $ this ->shouldInclude ($ request , $ data )
79+ ? $ data
80+ : new MissingValue ();
81+ }
82+
83+ /**
84+ * Check if a model should be included based on all filter rules
85+ *
86+ * @param Request $request
87+ * @param Resource $model
88+ * @return bool
89+ */
90+ protected function shouldInclude (Request $ request , mixed $ model ): bool
91+ {
92+ // All rules must pass
93+ foreach ($ this ->rules as $ rule ) {
94+ if (!$ rule ->passes ($ request , $ model )) {
95+ return false ;
96+ }
97+ }
98+
99+ return true ;
100+ }
101+ }
0 commit comments