Passed
Push — master ( 8873b6...1d5b74 )
by Aimeos
05:55
created

Map::intersectAssoc()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license MIT, http://opensource.org/licenses/MIT
5
 * @author Taylor Otwell, Aimeos.org developers
6
 * @package MW
7
 */
8
9
10
namespace Aimeos\MW;
11
12
13
/**
14
 * Handling and operating on a list of items easily
15
 * Inspired by Laravel Collection class, PHP map data structure and Javascript
16
 *
17
 * @package MW
18
 */
19
class Map implements MapIface
20
{
21
	protected static $methods = [];
22
	protected $items = [];
23
24
25
	/**
26
	 * Create a new map.
27
	 *
28
	 * @param iterable $items List of items
29
	 */
30
	public function __construct( iterable $items = [] )
31
	{
32
		$this->items = $this->getArray( $items );
33
	}
34
35
36
   /**
37
	 * Dynamically handle calls to the class.
38
	 *
39
	 * @param string $name Method name
40
	 * @param array $params List of parameters
41
	 * @return mixed Result from called function
42
	 *
43
	 * @throws \BadMethodCallException
44
	 */
45
	public static function __callStatic( string $name, array $params )
46
	{
47
		if( !isset( static::$methods[$name] ) ) {
48
			throw new \BadMethodCallException( sprintf( 'Method %s::%s does not exist.', static::class, $name ) );
49
		}
50
51
		return call_user_func_array( \Closure::bind( static::$methods[$name], null, static::class ), $params );
52
	}
53
54
55
	/**
56
	 * Dynamically handle calls to the class.
57
	 *
58
	 * @param string $name Method name
59
	 * @param array $params List of parameters
60
	 * @return mixed Result from called function
61
	 *
62
	 * @throws \BadMethodCallException
63
	 */
64
	public function __call( string $name, array $params )
65
	{
66
		if( !isset( static::$methods[$name] ) ) {
67
			throw new \BadMethodCallException( sprintf( 'Method %s::%s does not exist.', static::class, $name ) );
68
		}
69
70
		return call_user_func_array( static::$methods[$name]->bindTo( $this, static::class ), $params );
71
	}
72
73
74
	/**
75
	 * Create a new map instance if the value isn't one already.
76
	 *
77
	 * @param iterable $items List of items
78
	 * @return MapIface New map
79
	 */
80
	public static function from( iterable $items = [] ) : MapIface
81
	{
82
		return new static( $items );
83
	}
84
85
86
	/**
87
	 * Register a custom method that has access to the class properties
88
	 *
89
	 * @param string $name Method name
90
	 * @param \Closure $function Anonymous method
91
	 */
92
	public static function method( string $name, \Closure $function )
93
	{
94
		static::$methods[$name] = $function;
95
	}
96
97
98
	/**
99
	 * Removes all items from the map.
100
	 *
101
	 * @return MapIface Same map for fluid interface
102
	 */
103
	public function clear()
104
	{
105
		$this->items = [];
106
		return $this;
107
	}
108
109
110
	/**
111
	 * Push all of the given items onto the map.
112
	 *
113
	 * @param iterable $items List of items
114
	 * @return MapIface Updated map for fluid interface
115
	 */
116
	public function concat( iterable $items ) : MapIface
117
	{
118
		foreach( $items as $item ) {
119
			$this->items[] = $item;
120
		}
121
122
		return $this;
123
	}
124
125
126
	/**
127
	 * Creates a new map with the same items.
128
	 *
129
	 * @return MapIface New map
130
	 */
131
	public function copy() : MapIface
132
	{
133
		return new static( $this->items );
134
	}
135
136
137
	/**
138
	 * Count the number of items in the map.
139
	 *
140
	 * @return int Number of items
141
	 */
142
	public function count() : int
143
	{
144
		return count( $this->items );
145
	}
146
147
148
	/**
149
	 * Get the items in the map whose keys are not present in the given items.
150
	 *
151
	 * @param iterable $items List of items
152
	 * @param  callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>)
153
	 * @return MapIface New map
154
	 */
155
	public function diff( iterable $items, callable $callback = null ) : MapIface
156
	{
157
		if( $callback ) {
158
			return new static( array_udiff( $this->items, $this->getArray( $items ), $callback ) );
159
		}
160
161
		return new static( array_diff( $this->items, $this->getArray( $items ) ) );
162
	}
163
164
165
	/**
166
	 * Get the items in the collection whose keys and values are not present in the given items.
167
	 *
168
	 * @param iterable $items List of items
169
	 * @return MapIface New map
170
	 */
171
	public function diffAssoc( iterable $items, callable $callback = null ) : MapIface
172
	{
173
		if( $callback ) {
174
			return new static( array_diff_uassoc( $this->items, $this->getArray( $items ), $callback ) );
175
		}
176
177
		return new static( array_diff_assoc( $this->items, $this->getArray( $items ) ) );
178
	}
179
180
181
	/**
182
	 * Get the items in the collection whose keys are not present in the given items.
183
	 *
184
	 * @param iterable $items List of items
185
	 * @return MapIface New map
186
	 */
187
	public function diffKeys( iterable $items, callable $callback = null ) : MapIface
188
	{
189
		if( $callback ) {
190
			return new static( array_diff_ukey( $this->items, $this->getArray( $items ), $callback ) );
191
		}
192
193
		return new static( array_diff_key( $this->items, $this->getArray( $items ) ) );
194
	}
195
196
197
	/**
198
	 * Execute a callback over each item.
199
	 *
200
	 * @param callable $callback Function with (item, key) parameters and returns true/false
201
	 * @return MapIface Same map for fluid interface
202
	 */
203
	public function each( callable $callback ) : MapIface
204
	{
205
		foreach( $this->items as $key => $item )
206
		{
207
			if( $callback( $item, $key ) === false ) {
208
				break;
209
			}
210
		}
211
212
		return $this;
213
	}
214
215
216
	/**
217
	 * Run a filter over each of the items.
218
	 *
219
	 * @param  callable|null $callback Function with (item) parameter and returns true/false
220
	 * @return MapIface New map
221
	 */
222
	public function filter( callable $callback = null ) : MapIface
223
	{
224
		if( $callback ) {
225
			return new static( array_filter( $this->items, $callback, ARRAY_FILTER_USE_BOTH ) );
226
		}
227
228
		return new static( array_filter( $this->items ) );
229
	}
230
231
232
233
	/**
234
	 * Get the first item from the map passing the given truth test.
235
	 *
236
	 * @param callable|null $callback Function with (item, key) parameters and returns true/false
237
	 * @param mixed $default Default value if no item matches
238
	 * @return mixed First value of map or default value
239
	 */
240
	public function first( callable $callback = null, $default = null )
241
	{
242
		if( $callback )
243
		{
244
			foreach( $this->items as $key => $value )
245
			{
246
				if( $callback( $value, $key ) ) {
247
					return $value;
248
				}
249
			}
250
251
			return $default;
252
		}
253
254
		return reset( $this->items ) ?: $default;
255
	}
256
257
258
	/**
259
	 * Get an item from the map by key.
260
	 *
261
	 * @param mixed $key Key of the requested item
262
	 * @param mixed $default Default value if no item matches
263
	 * @return mixed Value from map or default value
264
	 */
265
	public function get( $key, $default = null )
266
	{
267
		return array_key_exists( $key, $this->items ) ? $this->items[$key] : $default;
268
	}
269
270
271
	/**
272
	 * Get an iterator for the items.
273
	 *
274
	 * @return \Iterator Over map items
275
	 */
276
	public function getIterator() : \Iterator
277
	{
278
		return new \ArrayIterator( $this->items );
279
	}
280
281
282
	/**
283
	 * Determine if an item exists in the map by key.
284
	 *
285
	 * @param mixed $key Key of the requested item
286
	 * @return bool True if key is available in map, false if not
287
	 */
288
	public function has( $key ) : bool
289
	{
290
		return array_key_exists( $key, $this->items );
291
	}
292
293
294
	/**
295
	 * Intersect the map with the given items.
296
	 *
297
	 * @param iterable $items List of items
298
	 * @param  callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>)
299
	 * @return MapIface New map
300
	 */
301
	public function intersect( iterable $items, callable $callback = null ) : MapIface
302
	{
303
		if( $callback ) {
304
			return new static( array_uintersect( $this->items, $this->getArray( $items ), $callback ) );
305
		}
306
307
		return new static( array_intersect( $this->items, $this->getArray( $items ) ) );
308
	}
309
310
311
	/**
312
	 * Intersect the map with the given items by key.
313
	 *
314
	 * @param iterable $items List of items
315
	 * @param  callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>)
316
	 * @return MapIface New map
317
	 */
318
	public function intersectAssoc( iterable $items, callable $callback = null ) : MapIface
319
	{
320
		if( $callback ) {
321
			return new static( array_uintersect_assoc( $this->items, $this->getArray( $items ), $callback ) );
322
		}
323
324
		return new static( array_intersect_assoc( $this->items, $this->getArray( $items ) ) );
325
	}
326
327
328
	/**
329
	 * Intersect the map with the given items by key.
330
	 *
331
	 * @param iterable $items List of items
332
	 * @param  callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>)
333
	 * @return MapIface New map
334
	 */
335
	public function intersectKeys( iterable $items, callable $callback = null ) : MapIface
336
	{
337
		if( $callback ) {
338
			return new static( array_intersect_ukey( $this->items, $this->getArray( $items ), $callback ) );
339
		}
340
341
		return new static( array_intersect_key( $this->items, $this->getArray( $items ) ) );
342
	}
343
344
345
	/**
346
	 * Determine if the map is empty or not.
347
	 *
348
	 * @return bool True if map is empty, false if not
349
	 */
350
	public function isEmpty() : bool
351
	{
352
		return empty( $this->items );
353
	}
354
355
356
	/**
357
	 * Get the keys of the map items.
358
	 *
359
	 * @return MapIface New map
360
	 */
361
	public function keys() : MapIface
362
	{
363
		return new static( array_keys( $this->items ) );
364
	}
365
366
367
	/**
368
	 * Sort the map keys.
369
	 *
370
	 * @param callable|null $callback Function with (keyA, keyB) parameters and returns -1 (<), 0 (=) and 1 (>)
371
	 * @param int $options Sort options for ksort()
372
	 * @return MapIface Updated map for fluid interface
373
	 */
374
	public function ksort( callable $callback = null, int $options = SORT_REGULAR ) : MapIface
375
	{
376
		$callback ? uksort( $this->items, $callback ) : ksort( $this->items, $options );
377
		return $this;
378
	}
379
380
381
	/**
382
	 * Get the last item from the map.
383
	 *
384
	 * @param callable|null $callback Function with (item, key) parameters and returns true/false
385
	 * @param mixed $default Default value if no item matches
386
	 * @return mixed Last value of map or default value
387
	 */
388
	public function last( callable $callback = null, $default = null )
389
	{
390
		if( $callback )
391
		{
392
			foreach( array_reverse( $this->items, true ) as $key => $value )
393
			{
394
				if( $callback( $value, $key ) ) {
395
					return $value;
396
				}
397
			}
398
399
			return $default;
400
		}
401
402
		return end( $this->items ) ?: $default;
403
	}
404
405
406
	/**
407
	 * Calls the provided function once for each element and constructs a new array from the results
408
	 *
409
	 * @param callable $callback Function with (item, key) parameters and returns computed result
410
	 * @return MapIface New map with the original keys and the computed values
411
	 */
412
	public function map( callable $callback ) : MapIface
413
	{
414
		$keys = array_keys( $this->items );
415
		$items = array_map( $callback, $this->items, $keys );
416
417
		return new static( array_combine( $keys, $items ) ?: [] );
418
	}
419
420
421
	/**
422
	 * Merge the map with the given items.
423
	 * Items with the same keys will be overwritten
424
	 *
425
	 * @param iterable $items List of items
426
	 * @return MapIface Updated map for fluid interface
427
	 */
428
	public function merge( iterable $items ) : MapIface
429
	{
430
		$this->items = array_merge( $this->items, $this->getArray( $items ) );
431
		return $this;
432
	}
433
434
435
	/**
436
	 * Determine if an item exists at an offset.
437
	 *
438
	 * @param mixed $key Key to check for
439
	 * @return bool True if key exists, false if not
440
	 */
441
	public function offsetExists( $key )
442
	{
443
		return array_key_exists( $key, $this->items );
444
	}
445
446
447
	/**
448
	 * Get an item at a given offset.
449
	 *
450
	 * @param mixed $key Key to return the item for
451
	 * @return mixed Value associated to the given key
452
	 */
453
	public function offsetGet( $key )
454
	{
455
		return $this->items[$key];
456
	}
457
458
459
	/**
460
	 * Set the item at a given offset.
461
	 *
462
	 * @param mixed $key Key to set the item for
463
	 * @param mixed $value New value set for the key
464
	 */
465
	public function offsetSet( $key, $value )
466
	{
467
		if( $key !== null ) {
468
			$this->items[$key] = $value;
469
		} else {
470
			$this->items[] = $value;
471
		}
472
	}
473
474
475
	/**
476
	 * Unset the item at a given offset.
477
	 *
478
	 * @param string $key Key for unsetting the item
479
	 */
480
	public function offsetUnset( $key )
481
	{
482
		unset( $this->items[$key] );
483
	}
484
485
486
	/**
487
	 * Pass the map to the given callback and return the result.
488
	 *
489
	 * @param callable $callback Function with (map) parameter and returns arbitrary result
490
	 * @return mixed Result returned by the callback
491
	 */
492
	public function pipe( callable $callback )
493
	{
494
		return $callback( $this );
495
	}
496
497
498
	/**
499
	 * Get and remove the last item from the map.
500
	 *
501
	 * @return mixed Last element of the map or null if empty
502
	 */
503
	public function pop()
504
	{
505
		return array_pop( $this->items );
506
	}
507
508
509
	/**
510
	 * Get and remove an item from the map.
511
	 *
512
	 * @param mixed $key Key to retrieve the value for
513
	 * @param mixed $default Default value if key isn't available
514
	 * @return mixed Value from map or default value
515
	 */
516
	public function pull( $key, $default = null )
517
	{
518
		$value = $this->get( $key, $default );
519
		unset( $this->items[$key] );
520
521
		return $value;
522
	}
523
524
525
	/**
526
	 * Push an item onto the end of the map.
527
	 *
528
	 * @param mixed $value Value to add to the end
529
	 * @return MapIface Same map for fluid interface
530
	 */
531
	public function push( $value ) : MapIface
532
	{
533
		$this->items[] = $value;
534
		return $this;
535
	}
536
537
538
	/**
539
	 * Iteratively reduces the array to a single value using a callback function
540
	 *
541
	 * @param callable $callback Function with (result, item) parameters and returns result
542
	 * @param mixed $initial Initial value when computing the result
543
	 * @return mixed Value computed by the callback function
544
	 */
545
	public function reduce( callable $callback, $initial = null )
546
	{
547
		return array_reduce( $this->items, $callback, $initial );
548
	}
549
550
551
	/**
552
	 * Remove an item from the map by key.
553
	 *
554
	 * @param string|iterable $keys List of keys
555
	 * @return MapIface Same map for fluid interface
556
	 */
557
	public function remove( $keys ) : MapIface
558
	{
559
		foreach( (array) $keys as $key ) {
560
			unset( $this->items[$key] );
561
		}
562
563
		return $this;
564
	}
565
566
567
	/**
568
	 * Recursively replaces items in the map with the given items
569
	 *
570
	 * @param iterable $items List of items
571
	 * @return MapIface Updated map for fluid interface
572
	 */
573
	public function replace( iterable $items ) : MapIface
574
	{
575
		$this->items = array_replace_recursive( $this->items, $this->getArray( $items ) );
576
		return $this;
577
	}
578
579
580
	/**
581
	 * Reverse items order.
582
	 *
583
	 * @return MapIface Updated map for fluid interface
584
	 */
585
	public function reverse() : MapIface
586
	{
587
		$this->items = array_reverse( $this->items, true );
588
		return $this;
589
	}
590
591
592
	/**
593
	 * Search the map for a given value and return the corresponding key if successful.
594
	 *
595
	 * @param mixed $value Item to search for
596
	 * @param bool $strict True if type of the item should be checked too
597
	 * @return mixed Value from map or null if not found
598
	 */
599
	public function search( $value, $strict = true )
600
	{
601
		if( ( $result = array_search( $value, $this->items, $strict ) ) !== false ) {
602
			return $result;
603
		}
604
	}
605
606
607
	/**
608
	 * Sets an item in the map by key.
609
	 *
610
	 * @param mixed $key Key to set the new value for
611
	 * @param mixed $value New item that should be set
612
	 * @return MapIface Same map for fluid interface
613
	 */
614
	public function set( $key, $value ) : MapIface
615
	{
616
		$this->items[$key] = $value;
617
		return $this;
618
	}
619
620
621
	/**
622
	 * Get and remove the first item from the map.
623
	 *
624
	 * @return mixed Value from map or null if not found
625
	 */
626
	public function shift()
627
	{
628
		return array_shift( $this->items );
629
	}
630
631
632
	/**
633
	 * Shuffle the items in the map.
634
	 *
635
	 * @return MapIface Updated map for fluid interface
636
	 */
637
	public function shuffle() : MapIface
638
	{
639
		shuffle( $this->items );
640
		return $this;
641
	}
642
643
644
	/**
645
	 * Slice the underlying map array.
646
	 *
647
	 * @param int $offset Number of items to start from
648
	 * @param int $length Number of items to return
649
	 * @return MapIface New map
650
	 */
651
	public function slice( int $offset, int $length = null ) : MapIface
652
	{
653
		return new static( array_slice( $this->items, $offset, $length, true ) );
654
	}
655
656
657
	/**
658
	 * Sort through each item with a callback.
659
	 *
660
	 * @param callable|null $callback Function with (itemA, itemB) parameters and returns -1 (<), 0 (=) and 1 (>)
661
	 * @param int $options Sort options for asort()
662
	 * @return MapIface Updated map for fluid interface
663
	 */
664
	public function sort( callable $callback = null, int $options = SORT_REGULAR ) : MapIface
665
	{
666
		$callback ? uasort( $this->items, $callback ) : asort( $this->items, $options );
667
		return $this;
668
	}
669
670
671
	/**
672
	 * Splice a portion of the underlying map array.
673
	 *
674
	 * @param int $offset Number of items to start from
675
	 * @param int|null $length Number of items to remove
676
	 * @param mixed $replacement List of items to insert
677
	 * @return MapIface New map
678
	 */
679
	public function splice( int $offset, int $length = null, $replacement = [] ) : MapIface
680
	{
681
		if( $length === null ) {
682
			return new static( array_splice( $this->items, $offset ) );
683
		}
684
685
		return new static( array_splice( $this->items, $offset, $length, $replacement ) );
686
	}
687
688
689
	/**
690
	 * Get the map of items as a plain array.
691
	 *
692
	 * @return array Plain array
693
	 */
694
	public function toArray() : array
695
	{
696
		return $this->items;
697
	}
698
699
700
	/**
701
	 * Union the map with the given items.
702
	 * Existing keys in the map will not be overwritten
703
	 *
704
	 * @param iterable $items List of items
705
	 * @return MapIface Updated map for fluid interface
706
	 */
707
	public function union( iterable $items ) : MapIface
708
	{
709
		$this->items += $this->getArray( $items );
710
		return $this;
711
	}
712
713
714
	/**
715
	 * Return only unique items from the map.
716
	 *
717
	 * @return MapIface New map
718
	 */
719
	public function unique() : MapIface
720
	{
721
		return new static( array_unique( $this->items ) );
722
	}
723
724
725
	/**
726
	 * Push an item onto the beginning of the map.
727
	 *
728
	 * @param mixed $value Item to add at the beginning
729
	 * @param mixed $key Key for the item
730
	 * @return MapIface Same map for fluid interface
731
	 */
732
	public function unshift( $value, $key = null ) : MapIface
733
	{
734
		if( $key === null ) {
735
			array_unshift( $this->items, $value );
736
		} else {
737
			$this->items = [$key => $value] + $this->items;
738
		}
739
740
		return $this;
741
	}
742
743
744
	/**
745
	 * Reset the keys on the underlying array.
746
	 *
747
	 * @return MapIface New map of the values
748
	 */
749
	public function values() : MapIface
750
	{
751
		return new static( array_values( $this->items ) );
752
	}
753
754
755
	/**
756
	 * Returns an array of the given items
757
	 *
758
	 * @param iterable $items List of items
759
	 * @return array Plain array
760
	 */
761
	protected function getArray( iterable $items ) : array
762
	{
763
		if( is_array( $items ) ) {
764
			return $items;
765
		} elseif( $items instanceof self ) {
766
			return $items->toArray();
767
		} elseif( $items instanceof \Traversable ) {
768
			return iterator_to_array( $items );
769
		}
770
771
		return (array) $items;
772
	}
773
}
774