Skip to content

Commit 4adcdab

Browse files
authored
BelongsTo and HasOne fields. (#263)
* wip * Apply fixes from StyleCI (#262)
1 parent 3c2123c commit 4adcdab

File tree

8 files changed

+320
-1
lines changed

8 files changed

+320
-1
lines changed

src/Fields/BelongsTo.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,26 @@
33
namespace Binaryk\LaravelRestify\Fields;
44

55
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
6+
use Binaryk\LaravelRestify\Repositories\Repository;
67
use Closure;
78
use Illuminate\Database\Eloquent\Model;
89

910
class BelongsTo extends EagerField
1011
{
1112
public ?Closure $storeParentCallback;
1213

14+
public function __construct($attribute, $relation, $parentRepository)
15+
{
16+
if (! is_a(app($parentRepository), Repository::class)) {
17+
abort(500, "Invalid parent repository [{$parentRepository}]. Expended instance of ".Repository::class);
18+
}
19+
20+
parent::__construct($attribute);
21+
22+
$this->relation = $relation;
23+
$this->parentRepository = $parentRepository;
24+
}
25+
1326
public function storeParent(RestifyRequest $request, Model $child): self
1427
{
1528
if (is_callable($this->storeParentCallback)) {
@@ -23,7 +36,7 @@ public function storeParent(RestifyRequest $request, Model $child): self
2336

2437
$child->{$this->attribute} = null;
2538

26-
$child->{$this->attribute} = $child->{$this->relationship}()->create(
39+
$child->{$this->attribute} = $child->{$this->relation}()->create(
2740
$request->input($this->attribute)
2841
);
2942

@@ -36,4 +49,13 @@ public function storeParentCallback(callable $callback)
3649

3750
return $this;
3851
}
52+
53+
public function resolve($repository, $attribute = null)
54+
{
55+
$model = $repository->resource;
56+
57+
$this->value = $this->parentRepository::resolveWith(
58+
$model->{$this->relation}()->first()
59+
);
60+
}
3961
}

src/Fields/BelongsToMany.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\Fields;
4+
5+
class BelongsToMany extends Field
6+
{
7+
public function __construct($attribute, callable $resolveCallback = null, $repository = null)
8+
{
9+
parent::__construct($attribute, $resolveCallback);
10+
}
11+
}

src/Fields/EagerField.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Fields;
4+
5+
class EagerField extends Field
6+
{
7+
/**
8+
* @var string
9+
*/
10+
protected $relation;
11+
12+
/**
13+
* @var string
14+
*/
15+
protected $parentRepository;
16+
}

src/Fields/HasOne.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Fields;
4+
5+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
6+
use Binaryk\LaravelRestify\Models\CreationAware;
7+
use Binaryk\LaravelRestify\Repositories\Repository;
8+
use Illuminate\Database\Eloquent\Model;
9+
10+
class HasOne extends EagerField
11+
{
12+
public $storeParentCallback;
13+
14+
public function __construct($attribute, $relation, $parentRepository)
15+
{
16+
if (! is_a(app($parentRepository), Repository::class)) {
17+
abort(500, "Invalid HasOne repository [{$parentRepository}]. Expended instance of ".Repository::class);
18+
}
19+
20+
parent::__construct($attribute);
21+
22+
$this->relation = $relation;
23+
$this->parentRepository = $parentRepository;
24+
}
25+
26+
public function storeChild(RestifyRequest $request, Model $parent): self
27+
{
28+
if (is_callable($this->storeParentCallback)) {
29+
call_user_func_array($this->storeParentCallback, [
30+
$request,
31+
$parent,
32+
]);
33+
34+
return $this;
35+
}
36+
37+
$parent->{$this->attribute} = null;
38+
39+
$repository = $this->parentRepository::resolveWith(
40+
$model = $parent->{$this->relation}()->getModel()
41+
)->allowToStore($request, $request->input($this->attribute));
42+
43+
$model = new $model;
44+
45+
if ($model instanceof CreationAware) {
46+
$model::createWithAttributes($request->input($this->attribute));
47+
48+
return $this;
49+
}
50+
51+
$parent->{$this->attribute} = $repository::resolveWith(
52+
$model->create($request->input($this->attribute))
53+
);
54+
55+
return $this;
56+
}
57+
58+
public function storeParentCallback(callable $callback)
59+
{
60+
$this->storeParentCallback = $callback;
61+
62+
return $this;
63+
}
64+
65+
public function resolve($repository, $attribute = null)
66+
{
67+
$model = $repository->resource;
68+
69+
$this->value = $this->parentRepository::resolveWith(
70+
$model->{$this->relation}()->first()
71+
);
72+
}
73+
}

src/Repositories/Repository.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
use Binaryk\LaravelRestify\Contracts\RestifySearchable;
66
use Binaryk\LaravelRestify\Controllers\RestResponse;
77
use Binaryk\LaravelRestify\Exceptions\InstanceOfException;
8+
use Binaryk\LaravelRestify\Fields\EagerField;
89
use Binaryk\LaravelRestify\Fields\Field;
910
use Binaryk\LaravelRestify\Fields\FieldCollection;
11+
use Binaryk\LaravelRestify\Fields\HasOne;
1012
use Binaryk\LaravelRestify\Filter;
1113
use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreBulkRequest;
1214
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
@@ -319,10 +321,19 @@ private function updateBulkFields(RestifyRequest $request)
319321
private function storeFields(RestifyRequest $request)
320322
{
321323
return $this->collectFields($request)
324+
->filter(fn (Field $field) => ! $field instanceof EagerField)
322325
->forStore($request, $this)
323326
->authorizedStore($request);
324327
}
325328

329+
private function hasOneFields(RestifyRequest $request)
330+
{
331+
return $this->collectFields($request)
332+
->forStore($request, $this)
333+
->filter(fn (Field $field) => $field instanceof HasOne)
334+
->authorizedStore($request);
335+
}
336+
326337
private function storeBulkFields(RestifyRequest $request)
327338
{
328339
return $this->collectFields($request)
@@ -641,6 +652,9 @@ public function store(RestifyRequest $request)
641652
}
642653
}
643654

655+
$this->hasOneFields($request)
656+
->each(fn (HasOne $field) => $field->storeChild($request, $this->resource));
657+
644658
$this->storeFields($request)->each(fn (Field $field) => $field->invokeAfter($request, $this->resource));
645659
});
646660

tests/Fixtures/User/User.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ public function posts()
8282
return $this->hasMany(Post::class);
8383
}
8484

85+
public function post()
86+
{
87+
return $this->hasOne(Post::class);
88+
}
89+
8590
public function companies()
8691
{
8792
return $this->belongsToMany(Company::class, 'company_user', 'user_id', 'company_id')->withPivot([

tests/Unit/BelongsToFieldTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Unit;
4+
5+
use Binaryk\LaravelRestify\Fields\BelongsTo;
6+
use Binaryk\LaravelRestify\Fields\Field;
7+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
8+
use Binaryk\LaravelRestify\Repositories\Repository;
9+
use Binaryk\LaravelRestify\Restify;
10+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
11+
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
12+
use Binaryk\LaravelRestify\Tests\Fixtures\User\UserRepository;
13+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
14+
15+
class BelongsToFieldTest extends IntegrationTest
16+
{
17+
protected function setUp(): void
18+
{
19+
parent::setUp();
20+
Restify::repositories([
21+
PostWithUserRepository::class,
22+
]);
23+
}
24+
25+
public function test_field_will_be_returned_in_relations()
26+
{
27+
factory(Post::class)->create([
28+
'user_id' => factory(User::class),
29+
]);
30+
31+
$this->getJson('/restify-api/'.PostWithUserRepository::uriKey())
32+
->assertJsonStructure([
33+
'data' => [
34+
[
35+
'attributes' => [
36+
'user' => [
37+
'attributes',
38+
],
39+
],
40+
],
41+
],
42+
]);
43+
}
44+
45+
public function test_belongs_to_field_is_ignored_when_storing()
46+
{
47+
factory(Post::class)->create([
48+
'user_id' => factory(User::class),
49+
]);
50+
51+
$this->postJson('/restify-api/'.PostWithUserRepository::uriKey(), [
52+
'title' => 'New Post with user',
53+
'user' => [
54+
'name' => 'Eduard Lupacescu',
55+
'email' => '[email protected]',
56+
],
57+
])
58+
->assertCreated();
59+
}
60+
}
61+
62+
class PostWithUserRepository extends Repository
63+
{
64+
public static $model = Post::class;
65+
66+
public function fields(RestifyRequest $request)
67+
{
68+
return [
69+
Field::make('title'),
70+
71+
BelongsTo::make('user', 'user', UserRepository::class),
72+
];
73+
}
74+
75+
public static function uriKey()
76+
{
77+
return 'posts-with-user-repository';
78+
}
79+
}

tests/Unit/HasOneFieldTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Unit;
4+
5+
use Binaryk\LaravelRestify\Fields\Field;
6+
use Binaryk\LaravelRestify\Fields\HasOne;
7+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
8+
use Binaryk\LaravelRestify\Repositories\Repository;
9+
use Binaryk\LaravelRestify\Restify;
10+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
11+
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
12+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
13+
14+
class HasOneFieldTest extends IntegrationTest
15+
{
16+
protected function setUp(): void
17+
{
18+
parent::setUp();
19+
Restify::repositories([
20+
UserWithPostRepository::class,
21+
]);
22+
}
23+
24+
public function test_has_one_will_be_returned_in_relations()
25+
{
26+
$user = factory(User::class)->create();
27+
factory(Post::class)->create([
28+
'user_id' => $user->id,
29+
]);
30+
31+
$this->getJson('/restify-api/'.UserWithPostRepository::uriKey())
32+
->assertJsonStructure([
33+
'data' => [
34+
[
35+
'attributes' => [
36+
'name',
37+
'post',
38+
],
39+
],
40+
],
41+
]);
42+
}
43+
44+
public function test_has_one_field_is_saved_when_storing()
45+
{
46+
factory(Post::class)->create([
47+
'user_id' => factory(User::class),
48+
]);
49+
50+
$this->postJson('/restify-api/'.UserWithPostRepository::uriKey(), [
51+
'name' => 'Eduard Lupacescu',
52+
'email' => '[email protected]',
53+
'password' => 'secret!',
54+
'post' => [
55+
'title' => 'New Post with user',
56+
'description' => 'New Post description',
57+
],
58+
])
59+
->dump()
60+
->assertCreated();
61+
}
62+
}
63+
64+
class UserWithPostRepository extends Repository
65+
{
66+
public static $model = User::class;
67+
68+
public function fields(RestifyRequest $request)
69+
{
70+
return [
71+
Field::new('name'),
72+
Field::new('email'),
73+
Field::new('password'),
74+
75+
HasOne::make('post', 'post', PostRepository::class),
76+
];
77+
}
78+
79+
public static function uriKey()
80+
{
81+
return 'user-with-post-repository';
82+
}
83+
}
84+
85+
class PostRepository extends Repository
86+
{
87+
public static $model = Post::class;
88+
89+
public function fields(RestifyRequest $request)
90+
{
91+
return [
92+
Field::new('title')->storingRules('required')->messages([
93+
'required' => 'This field is required',
94+
]),
95+
96+
Field::new('description')->storingRules('required'),
97+
];
98+
}
99+
}

0 commit comments

Comments
 (0)