EmbedsMany.php 8.84 KB
Newer Older
1 2 3
<?php namespace Jenssegers\Mongodb\Relations;

use Illuminate\Database\Eloquent\Model;
4
use Illuminate\Pagination\LengthAwarePaginator;
5
use Illuminate\Pagination\Paginator;
Jens Segers's avatar
Jens Segers committed
6
use MongoDB\BSON\ObjectID;
7

Jens Segers's avatar
Jens Segers committed
8 9
class EmbedsMany extends EmbedsOneOrMany
{
Jens Segers's avatar
Jens Segers committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
    /**
     * Initialize the relation on a set of models.
     *
     * @param  array   $models
     * @param  string  $relation
     */
    public function initRelation(array $models, $relation)
    {
        foreach ($models as $model) {
            $model->setRelation($relation, $this->related->newCollection());
        }

        return $models;
    }

25 26 27
    /**
     * Get the results of the relationship.
     *
28
     * @return \Illuminate\Database\Eloquent\Collection
29 30 31
     */
    public function getResults()
    {
32
        return $this->toCollection($this->getEmbedded());
33 34
    }

35
    /**
36
     * Save a new model and attach it to the parent model.
37
     *
38
     * @param  \Illuminate\Database\Eloquent\Model $model
39
     * @return \Illuminate\Database\Eloquent\Model
40
     */
41
    public function performInsert(Model $model)
42
    {
43
        // Generate a new key if needed.
Jens Segers's avatar
Jens Segers committed
44
        if ($model->getKeyName() == '_id' and ! $model->getKey()) {
Jens Segers's avatar
Jens Segers committed
45
            $model->setAttribute('_id', new ObjectID);
46
        }
47

48
        // For deeply nested documents, let the parent handle the changes.
Jens Segers's avatar
Jens Segers committed
49
        if ($this->isNested()) {
50 51 52 53 54
            $this->associate($model);

            return $this->parent->save();
        }

55
        // Push the new model to the database.
56
        $result = $this->getBaseQuery()->push($this->localKey, $model->getAttributes(), true);
57 58

        // Attach the model to its parent.
Jens Segers's avatar
Jens Segers committed
59 60 61
        if ($result) {
            $this->associate($model);
        }
62 63

        return $result ? $model : false;
64 65
    }

66
    /**
67
     * Save an existing model and attach it to the parent model.
68
     *
69
     * @param  \Illuminate\Database\Eloquent\Model  $model
70
     * @return Model|bool
71
     */
72
    public function performUpdate(Model $model)
73
    {
74
        // For deeply nested documents, let the parent handle the changes.
Jens Segers's avatar
Jens Segers committed
75
        if ($this->isNested()) {
76 77 78 79 80
            $this->associate($model);

            return $this->parent->save();
        }

81 82 83
        // Get the correct foreign key value.
        $foreignKey = $this->getForeignKeyValue($model);

84 85 86
        // Use array dot notation for better update behavior.
        $values = array_dot($model->getDirty(), $this->localKey . '.$.');

87
        // Update document in database.
88 89
        $result = $this->getBaseQuery()->where($this->localKey . '.' . $model->getKeyName(), $foreignKey)
                                       ->update($values);
90 91

        // Attach the model to its parent.
Jens Segers's avatar
Jens Segers committed
92 93 94
        if ($result) {
            $this->associate($model);
        }
95 96

        return $result ? $model : false;
97 98 99
    }

    /**
100
     * Delete an existing model and detach it from the parent model.
101
     *
102
     * @param  Model  $model
103
     * @return int
104
     */
105
    public function performDelete(Model $model)
106
    {
Jens Segers's avatar
Jens Segers committed
107
        // For deeply nested documents, let the parent handle the changes.
Jens Segers's avatar
Jens Segers committed
108
        if ($this->isNested()) {
Jens Segers's avatar
Jens Segers committed
109 110 111 112 113
            $this->dissociate($model);

            return $this->parent->save();
        }

114 115
        // Get the correct foreign key value.
        $foreignKey = $this->getForeignKeyValue($model);
116

117
        $result = $this->getBaseQuery()->pull($this->localKey, [$model->getKeyName() => $foreignKey]);
118

Jens Segers's avatar
Jens Segers committed
119 120 121
        if ($result) {
            $this->dissociate($model);
        }
122 123 124 125 126 127 128 129 130 131 132 133

        return $result;
    }

    /**
     * Associate the model instance to the given parent, without saving it to the database.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function associate(Model $model)
    {
Jens Segers's avatar
Jens Segers committed
134
        if (! $this->contains($model)) {
135
            return $this->associateNew($model);
Jens Segers's avatar
Jens Segers committed
136
        } else {
137
            return $this->associateExisting($model);
138
        }
139 140 141
    }

    /**
142
     * Dissociate the model instance from the given parent, without saving it to the database.
143 144 145 146
     *
     * @param  mixed  $ids
     * @return int
     */
147
    public function dissociate($ids = [])
148 149 150
    {
        $ids = $this->getIdsArrayFrom($ids);

151
        $records = $this->getEmbedded();
152

153 154
        $primaryKey = $this->related->getKeyName();

155
        // Remove the document from the parent model.
Jens Segers's avatar
Jens Segers committed
156 157
        foreach ($records as $i => $record) {
            if (in_array($record[$primaryKey], $ids)) {
158
                unset($records[$i]);
159 160 161
            }
        }

162
        $this->setEmbedded($records);
163

164 165 166 167 168 169
        // We return the total number of deletes for the operation. The developers
        // can then check this number as a boolean type value or get this total count
        // of records deleted for logging, etc.
        return count($ids);
    }

170 171 172 173 174 175
    /**
     * Destroy the embedded models for the given IDs.
     *
     * @param  mixed  $ids
     * @return int
     */
176
    public function destroy($ids = [])
177 178 179 180 181 182 183 184 185
    {
        $count = 0;

        $ids = $this->getIdsArrayFrom($ids);

        // Get all models matching the given ids.
        $models = $this->getResults()->only($ids);

        // Pull the documents from the database.
Jens Segers's avatar
Jens Segers committed
186 187 188 189
        foreach ($models as $model) {
            if ($model->delete()) {
                $count++;
            }
190 191 192 193 194
        }

        return $count;
    }

195
    /**
196 197 198 199 200 201 202
     * Delete all embedded models.
     *
     * @return int
     */
    public function delete()
    {
        // Overwrite the local key with an empty array.
203
        $result = $this->query->update([$this->localKey => []]);
204

Jens Segers's avatar
Jens Segers committed
205 206 207
        if ($result) {
            $this->setEmbedded([]);
        }
208 209 210 211 212 213

        return $result;
    }

    /**
     * Destroy alias.
214
     *
215
     * @param  mixed  $ids
216 217
     * @return int
     */
218
    public function detach($ids = [])
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    {
        return $this->destroy($ids);
    }

    /**
     * Save alias.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function attach(Model $model)
    {
        return $this->save($model);
    }

234 235 236 237 238 239 240 241
    /**
     * Associate a new model instance to the given parent, without saving it to the database.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return \Illuminate\Database\Eloquent\Model
     */
    protected function associateNew($model)
    {
242
        // Create a new key if needed.
Jens Segers's avatar
Jens Segers committed
243
        if (! $model->getAttribute('_id')) {
Jens Segers's avatar
Jens Segers committed
244
            $model->setAttribute('_id', new ObjectID);
245 246
        }

247
        $records = $this->getEmbedded();
248

249
        // Add the new model to the embedded documents.
250
        $records[] = $model->getAttributes();
251

252
        return $this->setEmbedded($records);
253 254 255 256 257 258 259 260 261 262 263
    }

    /**
     * Associate an existing model instance to the given parent, without saving it to the database.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return \Illuminate\Database\Eloquent\Model
     */
    protected function associateExisting($model)
    {
        // Get existing embedded documents.
264
        $records = $this->getEmbedded();
265 266

        $primaryKey = $this->related->getKeyName();
267

268 269 270
        $key = $model->getKey();

        // Replace the document in the parent model.
Jens Segers's avatar
Jens Segers committed
271 272
        foreach ($records as &$record) {
            if ($record[$primaryKey] == $key) {
273
                $record = $model->getAttributes();
274 275 276 277
                break;
            }
        }

278
        return $this->setEmbedded($records);
279 280
    }

281 282 283
    /**
     * Get a paginator for the "select" statement.
     *
284
     * @param  int  $perPage
285 286
     * @return \Illuminate\Pagination\Paginator
     */
287
    public function paginate($perPage = null)
288
    {
289
        $page = Paginator::resolveCurrentPage();
290 291 292 293
        $perPage = $perPage ?: $this->related->getPerPage();

        $results = $this->getEmbedded();

294
        $total = count($results);
295

296
        $start = ($page - 1) * $perPage;
297 298
        $sliced = array_slice($results, $start, $perPage);

299
        return new LengthAwarePaginator($sliced, $total, $perPage, $page, [
Jens Segers's avatar
Jens Segers committed
300
            'path' => Paginator::resolveCurrentPath(),
301
        ]);
302 303
    }

304
    /**
305
     * Get the embedded records array.
306 307 308
     *
     * @return array
     */
309
    protected function getEmbedded()
310
    {
311
        return parent::getEmbedded() ?: [];
312 313 314
    }

    /**
315
     * Set the embedded records array.
316
     *
317
     * @param  array  $models
318
     */
319
    protected function setEmbedded($models)
320
    {
Jens Segers's avatar
Jens Segers committed
321 322 323
        if (! is_array($models)) {
            $models = [$models];
        }
324

325
        return parent::setEmbedded(array_values($models));
326 327
    }

328 329 330 331 332 333 334 335 336
    /**
     * Handle dynamic method calls to the relationship.
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
337
        if (method_exists('Illuminate\Database\Eloquent\Collection', $method)) {
338
            return call_user_func_array([$this->getResults(), $method], $parameters);
339 340 341 342
        }

        return parent::__call($method, $parameters);
    }
343
}