Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@
]
}
},
"minimum-stability": "dev"
"minimum-stability": "stable"
}
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ protected function toRelationships(Request $request): array

`toRelationships` must returns an array, keyed by string, of `JsonApiResource` or `JsonApiCollection`.

#### Laravel conditional relationships
#### Laravel conditional relationships
_**@see** [laravel: eloquent-conditional-relationships](https://laravel.com/docs/9.x/eloquent-resources#conditional-relationships)_

Support laravel conditional relationships.
Expand Down Expand Up @@ -331,6 +331,7 @@ protected function toRelationships(Request $request): array
}
```


### toLinks
_**@see** [{json:api}: resource-linkage](https://jsonapi.org/format/#document-resource-object-links)_

Expand Down
12 changes: 6 additions & 6 deletions src/Descriptors/Describer.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,17 @@ public function whenFilled(): static
/**
* @param \Illuminate\Http\Request $request
* @param T $model
* @param string $field
* @param string $attribute
*
* @return mixed
*/
public function valueFor(Request $request, mixed $model, string $field): mixed
public function valueFor(Request $request, mixed $model, string $attribute): mixed
{
if (!$this->check($request, $model, $field)) {
if (!$this->check($request, $model, $attribute)) {
return new MissingValue();
}

return $this->resolveFor($request, $model, $field);
return $this->resolveFor($request, $model, $attribute);
}

/**
Expand Down Expand Up @@ -134,11 +134,11 @@ private function retrieveValue(mixed $model, string $attribute): mixed
/**
* @param \Illuminate\Http\Request $request
* @param T $model
* @param string $field
* @param string $attribute
*
* @return mixed
*/
abstract protected function resolveFor(Request $request, mixed $model, string $field): mixed;
abstract protected function resolveFor(Request $request, mixed $model, string $attribute): mixed;

/**
* @return string|Closure|null
Expand Down
57 changes: 31 additions & 26 deletions src/Descriptors/Relations/Relation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Ark4ne\JsonApi\Descriptors\Describer;
use Ark4ne\JsonApi\Resources\Relationship;
use Ark4ne\JsonApi\Support\Includes;
use Ark4ne\JsonApi\Traits\HasRelationLoad;
use Closure;
use Illuminate\Database\Eloquent\Model;
Expand All @@ -25,12 +26,13 @@ abstract class Relation extends Describer

/**
* @param class-string<\Ark4ne\JsonApi\Resources\JsonApiResource|\Ark4ne\JsonApi\Resources\JsonApiCollection> $related
* @param string|\Closure|null $relation
* @param string|\Closure|null $relation
*/
public function __construct(
protected string $related,
protected string $related,
protected null|string|Closure $relation
) {
)
{
}

/**
Expand Down Expand Up @@ -63,7 +65,7 @@ public function meta(Closure $meta): static

/**
* @param bool|null $whenIncluded
* @return $this
* @return static
*/
public function whenIncluded(null|bool $whenIncluded = null): static
{
Expand All @@ -73,39 +75,42 @@ public function whenIncluded(null|bool $whenIncluded = null): static
$this->whenIncluded = $whenIncluded;
}

return $this;
return $this->when(fn(
Request $request,
Model $model,
string $attribute
): bool => !$this->whenIncluded || Includes::include($request, $attribute));
}

/**
* @see \Illuminate\Http\Resources\ConditionallyLoadsAttributes::whenLoaded
*
* @param string|null $relation
*
* @return static
* @see \Illuminate\Http\Resources\ConditionallyLoadsAttributes::whenLoaded
*/
public function whenLoaded(null|string $relation = null): self
public function whenLoaded(null|string $relation = null): static
{
return $this->when(fn(
Request $request,
Model $model,
string $attribute
Model $model,
string $attribute
): bool => $model->relationLoaded($relation ?? (is_string($this->relation) ? $this->relation : $attribute)));
}

/**
* @see \Illuminate\Http\Resources\ConditionallyLoadsAttributes::whenPivotLoadedAs
*
* @param string $table
* @param string $table
* @param string|null $accessor
*
* @return static
* @see \Illuminate\Http\Resources\ConditionallyLoadsAttributes::whenPivotLoadedAs
*
*/
public function whenPivotLoaded(string $table, null|string $accessor = null): self
public function whenPivotLoaded(string $table, null|string $accessor = null): static
{
return $this->when(fn(
Request $request,
Model $model,
string $attribute
Model $model,
string $attribute
): bool => ($pivot = $model->{$accessor ?? (is_string($this->relation) ? $this->relation : $attribute)})
&& (
$pivot instanceof $table ||
Expand All @@ -114,21 +119,21 @@ public function whenPivotLoaded(string $table, null|string $accessor = null): se
);
}

public function resolveFor(Request $request, mixed $model, string $field): Relationship
public function resolveFor(Request $request, mixed $model, string $attribute): Relationship
{
$retriever = $this->retriever();

if ($retriever instanceof Closure) {
$value = static fn() => $retriever($model, $field);
$value = static fn() => $retriever($model, $attribute);
} else {
$value = static fn() => match (true) {
$model instanceof Model => $model->getRelationValue($retriever ?? $field),
Arr::accessible($model) => $model[$retriever ?? $field],
default => $model->{$retriever ?? $field}
$model instanceof Model => $model->getRelationValue($retriever ?? $attribute),
Arr::accessible($model) => $model[$retriever ?? $attribute],
default => $model->{$retriever ?? $attribute}
};
}

$relation = $this->value(fn() => $this->check($request, $model, $field) ? $value() : new MissingValue());
$relation = $this->value(fn() => $this->check($request, $model, $attribute) ? $value() : new MissingValue());

if ($this->whenIncluded !== null) {
$relation->whenIncluded($this->whenIncluded);
Expand All @@ -139,14 +144,14 @@ public function resolveFor(Request $request, mixed $model, string $field): Relat

/**
* @param \Illuminate\Http\Request $request
* @param T $model
* @param string $field
* @param T $model
* @param string $attribute
*
* @return mixed
*/
public function valueFor(Request $request, mixed $model, string $field): mixed
public function valueFor(Request $request, mixed $model, string $attribute): mixed
{
return $this->resolveFor($request, $model, $field);
return $this->resolveFor($request, $model, $attribute);
}

abstract protected function value(Closure $value): Relationship;
Expand Down
2 changes: 1 addition & 1 deletion src/Descriptors/Relations/RelationRaw.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ protected function value(Closure $value): Relationship

public static function fromRelationship(Relationship $relationship): self
{
return new self($relationship->getResource(), fn() => $relationship);
return new self($relationship->getResource(), static fn() => $relationship);
}
}
2 changes: 1 addition & 1 deletion src/Resources/Concerns/ConditionallyLoadsAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected function applyWhen(bool|Closure $condition, iterable $data): MergeValu
$value = new ValueMixed(is_callable($raw) ? $raw : static fn () => $raw);
}

return $value->when(fn () => value($condition));
return $value->when(static fn () => value($condition));
}));
}

Expand Down
7 changes: 7 additions & 0 deletions tests/Feature/Comment/CollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Test\Feature\Comment;

use DateTimeInterface;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Test\app\Http\Resources\PostResource;
Expand Down Expand Up @@ -82,6 +83,8 @@ private function getJsonResult(Collection $comments, ?array $attributes = null,
))
->reduce(fn(Collection $all, Collection $value) => $all->merge($value), collect());

$isLaravel12 = ((int)explode('.', Application::VERSION, 2)[0]) >= 12;

return collect(array_filter([
'data' => $data,
'included' => $include->uniqueStrict()->values()->all(),
Expand All @@ -99,21 +102,25 @@ private function getJsonResult(Collection $comments, ?array $attributes = null,
[
'active' => false,
'label' => "&laquo; Previous",
...($isLaravel12 ? ['page' => null] : []),
'url' => null,
],
[
'active' => true,
'label' => '1',
...($isLaravel12 ? ['page' => 1] : []),
'url' => "http://localhost/comment?page=1",
],
...(array_map(static fn($value) => [
'active' => false,
'label' => (string)$value,
...($isLaravel12 ? ['page' => $value] : []),
'url' => "http://localhost/comment?page=$value",
], range(2, 10))),
[
'active' => false,
'label' => "Next &raquo;",
...($isLaravel12 ? ['page' => 2] : []),
'url' => "http://localhost/comment?page=2",
],
],
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function testSchema()
$user->loads['main-post'] = 'post';
$user->loads['posts'] = 'posts';
$user->loads['comments'] = [
'comments' => fn(Builder $q) => $q->where('content', 'like', '%e%')
'comments' => UserResource::schema()->loads['comments']['comments']
];

$post->relationships['user'] = $user;
Expand Down
6 changes: 3 additions & 3 deletions tests/Unit/Descriptors/ValueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public static function modelsValues()
* @dataProvider values
*/
#[DataProvider('values')]
public function testConvertValue($class, $value, $excepted)
public function testConvertValue($class, $value, $excepted, $_ignored)
{
/** @var \Ark4ne\JsonApi\Descriptors\Values\Value $v */
$v = new $class(null);
Expand All @@ -102,7 +102,7 @@ public function testConvertValue($class, $value, $excepted)
* @dataProvider modelsValues
*/
#[DataProvider('modelsValues')]
public function testValueFor($model, $class, $value, $excepted)
public function testValueFor($model, $class, $value, $excepted, $_ignored)
{
data_set($model, 'attr', $value);

Expand All @@ -118,7 +118,7 @@ public function testValueFor($model, $class, $value, $excepted)
* @dataProvider modelsValues
*/
#[DataProvider('modelsValues')]
public function testValueForWithNull($model, $class, $value, $excepted)
public function testValueForWithNull($model, $class, $value, $excepted, $_ignored)
{
data_set($model, 'attr', null);

Expand Down
65 changes: 34 additions & 31 deletions tests/Unit/Resources/Concerns/ConditionallyLoadsAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,46 +78,49 @@ public function testApplyWhen()
'missing.1' => 'abc',
'missing.2' => 123,
]);
$this->assertEquals(new MergeValue([
'missing.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => false),
'missing.2' => (new ValueMixed(fn() => 123))->when(fn() => false),
]), $actual);
$this->assertInstanceOf(MergeValue::class, $actual);
$this->assertInstanceOf(ValueMixed::class, $actual->data['missing.1']);
$this->assertInstanceOf(ValueMixed::class, $actual->data['missing.2']);
$this->assertEquals('abc', $actual->data['missing.1']->retriever()());
$this->assertEquals(123, $actual->data['missing.2']->retriever()());
$actual = Reflect::invoke($stub, 'applyWhen', true, [
'present.1' => 'abc',
'present.2' => 123,
]);
$this->assertEquals(new MergeValue([
'present.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => true),
'present.2' => (new ValueMixed(fn() => 123))->when(fn() => true),
]), $actual);
$this->assertInstanceOf(ValueMixed::class, $actual->data['present.1']);
$this->assertInstanceOf(ValueMixed::class, $actual->data['present.2']);
$this->assertEquals('abc', $actual->data['present.1']->retriever()());
$this->assertEquals(123, $actual->data['present.2']->retriever()());

$actual = Reflect::invoke($stub, 'applyWhen', true, [
'present.1' => (new ValueMixed(fn() => 'abc')),
'present.2' => (new ValueMixed(fn() => 123)),
'present.3' => (new RelationOne('present', fn() => 'abc')),
'present.4' => (new RelationOne('present', fn() => 123)),
'present.5' => (new Relationship(UserResource::class, fn() => null)),
'present.1' => $p1 = (new ValueMixed(fn() => 'abc')),
'present.2' => $p2 = (new ValueMixed(fn() => 123)),
'present.3' => $p3 = (new RelationOne('present', fn() => 'abc')),
'present.4' => $p4 = (new RelationOne('present', fn() => 123)),
'present.5' => $p5 = (new Relationship(UserResource::class, fn() => null)),
]);
$this->assertEquals(new MergeValue([
'present.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => true),
'present.2' => (new ValueMixed(fn() => 123))->when(fn() => true),
'present.3' => (new RelationOne('present', fn() => 'abc'))->when(fn() => true),
'present.4' => (new RelationOne('present', fn() => 123))->when(fn() => true),
'present.5' => RelationRaw::fromRelationship(new Relationship(UserResource::class, fn() => null))->when(fn() => true),
]), $actual);
$this->assertInstanceOf(MergeValue::class, $actual);
$this->assertEquals($p1, $actual->data['present.1']);
$this->assertEquals($p2, $actual->data['present.2']);
$this->assertEquals($p3, $actual->data['present.3']);
$this->assertEquals($p4, $actual->data['present.4']);
$this->assertInstanceOf(RelationRaw::class, $actual->data['present.5']);
$this->assertInstanceOf(Relationship::class, $actual->data['present.5']->retriever()());

$actual = Reflect::invoke($stub, 'applyWhen', false, [
'missing.1' => (new ValueMixed(fn() => 'abc')),
'missing.2' => (new ValueMixed(fn() => 123)),
'missing.3' => (new RelationOne('present', fn() => 'abc')),
'missing.4' => (new RelationOne('present', fn() => 123)),
'missing.1' => $p1 = (new ValueMixed(fn() => 'abc')),
'missing.2' => $p2 = (new ValueMixed(fn() => 123)),
'missing.3' => $p3 = (new RelationOne('present', fn() => 'abc')),
'missing.4' => $p4 = (new RelationOne('present', fn() => 123)),
'missing.5' => (new Relationship(UserResource::class, fn() => null)),
]);
$this->assertEquals(new MergeValue([
'missing.1' => (new ValueMixed(fn() => 'abc'))->when(fn() => false),
'missing.2' => (new ValueMixed(fn() => 123))->when(fn() => false),
'missing.3' => (new RelationOne('present', fn() => 'abc'))->when(fn() => false),
'missing.4' => (new RelationOne('present', fn() => 123))->when(fn() => false),
'missing.5' => RelationRaw::fromRelationship(new Relationship(UserResource::class, fn() => null))->when(fn() => false),
]), $actual);
$this->assertInstanceOf(MergeValue::class, $actual);
$this->assertEquals($p1, $actual->data['missing.1']);
$this->assertEquals($p2, $actual->data['missing.2']);
$this->assertEquals($p3, $actual->data['missing.3']);
$this->assertEquals($p4, $actual->data['missing.4']);
$this->assertInstanceOf(RelationRaw::class, $actual->data['missing.5']);
$this->assertInstanceOf(Relationship::class, $actual->data['missing.5']->retriever()());
}

public function testWhenHas()
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Support/ValuesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public static function dataAttribute()
* @dataProvider dataAttribute
*/
#[DataProvider('dataAttribute')]
public function testHasAttribute($data, $attribute, $expected)
public function testHasAttribute($data, $attribute, $expected, $_ignored)
{
$this->assertEquals($expected, Values::hasAttribute($data, $attribute));
}
Expand Down