Completed
Push — master ( aed267...b37349 )
by Oliver
02:18
created

MetaItemCollection   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 8
Bugs 1 Features 2
Metric Value
c 8
b 1
f 2
dl 0
loc 282
wmc 33
lcom 2
cbo 1
rs 9.4

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A modelKeys() 0 12 3
A originalModelKeys() 0 4 1
A add() 0 19 4
A findItem() 0 12 3
A observeDeletions() 0 8 3
A observeDeletion() 0 10 2
A getMetaItemClass() 0 4 1
A setMetaItemClass() 0 8 2
A getDefaultTag() 0 4 1
A setDefaultTag() 0 6 1
A __call() 0 7 3
A __isset() 0 4 1
A __unset() 0 8 2
A __get() 0 14 3
A __set() 0 19 2
1
<?php
2
3
/*
4
 * This file is part of Mailable.
5
 *
6
 * (c) Oliver Green <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace BoxedCode\Eloquent\Meta;
13
14
use Illuminate\Database\Eloquent\Collection as CollectionBase;
15
use BoxedCode\Eloquent\Meta\Contracts\MetaItem as MetaItemContract;
16
use BoxedCode\Eloquent\Meta\Contracts\MetaItemCollection as CollectionContract;
17
use InvalidArgumentException;
18
19
class MetaItemCollection extends CollectionBase implements CollectionContract
1 ignored issue
show
Bug introduced by
There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: forget, get, keys, where
Loading history...
20
{
21
    /**
22
     * Fully qualified class name to use when creating new items via magic methods.
23
     *
24
     * @var string
25
     */
26
    protected static $item_class;
27
28
    /**
29
     * The default tag name to use when using magic methods.
30
     *
31
     * @var string
32
     */
33
    protected $default_tag = 'default';
34
35
    /**
36
     * Keys of the models that the collection was constructed with.
37
     *
38
     * @var array
39
     */
40
    protected $original_model_keys = [];
41
42
    /**
43
     * MetaItemCollection constructor.
44
     *
45
     * @param array $items
46
     */
47
    public function __construct($items = [])
48
    {
49
        parent::__construct($items);
50
51
        $this->original_model_keys = $this->modelKeys();
52
53
        $this->observeDeletions($this->items);
54
    }
55
56
    /**
57
     * Get the array of primary keys.
58
     *
59
     * @return array
60
     */
61
    public function modelKeys()
62
    {
63
        $keys = [];
64
65
        foreach ($this->items as $item) {
66
            if ($item instanceof MetaItemContract) {
67
                $keys[] = $item->getKey();
68
            }
69
        }
70
71
        return $keys;
72
    }
73
74
    /**
75
     * Get the array of primary keys the collection was constructed with.
76
     *
77
     * @return array
78
     */
79
    public function originalModelKeys()
80
    {
81
        return $this->original_model_keys;
82
    }
83
84
    /**
85
     * Add an item to the collection.
86
     *
87
     * @param mixed $item
88
     * @return $this
89
     * @throws InvalidArgumentException
90
     */
91
    public function add($item)
92
    {
93
        if ($item instanceof MetaItemContract) {
94
95
            if (! is_null($this->findItem($item->key, $item->tag))) {
96
                $tag = $item->tag ?: $this->default_tag;
97
98
                $key = $item->key;
99
100
                throw new InvalidArgumentException("Unique key / tag constraint failed. [$key/$tag]");
101
            }
102
103
            $this->observeDeletions([$item]);
104
        }
105
106
        $this->items[] = $item;
107
108
        return $this;
109
    }
110
111
    /**
112
     * Get the collection key form an item key and tag.
113
     *
114
     * @param mixed $key
115
     * @param null $tag
116
     * @return mixed
117
     */
118
    public function findItem($key, $tag = null)
119
    {
120
        $collection = $this->whereKey($key);
121
122
        if (! is_null($tag)) {
123
            $collection = $collection->whereTag($tag);
124
        }
125
126
        if ($collection->count() > 0) {
127
            return $collection->keys()->first();
128
        }
129
    }
130
131
    /**
132
     * Set deletion listeners on an array of items.
133
     *
134
     * @param array $items
135
     */
136
    protected function observeDeletions(array $items)
137
    {
138
        foreach ($items as $item) {
139
            if ($item instanceof MetaItemContract) {
140
                $this->observeDeletion($item);
141
            }
142
        }
143
    }
144
145
    /**
146
     * Set a deletion listener on an item.
147
     *
148
     * @param \BoxedCode\Eloquent\Meta\Contracts\MetaItem $item
149
     */
150
    protected function observeDeletion(MetaItemContract $item)
151
    {
152
        $item::deleted(function ($model) {
153
            $key = $this->findItem($model->key, $model->tag);
154
155
            if (! is_null($key)) {
156
                $this->forget($key);
157
            }
158
        });
159
    }
160
161
    /**
162
     * Get the class name that will be used to construct new
163
     * items via the magic methods.
164
     *
165
     * @return string
166
     */
167
    public static function getMetaItemClass()
168
    {
169
        return static::$item_class;
170
    }
171
172
    /**
173
     * Set the class name that will be used to construct new
174
     * items via the magic methods.
175
     *
176
     * @param $class
177
     */
178
    public static function setMetaItemClass($class)
179
    {
180
        if (is_object($class)) {
181
            $class = get_class($class);
182
        }
183
184
        static::$item_class = $class;
185
    }
186
187
    /**
188
     * Get the default tag name that will be used to construct new
189
     * items via the magic methods.
190
     *
191
     * @return string
192
     */
193
    public function getDefaultTag()
194
    {
195
        return $this->default_tag;
196
    }
197
198
    /**
199
     * Set the default tag name that will be used to construct new
200
     * items via the magic methods.
201
     *
202
     * @param $name
203
     * @return $this
204
     */
205
    public function setDefaultTag($name)
206
    {
207
        $this->default_tag = $name;
208
209
        return $this;
210
    }
211
212
    /**
213
     * Resolve calls to filter the collection by item attributes.
214
     *
215
     * @param string $name
216
     * @param array $arguments
217
     * @return static
218
     */
219
    public function __call($name, $arguments)
220
    {
221
        if (starts_with($name, 'where') && 1 === count($arguments)) {
222
            $key = snake_case(substr($name, 5));
223
            return $this->where($key, $arguments[0]);
224
        }
225
    }
226
227
    /**
228
     * Resolve calls to check whether an item with a specific key name exists.
229
     *
230
     * @param $name
231
     * @return bool
232
     */
233
    public function __isset($name)
234
    {
235
        return ! is_null($this->findItem($name, $this->default_tag));
236
    }
237
238
    /**
239
     * Resolve calls to unset an item with a specific key name.
240
     *
241
     * @param $name
242
     */
243
    public function __unset($name)
244
    {
245
        $key = $this->findItem($name, $this->default_tag);
246
247
        if (! is_null($key)) {
248
            $this->forget($key);
249
        }
250
    }
251
252
    /**
253
     * Resolve calls to get an item with a specific key name or a
254
     * collection of items with a specific tag name.
255
     *
256
     * @param $name
257
     * @return mixed
258
     */
259
    public function __get($name)
260
    {
261
        $key = $this->findItem($name, $this->default_tag);
262
263
        if (! is_null($key)) {
264
            return $this->get($key)->value;
265
        }
266
267
        $tag = $this->where('tag', $name);
268
269
        if ($tag->count() > 0) {
270
            return $tag->setDefaultTag($name);
271
        }
272
    }
273
274
    /**
275
     * Resolve calls to set a new item to the collection or
276
     * update an existing key.
277
     *
278
     * @param $name
279
     * @param $value
280
     */
281
    public function __set($name, $value)
282
    {
283
        $key = $this->findItem($name, $this->default_tag);
284
285
        if (! is_null($key)) {
286
            $this->get($key)->value = $value;
287
        }
288
        else {
289
            $attr = [
290
                'key'   => $name,
291
                'value' => $value,
292
                'tag'   => $this->default_tag
293
            ];
294
295
            $class = static::$item_class;
296
297
            $this->add(new $class($attr));
298
        }
299
    }
300
}
301