Passed
Push — master ( 288a96...964b09 )
by Daniel
10:30
created

Map::getIterator()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 12
nc 16
nop 0
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM;
4
5
use ArrayAccess;
6
use Countable;
7
use IteratorAggregate;
8
9
/**
10
 * Creates a map from an SS_List by defining a key column and a value column.
11
 */
12
class Map implements ArrayAccess, Countable, IteratorAggregate
13
{
14
15
    protected $list, $keyField, $valueField;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
16
17
    /**
18
     * @see Map::unshift()
19
     *
20
     * @var array $firstItems
21
     */
22
    protected $firstItems = array();
23
24
    /**
25
     * @see Map::push()
26
     *
27
     * @var array $lastItems
28
     */
29
    protected $lastItems = array();
30
31
    /**
32
     * Construct a new map around an SS_list.
33
     *
34
     * @param SS_List $list The list to build a map from
35
     * @param string $keyField The field to use as the key of each map entry
36
     * @param string $valueField The field to use as the value of each map entry
37
     */
38
    public function __construct(SS_List $list, $keyField = "ID", $valueField = "Title")
39
    {
40
        $this->list = $list;
41
        $this->keyField = $keyField;
42
        $this->valueField = $valueField;
43
    }
44
45
    /**
46
     * Set the key field for this map.
47
     *
48
     * @var string $keyField
49
     */
50
    public function setKeyField($keyField)
51
    {
52
        $this->keyField = $keyField;
53
    }
54
55
    /**
56
     * Set the value field for this map.
57
     *
58
     * @var string $valueField
59
     */
60
    public function setValueField($valueField)
61
    {
62
        $this->valueField = $valueField;
63
    }
64
65
    /**
66
     * Return an array equivalent to this map.
67
     *
68
     * @return array
69
     */
70
    public function toArray()
71
    {
72
        $array = array();
73
74
        foreach ($this as $k => $v) {
75
            $array[$k] = $v;
76
        }
77
78
        return $array;
79
    }
80
81
    /**
82
     * Return all the keys of this map.
83
     *
84
     * @return array
85
     */
86
    public function keys()
87
    {
88
        return array_keys($this->toArray());
89
    }
90
91
    /**
92
     * Return all the values of this map.
93
     *
94
     * @return array
95
     */
96
    public function values()
97
    {
98
        return array_values($this->toArray());
99
    }
100
101
    /**
102
     * Unshift an item onto the start of the map.
103
     *
104
     * Stores the value in addition to the {@link DataQuery} for the map.
105
     *
106
     * @var string $key
107
     * @var mixed $value
108
     * @return $this
109
     */
110
    public function unshift($key, $value)
111
    {
112
        $oldItems = $this->firstItems;
113
        $this->firstItems = array(
114
            $key => $value
115
        );
116
117
        if ($oldItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $oldItems of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
118
            $this->firstItems = $this->firstItems + $oldItems;
119
        }
120
121
        return $this;
122
    }
123
124
    /**
125
     * Pushes an item onto the end of the map.
126
     *
127
     * @var string $key
128
     * @var mixed $value
129
     * @return $this
130
     */
131
    public function push($key, $value)
132
    {
133
        $oldItems = $this->lastItems;
134
135
        $this->lastItems = array(
136
            $key => $value
137
        );
138
139
        if ($oldItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $oldItems of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
140
            $this->lastItems = $this->lastItems + $oldItems;
141
        }
142
143
        return $this;
144
    }
145
146
    // ArrayAccess
147
148
    /**
149
     * @var string $key
150
     *
151
     * @return boolean
152
     */
153
    public function offsetExists($key)
154
    {
155
        if (isset($this->firstItems[$key])) {
156
            return true;
157
        }
158
159
        if (isset($this->lastItems[$key])) {
160
            return true;
161
        }
162
163
        $record = $this->list->find($this->keyField, $key);
164
165
        return $record != null;
166
    }
167
168
    /**
169
     * @var string $key
170
     *
171
     * @return mixed
172
     */
173
    public function offsetGet($key)
174
    {
175
        if (isset($this->firstItems[$key])) {
176
            return $this->firstItems[$key];
177
        }
178
179
        if (isset($this->lastItems[$key])) {
180
            return $this->lastItems[$key];
181
        }
182
183
        $record = $this->list->find($this->keyField, $key);
184
185
        if ($record) {
186
            $col = $this->valueField;
187
188
            return $record->$col;
189
        }
190
191
        return null;
192
    }
193
194
    /**
195
     * Sets a value in the map by a given key that has been set via
196
     * {@link Map::push()} or {@link Map::unshift()}
197
     *
198
     * Keys in the map cannot be set since these values are derived from a
199
     * {@link DataQuery} instance. In this case, use {@link Map::toArray()}
200
     * and manipulate the resulting array.
201
     *
202
     * @var string $key
203
     * @var mixed $value
204
     */
205
    public function offsetSet($key, $value)
206
    {
207
        if (isset($this->firstItems[$key])) {
208
            $this->firstItems[$key] = $value;
209
        }
210
211
        if (isset($this->lastItems[$key])) {
212
            $this->lastItems[$key] = $value;
213
        }
214
215
        user_error(
216
            'Map is read-only. Please use $map->push($key, $value) to append values',
217
            E_USER_ERROR
218
        );
219
    }
220
221
    /**
222
     * Removes a value in the map by a given key which has been added to the map
223
     * via {@link Map::push()} or {@link Map::unshift()}
224
     *
225
     * Keys in the map cannot be unset since these values are derived from a
226
     * {@link DataQuery} instance. In this case, use {@link Map::toArray()}
227
     * and manipulate the resulting array.
228
     *
229
     * @var string $key
230
     * @var mixed $value
231
     */
232
    public function offsetUnset($key)
233
    {
234
        if (isset($this->firstItems[$key])) {
235
            unset($this->firstItems[$key]);
236
237
            return;
238
        }
239
240
        if (isset($this->lastItems[$key])) {
241
            unset($this->lastItems[$key]);
242
243
            return;
244
        }
245
246
        user_error(
247
            "Map is read-only. Unset cannot be called on keys derived from the DataQuery",
248
            E_USER_ERROR
249
        );
250
    }
251
252
    /**
253
     * Returns an Map_Iterator instance for iterating over the complete set
254
     * of items in the map.
255
     *
256
     * Satisfies the IteratorAggreagte interface.
257
     *
258
     * @return Map_Iterator
0 ignored issues
show
Bug introduced by
The type SilverStripe\ORM\Map_Iterator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
259
     */
260
    public function getIterator()
261
    {
262
        $keyField = $this->keyField;
263
        $valueField = $this->valueField;
0 ignored issues
show
Unused Code introduced by
The assignment to $valueField is dead and can be removed.
Loading history...
264
265
        foreach ($this->firstItems as $k => $v) {
266
            yield $k => $v;
267
        }
268
269
        foreach ($this->list as $record) {
270
            if (isset($this->firstItems[$record->$keyField])) {
271
                continue;
272
            }
273
            if (isset($this->lastItems[$record->$keyField])) {
274
                continue;
275
            }
276
            yield $this->extractValue($record, $this->keyField) => $this->extractValue($record, $this->valueField);
277
        }
278
279
        foreach ($this->lastItems as $k => $v) {
280
            yield $k => $v;
281
        }
282
    }
283
284
    /**
285
     * Extracts a value from an item in the list, where the item is either an
286
     * object or array.
287
     *
288
     * @param  array|object $item
289
     * @param  string $key
290
     * @return mixed
291
     */
292
    protected function extractValue($item, $key)
293
    {
294
        if (is_object($item)) {
295
            if (method_exists($item, 'hasMethod') && $item->hasMethod($key)) {
296
                return $item->{$key}();
297
            }
298
            return $item->{$key};
299
        } else {
300
            if (array_key_exists($key, $item)) {
301
                return $item[$key];
302
            }
303
        }
304
    }
305
306
    /**
307
     * Returns the count of items in the list including the additional items set
308
     * through {@link Map::push()} and {@link Map::unshift}.
309
     *
310
     * @return int
311
     */
312
    public function count()
313
    {
314
        return $this->list->count() +
315
            count($this->firstItems) +
316
            count($this->lastItems);
317
    }
318
}
319