Skip to content

Commit 60ca5ae

Browse files
committed
add belongsToMany relation
1 parent 89acddd commit 60ca5ae

File tree

15 files changed

+640
-60
lines changed

15 files changed

+640
-60
lines changed

composer.lock

Lines changed: 15 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/relations.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,67 @@ class CountryModel extends Model
229229
}
230230
}
231231
```
232+
233+
### Usage
234+
235+
```php
236+
model(UserModel::class)->with('posts')->find(1);
237+
```
238+
239+
## Many to many
240+
241+
A `belongsToMany` relationship is used for many-to-many associations between two models. This relationship involves an intermediate pivot table that links records in one table to records in another.
242+
243+
### Example
244+
245+
Consider an application where:
246+
247+
- *Students* can enroll in multiple *Courses*.
248+
- A *Course* can have multiple *Students*.
249+
250+
Since both Students and Courses can be related to each other in many ways, we use a pivot table to manage this association.
251+
252+
```php
253+
class StudentModel extends Model
254+
{
255+
use HasRelations;
256+
257+
// ...
258+
259+
public function initialize()
260+
{
261+
$this->initRelations();
262+
}
263+
264+
public function courses(): Relation
265+
{
266+
return $this->belongsToMany(CourseModel::class);
267+
}
268+
}
269+
```
270+
```php
271+
class CourseModel extends Model
272+
{
273+
use HasRelations;
274+
275+
// ...
276+
277+
public function initialize()
278+
{
279+
$this->initRelations();
280+
}
281+
282+
public function students(): Relation
283+
{
284+
return $this->belongsToMany(StudentModel::class);
285+
}
286+
}
287+
```
288+
289+
### Usage
290+
291+
```php
292+
model(StudentModel::class)->with('courses')->find(1);
293+
294+
model(CourseModel::class)->with('students')->find(1);
295+
```

src/Enums/RelationTypes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ enum RelationTypes
99
case hasOne;
1010
case hasMany;
1111
case belongsTo;
12+
case belongsToMany;
1213
}

src/Many.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Michalsn\CodeIgniterNestedModel;
4+
5+
readonly class Many
6+
{
7+
public function __construct(public string $pivotTable, public string $pivotForeignKey, public string $pivotRelatedKey)
8+
{
9+
}
10+
}

src/Relation.php

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Relation
1717
private ?Closure $conditions = null;
1818
private ?OfMany $ofMany = null;
1919
private ?Through $through = null;
20+
private ?Many $many = null;
2021

2122
/**
2223
* @var list<With>
@@ -114,42 +115,92 @@ public function hasThrough(): bool
114115
return $this->through !== null;
115116
}
116117

117-
public function applyThrough(array $id, string $primaryKey): static
118+
public function setMany(string $pivotTable, string $pivotForeignKey, string $pivotRelatedKey): static
118119
{
119-
if ($this->through === null) {
120-
$this->model->whereIn(
121-
sprintf(
122-
'%s.%s',
123-
$this->model->getTable(),
124-
$this->foreignKey
125-
),
126-
$id
127-
);
120+
$this->many = new Many($pivotTable, $pivotForeignKey, $pivotRelatedKey);
121+
122+
return $this;
123+
}
124+
125+
public function hasMany(): bool
126+
{
127+
return $this->many !== null;
128+
}
129+
130+
public function getMany(): ?Many
131+
{
132+
return $this->many;
133+
}
134+
135+
public function applyRelation(array $id, string $primaryKey): static
136+
{
137+
if ($this->through !== null) {
138+
$this->model
139+
->select(sprintf('%s.*', $this->model->getTable()))
140+
->join(
141+
$this->through->model->getTable(),
142+
sprintf(
143+
'%s.%s = %s.%s',
144+
$this->through->model->getTable(),
145+
$this->through->foreignKey,
146+
$this->model->getTable(),
147+
$this->primaryKey
148+
),
149+
'LEFT'
150+
)
151+
->whereIn(
152+
sprintf(
153+
'%s.%s',
154+
$this->through->model->getTable(),
155+
$primaryKey
156+
),
157+
$id
158+
);
128159

129160
return $this;
130161
}
131162

132-
$this->model
133-
->select(sprintf('%s.*', $this->model->getTable()))
134-
->join(
135-
$this->through->model->getTable(),
136-
sprintf(
137-
'%s.%s = %s.%s',
138-
$this->through->model->getTable(),
139-
$this->through->foreignKey,
140-
$this->model->getTable(),
141-
$this->primaryKey
142-
),
143-
'LEFT'
144-
)
145-
->whereIn(
146-
sprintf(
147-
'%s.%s',
148-
$this->through->model->getTable(),
149-
$primaryKey
150-
),
151-
$id
152-
);
163+
if ($this->many !== null) {
164+
$this->model
165+
->select(
166+
sprintf(
167+
'%s.*, %s.%s',
168+
$this->model->getTable(),
169+
$this->many->pivotTable,
170+
$this->many->pivotForeignKey
171+
)
172+
)
173+
->join(
174+
$this->many->pivotTable,
175+
sprintf(
176+
'%s.%s = %s.%s',
177+
$this->many->pivotTable,
178+
$this->many->pivotRelatedKey,
179+
$this->model->getTable(),
180+
get_primary_key($this->model)
181+
),
182+
'LEFT'
183+
)
184+
->whereIn(
185+
sprintf(
186+
'%s.%s',
187+
$this->many->pivotTable,
188+
$this->many->pivotForeignKey
189+
),
190+
$id
191+
);
192+
193+
return $this;
194+
}
195+
196+
$this->model->whereIn(
197+
sprintf(
198+
'%s.%s',
199+
$this->model->getTable(),
200+
$this->foreignKey
201+
),
202+
$id
203+
);
153204

154205
return $this;
155206
}
@@ -208,4 +259,36 @@ private function getOrderField(Model $model): string
208259

209260
return $refProp->getValue($model);
210261
}
262+
263+
public function filterResult(array|Entity $row, string $returnType): array|Entity
264+
{
265+
if ($row === [] || $this->type !== RelationTypes::belongsToMany) {
266+
return $row;
267+
}
268+
269+
if ($returnType === 'array') {
270+
unset($row[$this->many->pivotForeignKey]);
271+
} else {
272+
unset($row->{$this->many->pivotForeignKey});
273+
}
274+
275+
return $row;
276+
}
277+
278+
public function filterResults(array|Entity $results, string $returnType): array|Entity
279+
{
280+
if ($this->type !== RelationTypes::belongsToMany) {
281+
return $results;
282+
}
283+
284+
foreach ($results as &$row) {
285+
if ($returnType === 'array') {
286+
unset($row[$this->many->pivotForeignKey]);
287+
} else {
288+
unset($row->{$this->many->pivotForeignKey});
289+
}
290+
}
291+
292+
return $results;
293+
}
211294
}

src/Traits/HasLazyRelations.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,21 @@ private function handleRelation(string $name)
3939

4040
$relation = $model->{$name}();
4141

42-
$relation->model->where($relation->foreignKey, $this->attributes[$relation->primaryKey]);
43-
4442
if (in_array($relation->type, [RelationTypes::hasOne, RelationTypes::belongsTo], true)) {
45-
$this->attributes[$name] = $relation->model->first();
43+
$row = $this->attributes[$name] = $relation->filterResult(
44+
$relation
45+
->applyRelation([$this->attributes[$relation->primaryKey]], $relation->foreignKey)
46+
->model
47+
->first(),
48+
'object'
49+
);
4650
} else {
47-
$this->attributes[$name] = $relation->model->findAll();
51+
$this->attributes[$name] = $relation->filterResults(
52+
$relation->applyRelation([$this->attributes[$relation->primaryKey]], $relation->foreignKey)
53+
->model
54+
->findAll(),
55+
'object'
56+
);
4857
}
4958

5059
return $this->attributes[$name];

0 commit comments

Comments
 (0)