Passed
Pull Request — master (#40)
by Giacomo
05:03
created

EmbedsMany::__call()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
1
<?php
2
3
namespace OfflineAgency\MongoAutoSync\Relationships;
4
5
use Illuminate\Database\Eloquent\Collection;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Model as EloquentModel;
8
use Illuminate\Pagination\LengthAwarePaginator;
9
use Illuminate\Pagination\Paginator;
10
use MongoDB\BSON\ObjectID;
11
12
class EmbedsMany extends EmbedsOneOrMany
13
{
14
    /**
15
     * {@inheritdoc}
16
     */
17
    public function initRelation(array $models, $relation)
18
    {
19
        foreach ($models as $model) {
20
            $model->setRelation($relation, $this->related->newCollection());
21
        }
22
23
        return $models;
24
    }
25
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public function getResults()
30
    {
31
        return $this->toCollection($this->getEmbedded());
32
    }
33
34
    /**
35
     * Save a new model and attach it to the parent model.
36
     * @param Model $model
37
     * @return Model|bool
38
     */
39
    public function performInsert(Model $model)
40
    {
41
        // Generate a new key if needed.
42
        if ($model->getKeyName() == '_id' && ! $model->getKey()) {
43
            $model->setAttribute('_id', new ObjectID);
44
        }
45
46
        // For deeply nested documents, let the parent handle the changes.
47
        if ($this->isNested()) {
48
            $this->associate($model);
49
50
            return $this->parent->save() ? $model : false;
51
        }
52
53
        // Push the new model to the database.
54
        $result = $this->getBaseQuery()->push($this->localKey, $model->getAttributes(), true);
55
56
        // Attach the model to its parent.
57
        if ($result) {
58
            $this->associate($model);
59
        }
60
61
        return $result ? $model : false;
62
    }
63
64
    /**
65
     * Save an existing model and attach it to the parent model.
66
     * @param Model $model
67
     * @return Model|bool
68
     */
69
    public function performUpdate(Model $model)
70
    {
71
        // For deeply nested documents, let the parent handle the changes.
72
        if ($this->isNested()) {
73
            $this->associate($model);
74
75
            return $this->parent->save();
76
        }
77
78
        // Get the correct foreign key value.
79
        $foreignKey = $this->getForeignKeyValue($model);
80
81
        $values = $this->getUpdateValues($model->getDirty(), $this->localKey.'.$.');
82
83
        // Update document in database.
84
        $result = $this->getBaseQuery()->where($this->localKey.'.'.$model->getKeyName(), $foreignKey)
85
            ->update($values);
86
87
        // Attach the model to its parent.
88
        if ($result) {
89
            $this->associate($model);
90
        }
91
92
        return $result ? $model : false;
93
    }
94
95
    /**
96
     * Delete an existing model and detach it from the parent model.
97
     * @param Model $model
98
     * @return int
99
     */
100
    public function performDelete(Model $model)
101
    {
102
        // For deeply nested documents, let the parent handle the changes.
103
        if ($this->isNested()) {
104
            $this->dissociate($model);
105
106
            return $this->parent->save();
107
        }
108
109
        // Get the correct foreign key value.
110
        $foreignKey = $this->getForeignKeyValue($model);
111
112
        $result = $this->getBaseQuery()->pull($this->localKey, [$model->getKeyName() => $foreignKey]);
113
114
        if ($result) {
115
            $this->dissociate($model);
116
        }
117
118
        return $result;
119
    }
120
121
    /**
122
     * Associate the model instance to the given parent, without saving it to the database.
123
     * @param Model $model
124
     * @return Model
125
     */
126
    public function associate(Model $model)
127
    {
128
        if (! $this->contains($model)) {
0 ignored issues
show
Bug introduced by
The method contains() does not exist on OfflineAgency\MongoAutoS...elationships\EmbedsMany. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

128
        if (! $this->/** @scrutinizer ignore-call */ contains($model)) {
Loading history...
129
            return $this->associateNew($model);
130
        }
131
132
        return $this->associateExisting($model);
133
    }
134
135
    /**
136
     * Dissociate the model instance from the given parent, without saving it to the database.
137
     * @param mixed $ids
138
     * @return int
139
     */
140
    public function dissociate($ids = [])
141
    {
142
        $ids = $this->getIdsArrayFrom($ids);
143
144
        $records = $this->getEmbedded();
145
146
        $primaryKey = $this->related->getKeyName();
147
148
        // Remove the document from the parent model.
149
        foreach ($records as $i => $record) {
150
            if (in_array($record[$primaryKey], $ids)) {
151
                unset($records[$i]);
152
            }
153
        }
154
155
        $this->setEmbedded($records);
156
157
        // We return the total number of deletes for the operation. The developers
158
        // can then check this number as a boolean type value or get this total count
159
        // of records deleted for logging, etc.
160
        return count($ids);
161
    }
162
163
    /**
164
     * Destroy the embedded models for the given IDs.
165
     * @param mixed $ids
166
     * @return int
167
     */
168
    public function destroy($ids = [])
169
    {
170
        $count = 0;
171
172
        $ids = $this->getIdsArrayFrom($ids);
173
174
        // Get all models matching the given ids.
175
        $models = $this->getResults()->only($ids);
176
177
        // Pull the documents from the database.
178
        foreach ($models as $model) {
179
            if ($model->delete()) {
180
                $count++;
181
            }
182
        }
183
184
        return $count;
185
    }
186
187
    /**
188
     * Delete all embedded models.
189
     * @return int
190
     */
191
    public function delete()
192
    {
193
        // Overwrite the local key with an empty array.
194
        $result = $this->query->update([$this->localKey => []]);
195
196
        if ($result) {
197
            $this->setEmbedded([]);
198
        }
199
200
        return $result;
201
    }
202
203
    /**
204
     * Destroy alias.
205
     * @param mixed $ids
206
     * @return int
207
     */
208
    public function detach($ids = [])
209
    {
210
        return $this->destroy($ids);
211
    }
212
213
    /**
214
     * Save alias.
215
     * @param Model $model
216
     * @return Model
217
     */
218
    public function attach(Model $model)
219
    {
220
        return $this->save($model);
221
    }
222
223
    /**
224
     * Associate a new model instance to the given parent, without saving it to the database.
225
     * @param Model $model
226
     * @return Model
227
     */
228
    protected function associateNew($model)
229
    {
230
        // Create a new key if needed.
231
        if ($model->getKeyName() === '_id' && ! $model->getAttribute('_id')) {
232
            $model->setAttribute('_id', new ObjectID);
233
        }
234
235
        $records = $this->getEmbedded();
236
237
        // Add the new model to the embedded documents.
238
        $records[] = $model->getAttributes();
239
240
        return $this->setEmbedded($records);
241
    }
242
243
    /**
244
     * Associate an existing model instance to the given parent, without saving it to the database.
245
     * @param Model $model
246
     * @return Model
247
     */
248
    protected function associateExisting($model)
249
    {
250
        // Get existing embedded documents.
251
        $records = $this->getEmbedded();
252
253
        $primaryKey = $this->related->getKeyName();
254
255
        $key = $model->getKey();
256
257
        // Replace the document in the parent model.
258
        foreach ($records as &$record) {
259
            if ($record[$primaryKey] == $key) {
260
                $record = $model->getAttributes();
261
                break;
262
            }
263
        }
264
265
        return $this->setEmbedded($records);
266
    }
267
268
    /**
269
     * @param null $perPage
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $perPage is correct as it would always require null to be passed?
Loading history...
270
     * @param array $columns
271
     * @param string $pageName
272
     * @param null $page
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $page is correct as it would always require null to be passed?
Loading history...
273
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
274
     */
275
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
276
    {
277
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
0 ignored issues
show
introduced by
$page is of type null, thus it always evaluated to false.
Loading history...
278
        $perPage = $perPage ?: $this->related->getPerPage();
0 ignored issues
show
introduced by
$perPage is of type null, thus it always evaluated to false.
Loading history...
279
280
        $results = $this->getEmbedded();
281
        $results = $this->toCollection($results);
282
        $total = $results->count();
283
        $start = ($page - 1) * $perPage;
284
285
        $sliced = $results->slice(
286
            $start,
287
            $perPage
288
        );
289
290
        return new LengthAwarePaginator(
291
            $sliced,
292
            $total,
293
            $perPage,
294
            $page,
295
            [
296
                'path' => Paginator::resolveCurrentPath(),
297
            ]
298
        );
299
    }
300
301
    /**
302
     * {@inheritdoc}
303
     */
304
    protected function getEmbedded()
305
    {
306
        return parent::getEmbedded() ?: [];
307
    }
308
309
    /**
310
     * {@inheritdoc}
311
     */
312
    protected function setEmbedded($models)
313
    {
314
        if (! is_array($models)) {
315
            $models = [$models];
316
        }
317
318
        return parent::setEmbedded(array_values($models));
319
    }
320
321
    /**
322
     * {@inheritdoc}
323
     */
324
    public function __call($method, $parameters)
325
    {
326
        if (method_exists(Collection::class, $method)) {
327
            return call_user_func_array([$this->getResults(), $method], $parameters);
328
        }
329
330
        return parent::__call($method, $parameters);
331
    }
332
333
    /**
334
     * Get the name of the "where in" method for eager loading.
335
     * @param \Illuminate\Database\Eloquent\Model $model
336
     * @param string $key
337
     * @return string
338
     */
339
    protected function whereInMethod(EloquentModel $model, $key)
340
    {
341
        return 'whereIn';
342
    }
343
}
344