Skip to content

Commit 4c57a3c

Browse files
authored
Searchable model. (#354)
* Searchble model. * Apply fixes from StyleCI (#355) * wip * wip * Apply fixes from StyleCI (#356)
1 parent a04d5b2 commit 4c57a3c

File tree

9 files changed

+132
-59
lines changed

9 files changed

+132
-59
lines changed

docs/docs/4.0/repository-pattern/repository-pattern.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,9 +817,13 @@ eager load a relationship in terms of using it in fields, or whatever else:
817817
```php
818818
// UserRepository.php
819819

820-
public static $with = ['posts'];
820+
public static $withs = ['posts'];
821821
```
822822

823+
:::warn `withs` is not type
824+
Laravel uses the `with` property on models, on repositories we use `$withs`, it's not a typo.
825+
:::
826+
823827
## Store bulk flow
824828

825829
The bulk store means that you can create many entries at once, for example if you have a list of invoice entries,

src/Eager/Related.php

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,30 @@
33
namespace Binaryk\LaravelRestify\Eager;
44

55
use Binaryk\LaravelRestify\Fields\EagerField;
6+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
67
use Binaryk\LaravelRestify\Repositories\Repository;
8+
use Binaryk\LaravelRestify\Resolvable;
79
use Binaryk\LaravelRestify\Traits\Make;
10+
use Illuminate\Database\Eloquent\Builder;
11+
use Illuminate\Database\Eloquent\Relations\Relation;
12+
use Illuminate\Support\Arr;
13+
use Illuminate\Support\Collection;
14+
use Illuminate\Support\Str;
815
use JsonSerializable;
916

10-
class Related implements JsonSerializable
17+
class Related implements JsonSerializable, Resolvable
1118
{
1219
use Make;
1320

1421
private string $relation;
1522

23+
/**
24+
* This is the default value.
25+
*
26+
* @var callable|string|int
27+
*/
28+
private $value;
29+
1630
private ?EagerField $field;
1731

1832
public function __construct(string $relation, EagerField $field = null)
@@ -31,11 +45,56 @@ public function getRelation(): string
3145
return $this->relation;
3246
}
3347

48+
public function getValue()
49+
{
50+
return $this->value;
51+
}
52+
3453
public function resolveField(Repository $repository): EagerField
3554
{
3655
return $this->field->resolve($repository);
3756
}
3857

58+
public function resolve(RestifyRequest $request, Repository $repository): self
59+
{
60+
if (Str::contains($this->getRelation(), '.')) {
61+
$repository->resource->loadMissing($this->getRelation());
62+
63+
$key = Str::before($this->getRelation(), '.');
64+
65+
$this->value = Arr::get($repository->resource->relationsToArray(), $key);
66+
67+
return $this;
68+
}
69+
70+
/** * To avoid circular relationships and deep stack calls, we will do not load eager fields. */
71+
if ($this->isEager() && $repository->isEagerState() === false) {
72+
$this->value = $this->resolveField($repository)->value;
73+
74+
return $this;
75+
}
76+
77+
$paginator = $repository->resource->relationLoaded($this->getRelation())
78+
? $repository->resource->{$this->getRelation()}
79+
: $repository->resource->{$this->getRelation()}();
80+
81+
switch ($paginator) {
82+
case $paginator instanceof Builder:
83+
$this->value = ($repository::$relatedCast)::fromBuilder($request, $paginator, $repository);
84+
break;
85+
case $paginator instanceof Relation:
86+
$this->value = ($repository::$relatedCast)::fromRelation($request, $paginator, $repository);
87+
break;
88+
case $paginator instanceof Collection:
89+
$this->value = $paginator;
90+
break;
91+
default:
92+
$this->value = $paginator;
93+
}
94+
95+
return $this;
96+
}
97+
3998
public function jsonSerialize()
4099
{
41100
return [

src/Fields/Concerns/Attachable.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Auth\Access\AuthorizationException;
1010
use Illuminate\Database\Eloquent\Relations\Pivot;
1111
use Illuminate\Support\Arr;
12+
use Illuminate\Validation\ValidationException;
1213

1314
trait Attachable
1415
{
@@ -17,6 +18,11 @@ trait Attachable
1718
*/
1819
private $canAttachCallback;
1920

21+
/**
22+
* @var Closure
23+
*/
24+
private $validationCallback;
25+
2026
/**
2127
* @var Closure
2228
*/
@@ -140,4 +146,38 @@ public function collectPivotFields(): PivotsCollection
140146
{
141147
return PivotsCollection::make($this->pivotFields);
142148
}
149+
150+
public function validationCallback(Closure $validationCallback)
151+
{
152+
$this->validationCallback = $validationCallback;
153+
154+
return $this;
155+
}
156+
157+
public function validate(RestifyRequest $request, $pivot): bool
158+
{
159+
if (is_callable($this->validationCallback)) {
160+
throw_unless(
161+
call_user_func($this->validationCallback, $request, $pivot),
162+
ValidationException::withMessages([__('Invalid data.')])
163+
);
164+
}
165+
166+
return true;
167+
}
168+
169+
public function unique(): self
170+
{
171+
$this->validationCallback = function (RestifyRequest $request, $pivot) {
172+
$valid = $this->getRelation($request->repository())
173+
->where($pivot->toArray())
174+
->count() === 0;
175+
176+
throw_unless($valid, ValidationException::withMessages([__('Invalid data. The relation must be unique.')]));
177+
178+
return $valid;
179+
};
180+
181+
return $this;
182+
}
143183
}

src/Repositories/Concerns/InteractsWithAttachers.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ public function authorizeBelongsToMany(RestifyRequest $request): self
2222
abort(400, "Missing BelongsToMany or MorphToMany field for [{$request->relatedRepository}]. This field should be in the related of the [{$class}] class. Or you are not authorized to use that repository (see `allowRestify` policy method).");
2323
}
2424

25-
$field->authorizeToAttach(
26-
$request,
27-
);
28-
2925
return $this;
3026
}
3127

src/Repositories/Repository.php

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,12 @@
2424
use Binaryk\LaravelRestify\Traits\InteractWithSearch;
2525
use Binaryk\LaravelRestify\Traits\PerformsQueries;
2626
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
27-
use Illuminate\Database\Eloquent\Builder;
2827
use Illuminate\Database\Eloquent\Model;
29-
use Illuminate\Database\Eloquent\Relations\Relation;
3028
use Illuminate\Http\Request;
3129
use Illuminate\Http\Resources\ConditionallyLoadsAttributes;
3230
use Illuminate\Http\Resources\DelegatesToResource;
3331
use Illuminate\Pagination\AbstractPaginator;
3432
use Illuminate\Routing\Router;
35-
use Illuminate\Support\Arr;
3633
use Illuminate\Support\Collection;
3734
use Illuminate\Support\Facades\DB;
3835
use Illuminate\Support\Str;
@@ -575,9 +572,7 @@ public function resolveIndexPivots(RestifyRequest $request): array
575572
*/
576573
public function resolveRelationships($request): array
577574
{
578-
$withs = collect();
579-
580-
static::collectRelated()
575+
return static::collectRelated()
581576
->authorized($request)
582577
->inRequest($request)
583578
->when($request->isShowRequest(), function (RelatedCollection $collection) use ($request) {
@@ -587,39 +582,9 @@ public function resolveRelationships($request): array
587582
return $collection->forIndex($request, $this);
588583
})
589584
->mapIntoRelated($request)
590-
->each(function (Related $related) use ($request, $withs) {
591-
$relation = $related->getRelation();
592-
593-
if (Str::contains($relation, '.')) {
594-
$this->resource->loadMissing($relation);
595-
596-
return $withs->put($key = Str::before($relation, '.'), Arr::get($this->resource->relationsToArray(), $key));
597-
}
598-
599-
/** * To avoid circular relationships and deep stack calls, we will do not load eager fields. */
600-
if ($related->isEager() && $this->isEagerState() === false) {
601-
return $withs->put($relation, $related->resolveField($this)->value);
602-
}
603-
604-
$paginator = $this->resource->relationLoaded($relation)
605-
? $this->resource->{$relation}
606-
: $this->resource->{$relation}();
607-
608-
collect([
609-
Builder::class => fn () => $withs->put($relation, (static::$relatedCast)::fromBuilder($request, $paginator, $this)),
610-
611-
Relation::class => fn () => $withs->put($relation, (static::$relatedCast)::fromRelation($request, $paginator, $this)),
612-
613-
Collection::class => fn () => $withs->put($relation, $paginator),
614-
615-
Model::class => fn () => fn () => $withs->put($relation, $paginator),
616-
617-
])->first(fn ($fn, $class) => $paginator instanceof $class,
618-
fn () => fn () => $withs->put($relation, $paginator)
619-
)();
620-
});
621-
622-
return $withs->all();
585+
->map(function (Related $related) use ($request) {
586+
return $related->resolve($request, $this)->getValue();
587+
})->all();
623588
}
624589

625590
/**
@@ -830,6 +795,8 @@ public function attach(RestifyRequest $request, $repositoryId, Collection $pivot
830795
$fields = $eagerField->collectPivotFields()->filter(fn ($pivotField) => $request->has($pivotField->attribute))->values();
831796

832797
$pivots->map(function ($pivot) use ($request, $fields, $eagerField) {
798+
$eagerField->validate($request, $pivot);
799+
833800
static::validatorForAttach($request)->validate();
834801

835802
static::fillFields($request, $pivot, $fields);

src/Resolvable.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify;
4+
5+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
6+
use Binaryk\LaravelRestify\Repositories\Repository;
7+
8+
interface Resolvable
9+
{
10+
public function resolve(RestifyRequest $request, Repository $repository): self;
11+
}

src/Services/Search/RepositorySearchService.php

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ public function search(RestifyRequest $request, Repository $repository)
2323
{
2424
$this->repository = $repository;
2525

26-
$query = $this->prepareMatchFields($request, $this->prepareSearchFields($request, $repository::query($request), $this->fixedInput), $this->fixedInput);
26+
$query = $this->prepareMatchFields(
27+
$request,
28+
$this->prepareSearchFields($request, $this->prepareRelations($request, $repository::query($request)), $this->fixedInput),
29+
$this->fixedInput);
2730

2831
$query = $this->applyFilters($request, $repository, $query);
2932

@@ -118,17 +121,9 @@ public function prepareOrders(RestifyRequest $request, $query)
118121
return $query;
119122
}
120123

121-
public function prepareRelations(RestifyRequest $request, $query, $extra = [])
124+
public function prepareRelations(RestifyRequest $request, $query)
122125
{
123-
$relations = array_merge($extra, explode(',', $request->input('related')));
124-
125-
foreach ($relations as $relation) {
126-
if (in_array($relation, $this->repository->getWiths())) {
127-
$query->with($relation);
128-
}
129-
}
130-
131-
return $query;
126+
return $query->with($this->repository->getWiths());
132127
}
133128

134129
public function prepareSearchFields(RestifyRequest $request, $query, $extra = [])

tests/Controllers/RepositoryIndexControllerTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ public function test_repository_with_nested_relations()
141141
$response = $this->getJson(CompanyRepository::uriKey().'?related=users.posts')
142142
->assertOk();
143143

144-
$this->assertCount(1, $response->json('data.0.relationships.users'));
145-
$this->assertCount(1, $response->json('data.0.relationships.users.0.posts'));
144+
$this->assertCount(1, $response->json('data.0.relationships')['users.posts']);
145+
$this->assertCount(1, $response->json('data.0.relationships')['users.posts'][0]['posts']);
146146
}
147147

148148
public function test_paginated_repository_with_relations()

tests/Feature/Filters/FilterDefinitionTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ public function test_can_filter_using_belongs_to_field()
9494
]),
9595
]);
9696

97-
$json = $this->getJson(PostRepository::uriKey().'?related=user&sort=-users.name')->json();
97+
$json = $this->getJson(PostRepository::uriKey().'?related=user&sort=-users.name')
98+
->json();
9899

99100
$this->assertSame(
100101
'Zez',

0 commit comments

Comments
 (0)