Create abstract class contains common logic.
namespace App\Models\Relations;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection as BaseCollection;
abstract class RelationWithCompositeKeys extends Relation
{
/** @var Model|Builder */
protected $query;
/** @var Model */
protected $parent;
/** @var array */
protected array $foreignKeys;
/** @var array */
protected array $localKeys;
/**
* Create a new has one or many relationship instance.
*
* @param Model $parent
* @param Builder $query
* @param array $foreignKeys
* @param array $localKeys
*/
public function __construct(Model $parent, Builder $query, array $foreignKeys, array $localKeys)
{
$this->localKeyss = $localKeys;
$this->foreignKeys = $foreignKeys;
parent::__construct($query, $parent);
}
/**
* @return void
*/
public function addConstraints(): void
{
if (static::$constraints) {
$this
->query
->join($this->parent->getTable(), function (JoinClause $join): void {
foreach ($this->localKeys as $index => $key) {
if ($index === 0) {
$join->on(
$this->getQualifiedKeyName($key),
$this->getQualifiedForeignKeyName($this->foreignKeys[$index])
);
$join->where($this->getQualifiedKeyName($key), $this->parent->{$key});
continue;
}
$join->whereColumn(
$this->getQualifiedKeyName($key),
$this->getQualifiedForeignKeyName($this->foreignKeys[$index])
);
}
})
->select($this->getQualifiedForeignKeyName('*'));
}
}
/**
* @param array $models
*/
public function addEagerConstraints(array $models): void
{
foreach ($this->localKeys as $index => $key) {
$this->query
->whereIn(
$this->getQualifiedForeignKeyName($this->foreignKeys[$index]),
collect($models)->pluck($key)
);
}
}
/**
* @param array $models
* @param string $relation
* @return array<Model>
*/
public function initRelation(array $models, $relation): array
{
foreach ($models as $model) {
$model->setRelation(
$relation,
$this->related->newCollection()
);
}
return $models;
}
/**
* @param array $models
* @param Collection $results
* @param string $relation
* @return array<Model>
*/
public function match(array $models, Collection $results, $relation): array
{
if ($results->isEmpty()) {
return $models;
}
foreach ($models as $model) {
$model->setRelation(
$relation,
$this->getRelationValue($results, $model),
);
}
return $models;
}
/**
* @return Collection|array<Model>|Model
*/
abstract public function getResults();
/**
* @param Collection|BaseCollection $results
* @param Model $model
* @return Collection|array<Model>|Model|null
*/
protected function getRelationValue($results, Model $model)
{
foreach ($this->foreignKeys as $index => $key) {
$results = $results->filter(function (Model $related) use ($model, $index, $key) {
return $related->{$key} === $model->{$this->localKeys[$index]};
});
}
return $results->values();
}
/**
* @param string $key
* @return string
*/
protected function getQualifiedForeignKeyName(string $key): string
{
return "{$this->query->getModel()->getTable()}.$key";
}
/**
* @param string $key
* @return string
*/
protected function getQualifiedKeyName(string $key): string
{
return "{$this->parent->getTable()}.$key";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
Create classes extends the above abstract class:
namespace App\Models\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection as BaseCollection;
class HasOneWithCompositeKeys extends RelationWithCompositeKeys
{
/**
* @return ?Model
*/
public function getResults(): ?Model
{
return $this->query->first();
}
/**
* @param Collection|BaseCollection $results
* @param Model $model
* @return Model
*/
protected function getRelationValue($results, Model $model): Model
{
$relations = parent::getRelationValue($results, $model);
return $relations->first();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace App\Models\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class HasManyWithCompositeKeys extends RelationWithCompositeKeys
{
/**
* @return Collection|array<Model>
*/
public function getResults(): Collection
{
return $this->query->get();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace App\Models\Relations;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class HasManyWithCompositeKeys extends RelationWithCompositeKeys
{
/**
* @return Collection|array<Model>
*/
public function getResults(): Collection
{
return $this->query->get();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Create traits to use in Models:
namespace App\Models\Traits;
use App\Models\Relations\HasManyWithCompositeKeys;
use App\Models\Relations\HasOneWithCompositeKeys;
trait HasRelationshipWithCompositeKeys
{
/**
* @param $related
* @param array $foreignKeys
* @param array $localKeys
* @return HasOneWithCompositeKeys
*/
public function hasOneWithCompositeKeys($related, array $foreignKeys, array $localKeys): HasOneWithCompositeKeys
{
return new HasOneWithCompositeKeys(
$this,
$this->newRelatedInstance($related)->newQuery(),
$foreignKeys,
$localKeys
);
}
/**
* @param $related
* @param array $foreignKeys
* @param array $localKeys
* @return HasManyWithCompositeKeys
*/
public function hasManyWithCompositeKeys($related, array $foreignKeys, array $localKeys): HasManyWithCompositeKeys
{
return new HasManyWithCompositeKeys(
$this,
$this->newRelatedInstance($related)->newQuery(),
$foreignKeys,
$localKeys
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Usage:
namespace App;
use Illuminate\Database\Eloquent\Model;
class A extends Model
{
use \App\Models\Traits\HasRelationshipWithCompositeKeys;
public function b()
{
return $this->hasManyWithCompositeKeys('B', ['foreignKey1', 'foreignKey2'], ['localKey1', 'localKey2']);
}
public function c()
{
return $this->hasOneWithCompositeKeys('C', ['foreignKey1', 'foreignKey2'], ['localKey1', 'localKey2']);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Refs:
- https://stitcher.io/blog/laravel-custom-relation-classes
Comment