Skip to content

Commit e23e241

Browse files
authored
Merge pull request #60 from Lomkit/feature/lazy-validation
🚧 lazy validation
2 parents 4bd7b5f + 1191570 commit e23e241

31 files changed

+1374
-966
lines changed

README.md

Lines changed: 61 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,66 +20,68 @@ Here is a quick look at what you can do using API search method:
2020
```
2121
// POST api/posts/search
2222
{
23-
"scopes": [
24-
{"name": "withTrashed", "parameters": [true]}
25-
],
26-
"filters": [
27-
{
28-
"field": "id", "operator": ">", "value": 1, "type": "or"
29-
},
30-
{
31-
"nested": [
32-
{"field": "user.posts.id", "operator": "<", "value": 2},
33-
{"field": "user.id", "operator": ">", "value": 3, "type": "or"}
23+
"search": {
24+
"scopes": [
25+
{"name": "withTrashed", "parameters": [true]}
26+
],
27+
"filters": [
28+
{
29+
"field": "id", "operator": ">", "value": 1, "type": "or"
30+
},
31+
{
32+
"nested": [
33+
{"field": "user.posts.id", "operator": "<", "value": 2},
34+
{"field": "user.id", "operator": ">", "value": 3, "type": "or"}
35+
]
36+
}
37+
],
38+
"sorts": [
39+
{"field": "user_id", "direction": "desc"},
40+
{"field": "id", "direction": "asc"}
41+
],
42+
"selects": [
43+
{"field": "id"}
44+
],
45+
"includes": [
46+
{
47+
"relation": "posts",
48+
"filters": [
49+
{"field": "id", "operator": "in", "value": [1, 3]}
50+
],
51+
"limit": 2
52+
},
53+
{
54+
"relation": "user",
55+
"filters": [
56+
{
57+
"field": "languages.pivot.boolean",
58+
"operator": "=",
59+
"value": true
60+
}
61+
]
62+
}
63+
],
64+
"aggregates": [
65+
{
66+
"relation": "stars",
67+
"type": "max",
68+
"field": "rate",
69+
"filters": [
70+
{"field": "approved", "value": true}
71+
]
72+
},
73+
],
74+
"instructions": [
75+
{
76+
"name": "odd-even-id",
77+
"fields": [
78+
{ "name": "type", "value": "odd" }
3479
]
35-
}
36-
],
37-
"sorts": [
38-
{"field": "user_id", "direction": "desc"},
39-
{"field": "id", "direction": "asc"}
40-
],
41-
"selects": [
42-
{"field": "id"}
43-
],
44-
"includes": [
45-
{
46-
"relation": "posts",
47-
"filters": [
48-
{"field": "id", "operator": "in", "value": [1, 3]}
49-
],
50-
"limit": 2
51-
},
52-
{
53-
"relation": "user",
54-
"filters": [
55-
{
56-
"field": "languages.pivot.boolean",
57-
"operator": "=",
58-
"value": true
59-
}
60-
]
61-
}
62-
],
63-
"aggregates": [
64-
{
65-
"relation": "stars",
66-
"type": "max",
67-
"field": "rate",
68-
"filters": [
69-
{"field": "approved", "value": true}
70-
]
71-
},
72-
],
73-
"instructions": [
74-
{
75-
"name": "odd-even-id",
76-
"fields": [
77-
{ "name": "type", "value": "odd" }
78-
]
79-
}
80-
],
81-
"page": 2,
82-
"limit": 10
80+
}
81+
],
82+
"page": 2,
83+
"limit": 10
84+
}
8385
}
8486
```
8587

src/Concerns/PerformsRestOperations.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function search(SearchRequest $request)
4444
$request->resource($resource = static::newResource());
4545

4646
$query = app()->make(QueryBuilder::class, ['resource' => $resource, 'query' => null])
47-
->search($request->all());
47+
->search($request->input('search', []));
4848

4949
return $resource::newResponse()
5050
->resource($resource)

src/Concerns/Resourcable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ trait Resourcable
1818
*
1919
* @param resource $resource
2020
*
21-
* @return array
21+
* @return self
2222
*/
2323
public function resource(Resource $resource)
2424
{

src/Concerns/Resource/Paginable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ trait Paginable
1717
*/
1818
public function paginate(Builder $query, RestRequest $request)
1919
{
20-
return $query->paginate($request->input('limit', 50));
20+
return $query->paginate($request->input('search.limit', 50), ['*'], 'page', $request->input('search.page', 1));
2121
}
2222
}

src/Concerns/Resource/Relationable.php

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -46,39 +46,6 @@ public function relationResource($name)
4646
return $this->relation($name)?->resource();
4747
}
4848

49-
/**
50-
* Get nested relations with their names as keys.
51-
*
52-
* @param RestRequest $request
53-
* @param string $prefix
54-
* @param array $loadedRelations
55-
*
56-
* @return array
57-
*/
58-
public function nestedRelations(RestRequest $request, string $prefix = '', array $loadedRelations = [])
59-
{
60-
if ($prefix !== '') {
61-
$prefix = $prefix.'.';
62-
}
63-
64-
$relations = [];
65-
66-
foreach (
67-
collect($this->getRelations($request))
68-
->filter(function ($relation) use ($loadedRelations) {
69-
return !in_array($relation->relation, $loadedRelations);
70-
})
71-
as $relation
72-
) {
73-
$relations[$prefix.$relation->relation] = $relation;
74-
foreach ($relation->resource()->nestedRelations($request, $prefix.$relation->relation, array_merge($loadedRelations, [$relation->relation])) as $key => $value) {
75-
$relations[$key] = $value;
76-
}
77-
}
78-
79-
return $relations;
80-
}
81-
8249
/**
8350
* The calculated relations if already done in this request.
8451
*

src/Http/Requests/MutateRequest.php

Lines changed: 8 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
namespace Lomkit\Rest\Http\Requests;
44

5-
use Illuminate\Validation\Rule;
6-
use Lomkit\Rest\Http\Resource;
7-
use Lomkit\Rest\Rules\ArrayWith;
8-
use Lomkit\Rest\Rules\CustomRulable;
5+
use Lomkit\Rest\Rules\MutateRules;
96

107
class MutateRequest extends RestRequest
118
{
@@ -19,94 +16,13 @@ class MutateRequest extends RestRequest
1916
*/
2017
public function rules()
2118
{
22-
return $this->mutateRules($this->route()->controller::newResource());
23-
}
24-
25-
/**
26-
* Define the validation rules for mutating resources.
27-
*
28-
* @param resource $resource
29-
* @param string $prefix
30-
* @param array $loadedRelations
31-
*
32-
* @return array
33-
*
34-
* This method specifies the validation rules for resource mutations, including create, update, attach, or detach.
35-
* It includes rules for the operation type, attributes, keys, and custom rules.
36-
*/
37-
public function mutateRules(Resource $resource, $prefix = 'mutate.*', $loadedRelations = [])
38-
{
39-
return array_merge(
40-
[
41-
$prefix.'.operation' => [
42-
'required_with:'.$prefix,
43-
Rule::in('create', 'update', ...($prefix === '' ? [] : ['attach', 'detach'])),
44-
],
45-
$prefix.'.attributes' => [
46-
'prohibited_if:'.$prefix.'.operation,attach',
47-
'prohibited_if:'.$prefix.'.operation,detach',
48-
new ArrayWith($resource->getFields($this)),
49-
],
50-
$prefix.'.key' => [
51-
'required_if:'.$prefix.'.operation,update',
52-
'required_if:'.$prefix.'.operation,attach',
53-
'required_if:'.$prefix.'.operation,detach',
54-
'prohibited_if:'.$prefix.'.operation,create',
55-
'exists:'.$resource::newModel()->getTable().','.$resource::newModel()->getKeyName(),
56-
],
57-
$prefix => [
58-
CustomRulable::make()->resource($resource),
59-
],
60-
],
61-
$this->relationRules($resource, $prefix.'.relations', $loadedRelations)
62-
);
63-
}
64-
65-
/**
66-
* Define relation-specific validation rules for mutations.
67-
*
68-
* @param resource $resource
69-
* @param string $prefix
70-
* @param array $loadedRelations
71-
*
72-
* @return array
73-
*
74-
* This protected method specifies validation rules for resource relations during mutations.
75-
* It ensures that relations are properly validated for the given operation type.
76-
*/
77-
protected function relationRules(Resource $resource, string $prefix = '', $loadedRelations = [])
78-
{
79-
$resourceRelationsNotLoaded = collect($resource->getRelations($this))
80-
->filter(function ($relation) use ($loadedRelations) {
81-
return !in_array($relation->relation, $loadedRelations);
82-
});
83-
84-
$rules = [
85-
$prefix => [
86-
new ArrayWith(
87-
$resourceRelationsNotLoaded->map(function ($resourceRelationNotLoaded) {
88-
return $resourceRelationNotLoaded->relation;
89-
})->toArray()
90-
),
91-
],
19+
return [
20+
'mutate.*' => new MutateRules(
21+
$this->route()->controller::newResource(),
22+
$this,
23+
null,
24+
true
25+
),
9226
];
93-
94-
foreach (
95-
$resourceRelationsNotLoaded as $relation
96-
) {
97-
$prefixRelation = $prefix.'.'.$relation->relation;
98-
99-
if ($relation->hasMultipleEntries()) {
100-
$prefixRelation .= '.*';
101-
}
102-
103-
$rules = array_merge_recursive(
104-
$rules,
105-
$relation->rules($resource, $prefix.'.'.$relation->relation),
106-
$this->mutateRules($relation->resource(), $prefixRelation, array_merge($loadedRelations, [$relation->relation]))
107-
);
108-
}
109-
110-
return $rules;
11127
}
11228
}

src/Http/Requests/OperateRequest.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Lomkit\Rest\Actions\Action;
77
use Lomkit\Rest\Http\Resource;
88
use Lomkit\Rest\Rules\ActionField;
9+
use Lomkit\Rest\Rules\SearchRules;
910
use Symfony\Component\HttpKernel\Exception\HttpException;
1011

1112
class OperateRequest extends RestRequest
@@ -21,35 +22,37 @@ class OperateRequest extends RestRequest
2122
*/
2223
public function rules()
2324
{
24-
return $this->operateRules($this->route()->controller::newResource());
25+
return $this
26+
->resource($this->route()->controller::newResource())
27+
->operateRules();
2528
}
2629

2730
/**
2831
* Define the validation rules for resource operations.
2932
*
30-
* @param resource $resource
31-
*
3233
* @return array
3334
*
3435
* This method specifies the validation rules for resource operations.
3536
* It checks if the requested action exists for the given resource, and if so,
3637
* it includes validation rules for fields associated with the operation.
3738
*/
38-
public function operateRules(Resource $resource)
39+
public function operateRules()
3940
{
40-
if (!$resource->actionExists($this, $this->route()->parameter('action'))) {
41+
if (!$this->resource->actionExists($this, $this->route()->parameter('action'))) {
4142
throw new HttpException(404);
4243
}
4344

44-
$operatedAction = $resource->action($this, $this->route()->parameter('action'));
45+
$operatedAction = $this->resource->action($this, $this->route()->parameter('action'));
4546

4647
return array_merge(
4748
$operatedAction->isStandalone() ? [
4849
'search' => [
4950
'prohibited',
5051
],
5152
] : [],
52-
!$operatedAction->isStandalone() ? app(SearchRequest::class)->searchRules($resource, 'search') : [],
53+
!$operatedAction->isStandalone() ? [
54+
'search' => [new SearchRules($this->resource, $this)],
55+
] : [],
5356
[
5457
'fields.*.name' => [
5558
Rule::in(array_keys($operatedAction->fields($this))),

0 commit comments

Comments
 (0)