AbstractCollection::getType()
last analyzed

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
c 0
b 0
f 0
nc 1
1
<?php
2
3
namespace As3\Modlr\Models\Collections;
4
5
use \Countable;
6
use \Iterator;
7
use As3\Modlr\Models\AbstractModel;
8
use As3\Modlr\Store\Store;
9
10
/**
11
 * Collection that contains record representations from a persistence (database) layer.
12
 * These representations can either be of first-order Models or fragmentted Embeds.
13
 *
14
 * @author Jacob Bare <[email protected]>
15
 */
16
abstract class AbstractCollection implements Iterator, Countable
17
{
18
    /**
19
     * Models added to this collection.
20
     * Tracks newly added models for rollback/change purposes.
21
     *
22
     * @var AbstractModel[]
23
     */
24
    protected $added = [];
25
26
    /**
27
     * Whether the collection has been loaded with data from the persistence layer
28
     *
29
     * @var bool
30
     */
31
    protected $loaded = true;
32
33
    /**
34
     * Current models assigned to this collection.
35
     * Needed for iteration, access, and count purposes.
36
     *
37
     * @var AbstractModel[]
38
     */
39
    protected $models = [];
40
41
    /**
42
     * Gets the position of each model key in the collection.
43
     *
44
     * @var string[]
45
     */
46
    protected $modelKeyMap = [];
47
48
    /**
49
     * Original models assigned to this collection.
50
     *
51
     * @var AbstractModel[]
52
     */
53
    protected $original = [];
54
55
    /**
56
     * The array position.
57
     *
58
     * @var int
59
     */
60
    protected $pos = 0;
61
62
    /**
63
     * Models removed from this collection.
64
     * Tracks removed models for rollback/change purposes.
65
     *
66
     * @var AbstractModel[]
67
     */
68
    protected $removed = [];
69
70
    /**
71
     * The store for handling storage operations.
72
     *
73
     * @var Store
74
     */
75
    protected $store;
76
77
    /**
78
     * The total count of the collection,
79
     * Acts as if no offsets or limits were originally applied to the Model set.
80
     *
81
     * @var int
82
     */
83
    protected $totalCount;
84
85
    /**
86
     * Constructor.
87
     *
88
     * @param   Store           $store
89
     * @param   AbstractModel[] $models
90
     * @param   int             $totalCount
91
     */
92
    public function __construct(Store $store, array $models = [], $totalCount)
93
    {
94
        $this->pos = 0;
95
        $this->store = $store;
96
        $this->setModels($models);
97
        $this->totalCount = (Integer) $totalCount;
98
    }
99
100
    /**
101
     * Calculates the change set of this collection.
102
     *
103
     * @return  array
104
     */
105
    public function calculateChangeSet()
106
    {
107
        if (false === $this->isDirty()) {
108
            return [];
109
        }
110
        return [
111
            'old' => empty($this->original) ? null : $this->original,
112
            'new' => empty($this->models) ? null : $this->models,
113
        ];
114
    }
115
116
    /**
117
     * Clears/empties the collection.
118
     *
119
     * @return  self
120
     */
121
    public function clear()
122
    {
123
        $this->models = [];
124
        $this->modelKeyMap = [];
125
        $this->added = [];
126
        $this->removed = $this->original;
127
        return $this;
128
    }
129
130
    /**
131
     * {@inheritDoc}
132
     */
133
    public function count()
134
    {
135
        return count($this->models);
136
    }
137
138
    /**
139
     * {@inheritDoc}
140
     */
141
    public function current()
142
    {
143
        $key = $this->modelKeyMap[$this->pos];
144
        return $this->models[$key];
145
    }
146
147
    /**
148
     * Gets a single model result from the collection.
149
     *
150
     * @return  AbstractModel|null
151
     */
152
    public function getSingleResult()
153
    {
154
        if (0 === $this->count()) {
155
            return null;
156
        }
157
        $this->rewind();
158
        return $this->current();
159
    }
160
161
    /**
162
     * Gets the 'total' model count, as if a limit and offset were not applied.
163
     *
164
     * @return  int
165
     */
166
    public function getTotalCount()
167
    {
168
        return $this->totalCount;
169
    }
170
171
    /**
172
     * Gets the model collection type.
173
     *
174
     * @return  string
175
     */
176
    abstract public function getType();
177
178
    /**
179
     * Determines if the Model is included in the collection.
180
     *
181
     * @param   AbstractModel   $model  The model to check.
182
     * @return  bool
183
     */
184
    public function has(AbstractModel $model)
185
    {
186
        $key = $model->getCompositeKey();
187
        return isset($this->models[$key]);
188
    }
189
190
    /**
191
     * Determines if any models in this collection are dirty (have changes).
192
     *
193
     * @return  bool
194
     */
195
    public function hasDirtyModels()
196
    {
197
        foreach ($this->models as $model) {
198
            if (true === $model->isDirty()) {
199
                return true;
200
            }
201
        }
202
        return false;
203
    }
204
205
    /**
206
     * Determines if the collection is dirty.
207
     *
208
     * @return  bool
209
     */
210
    public function isDirty()
211
    {
212
        return !empty($this->added) || !empty($this->removed);
213
    }
214
215
    /**
216
     * Determines if this collection is empty.
217
     *
218
     * @return  bool
219
     */
220
    public function isEmpty()
221
    {
222
        return 0 === $this->count();
223
    }
224
225
    /**
226
     * Determines if models in this collection have been loaded from the persistence layer.
227
     *
228
     * @return  bool
229
     */
230
    public function isLoaded()
231
    {
232
        return $this->loaded;
233
    }
234
235
    /**
236
     * {@inheritDoc}
237
     */
238
    public function key()
239
    {
240
        return $this->pos;
241
    }
242
243
    /**
244
     * {@inheritDoc}
245
     */
246
    public function next()
247
    {
248
        ++$this->pos;
249
    }
250
251
    /**
252
     * Pushes a Model into the collection.
253
     *
254
     * @param   AbstractModel   $model  The model to push.
255
     * @return  self
256
     */
257 View Code Duplication
    public function push(AbstractModel $model)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
258
    {
259
        $this->validateAdd($model);
260
        if (true === $this->willAdd($model)) {
261
            return $this;
262
        }
263
        if (true === $this->willRemove($model)) {
264
            $this->evict('removed', $model);
265
            $this->set('models', $model);
266
            return $this;
267
        }
268
        if (true === $this->hasOriginal($model)) {
269
            return $this;
270
        }
271
        $this->set('added', $model);
272
        $this->set('models', $model);
273
        return $this;
274
    }
275
276
    /**
277
     * Removes a model from the collection.
278
     *
279
     * @param   AbstractModel   $model  The model to remove.
280
     * @return  self
281
     */
282 View Code Duplication
    public function remove(AbstractModel $model)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
283
    {
284
        $this->validateAdd($model);
285
        if (true === $this->willRemove($model)) {
286
            return $this;
287
        }
288
289
        if (true === $this->willAdd($model)) {
290
            $this->evict('added', $model);
291
            $this->evict('models', $model);
292
            return $this;
293
        }
294
295
        if (true === $this->hasOriginal($model)) {
296
            $this->evict('models', $model);
297
            $this->set('removed', $model);
298
        }
299
        return $this;
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     */
305
    public function rewind()
306
    {
307
        $this->pos = 0;
308
    }
309
310
    /**
311
     * Rollsback the collection it it's original state.
312
     *
313
     * @return  self
314
     */
315
    public function rollback()
316
    {
317
        $this->models = $this->original;
318
        $this->added = [];
319
        $this->removed = [];
320
        return $this;
321
    }
322
323
    /**
324
     * {@inheritDoc}
325
     */
326
    public function valid()
327
    {
328
        return isset($this->modelKeyMap[$this->pos]);
329
    }
330
331
    /**
332
     * Determines if the model is scheduled for addition to the collection.
333
     *
334
     * @param   AbstractModel   $model  The model to check.
335
     * @return  bool
336
     */
337
    public function willAdd(AbstractModel $model)
338
    {
339
        $key = $model->getCompositeKey();
340
        return isset($this->added[$key]);
341
    }
342
343
    /**
344
     * Determines if the model is scheduled for removal from the collection.
345
     *
346
     * @param   AbstractModel   $model  The model to check.
347
     * @return  bool
348
     */
349
    public function willRemove(AbstractModel $model)
350
    {
351
        $key = $model->getCompositeKey();
352
        return isset($this->removed[$key]);
353
    }
354
355
    /**
356
     * Adds an model to this collection.
357
     * Is used during initial collection construction.
358
     *
359
     * @param   AbstractModel   $model
360
     * @return  self
361
     */
362
    protected function add(AbstractModel $model)
363
    {
364
        if (true === $this->has($model)) {
365
            return $this;
366
        }
367
        $this->validateAdd($model);
368
        if (true === $model->getState()->is('empty')) {
369
            $this->loaded = false;
370
        }
371
372
        $key = $model->getCompositeKey();
373
        $this->models[$key] = $model;
374
        $this->modelKeyMap[] = $key;
375
376
        if (false === $this->hasOriginal($model)) {
377
            $this->original[$key] = $model;
378
        }
379
        return $this;
380
    }
381
382
    /**
383
     * Evicts a model from a collection property (original, added, removed, models).
384
     *
385
     * @param   string          $property   The property key
386
     * @param   AbstractModel   $model      The model to set.
387
     * @return  self
388
     */
389
    protected function evict($property, AbstractModel $model)
390
    {
391
        $key = $model->getCompositeKey();
392
        if (isset($this->{$property})) {
393
            unset($this->{$property}[$key]);
394
        }
395
396
        if ('models' === $property) {
397
            $keys = array_flip($this->modelKeyMap);
398
            if (isset($keys[$key])) {
399
                unset($keys[$key]);
400
                $this->modelKeyMap = array_keys($keys);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_keys($keys) of type array<integer,integer|string> is incompatible with the declared type array<integer,string> of property $modelKeyMap.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
401
                $this->totalCount--;
402
            }
403
        }
404
        return $this;
405
    }
406
407
    /**
408
     * Determines if the model is included in the original set.
409
     *
410
     * @param   AbstractModel   $model  The model to check.
411
     * @return  bool
412
     */
413
    protected function hasOriginal(AbstractModel $model)
414
    {
415
        $key = $model->getCompositeKey();
416
        return isset($this->original[$key]);
417
    }
418
419
    /**
420
     * Sets a model to a collection property (original, added, removed, models).
421
     *
422
     * @param   string          $property   The property key
423
     * @param   AbstractModel   $model      The model to set.
424
     * @return  self
425
     */
426
    protected function set($property, AbstractModel $model)
427
    {
428
        $key = $model->getCompositeKey();
429
        $this->{$property}[$key] = $model;
430
431
        if ('models' === $property) {
432
            $keys = array_flip($this->modelKeyMap);
433
            if (!isset($keys[$key])) {
434
                $this->modelKeyMap[] = $key;
435
                $this->totalCount++;
436
            }
437
        }
438
        return $this;
439
    }
440
441
    /**
442
     * Sets an array of models to the collection.
443
     *
444
     * @param   AbstractModel[]     $models
445
     * @return  self
446
     */
447
    protected function setModels(array $models)
448
    {
449
        foreach ($models as $model) {
450
            $this->add($model);
451
        }
452
        return $this;
453
    }
454
455
    /**
456
     * Validates that the collection supports the incoming model.
457
     *
458
     * @param   AbstractModel   $model  The model to validate.
459
     * @throws  \InvalidArgumentException
460
     */
461
    abstract protected function validateAdd(AbstractModel $model);
462
}
463