CollectionWriteTrait::onItemAdd()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Nayjest\Collection;
4
5
use Evenement\EventEmitterTrait;
6
use Traversable;
7
8
/**
9
 * Trait CollectionWriteTrait.
10
 *
11
 * @implements \Nayjest\Collection\CollectionWriteInterface
12
 */
13
trait CollectionWriteTrait
14
{
15
    use EventEmitterTrait;
16
17
    /**
18
     * used to avoid emitting multiple 'change' events in complex operations.
19
     * @var bool|string
20
     */
21
    protected $onChangeAlreadyEmittedBy = false;
22
23
    /**
24
     * Emits onChange if not emitted before.
25
     *
26
     * @param string|bool $method if false, does not require calling endEmitOnChange
27
     */
28 32
    protected function emitOnChange($method = false)
29
    {
30 32
        if (!$this->onChangeAlreadyEmittedBy) {
31 32
            $this->emit('change', [$this]);
32 32
            $this->onChangeAlreadyEmittedBy = $method;
33 32
        }
34 32
    }
35
36 32
    protected function endEmitOnChange($method)
37
    {
38 32
        if ($this->onChangeAlreadyEmittedBy === $method) {
39 32
            $this->onChangeAlreadyEmittedBy = false;
40 32
        }
41 32
    }
42
43
    /**
44
     * Returns reference to array storing collection items.
45
     *
46
     * @return array
47
     */
48
    abstract protected function &items();
49
50
    /**
51
     * Adds event listener.
52
     *
53
     * @param callable $callback callback that accepts added item and collection as argument
54
     * @return $this
55
     */
56 1
    public function onItemAdd(callable $callback)
57
    {
58 1
        $this->on('item.add', $callback);
59 1
        return $this;
60
    }
61
62
    /**
63
     * Adds event listener.
64
     *
65
     * @param callable $callback callback that accepts removed item and collection as argument
66
     * @return $this
67
     */
68 1
    public function onItemRemove(callable $callback)
69
    {
70 1
        $this->on('item.remove', $callback);
71 1
        return $this;
72
    }
73
74
    /**
75
     * Adds event listener.
76
     *
77
     * @param callable $callback callback that accepts collection instance as argument
78
     * @return $this
79
     */
80 1
    public function onChange(callable $callback)
81
    {
82 1
        $this->on('change', $callback);
83 1
        return $this;
84
    }
85
86
    /**
87
     * Adds item to collection.
88
     *
89
     * @param $item
90
     * @param bool $prepend false by default
91
     * @return $this
92
     */
93 32
    public function add($item, $prepend = false)
94
    {
95 32
        $this->emitOnChange();
96 32
        $this->emit('item.add', [$item, $this]);
97 32
        $items = &$this->items();
98 32
        if ($prepend) {
99
            array_unshift($items, $item);
100
        } else {
101 32
            $items[] = $item;
102
        }
103
104 32
        return $this;
105
    }
106
107
    /**
108
     * Removes items equals to specified value from collection.
109
     *
110
     * @param $item
111
     *
112
     * @return $this
113
     */
114 2
    public function remove($item)
115
    {
116 2
        $this->emitOnChange();
117 2
        $this->emit('item.remove', [$item, $this]);
118
119 2
        $keys = array_keys($this->items(), $item, true);
120 2
        foreach ($keys as $key) {
121 2
            unset($this->items()[$key]);
122 2
        }
123 2
        return $this;
124
    }
125
126
    /**
127
     * Replaces items equal to $oldItem to $newItem.
128
     *
129
     * If $forceAdd is true, $newItem will be added to collection even if there is no $oldItem.
130
     *
131
     * @param $oldItem
132
     * @param $newItem
133
     * @param bool $forceAdd [optional] true by default
134
     * @return $this
135
     */
136
    public function replace($oldItem, $newItem, $forceAdd = true)
137
    {
138
        $this->emitOnChange(__METHOD__);
139
        $keys = array_keys($this->items(), $oldItem, true);
140
        if (count($keys) === 0) {
141
            if ($forceAdd) {
142
                $this->add($newItem);
143
            }
144
        } else {
145
            $this->remove($oldItem);
146
            foreach ($keys as $key) {
147
                $this->add($newItem);
148
                // move to old item position
149
                $newKeys = array_keys($this->items(), $newItem, true);
150
                $lastAddedKey = array_pop($newKeys);
151
                $this->items()[$key] = $this->items()[$lastAddedKey];
152
                unset($this->items()[$lastAddedKey]);
153
            }
154
        }
155
        $this->endEmitOnChange(__METHOD__);
156
        return $this;
157
    }
158
159
    /**
160
     * Removes all items from collection.
161
     *
162
     * @return $this
163
     */
164 32
    public function clear()
165
    {
166 32
        $this->emitOnChange(__METHOD__);
167 32
        $items = &$this->items();
168 32
        if (count($this->listeners('item.remove'))) {
169 1
            foreach ($items as $item) {
170 1
                $this->emit('item.remove', [$item, $this]);
171 1
            }
172 1
        }
173
174
        // It's not mistake that $items never used after assigning empty array.
175
        // Yep, it really clears the collection.
176 32
        $items = [];
177 32
        $this->endEmitOnChange(__METHOD__);
178 32
        return $this;
179
    }
180
181
    /**
182
     * Adds multiple items to collection.
183
     *
184
     * @param array|Traversable $items
185
     * @param bool $prepend false by default
186
     *
187
     * @return $this
188
     */
189
    public function addMany($items, $prepend = false)
190
    {
191
        $this->emitOnChange(__METHOD__);
192
        if ($prepend) {
193
            # if items must be added to beginning, we need to reverse them
194
            if (!is_array($items)) {
195
                $items = iterator_to_array($items);
196
            }
197
            $items = array_reverse($items);
198
        }
199
        foreach ($items as $item) {
200
            $this->add($item, $prepend);
201
        }
202
        $this->endEmitOnChange(__METHOD__);
203
        return $this;
204
    }
205
206
    /**
207
     * Removes old and sets new collection items.
208
     *
209
     * @param array|Traversable $items
210
     *
211
     * @return $this
212
     */
213 32
    public function set($items)
214
    {
215 32
        $this->emitOnChange(__METHOD__);
216 32
        $this->clear();
217 32
        foreach ($items as $item) {
218 32
            $this->add($item);
219 32
        }
220 32
        $this->endEmitOnChange(__METHOD__);
221 32
        return $this;
222
    }
223
224
    /**
225
     * Creates collection of items.
226
     *
227
     * Override it if you need to implement
228
     * derived collection that requires specific initialization.
229
     *
230
     * @param array $items
231
     *
232
     * @return static
233
     */
234 2
    protected function createCollection(array $items)
235
    {
236 2
        $collection = new static();
237 2
        $collection->set($items);
238
239 2
        return $collection;
240
    }
241
}
242