Completed
Push — remove-trailing-whitespace ( 59c1b8...a9f489 )
by Sam
11:05
created

SS_Map::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 5
rs 9.4286
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
3
/**
4
 * Creates a map from an SS_List by defining a key column and a value column.
5
 *
6
 * @package framework
7
 * @subpackage model
8
 */
9
class SS_Map implements ArrayAccess, Countable, IteratorAggregate {
10
11
	protected $list, $keyField, $valueField;
12
13
	/**
14
	 * @see SS_Map::unshift()
15
	 *
16
	 * @var array $firstItems
17
	 */
18
	protected $firstItems = array();
19
20
	/**
21
	 * @see SS_Map::push()
22
	 *
23
	 * @var array $lastItems
24
	 */
25
	protected $lastItems = array();
26
27
	/**
28
	 * Construct a new map around an SS_list.
29
	 *
30
	 * @param $list The list to build a map from
31
	 * @param $keyField The field to use as the key of each map entry
32
	 * @param $valueField The field to use as the value of each map entry
33
	 */
34
	public function __construct(SS_List $list, $keyField = "ID", $valueField = "Title") {
35
		$this->list = $list;
36
		$this->keyField = $keyField;
37
		$this->valueField = $valueField;
38
	}
39
40
	/**
41
	 * Set the key field for this map.
42
	 *
43
	 * @var string $keyField
44
	 */
45
	public function setKeyField($keyField) {
46
		$this->keyField = $keyField;
47
	}
48
49
	/**
50
	 * Set the value field for this map.
51
	 *
52
	 * @var string $valueField
53
	 */
54
	public function setValueField($valueField) {
55
		$this->valueField = $valueField;
56
	}
57
58
	/**
59
	 * Return an array equivalent to this map.
60
	 *
61
	 * @return array
62
	 */
63
	public function toArray() {
64
		$array = array();
65
66
		foreach($this as $k => $v) {
67
			$array[$k] = $v;
68
		}
69
70
		return $array;
71
	}
72
73
	/**
74
	 * Return all the keys of this map.
75
	 *
76
	 * @return array
77
	 */
78
	public function keys() {
79
		return array_keys($this->toArray());
80
	}
81
82
	/**
83
	 * Return all the values of this map.
84
	 *
85
	 * @return array
86
	 */
87
	public function values() {
88
		return array_values($this->toArray());
89
	}
90
91
	/**
92
	 * Unshift an item onto the start of the map.
93
	 *
94
	 * Stores the value in addition to the {@link DataQuery} for the map.
95
	 *
96
	 * @var string $key
97
	 * @var mixed $value
98
	 */
99 View Code Duplication
	public function unshift($key, $value) {
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...
100
		$oldItems = $this->firstItems;
101
		$this->firstItems = array(
102
			$key => $value
103
		);
104
105
		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...
106
			$this->firstItems = $this->firstItems + $oldItems;
107
		}
108
109
		return $this;
110
	}
111
112
	/**
113
	 * Pushes an item onto the end of the map.
114
	 *
115
	 * @var string $key
116
	 * @var mixed $value
117
	 */
118 View Code Duplication
	public function push($key, $value) {
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...
119
		$oldItems = $this->lastItems;
120
121
		$this->lastItems = array(
122
			$key => $value
123
		);
124
125
		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...
126
			$this->lastItems = $this->lastItems + $oldItems;
127
		}
128
129
		return $this;
130
	}
131
132
	// ArrayAccess
133
134
	/**
135
	 * @var string $key
136
	 *
137
	 * @return boolean
138
	 */
139
	public function offsetExists($key) {
140
		if(isset($this->firstItems[$key])) {
141
			return true;
142
		}
143
144
		if(isset($this->lastItems[$key])) {
145
			return true;
146
		}
147
148
		$record = $this->list->find($this->keyField, $key);
149
150
		return $record != null;
151
	}
152
153
	/**
154
	 * @var string $key
155
	 *
156
	 * @return mixed
157
	 */
158
	public function offsetGet($key) {
159
		if(isset($this->firstItems[$key])) {
160
			return $this->firstItems[$key];
161
		}
162
163
		if(isset($this->lastItems[$key])) {
164
			return $this->lastItems[$key];
165
		}
166
167
		$record = $this->list->find($this->keyField, $key);
168
169
		if($record) {
170
			$col = $this->valueField;
171
172
			return $record->$col;
173
		}
174
175
		return null;
176
	}
177
178
	/**
179
	 * Sets a value in the map by a given key that has been set via
180
	 * {@link SS_Map::push()} or {@link SS_Map::unshift()}
181
	 *
182
	 * Keys in the map cannot be set since these values are derived from a
183
	 * {@link DataQuery} instance. In this case, use {@link SS_Map::toArray()}
184
	 * and manipulate the resulting array.
185
	 *
186
	 * @var string $key
187
	 * @var mixed $value
188
	 */
189
	public function offsetSet($key, $value) {
190
		if(isset($this->firstItems[$key])) {
191
			return $this->firstItems[$key] = $value;
192
		}
193
194
		if(isset($this->lastItems[$key])) {
195
			return $this->lastItems[$key] = $value;
196
		}
197
198
		user_error(
199
			"SS_Map is read-only. Please use $map->push($key, $value) to append values",
0 ignored issues
show
Bug introduced by
The variable $map does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
200
			E_USER_ERROR
201
		);
202
	}
203
204
	/**
205
	 * Removes a value in the map by a given key which has been added to the map
206
	 * via {@link SS_Map::push()} or {@link SS_Map::unshift()}
207
	 *
208
	 * Keys in the map cannot be unset since these values are derived from a
209
	 * {@link DataQuery} instance. In this case, use {@link SS_Map::toArray()}
210
	 * and manipulate the resulting array.
211
	 *
212
	 * @var string $key
213
	 * @var mixed $value
214
	 */
215
	public function offsetUnset($key) {
216
		if(isset($this->firstItems[$key])) {
217
			unset($this->firstItems[$key]);
218
219
			return;
220
		}
221
222
		if(isset($this->lastItems[$key])) {
223
			unset($this->lastItems[$key]);
224
225
			return;
226
		}
227
228
		user_error(
229
			"SS_Map is read-only. Unset cannot be called on keys derived from the DataQuery",
230
			E_USER_ERROR
231
		);
232
	}
233
234
	/**
235
	 * Returns an SS_Map_Iterator instance for iterating over the complete set
236
	 * of items in the map.
237
	 *
238
	 * Satisfies the IteratorAggreagte interface.
239
	 *
240
	 * @return SS_Map_Iterator
241
	 */
242
	public function getIterator() {
243
		return new SS_Map_Iterator(
244
			$this->list->getIterator(),
245
			$this->keyField,
246
			$this->valueField,
247
			$this->firstItems,
248
			$this->lastItems
249
		);
250
	}
251
252
	/**
253
	 * Returns the count of items in the list including the additional items set
254
	 * through {@link SS_Map::push()} and {@link SS_Map::unshift}.
255
	 *
256
	 * @return int
257
	 */
258
	public function count() {
259
		return $this->list->count() +
260
			count($this->firstItems) +
261
			count($this->lastItems);
262
	}
263
}
264
265
/**
266
 * Builds a map iterator around an Iterator.  Called by SS_Map
267
 *
268
 * @package framework
269
 * @subpackage model
270
 */
271
class SS_Map_Iterator implements Iterator {
272
273
	protected $items;
274
	protected $keyField, $titleField;
275
276
	protected $firstItemIdx = 0;
277
278
	protected $endItemIdx;
279
280
	protected $firstItems = array();
281
	protected $lastItems = array();
282
283
	protected $excludedItems = array();
284
285
	/**
286
	 * @param Iterator $items The iterator to build this map from
287
	 * @param string $keyField The field to use for the keys
288
	 * @param string $titleField The field to use for the values
289
	 * @param array $fristItems An optional map of items to show first
0 ignored issues
show
Documentation introduced by
There is no parameter named $fristItems. Did you maybe mean $firstItems?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
290
	 * @param array $lastItems An optional map of items to show last
291
	 */
292
	public function __construct(Iterator $items, $keyField, $titleField, $firstItems = null, $lastItems = null) {
293
		$this->items = $items;
294
		$this->keyField = $keyField;
295
		$this->titleField = $titleField;
296
		$this->endItemIdx = null;
297
298
		if($firstItems) {
299
			foreach($firstItems as $k => $v) {
300
				$this->firstItems[] = array($k,$v);
301
				$this->excludedItems[] = $k;
302
			}
303
		}
304
305
		if($lastItems) {
306
			foreach($lastItems as $k => $v) {
307
				$this->lastItems[] = array($k, $v);
308
				$this->excludedItems[] = $k;
309
			}
310
		}
311
312
	}
313
314
	/**
315
	 * Rewind the Iterator to the first element.
316
	 *
317
	 * @return mixed
318
	 */
319
	public function rewind() {
320
		$this->firstItemIdx = 0;
321
		$this->endItemIdx = null;
322
323
		$rewoundItem = $this->items->rewind();
324
325
		if(isset($this->firstItems[$this->firstItemIdx])) {
326
			return $this->firstItems[$this->firstItemIdx][1];
327
		} else {
328
			if($rewoundItem) {
329
				if($rewoundItem->hasMethod($this->titleField)) {
330
					return $rewoundItem->{$this->titleField}();
331
				}
332
333
				return $rewoundItem->{$this->titleField};
334
			} else if(!$this->items->valid() && $this->lastItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lastItems 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...
335
				$this->endItemIdx = 0;
336
337
				return $this->lastItems[0][1];
338
			}
339
		}
340
	}
341
342
	/**
343
	 * Return the current element.
344
	 *
345
	 * @return mixed
346
	 */
347
	public function current() {
348
		if(($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) {
349
			return $this->lastItems[$this->endItemIdx][1];
350
		} else if(isset($this->firstItems[$this->firstItemIdx])) {
351
			return $this->firstItems[$this->firstItemIdx][1];
352
		} else {
353
			if($this->items->current()->hasMethod($this->titleField)) {
354
				return $this->items->current()->{$this->titleField}();
355
			}
356
357
			return $this->items->current()->{$this->titleField};
358
		}
359
	}
360
361
	/**
362
	 * Return the key of the current element.
363
	 *
364
	 * @return string
365
	 */
366
	public function key() {
367
		if(($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) {
368
			return $this->lastItems[$this->endItemIdx][0];
369
		} else if(isset($this->firstItems[$this->firstItemIdx])) {
370
			return $this->firstItems[$this->firstItemIdx][0];
371
		} else {
372
			return $this->items->current()->{$this->keyField};
373
		}
374
	}
375
376
	/**
377
	 * Move forward to next element.
378
	 *
379
	 * @return mixed
380
	 */
381
	public function next() {
382
		$this->firstItemIdx++;
383
384
		if(isset($this->firstItems[$this->firstItemIdx])) {
385
			return $this->firstItems[$this->firstItemIdx][1];
386
		} else {
387
			if(!isset($this->firstItems[$this->firstItemIdx-1])) {
388
				$this->items->next();
389
			}
390
391
			if($this->excludedItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->excludedItems 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...
392
				while(($c = $this->items->current()) && in_array($c->{$this->keyField}, $this->excludedItems, true)) {
393
					$this->items->next();
394
				}
395
			}
396
		}
397
398
		if(!$this->items->valid()) {
399
			// iterator has passed the preface items, off the end of the items
400
			// list. Track through the end items to go through to the next
401
			if($this->endItemIdx === null) {
402
				$this->endItemIdx = -1;
403
			}
404
405
			$this->endItemIdx++;
406
407
			if(isset($this->lastItems[$this->endItemIdx])) {
408
				return $this->lastItems[$this->endItemIdx];
409
			}
410
411
			return false;
412
		}
413
	}
414
415
	/**
416
	 * Checks if current position is valid.
417
	 *
418
	 * @return boolean
419
	 */
420
	public function valid() {
421
		return (
422
			(isset($this->firstItems[$this->firstItemIdx])) ||
423
			(($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) ||
424
			$this->items->valid()
425
		);
426
	}
427
}
428