Completed
Pull Request — master (#5408)
by Damian
23:40 queued 12:41
created

ArrayList::exclude()   C

Complexity

Conditions 11
Paths 21

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 29
rs 5.2653
cc 11
eloc 18
nc 21
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * A list object that wraps around an array of objects or arrays.
4
 *
5
 * Note that (like DataLists), the implementations of the methods from SS_Filterable, SS_Sortable and
6
 * SS_Limitable return a new instance of ArrayList, rather than modifying the existing instance.
7
 *
8
 * For easy reference, methods that operate in this way are:
9
 *
10
 *   - limit
11
 *   - reverse
12
 *   - sort
13
 *   - filter
14
 *   - exclude
15
 *
16
 * @package framework
17
 * @subpackage model
18
 */
19
class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sortable, SS_Limitable {
20
21
	/**
22
	 * Holds the items in the list
23
	 *
24
	 * @var array
25
	 */
26
	protected $items = array();
27
28
	/**
29
	 *
30
	 * @param array $items - an initial array to fill this object with
31
	 */
32
	public function __construct(array $items = array()) {
33
		$this->items = array_values($items);
34
		parent::__construct();
35
	}
36
37
	/**
38
	 * Return the class of items in this list, by looking at the first item inside it.
39
	 */
40
	public function dataClass() {
41
		if(count($this->items) > 0) return get_class($this->items[0]);
42
	}
43
44
	/**
45
	 * Return the number of items in this list
46
	 *
47
	 * @return int
48
	 */
49
	public function count() {
50
		return count($this->items);
51
	}
52
53
	/**
54
	 * Returns true if this list has items
55
	 *
56
	 * @return bool
57
	 */
58
	public function exists() {
59
		return !empty($this->items);
60
	}
61
62
	/**
63
	 * Returns an Iterator for this ArrayList.
64
	 * This function allows you to use ArrayList in foreach loops
65
	 *
66
	 * @return ArrayIterator
67
	 */
68
	public function getIterator() {
69
		foreach($this->items as $i => $item) {
70
			if(is_array($item)) $this->items[$i] = new ArrayData($item);
71
		}
72
		return new ArrayIterator($this->items);
73
	}
74
75
	/**
76
	 * Return an array of the actual items that this ArrayList contains.
77
	 *
78
	 * @return array
79
	 */
80
	public function toArray() {
81
		return $this->items;
82
	}
83
84
	/**
85
	 * Walks the list using the specified callback
86
	 *
87
	 * @param callable $callback
88
	 * @return DataList
89
	 */
90
	public function each($callback) {
91
		foreach($this as $item) {
92
			$callback($item);
93
		}
94
	}
95
96
	public function debug() {
97
		$val = "<h2>" . $this->class . "</h2><ul>";
98
		foreach($this->toNestedArray() as $item) {
99
			$val .= "<li style=\"list-style-type: disc; margin-left: 20px\">" . Debug::text($item) . "</li>";
100
		}
101
		$val .= "</ul>";
102
		return $val;
103
	}
104
105
	/**
106
	 * Return this list as an array and every object it as an sub array as well
107
	 *
108
	 * @return array
109
	 */
110
	public function toNestedArray() {
111
		$result = array();
112
113
		foreach ($this->items as $item) {
114
			if (is_object($item)) {
115
				if (method_exists($item, 'toMap')) {
116
					$result[] = $item->toMap();
117
				} else {
118
					$result[] = (array) $item;
119
				}
120
			} else {
121
				$result[] = $item;
122
			}
123
		}
124
125
		return $result;
126
	}
127
128
	/**
129
	 * Get a sub-range of this dataobjectset as an array
130
	 *
131
	 * @param int $offset
132
	 * @param int $length
133
	 * @return ArrayList
134
	 */
135
	public function limit($length, $offset = 0) {
136
		if(!$length) {
137
			$length = count($this->items);
138
		}
139
140
		$list = clone $this;
141
		$list->items = array_slice($this->items, $offset, $length);
142
143
		return $list;
144
	}
145
146
	/**
147
	 * Add this $item into this list
148
	 *
149
	 * @param mixed $item
150
	 */
151
	public function add($item) {
152
		$this->push($item);
153
	}
154
155
	/**
156
	 * Remove this item from this list
157
	 *
158
	 * @param mixed $item
159
	 */
160
	public function remove($item) {
161
		$renumberKeys = false;
162
		foreach ($this->items as $key => $value) {
163
			if ($item === $value) {
164
				$renumberKeys = true;
165
				unset($this->items[$key]);
166
			}
167
		}
168
		if($renumberKeys) $this->items = array_values($this->items);
169
	}
170
171
	/**
172
	 * Replaces an item in this list with another item.
173
	 *
174
	 * @param array|object $item
175
	 * @param array|object $with
176
	 * @return void;
0 ignored issues
show
Documentation introduced by
The doc-type void; could not be parsed: Expected "|" or "end of type", but got ";" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
177
	 */
178
	public function replace($item, $with) {
179
		foreach ($this->items as $key => $candidate) {
180
			if ($candidate === $item) {
181
				$this->items[$key] = $with;
182
				return;
183
			}
184
		}
185
	}
186
187
	/**
188
	 * Merges with another array or list by pushing all the items in it onto the
189
	 * end of this list.
190
	 *
191
	 * @param array|object $with
192
	 */
193
	public function merge($with) {
194
		foreach ($with as $item) $this->push($item);
195
	}
196
197
	/**
198
	 * Removes items from this list which have a duplicate value for a certain
199
	 * field. This is especially useful when combining lists.
200
	 *
201
	 * @param string $field
202
	 */
203
	public function removeDuplicates($field = 'ID') {
204
		$seen = array();
205
		$renumberKeys = false;
206
207
		foreach ($this->items as $key => $item) {
208
			$value = $this->extractValue($item, $field);
209
210
			if (array_key_exists($value, $seen)) {
211
				$renumberKeys = true;
212
				unset($this->items[$key]);
213
			}
214
215
			$seen[$value] = true;
216
		}
217
218
		if($renumberKeys) $this->items = array_values($this->items);
219
	}
220
221
	/**
222
	 * Pushes an item onto the end of this list.
223
	 *
224
	 * @param array|object $item
225
	 */
226
	public function push($item) {
227
		$this->items[] = $item;
228
	}
229
230
	/**
231
	 * Pops the last element off the end of the list and returns it.
232
	 *
233
	 * @return array|object
234
	 */
235
	public function pop() {
236
		return array_pop($this->items);
237
	}
238
239
	/**
240
	 * Add an item onto the beginning of the list.
241
	 *
242
	 * @param array|object $item
243
	 */
244
	public function unshift($item) {
245
		array_unshift($this->items, $item);
246
	}
247
248
	/**
249
	 * Shifts the item off the beginning of the list and returns it.
250
	 *
251
	 * @return array|object
252
	 */
253
	public function shift() {
254
		return array_shift($this->items);
255
	}
256
257
	/**
258
	 * Returns the first item in the list
259
	 *
260
	 * @return mixed
261
	 */
262
	public function first() {
263
		return reset($this->items);
264
	}
265
266
	/**
267
	 * Returns the last item in the list
268
	 *
269
	 * @return mixed
270
	 */
271
	public function last() {
272
		return end($this->items);
273
	}
274
275
	/**
276
	 * Returns a map of this list
277
	 *
278
	 * @param string $keyfield The 'key' field of the result array
279
	 * @param string $titlefield The value field of the result array
280
	 * @return SS_Map
281
	 */
282
	public function map($keyfield = 'ID', $titlefield = 'Title') {
283
		$list = clone $this;
284
		return new SS_Map($list, $keyfield, $titlefield);
285
	}
286
287
	/**
288
	 * Find the first item of this list where the given key = value
289
	 *
290
	 * @param string $key
291
	 * @param string $value
292
	 * @return mixed
293
	 */
294
	public function find($key, $value) {
295
		foreach ($this->items as $item) {
296
			if ($this->extractValue($item, $key) == $value) {
297
				return $item;
298
			}
299
		}
300
	}
301
302
	/**
303
	 * Returns an array of a single field value for all items in the list.
304
	 *
305
	 * @param string $colName
306
	 * @return array
307
	 */
308
	public function column($colName = 'ID') {
309
		$result = array();
310
311
		foreach ($this->items as $item) {
312
			$result[] = $this->extractValue($item, $colName);
313
		}
314
315
		return $result;
316
	}
317
318
	/**
319
	 * You can always sort a ArrayList
320
	 *
321
	 * @param string $by
322
	 * @return bool
323
	 */
324
	public function canSortBy($by) {
325
		return true;
326
	}
327
328
	/**
329
	 * Reverses an {@link ArrayList}
330
	 *
331
	 * @return ArrayList
332
	 */
333
	public function reverse() {
334
		$list = clone $this;
335
		$list->items = array_reverse($this->items);
336
337
		return $list;
338
	}
339
340
	/**
341
	 * Parses a specified column into a sort field and direction
342
	 *
343
	 * @param type $column String to parse containing the column name
344
	 * @param type $direction Optional Additional argument which may contain the direction
345
	 * @return array Sort specification in the form array("Column", SORT_ASC).
346
	 */
347
	protected function parseSortColumn($column, $direction = null) {
348
		// Substitute the direction for the column if column is a numeric index
349
		if($direction && (empty($column) || is_numeric($column))) {
350
			$column = $direction;
351
			$direction = null;
352
		}
353
354
		// Parse column specification, considering possible ansi sql quoting
355
		if(preg_match('/^"?(?<column>[^"\s]+)"?(\s+(?<direction>((asc)|(desc))(ending)?))?$/i', $column, $match)) {
356
			$column = $match['column'];
357
			if(empty($direction) && !empty($match['direction'])) {
358
				$direction = $match['direction'];
359
			}
360
		} else {
361
			throw new InvalidArgumentException("Invalid sort() column");
362
		}
363
364
		// Parse sort direction specification
365
		if(empty($direction) || preg_match('/^asc(ending)?$/i', $direction)) {
366
			$direction = SORT_ASC;
367
		} elseif(preg_match('/^desc(ending)?$/i', $direction)) {
368
			$direction = SORT_DESC;
369
		} else {
370
			throw new InvalidArgumentException("Invalid sort() direction");
371
		}
372
373
		return array($column, $direction);
374
	}
375
376
	/**
377
	 * Sorts this list by one or more fields. You can either pass in a single
378
	 * field name and direction, or a map of field names to sort directions.
379
	 *
380
	 * Note that columns may be double quoted as per ANSI sql standard
381
	 *
382
	 * @return DataList
383
	 * @see SS_List::sort()
384
	 * @example $list->sort('Name'); // default ASC sorting
385
	 * @example $list->sort('Name DESC'); // DESC sorting
386
	 * @example $list->sort('Name', 'ASC');
387
	 * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC'));
388
	 */
389
	public function sort() {
390
		$args = func_get_args();
391
392
		if(count($args)==0){
393
			return $this;
394
		}
395
		if(count($args)>2){
396
			throw new InvalidArgumentException('This method takes zero, one or two arguments');
397
		}
398
399
		// One argument and it's a string
400
		if(count($args)==1 && is_string($args[0])){
401
			list($column, $direction) = $this->parseSortColumn($args[0]);
402
			$columnsToSort[$column] = $direction;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$columnsToSort was never initialized. Although not strictly required by PHP, it is generally a good practice to add $columnsToSort = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
403
404
		} else if(count($args)==2) {
405
			list($column, $direction) = $this->parseSortColumn($args[0], $args[1]);
406
			$columnsToSort[$column] = $direction;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$columnsToSort was never initialized. Although not strictly required by PHP, it is generally a good practice to add $columnsToSort = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
407
408
		} else if(is_array($args[0])) {
409
			foreach($args[0] as $key => $value) {
410
				list($column, $direction) = $this->parseSortColumn($key, $value);
411
				$columnsToSort[$column] = $direction;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$columnsToSort was never initialized. Although not strictly required by PHP, it is generally a good practice to add $columnsToSort = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
412
			}
413
		} else {
414
			throw new InvalidArgumentException("Bad arguments passed to sort()");
415
		}
416
417
		// Store the original keys of the items as a sort fallback, so we can preserve the original order in the event
418
		// that array_multisort is unable to work out a sort order for them. This also prevents array_multisort trying
419
		// to inspect object properties which can result in errors with circular dependencies
420
		$originalKeys = array_keys($this->items);
421
422
		// This the main sorting algorithm that supports infinite sorting params
423
		$multisortArgs = array();
424
		$values = array();
425
		foreach($columnsToSort as $column => $direction) {
0 ignored issues
show
Bug introduced by
The variable $columnsToSort does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
426
			// The reason these are added to columns is of the references, otherwise when the foreach
427
			// is done, all $values and $direction look the same
428
			$values[$column] = array();
429
			$sortDirection[$column] = $direction;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sortDirection was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sortDirection = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
430
			// We need to subtract every value into a temporary array for sorting
431
			foreach($this->items as $index => $item) {
432
				$values[$column][] = $this->extractValue($item, $column);
433
			}
434
			// PHP 5.3 requires below arguments to be reference when using array_multisort together
435
			// with call_user_func_array
436
			// First argument is the 'value' array to be sorted
437
			$multisortArgs[] = &$values[$column];
438
			// First argument is the direction to be sorted,
439
			$multisortArgs[] = &$sortDirection[$column];
440
		}
441
442
		$multisortArgs[] = &$originalKeys;
443
444
		$list = clone $this;
445
		// As the last argument we pass in a reference to the items that all the sorting will be applied upon
446
		$multisortArgs[] = &$list->items;
447
		call_user_func_array('array_multisort', $multisortArgs);
448
		return $list;
449
	}
450
451
	/**
452
	 * Returns true if the given column can be used to filter the records.
453
	 *
454
	 * It works by checking the fields available in the first record of the list.
455
	 */
456
	public function canFilterBy($by) {
457
		$firstRecord = $this->first();
458
459
		if ($firstRecord === false) {
460
			return false;
461
		}
462
463
		return array_key_exists($by, $firstRecord);
464
	}
465
466
	/**
467
	 * Filter the list to include items with these charactaristics
468
	 *
469
	 * @return ArrayList
470
	 * @see SS_List::filter()
471
	 * @example $list->filter('Name', 'bob'); // only bob in the list
472
	 * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list
473
	 * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the Age 21 in list
474
	 * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43
475
	 * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43)));
476
	 *          // aziz with the age 21 or 43 and bob with the Age 21 or 43
477
	 */
478
	public function filter() {
479
480
		$keepUs = call_user_func_array(array($this, 'normaliseFilterArgs'), func_get_args());
481
482
		$itemsToKeep = array();
483
		foreach($this->items as $item){
484
			$keepItem = true;
485
			foreach ($keepUs as $column => $value) {
486
				if ((is_array($value) && !in_array($this->extractValue($item, $column), $value))
487
					|| (!is_array($value) && $this->extractValue($item, $column) != $value)
488
				) {
489
					$keepItem = false;
490
					break;
491
				}
492
			}
493
			if($keepItem) {
494
				$itemsToKeep[] = $item;
495
			}
496
		}
497
498
		$list = clone $this;
499
		$list->items = $itemsToKeep;
500
		return $list;
501
	}
502
503
	/**
504
	 * Return a copy of this list which contains items matching any of these charactaristics.
505
	 *
506
	 * @example // only bob in the list
507
	 *          $list = $list->filterAny('Name', 'bob');
508
	 * @example // azis or bob in the list
509
	 *          $list = $list->filterAny('Name', array('aziz', 'bob');
510
	 * @example // bob or anyone aged 21 in the list
511
	 *          $list = $list->filterAny(array('Name'=>'bob, 'Age'=>21));
512
	 * @example // bob or anyone aged 21 or 43 in the list
513
	 *          $list = $list->filterAny(array('Name'=>'bob, 'Age'=>array(21, 43)));
514
	 * @example // all bobs, phils or anyone aged 21 or 43 in the list
515
	 *          $list = $list->filterAny(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
516
	 *
517
	 * @param string|array See {@link filter()}
518
	 * @return DataList
519
	 */
520
	public function filterAny() {
521
		$keepUs = call_user_func_array(array($this, 'normaliseFilterArgs'), func_get_args());
522
523
		$itemsToKeep = array();
524
525
		foreach ($this->items as $item) {
526
			foreach ($keepUs as $column => $value) {
527
				$extractedValue = $this->extractValue($item, $column);
528
				$matches = is_array($value) ? in_array($extractedValue, $value) : $extractedValue == $value;
529
				if ($matches) {
530
					$itemsToKeep[] = $item;
531
					break;
532
				}
533
			}
534
		}
535
536
		$list = clone $this;
537
		$list->items = array_unique($itemsToKeep, SORT_REGULAR);
538
		return $list;
539
540
	}
541
542
	/**
543
	 * Take the "standard" arguments that the filter/exclude functions take and return a single array with
544
	 * 'colum' => 'value'
545
	 *
546
	 * @param $column array|string The column name to filter OR an assosicative array of column => value
547
	 * @param $value array|string|null The values to filter the $column against
548
	 *
549
	 * @return array The normalised keyed array
550
	 */
551
	protected function normaliseFilterArgs($column, $value = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $column is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
552
		if(count(func_get_args())>2){
553
			throw new InvalidArgumentException('filter takes one array or two arguments');
554
		}
555
556
		if(count(func_get_args()) == 1 && !is_array(func_get_arg(0))){
557
			throw new InvalidArgumentException('filter takes one array or two arguments');
558
		}
559
560
		$keepUs = array();
561
		if(count(func_get_args())==2){
562
			$keepUs[func_get_arg(0)] = func_get_arg(1);
563
		}
564
565
		if(count(func_get_args())==1 && is_array(func_get_arg(0))){
566
			foreach(func_get_arg(0) as $column => $value) {
567
				$keepUs[$column] = $value;
568
			}
569
		}
570
571
		return $keepUs;
572
	}
573
574
	/**
575
	 * Filter this list to only contain the given Primary IDs
576
	 *
577
	 * @param array $ids Array of integers, will be automatically cast/escaped.
578
	 * @return ArrayList
579
	 */
580
	public function byIDs($ids) {
581
		$ids = array_map('intval', $ids); // sanitize
582
		return $this->filter('ID', $ids);
583
	}
584
585
	public function byID($id) {
586
		$firstElement = $this->filter("ID", $id)->first();
587
588
		if ($firstElement === false) {
589
			return null;
590
		}
591
592
		return $firstElement;
593
	}
594
595
	/**
596
	 * @see SS_Filterable::filterByCallback()
597
	 *
598
	 * @example $list = $list->filterByCallback(function($item, $list) { return $item->Age == 9; })
599
	 * @param callable $callback
600
	 * @return ArrayList
601
	 */
602
	public function filterByCallback($callback) {
603
		if(!is_callable($callback)) {
604
			throw new LogicException(sprintf(
605
				"SS_Filterable::filterByCallback() passed callback must be callable, '%s' given",
606
				gettype($callback)
607
			));
608
		}
609
610
		$output = static::create();
611
612
		foreach($this as $item) {
613
			if(call_user_func($callback, $item, $this)) $output->push($item);
614
		}
615
616
		return $output;
617
	}
618
619
	/**
620
	 * Exclude the list to not contain items with these charactaristics
621
	 *
622
	 * @return ArrayList
623
	 * @see SS_List::exclude()
624
	 * @example $list->exclude('Name', 'bob'); // exclude bob from list
625
	 * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list
626
	 * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21
627
	 * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43
628
	 * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
629
	 *          // bob age 21 or 43, phil age 21 or 43 would be excluded
630
	 */
631
	public function exclude() {
632
633
		$removeUs = call_user_func_array(array($this, 'normaliseFilterArgs'), func_get_args());
634
635
		$hitsRequiredToRemove = count($removeUs);
636
		$matches = array();
637
		foreach($removeUs as $column => $excludeValue) {
638
			foreach($this->items as $key => $item){
639
				if(!is_array($excludeValue) && $this->extractValue($item, $column) == $excludeValue) {
640
					$matches[$key]=isset($matches[$key])?$matches[$key]+1:1;
641
				} elseif(is_array($excludeValue) && in_array($this->extractValue($item, $column), $excludeValue)) {
642
					$matches[$key]=isset($matches[$key])?$matches[$key]+1:1;
643
				}
644
			}
645
		}
646
647
		$keysToRemove = array_keys($matches,$hitsRequiredToRemove);
648
649
		$itemsToKeep = array();
650
		foreach($this->items as $key => $value) {
651
			if(!in_array($key, $keysToRemove)) {
652
				$itemsToKeep[] = $value;
653
			}
654
		}
655
656
		$list = clone $this;
657
		$list->items = $itemsToKeep;
658
		return $list;
659
	}
660
661
	protected function shouldExclude($item, $args) {
0 ignored issues
show
Unused Code introduced by
The parameter $item is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
662
663
	}
664
665
666
	/**
667
	 * Returns whether an item with $key exists
668
	 *
669
	 * @param mixed $key
0 ignored issues
show
Bug introduced by
There is no parameter named $key. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
670
	 * @return bool
671
	 */
672
	public function offsetExists($offset) {
673
		return array_key_exists($offset, $this->items);
674
	}
675
676
	/**
677
	 * Returns item stored in list with index $key
678
	 *
679
	 * @param mixed $key
0 ignored issues
show
Bug introduced by
There is no parameter named $key. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
680
	 * @return DataObject
681
	 */
682
	public function offsetGet($offset) {
683
		if ($this->offsetExists($offset)) return $this->items[$offset];
684
	}
685
686
	/**
687
	 * Set an item with the key in $key
688
	 *
689
	 * @param mixed $key
0 ignored issues
show
Bug introduced by
There is no parameter named $key. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
690
	 * @param mixed $value
691
	 */
692
	public function offsetSet($offset, $value) {
693
		if($offset == null) {
694
			$this->items[] = $value;
695
		} else {
696
			$this->items[$offset] = $value;
697
		}
698
	}
699
700
	/**
701
	 * Unset an item with the key in $key
702
	 *
703
	 * @param mixed $key
0 ignored issues
show
Bug introduced by
There is no parameter named $key. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
704
	 */
705
	public function offsetUnset($offset) {
706
		unset($this->items[$offset]);
707
	}
708
709
	/**
710
	 * Extracts a value from an item in the list, where the item is either an
711
	 * object or array.
712
	 *
713
	 * @param  array|object $item
714
	 * @param  string $key
715
	 * @return mixed
716
	 */
717
	protected function extractValue($item, $key) {
718
		if (is_object($item)) {
719
			if(method_exists($item, 'hasMethod') && $item->hasMethod($key)) {
720
				return $item->{$key}();
721
			}
722
			return $item->{$key};
723
		} else {
724
			if (array_key_exists($key, $item)) {
725
				return $item[$key];
726
			}
727
		}
728
	}
729
730
}
731