Skip to content

Commit

Permalink
[7.x] Nested relationships using the same parent (#494)
Browse files Browse the repository at this point in the history
* fix: [7.x] Singleton issues fixes.

* Fix styling

* fix: wip

* Fix styling

* fix: wip

* fix: tests

* fix: fixing assoc for eager loading

* Fix styling

* fix: wip parent relationship

* Fix styling

* fix: wip

* Fix styling

* fix: fixing recursive issue

* Fix styling

* fix: upgrading guideline

* fix: wip

Co-authored-by: binaryk <[email protected]>
  • Loading branch information
binaryk and binaryk authored Jul 29, 2022
1 parent 2e1f19f commit 8a5ad1c
Show file tree
Hide file tree
Showing 22 changed files with 628 additions and 187 deletions.
6 changes: 6 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Upgrading

## From 7.3.1 to 7.3.2

## Breaking

- The `$eagerState` repository property is now private, and it is of type `null|string` because it holds the parent repository that renders it.

## From 6.x to 7.x

High impact:
Expand Down
19 changes: 18 additions & 1 deletion src/Eager/Related.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Binaryk\LaravelRestify\Eager;

use Binaryk\LaravelRestify\Fields\EagerField;
use Binaryk\LaravelRestify\Filters\RelatedQuery;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Repositories\Repository;
use Binaryk\LaravelRestify\Traits\HasColumns;
Expand Down Expand Up @@ -36,6 +37,8 @@ class Related implements JsonSerializable
*/
private $resolverCallback;

public ?RelatedQuery $relatedQuery = null;

public function __construct(string $relation, EagerField $field = null)
{
$this->relation = $relation;
Expand Down Expand Up @@ -67,7 +70,9 @@ public function resolveField(Repository $repository): EagerField

public function resolve(RestifyRequest $request, Repository $repository): self
{
$request->related()->resolved($repository::uriKey().$repository->getKey().$this->getRelation());
$request->related()->resolved($this->uniqueIdentifierForRepository($repository));

ray($request->related()->resolvedRelationships);

if (is_callable($this->resolverCallback)) {
$this->value = call_user_func($this->resolverCallback, $request, $repository);
Expand Down Expand Up @@ -129,6 +134,18 @@ public function resolveUsing(callable $resolver): self
return $this;
}

public function withRelatedQuery(RelatedQuery $relatedQuery): self
{
$this->relatedQuery = $relatedQuery;

return $this;
}

public function uniqueIdentifierForRepository(Repository $repository): string
{
return $repository::uriKey().$repository->getKey().$this->getRelation();
}

#[ReturnTypeWillChange]
public function jsonSerialize()
{
Expand Down
50 changes: 42 additions & 8 deletions src/Eager/RelatedCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function intoAssoc(): self
$mapKey = is_numeric($key) ? $value : $key;

if ($value instanceof EagerField) {
$mapKey = $key ?: $value->getAttribute();
$mapKey = (is_numeric($key) || empty($key)) ? $value->getAttribute() : $key;
}

return [
Expand Down Expand Up @@ -93,8 +93,30 @@ public function forIndex(RestifyRequest $request, Repository $repository): self

public function inRequest(RestifyRequest $request, Repository $repository): self
{
return $this->filter(function ($field, $key) use ($request, $repository) {
return $request->related()->hasRelation($repository::uriKey().'.'.$key);
return $this->filter(function (mixed $repositoryRelatedField, $repositoryRelatedKey) use (
$request,
$repository
) {
if ($repositoryRelatedField instanceof EagerField) {
if ($repository->getEagerParent()) {
$relatedKey = $repository->getEagerParent().'.'.$repositoryRelatedKey;
} elseif ($request->related()->rootKey === $repository::uriKey()) {
$relatedKey = $repository::uriKey().'.'.$repositoryRelatedKey;
} else {
$relatedKey = $repositoryRelatedKey;
}

$relatedQuery = $request->related()->getRelatedQueryFor($relatedKey);

if ($relatedQuery) {
$repositoryRelatedField->withRelatedQuery($relatedQuery);
}

return (bool) $relatedQuery;
}

// might be a closure or a normal relationship
return $request->related()->hasRelation($repository::uriKey().'.'.$repositoryRelatedKey);
});
}

Expand Down Expand Up @@ -144,10 +166,22 @@ public function forRequest(RestifyRequest $request, Repository $repository): sel

public function unserialized(RestifyRequest $request, Repository $repository)
{
return $this->filter(fn (Related $related) => ! in_array(
$repository::uriKey().$repository->getKey().$related->getRelation(),
$request->related()->resolvedRelationships,
true
));
return $this->filter(function (Related $related) use ($request, $repository) {
return ! in_array(
$related->uniqueIdentifierForRepository($repository),
$request->related()->resolvedRelationships,
true
);
});
}

public function markQuerySerialized(RestifyRequest $request, Repository $repository): self
{
return $this->each(function (Related $related) {
// dd($related->getValue());
$related->relatedQuery?->serialized();

return $related;
});
}
}
30 changes: 23 additions & 7 deletions src/Fields/EagerField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Binaryk\LaravelRestify\Fields;

use Binaryk\LaravelRestify\Filters\RelatedQuery;
use Binaryk\LaravelRestify\Repositories\Repository;
use Binaryk\LaravelRestify\Restify;
use Binaryk\LaravelRestify\Traits\HasColumns;
Expand Down Expand Up @@ -29,6 +30,8 @@ class EagerField extends Field
*/
public string $repositoryClass;

private RelatedQuery $relatedQuery;

public function __construct($attribute, string $parentRepository = null)
{
parent::__construct(attribute: $attribute);
Expand All @@ -40,7 +43,7 @@ public function __construct($attribute, string $parentRepository = null)
}

if (is_null($parentRepository)) {
$this->repositoryClass = tap(Restify::repositoryClassForKey($attribute),
$this->repositoryClass = tap(Restify::repositoryClassForKey(str($attribute)->pluralStudly()->kebab()->toString()),
fn ($repository) => abort_unless($repository, 400, "Repository not found for the key [$attribute]."));
}

Expand Down Expand Up @@ -79,15 +82,16 @@ public function resolve($repository, $attribute = null)
}

try {
$this->value = $this->repositoryClass::resolveWith($relatedModel)
/**
* @var Repository $serializableRepository
*/
$serializableRepository = $this->repositoryClass::resolveWith($relatedModel);

$this->value = $serializableRepository
->allowToShow(app(Request::class))
->columns()
->eager($this);
} catch (AuthorizationException $e) {
if (is_null($relatedModel)) {
abort(403, 'You are not authorized to perform this action.');
}

} catch (AuthorizationException) {
$class = get_class($relatedModel);
$field = class_basename(get_called_class());
$policy = get_class(Gate::getPolicyFor($relatedModel));
Expand Down Expand Up @@ -128,4 +132,16 @@ public function getQualifiedKey(
): string {
return $this->getRelation($repository)->getRelated()->getQualifiedKeyName();
}

public function withRelatedQuery(RelatedQuery $relatedQuery): self
{
$this->relatedQuery = $relatedQuery;

return $this;
}

public function queryKeyThatRendered(): string
{
return $this->relatedQuery->relation;
}
}
4 changes: 4 additions & 0 deletions src/Filters/RelatedDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class RelatedDto

private bool $loaded = false;

public string $rootKey = '';

public function __construct(
?RelatedQueryCollection $related = null,
) {
Expand Down Expand Up @@ -125,6 +127,8 @@ public function makeTree(): array

public function sync(Request $request, Repository $repository): self
{
$this->rootKey = $repository::uriKey();

if ($this->loaded) {
return $this;
}
Expand Down
61 changes: 28 additions & 33 deletions src/Repositories/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Binaryk\LaravelRestify\Repositories;

use App\Restify\MediaRepository;
use Binaryk\LaravelRestify\Actions\Action;
use Binaryk\LaravelRestify\Contracts\RestifySearchable;
use Binaryk\LaravelRestify\Eager\Related;
Expand Down Expand Up @@ -164,11 +163,13 @@ class Repository implements RestifySearchable, JsonSerializable
public static bool|array $public = false;

/**
* Indicates if the repository is serializing for a eager relationship.
* Indicates if the repository is serializing for an eager relationship.
*
* @var bool
* The $eagerState will reference to the parent that renders this via related.
*
* @var null|string
*/
public bool $eagerState = false;
private null|string $eagerState = null;

/**
* Extra fields attached to the repository. Useful when display pivot fields.
Expand All @@ -184,6 +185,8 @@ class Repository implements RestifySearchable, JsonSerializable
*/
private PivotsCollection $pivots;

private ?Repository $parentRepository = null;

public function __construct()
{
$this->bootIfNotBooted();
Expand Down Expand Up @@ -327,7 +330,7 @@ public function withResource($resource): self
public static function resolveWith(Model $model): Repository
{
if (static::isMock()) {
return static::getMock()?->withResource($model);
return clone static::getMock()?->withResource($model);
}

return resolve(static::class)->withResource($model);
Expand Down Expand Up @@ -393,12 +396,6 @@ public function resolveShowAttributes(RestifyRequest $request)
$fields = $this->collectFields($request)
->forShow($request, $this)
->filter(fn (Field $field) => $field->authorize($request))
->when(
$this->isEagerState(),
function ($items) {
return $items->filter(fn (Field $field) => ! $field instanceof EagerField);
}
)
->each(fn (Field $field) => $field->resolveForShow($this))
->map(fn (Field $field) => $field->serializeToValue($request))
->mapWithKeys(fn ($value) => $value)
Expand Down Expand Up @@ -535,7 +532,7 @@ public function resolveRelationships($request): array
return [];
}

$related = static::collectRelated()
return static::collectRelated()
->forRequest($request, $this)
->mapIntoRelated($request, $this)
->unserialized($request, $this)
Expand All @@ -548,26 +545,6 @@ public function resolveRelationships($request): array
return $items;
})
->all();

// if (static::class === MediaRepository::class) {
// $related = static::collectRelated()
// ->intoAssoc()
// ->forRequest($request, $this)
// ->mapIntoRelated($request, $this)
//// ->unserialized($request, $this)
// ->map(fn (Related $related) => $related->resolve($request, $this)->getValue())
// ->map(function (mixed $items) {
// if ($items instanceof Collection) {
// return $items->filter();
// }
//
// return $items;
// })
// ->all();
//
// }

return $related;
}

/**
Expand Down Expand Up @@ -1148,14 +1125,20 @@ public function eager(EagerField $field = null): Repository
return $this;
}

$this->eagerState = $field->queryKeyThatRendered();
$this->columns($field->getColumns());

return $this;
}

public function isEagerState(): bool
{
return $this->eagerState === true;
return ! is_null($this->eagerState);
}

public function getEagerParent(): string
{
return $this->eagerState ?? '';
}

public function restifyjsSerialize(RestifyRequest $request): array
Expand Down Expand Up @@ -1187,4 +1170,16 @@ public static function isPublic(): bool
{
return static::$public;
}

public function withParentRepository(Repository $repository): self
{
$this->parentRepository = $repository;

return $this;
}

public function parentRepository(): ?Repository
{
return $this->parentRepository;
}
}
22 changes: 10 additions & 12 deletions tests/Actions/FieldActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,16 @@ public function handle(RestifyRequest $request, Post $post, int $row)
Field::new('description')->action($action),
]);

$this
->withoutExceptionHandling()
->postJson(PostRepository::route('bulk'), [
[
'title' => $title1 = 'First title',
'description' => 'first description',
],
[
'title' => $title2 = 'Second title',
'description' => 'second description',
],
])
$this->postJson(PostRepository::route('bulk'), [
[
'title' => $title1 = 'First title',
'description' => 'first description',
],
[
'title' => $title2 = 'Second title',
'description' => 'second description',
],
])
->assertJson(
fn (AssertableJson $json) => $json
->where('data.0.title', $title1)
Expand Down
Loading

0 comments on commit 8a5ad1c

Please sign in to comment.