44
55namespace PDPhilip \Elasticsearch \Relations ;
66
7- use Illuminate \Database \Eloquent \Builder ;
87use Illuminate \Database \Eloquent \Collection ;
98use Illuminate \Database \Eloquent \Model ;
109use 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
2313class 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