Skip to content

Commit ed6848f

Browse files
committed
BelongsToMany/MorphToMany Refactoring
1 parent bb29d4b commit ed6848f

4 files changed

Lines changed: 393 additions & 542 deletions

File tree

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ parameters:
77

88
-
99
message: "#^Method Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:push\\(\\) invoked with 3 parameters, 0 required\\.$#"
10-
count: 6
10+
count: 4
1111
path: src/Relations/MorphToMany.php

src/Relations/BelongsToMany.php

Lines changed: 29 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -4,75 +4,32 @@
44

55
namespace PDPhilip\Elasticsearch\Relations;
66

7-
use Illuminate\Database\Eloquent\Builder;
87
use Illuminate\Database\Eloquent\Collection;
98
use Illuminate\Database\Eloquent\Model;
109
use Illuminate\Database\Eloquent\Relations\BelongsToMany as EloquentBelongsToMany;
11-
use Illuminate\Support\Arr;
12-
13-
use function array_diff;
14-
use function array_keys;
15-
use function array_map;
16-
use function array_merge;
17-
use function array_values;
18-
use function assert;
19-
use function count;
20-
use function in_array;
21-
use function is_numeric;
10+
use PDPhilip\Elasticsearch\Relations\Traits\InteractsWithPivotTable;
11+
use PDPhilip\Elasticsearch\Relations\Traits\ManagesManyToMany;
2212

2313
class BelongsToMany extends EloquentBelongsToMany
2414
{
25-
/**
26-
* Get the key for comparing against the parent key in "has" query.
27-
*
28-
* @return string
29-
*/
15+
use InteractsWithPivotTable;
16+
use ManagesManyToMany;
17+
use ManagesRefresh;
18+
3019
public function getHasCompareKey()
3120
{
3221
return $this->getForeignKey();
3322
}
3423

35-
/** {@inheritdoc} */
36-
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
37-
{
38-
return $query;
39-
}
40-
41-
/** {@inheritdoc} */
42-
protected function hydratePivotRelation(array $models)
43-
{
44-
// Do nothing.
45-
}
46-
47-
/**
48-
* Set the select clause for the relation query.
49-
*
50-
* @return array
51-
*/
5224
protected function getSelectColumns(array $columns = ['*'])
5325
{
5426
return $columns;
5527
}
5628

57-
/** {@inheritdoc} */
58-
protected function shouldSelect(array $columns = ['*'])
59-
{
60-
return $columns;
61-
}
62-
63-
/** {@inheritdoc} */
64-
public function addConstraints()
65-
{
66-
if (static::$constraints) {
67-
$this->setWhere();
68-
}
69-
}
29+
// ------------------------------------------------------------------
30+
// Constraint & sync hooks
31+
// ------------------------------------------------------------------
7032

71-
/**
72-
* Set the where clause for the relation query.
73-
*
74-
* @return $this
75-
*/
7633
protected function setWhere()
7734
{
7835
$foreign = $this->getForeignKey();
@@ -81,172 +38,69 @@ protected function setWhere()
8138
return $this;
8239
}
8340

84-
/** {@inheritdoc} */
85-
public function save(Model $model, array $pivotAttributes = [], $touch = true)
41+
protected function getCurrentSyncIds(): array
8642
{
87-
$model->save(['touch' => false]);
88-
89-
$this->attach($model, $pivotAttributes, $touch);
90-
91-
return $model;
92-
}
93-
94-
/** {@inheritdoc} */
95-
public function create(array $attributes = [], array $joining = [], $touch = true)
96-
{
97-
$instance = $this->related->newInstance($attributes);
98-
99-
// Once we save the related model, we need to attach it to the base model via
100-
// through intermediate table so we'll use the existing "attach" method to
101-
// accomplish this which will insert the record and any more attributes.
102-
$instance->save(['touch' => false]);
103-
104-
$this->attach($instance, $joining, $touch);
105-
106-
return $instance;
107-
}
108-
109-
/** {@inheritdoc} */
110-
public function sync($ids, $detaching = true)
111-
{
112-
$changes = [
113-
'attached' => [],
114-
'detached' => [],
115-
'updated' => [],
116-
];
117-
118-
if ($ids instanceof Collection) {
119-
$ids = $this->parseIds($ids);
120-
} elseif ($ids instanceof Model) {
121-
$ids = $this->parseIds($ids);
122-
}
123-
124-
// First we need to attach any of the associated models that are not currently
125-
// in this joining table. We'll spin through the given IDs, checking to see
126-
// if they exist in the array of current ones, and if not we will insert.
127-
$current = match (\PDPhilip\Elasticsearch\Eloquent\Model::isElasticsearchModel($this->parent)) {
128-
true => $this->parent->{$this->relatedPivotKey} ?: [],
129-
false => $this->parent->{$this->relationName} ?: [],
130-
};
43+
$current = $this->isElasticParent()
44+
? ($this->parent->{$this->relatedPivotKey} ?: [])
45+
: ($this->parent->{$this->relationName} ?: []);
13146

13247
if ($current instanceof Collection) {
133-
$current = $this->parseIds($current);
48+
return $this->parseIds($current);
13449
}
13550

136-
$records = $this->formatRecordsList($ids);
137-
138-
$current = Arr::wrap($current);
139-
140-
$detach = array_diff($current, array_keys($records));
141-
142-
// We need to make sure we pass a clean array, so that it is not interpreted
143-
// as an associative array.
144-
$detach = array_values($detach);
145-
146-
// Next, we will take the differences of the currents and given IDs and detach
147-
// all of the entities that exist in the "current" array but are not in the
148-
// the array of the IDs given to the method which will complete the sync.
149-
if ($detaching && count($detach) > 0) {
150-
$this->detach($detach);
151-
152-
$changes['detached'] = (array) array_map(function ($v) {
153-
return is_numeric($v) ? (int) $v : (string) $v;
154-
}, $detach);
155-
}
156-
157-
// Now we are finally ready to attach the new records. Note that we'll disable
158-
// touching until after the entire operation is complete so we don't fire a
159-
// ton of touch operations until we are totally done syncing the records.
160-
$changes = array_merge(
161-
$changes,
162-
$this->attachNew($records, $current, false),
163-
);
164-
165-
if (count($changes['attached']) || count($changes['updated'])) {
166-
$this->touchIfTouching();
167-
}
168-
169-
return $changes;
51+
return $current;
17052
}
17153

172-
/** {@inheritdoc} */
173-
public function updateExistingPivot($id, array $attributes, $touch = true)
174-
{
175-
// Do nothing, we have no pivot table.
176-
return $this;
177-
}
54+
// ------------------------------------------------------------------
55+
// Attach / Detach
56+
// ------------------------------------------------------------------
17857

179-
/** {@inheritdoc} */
18058
public function attach($id, array $attributes = [], $touch = true)
18159
{
18260
if ($id instanceof Model) {
18361
$model = $id;
184-
18562
$id = $this->parseId($model);
186-
187-
// Attach the new parent id to the related model.
18863
$model->push($this->foreignPivotKey, $this->parent->{$this->parentKey}, true);
18964
} else {
19065
if ($id instanceof Collection) {
19166
$id = $this->parseIds($id);
19267
}
19368

19469
$query = $this->newRelatedQuery();
195-
19670
$query->whereIn($this->relatedKey, (array) $id);
197-
198-
// Attach the new parent id to the related model.
19971
$query->push($this->foreignPivotKey, $this->parent->{$this->parentKey}, true);
20072
}
20173

202-
// Attach the new ids to the parent model.
203-
if (\PDPhilip\Elasticsearch\Eloquent\Model::isElasticsearchModel($this->parent)) {
74+
if ($this->isElasticParent()) {
20475
$this->parent->push($this->relatedPivotKey, (array) $id, true);
20576
} else {
206-
$instance = new $this->related;
207-
$instance->forceFill([$this->relatedKey => $id]);
208-
$relationData = $this->parent->{$this->relationName}->push($instance)->unique($this->relatedKey);
209-
$this->parent->setRelation($this->relationName, $relationData);
77+
$this->addIdToParentRelationData($id);
21078
}
21179

212-
if (! $touch) {
213-
return;
80+
if ($touch) {
81+
$this->touchIfTouching();
21482
}
215-
216-
$this->touchIfTouching();
21783
}
21884

219-
/** {@inheritdoc} */
22085
public function detach($ids = [], $touch = true)
22186
{
22287
if ($ids instanceof Model) {
22388
$ids = $this->parseIds($ids);
22489
}
22590

226-
$query = $this->newRelatedQuery();
227-
228-
// If associated IDs were passed to the method we will only delete those
229-
// associations, otherwise all of the association ties will be broken.
230-
// We'll return the numbers of affected rows when we do the deletes.
23191
$ids = (array) $ids;
23292

233-
// Detach all ids from the parent model.
234-
if (\PDPhilip\Elasticsearch\Eloquent\Model::isElasticsearchModel($this->parent)) {
93+
if ($this->isElasticParent()) {
23594
$this->parent->pull($this->relatedPivotKey, $ids);
23695
} else {
237-
$value = $this->parent->{$this->relationName}
238-
->filter(fn ($rel) => ! in_array($rel->{$this->relatedKey}, $ids));
239-
$this->parent->setRelation($this->relationName, $value);
96+
$this->removeIdsFromParentRelationData($ids);
24097
}
24198

242-
// Prepare the query to select all related objects.
99+
$query = $this->newRelatedQuery();
243100
if (count($ids) > 0) {
244101
$query->whereIn($this->relatedKey, $ids);
245102
}
246103

247-
// Remove the relation to the parent.
248-
assert($this->parent instanceof Model);
249-
assert($query instanceof \PDPhilip\Elasticsearch\Eloquent\Builder);
250104
$query->pull($this->foreignPivotKey, $this->parent->{$this->parentKey});
251105

252106
if ($touch) {
@@ -256,14 +110,13 @@ public function detach($ids = [], $touch = true)
256110
return count($ids);
257111
}
258112

259-
/** {@inheritdoc} */
113+
// ------------------------------------------------------------------
114+
// Dictionary & key helpers
115+
// ------------------------------------------------------------------
116+
260117
protected function buildDictionary(Collection $results)
261118
{
262119
$foreign = $this->foreignPivotKey;
263-
264-
// First we will build a dictionary of child models keyed by the foreign key
265-
// of the relation so that we will easily and quickly match them to their
266-
// parents without having a possibly slow inner loops for every models.
267120
$dictionary = [];
268121

269122
foreach ($results as $result) {
@@ -275,52 +128,13 @@ protected function buildDictionary(Collection $results)
275128
return $dictionary;
276129
}
277130

278-
/** {@inheritdoc} */
279-
public function newPivotQuery()
280-
{
281-
return $this->newRelatedQuery();
282-
}
283-
284-
/**
285-
* Create a new query builder for the related model.
286-
*
287-
* @return Builder|Model
288-
*/
289-
public function newRelatedQuery()
290-
{
291-
return $this->related->newQuery();
292-
}
293-
294-
/**
295-
* Get the fully qualified foreign key for the relation.
296-
*
297-
* @return string
298-
*/
299131
public function getForeignKey()
300132
{
301133
return $this->foreignPivotKey;
302134
}
303135

304-
/** {@inheritdoc} */
305136
public function getQualifiedForeignPivotKeyName()
306137
{
307138
return $this->foreignPivotKey;
308139
}
309-
310-
/** {@inheritdoc} */
311-
public function getQualifiedRelatedPivotKeyName()
312-
{
313-
return $this->relatedPivotKey;
314-
}
315-
316-
/**
317-
* Get the name of the "where in" method for eager loading.
318-
*
319-
* @param string $key
320-
* @return string
321-
*/
322-
protected function whereInMethod(Model $model, $key)
323-
{
324-
return 'whereIn';
325-
}
326140
}

0 commit comments

Comments
 (0)