[TASK] Specific exception in ArrayUtility::removeByPath()
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Utility / ArrayUtilityTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
19 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
20
21 /**
22 * Test case
23 */
24 class ArrayUtilityTest extends UnitTestCase
25 {
26 ///////////////////////
27 // Tests concerning filterByValueRecursive
28 ///////////////////////
29 /**
30 * Data provider for filterByValueRecursiveCorrectlyFiltersArray
31 *
32 * Every array splits into:
33 * - String value to search for
34 * - Input array
35 * - Expected result array
36 */
37 public function filterByValueRecursive()
38 {
39 return [
40 'empty search array' => [
41 'banana',
42 [],
43 []
44 ],
45 'empty string as needle' => [
46 '',
47 [
48 '',
49 'apple'
50 ],
51 [
52 ''
53 ]
54 ],
55 'flat array searching for string' => [
56 'banana',
57 [
58 'apple',
59 'banana'
60 ],
61 [
62 1 => 'banana'
63 ]
64 ],
65 'flat array searching for string with two matches' => [
66 'banana',
67 [
68 'foo' => 'apple',
69 'firstbanana' => 'banana',
70 'secondbanana' => 'banana'
71 ],
72 [
73 'firstbanana' => 'banana',
74 'secondbanana' => 'banana'
75 ]
76 ],
77 'multi dimensional array searching for string with multiple matches' => [
78 'banana',
79 [
80 'foo' => 'apple',
81 'firstbanana' => 'banana',
82 'grape' => [
83 'foo2' => 'apple2',
84 'secondbanana' => 'banana',
85 'foo3' => []
86 ],
87 'bar' => 'orange'
88 ],
89 [
90 'firstbanana' => 'banana',
91 'grape' => [
92 'secondbanana' => 'banana'
93 ]
94 ]
95 ],
96 'multi dimensional array searching for integer with multiple matches' => [
97 42,
98 [
99 'foo' => 23,
100 'bar' => 42,
101 [
102 'foo' => 23,
103 'bar' => 42
104 ]
105 ],
106 [
107 'bar' => 42,
108 [
109 'bar' => 42
110 ]
111 ]
112 ],
113 'flat array searching for boolean TRUE' => [
114 true,
115 [
116 23 => false,
117 42 => true
118 ],
119 [
120 42 => true
121 ]
122 ],
123 'multi dimensional array searching for boolean FALSE' => [
124 false,
125 [
126 23 => false,
127 42 => true,
128 'foo' => [
129 23 => false,
130 42 => true
131 ]
132 ],
133 [
134 23 => false,
135 'foo' => [
136 23 => false
137 ]
138 ]
139 ],
140 'flat array searching for array' => [
141 [
142 'foo' => 'bar'
143 ],
144 [
145 'foo' => 'bar',
146 'foobar' => [
147 'foo' => 'bar'
148 ]
149 ],
150 [
151 'foobar' => [
152 'foo' => 'bar'
153 ]
154 ]
155 ]
156 ];
157 }
158
159 /**
160 * @test
161 * @dataProvider filterByValueRecursive
162 * @param array $needle
163 * @param array $haystack
164 * @param array $expectedResult
165 */
166 public function filterByValueRecursiveCorrectlyFiltersArray($needle, $haystack, $expectedResult)
167 {
168 $this->assertEquals(
169 $expectedResult,
170 ArrayUtility::filterByValueRecursive($needle, $haystack)
171 );
172 }
173
174 /**
175 * @test
176 */
177 public function filterByValueRecursiveMatchesReferencesToSameObject()
178 {
179 $instance = new \stdClass();
180 $this->assertEquals(
181 [$instance],
182 ArrayUtility::filterByValueRecursive($instance, [$instance])
183 );
184 }
185
186 /**
187 * @test
188 */
189 public function filterByValueRecursiveDoesNotMatchDifferentInstancesOfSameClass()
190 {
191 $this->assertEquals(
192 [],
193 ArrayUtility::filterByValueRecursive(new \stdClass(), [new \stdClass()])
194 );
195 }
196
197 ///////////////////////
198 // Tests concerning isValidPath
199 ///////////////////////
200 /**
201 * @test
202 */
203 public function isValidPathReturnsTrueIfPathExists()
204 {
205 $this->assertTrue(ArrayUtility::isValidPath(['foo' => 'bar'], 'foo'));
206 }
207
208 /**
209 * @test
210 */
211 public function isValidPathReturnsFalseIfPathDoesNotExist()
212 {
213 $this->assertFalse(ArrayUtility::isValidPath(['foo' => 'bar'], 'bar'));
214 }
215
216 ///////////////////////
217 // Tests concerning getValueByPath
218 ///////////////////////
219 /**
220 * @test
221 */
222 public function getValueByPathThrowsExceptionIfPathIsNotString()
223 {
224 $this->expectException(\InvalidArgumentException::class);
225 $this->expectExceptionCode(1476557628);
226
227 ArrayUtility::getValueByPath([], 123);
228 }
229
230 /**
231 * @test
232 */
233 public function getValueByPathThrowsExceptionIfPathIsEmpty()
234 {
235 $this->expectException(\RuntimeException::class);
236 $this->expectExceptionCode(1341397767);
237
238 ArrayUtility::getValueByPath([], '');
239 }
240
241 /**
242 * @test
243 */
244 public function getValueByPathReturnsFirstIndexIfPathIsZero()
245 {
246 $this->assertSame('foo', ArrayUtility::getValueByPath(['foo'], '0'));
247 }
248
249 /**
250 * @test
251 */
252 public function getValueByPathReturnsFirstIndexIfPathSegmentIsZero()
253 {
254 $this->assertSame('bar', ArrayUtility::getValueByPath(['foo' => ['bar']], 'foo/0'));
255 }
256
257 /**
258 * Data provider for getValueByPathThrowsExceptionIfPathNotExists
259 * Every array splits into:
260 * - Array to get value from
261 * - String path
262 * - Expected result
263 * @return array
264 */
265 public function getValueByPathInvalidPathDataProvider()
266 {
267 return [
268 'not existing index' => [
269 [
270 'foo' => ['foo']
271 ],
272 'foo/1',
273 false
274 ],
275 'not existing path 1' => [
276 [
277 'foo' => []
278 ],
279 'foo/bar/baz',
280 false
281 ],
282 'not existing path 2' => [
283 [
284 'foo' => [
285 'baz' => 42
286 ],
287 'bar' => []
288 ],
289 'foo/bar/baz',
290 false
291 ],
292 // Negative test: This could be improved and the test moved to
293 // the valid data provider if the method supports this
294 'doubletick encapsulated quoted doubletick does not work' => [
295 [
296 '"foo"bar"' => [
297 'baz' => 42
298 ],
299 'bar' => []
300 ],
301 '"foo\\"bar"/baz',
302 42
303 ],
304 // Negative test: Method could be improved here
305 'path with doubletick does not work' => [
306 [
307 'fo"o' => [
308 'bar' => 42
309 ]
310 ],
311 'fo"o/foobar',
312 42
313 ]
314 ];
315 }
316
317 /**
318 * @test
319 * @dataProvider getValueByPathInvalidPathDataProvider
320 * @param array $array
321 * @param string $path
322 */
323 public function getValueByPathThrowsExceptionIfPathNotExists(array $array, $path)
324 {
325 $this->expectException(\RuntimeException::class);
326 $this->expectExceptionCode(1341397869);
327 ArrayUtility::getValueByPath($array, $path);
328 }
329
330 /**
331 * @test
332 * @dataProvider getValueByPathInvalidPathDataProvider
333 * @param array $array
334 * @param string $path
335 */
336 public function getValueByPathThrowsSpecificExceptionIfPathNotExists(array $array, string $path)
337 {
338 $this->expectException(MissingArrayPathException::class);
339 $this->expectExceptionCode(1341397869);
340 ArrayUtility::getValueByPath($array, $path);
341 }
342
343 /**
344 * Data provider for getValueByPathReturnsCorrectValue
345 * Every array splits into:
346 * - Array to get value from
347 * - String path
348 * - Expected result
349 */
350 public function getValueByPathValidDataProvider()
351 {
352 $testObject = new \stdClass();
353 $testObject->foo = 'foo';
354 $testObject->bar = 'bar';
355 return [
356 'integer in multi level array' => [
357 [
358 'foo' => [
359 'bar' => [
360 'baz' => 42
361 ],
362 'bar2' => []
363 ]
364 ],
365 'foo/bar/baz',
366 42
367 ],
368 'zero integer in multi level array' => [
369 [
370 'foo' => [
371 'bar' => [
372 'baz' => 0
373 ]
374 ]
375 ],
376 'foo/bar/baz',
377 0
378 ],
379 'NULL value in multi level array' => [
380 [
381 'foo' => [
382 'baz' => null
383 ]
384 ],
385 'foo/baz',
386 null
387 ],
388 'get string value' => [
389 [
390 'foo' => [
391 'baz' => 'this is a test string'
392 ]
393 ],
394 'foo/baz',
395 'this is a test string'
396 ],
397 'get boolean value: FALSE' => [
398 [
399 'foo' => [
400 'baz' => false
401 ]
402 ],
403 'foo/baz',
404 false
405 ],
406 'get boolean value: TRUE' => [
407 [
408 'foo' => [
409 'baz' => true
410 ]
411 ],
412 'foo/baz',
413 true
414 ],
415 'get object value' => [
416 [
417 'foo' => [
418 'baz' => $testObject
419 ]
420 ],
421 'foo/baz',
422 $testObject
423 ],
424 'enclosed path' => [
425 [
426 'foo/bar' => [
427 'foobar' => 42
428 ]
429 ],
430 '"foo/bar"/foobar',
431 42
432 ]
433 ];
434 }
435
436 /**
437 * @test
438 * @dataProvider getValueByPathValidDataProvider
439 * @param array $array
440 * @param string $path
441 * @param mixed $expectedResult
442 */
443 public function getValueByPathGetsCorrectValue(array $array, $path, $expectedResult)
444 {
445 $this->assertEquals($expectedResult, ArrayUtility::getValueByPath($array, $path));
446 }
447
448 /**
449 * @test
450 */
451 public function getValueByPathAcceptsDifferentDelimiter()
452 {
453 $input = [
454 'foo' => [
455 'bar' => [
456 'baz' => 42
457 ],
458 'bar2' => []
459 ]
460 ];
461 $searchPath = 'foo%bar%baz';
462 $expected = 42;
463 $delimiter = '%';
464 $this->assertEquals(
465 $expected,
466 ArrayUtility::getValueByPath($input, $searchPath, $delimiter)
467 );
468 }
469
470 ///////////////////////
471 // Tests concerning setValueByPath
472 ///////////////////////
473 /**
474 * @test
475 */
476 public function setValueByPathThrowsExceptionIfPathIsEmpty()
477 {
478 $this->expectException(\RuntimeException::class);
479 $this->expectExceptionCode(1341406194);
480
481 ArrayUtility::setValueByPath([], '', null);
482 }
483
484 /**
485 * @test
486 */
487 public function setValueByPathThrowsExceptionIfPathIsNotAString()
488 {
489 $this->expectException(\InvalidArgumentException::class);
490 $this->expectExceptionCode(1478781081);
491
492 ArrayUtility::setValueByPath([], 123, null);
493 }
494
495 /**
496 * @test
497 */
498 public function setValueByPathThrowsExceptionIfPathSegmentIsEmpty()
499 {
500 $this->expectException(\RuntimeException::class);
501 $this->expectExceptionCode(1341406846);
502
503 ArrayUtility::setValueByPath(['foo' => 'bar'], '/foo', 'value');
504 }
505
506 /**
507 * @test
508 */
509 public function setValueByPathCanUseZeroAsPathSegment()
510 {
511 $this->assertSame(['foo' => ['value']], ArrayUtility::setValueByPath(['foo' => []], 'foo/0', 'value'));
512 }
513
514 /**
515 * @test
516 */
517 public function setValueByPathCanUseZeroAsPath()
518 {
519 $this->assertSame(['value', 'bar'], ArrayUtility::setValueByPath(['foo', 'bar'], '0', 'value'));
520 }
521
522 /**
523 * Data provider for setValueByPathSetsCorrectValueDataProvider
524 *
525 * Every array splits into:
526 * - Array to set value in
527 * - String path
528 * - Value to set
529 * - Expected result
530 */
531 public function setValueByPathSetsCorrectValueDataProvider()
532 {
533 $testObject = new \stdClass();
534 $testObject->foo = 'foo';
535 $testObject->bar = 'bar';
536 return [
537 'set integer value: 42' => [
538 [
539 'foo' => [
540 'bar' => [
541 'baz' => 0
542 ]
543 ]
544 ],
545 'foo/bar/baz',
546 42,
547 [
548 'foo' => [
549 'bar' => [
550 'baz' => 42
551 ]
552 ]
553 ]
554 ],
555 'set integer value: 0' => [
556 [
557 'foo' => [
558 'bar' => [
559 'baz' => 42
560 ]
561 ]
562 ],
563 'foo/bar/baz',
564 0,
565 [
566 'foo' => [
567 'bar' => [
568 'baz' => 0
569 ]
570 ]
571 ]
572 ],
573 'set null value' => [
574 [
575 'foo' => [
576 'bar' => [
577 'baz' => 42
578 ]
579 ]
580 ],
581 'foo/bar/baz',
582 null,
583 [
584 'foo' => [
585 'bar' => [
586 'baz' => null
587 ]
588 ]
589 ]
590 ],
591 'set array value' => [
592 [
593 'foo' => [
594 'bar' => [
595 'baz' => 42
596 ]
597 ]
598 ],
599 'foo/bar/baz',
600 [
601 'foo' => 123
602 ],
603 [
604 'foo' => [
605 'bar' => [
606 'baz' => [
607 'foo' => 123
608 ]
609 ]
610 ]
611 ]
612 ],
613 'set boolean value: FALSE' => [
614 [
615 'foo' => [
616 'bar' => [
617 'baz' => true
618 ]
619 ]
620 ],
621 'foo/bar/baz',
622 false,
623 [
624 'foo' => [
625 'bar' => [
626 'baz' => false
627 ]
628 ]
629 ]
630 ],
631 'set boolean value: TRUE' => [
632 [
633 'foo' => [
634 'bar' => [
635 'baz' => null
636 ]
637 ]
638 ],
639 'foo/bar/baz',
640 true,
641 [
642 'foo' => [
643 'bar' => [
644 'baz' => true
645 ]
646 ]
647 ]
648 ],
649 'set object value' => [
650 [
651 'foo' => [
652 'bar' => [
653 'baz' => null
654 ]
655 ]
656 ],
657 'foo/bar/baz',
658 $testObject,
659 [
660 'foo' => [
661 'bar' => [
662 'baz' => $testObject
663 ]
664 ]
665 ]
666 ],
667 'multi keys in array' => [
668 [
669 'foo' => [
670 'bar' => [
671 'baz' => 'value'
672 ],
673 'bar2' => [
674 'baz' => 'value'
675 ]
676 ]
677 ],
678 'foo/bar2/baz',
679 'newValue',
680 [
681 'foo' => [
682 'bar' => [
683 'baz' => 'value'
684 ],
685 'bar2' => [
686 'baz' => 'newValue'
687 ]
688 ]
689 ]
690 ]
691 ];
692 }
693
694 /**
695 * @test
696 * @dataProvider setValueByPathSetsCorrectValueDataProvider
697 * @param array $array
698 * @param string $path
699 * @param string $value
700 * @param array $expectedResult
701 */
702 public function setValueByPathSetsCorrectValue(array $array, $path, $value, $expectedResult)
703 {
704 $this->assertEquals(
705 $expectedResult,
706 ArrayUtility::setValueByPath($array, $path, $value)
707 );
708 }
709
710 /**********************
711 /* Tests concerning removeByPath
712 ***********************/
713
714 /**
715 * @test
716 */
717 public function removeByPathThrowsExceptionIfPathIsEmpty()
718 {
719 $this->expectException(\RuntimeException::class);
720 $this->expectExceptionCode(1371757718);
721
722 ArrayUtility::removeByPath([], '');
723 }
724
725 /**
726 * @test
727 */
728 public function removeByPathThrowsExceptionIfPathIsNotAString()
729 {
730 $this->expectException(\RuntimeException::class);
731 $this->expectExceptionCode(1371757719);
732
733 ArrayUtility::removeByPath([], ['foo']);
734 }
735
736 /**
737 * @test
738 */
739 public function removeByPathThrowsExceptionWithEmptyPathSegment()
740 {
741 $inputArray = [
742 'foo' => [
743 'bar' => 42,
744 ]
745 ];
746
747 $this->expectException(\RuntimeException::class);
748 $this->expectExceptionCode(1371757720);
749
750 ArrayUtility::removeByPath($inputArray, 'foo//bar');
751 }
752
753 /**
754 * @test
755 */
756 public function removeByPathRemovesFirstIndexWithZeroAsPathSegment()
757 {
758 $inputArray = [
759 'foo' => ['bar']
760 ];
761
762 $this->assertSame(['foo' => []], ArrayUtility::removeByPath($inputArray, 'foo/0'));
763 }
764
765 /**
766 * @test
767 */
768 public function removeByPathRemovesFirstIndexWithZeroAsPath()
769 {
770 $inputArray = ['bar'];
771
772 $this->assertSame([], ArrayUtility::removeByPath($inputArray, '0'));
773 }
774
775 /**
776 * @test
777 */
778 public function removeByPathThrowsExceptionIfPathDoesNotExistInArray()
779 {
780 $inputArray = [
781 'foo' => [
782 'bar' => 42,
783 ]
784 ];
785
786 $this->expectException(\RuntimeException::class);
787 $this->expectExceptionCode(1371758436);
788
789 ArrayUtility::removeByPath($inputArray, 'foo/baz');
790 }
791
792 /**
793 * @test
794 */
795 public function removeByPathThrowsSpecificExceptionIfPathDoesNotExistInArray()
796 {
797 $inputArray = [
798 'foo' => [
799 'bar' => 42,
800 ]
801 ];
802
803 $this->expectException(MissingArrayPathException::class);
804 $this->expectExceptionCode(1371758436);
805
806 ArrayUtility::removeByPath($inputArray, 'foo/baz');
807 }
808
809 /**
810 * @test
811 */
812 public function removeByPathAcceptsGivenDelimiter()
813 {
814 $inputArray = [
815 'foo' => [
816 'toRemove' => 42,
817 'keep' => 23
818 ],
819 ];
820 $path = 'foo.toRemove';
821 $expected = [
822 'foo' => [
823 'keep' => 23,
824 ],
825 ];
826 $this->assertEquals(
827 $expected,
828 ArrayUtility::removeByPath($inputArray, $path, '.')
829 );
830 }
831
832 /**
833 * Data provider for removeByPathRemovesCorrectPath
834 */
835 public function removeByPathRemovesCorrectPathDataProvider()
836 {
837 return [
838 'single value' => [
839 [
840 'foo' => [
841 'toRemove' => 42,
842 'keep' => 23
843 ],
844 ],
845 'foo/toRemove',
846 [
847 'foo' => [
848 'keep' => 23,
849 ],
850 ],
851 ],
852 'whole array' => [
853 [
854 'foo' => [
855 'bar' => 42
856 ],
857 ],
858 'foo',
859 [],
860 ],
861 'sub array' => [
862 [
863 'foo' => [
864 'keep' => 23,
865 'toRemove' => [
866 'foo' => 'bar',
867 ],
868 ],
869 ],
870 'foo/toRemove',
871 [
872 'foo' => [
873 'keep' => 23,
874 ],
875 ],
876 ],
877 ];
878 }
879
880 /**
881 * @test
882 * @dataProvider removeByPathRemovesCorrectPathDataProvider
883 * @param array $array
884 * @param string $path
885 * @param array $expectedResult
886 */
887 public function removeByPathRemovesCorrectPath(array $array, $path, $expectedResult)
888 {
889 $this->assertEquals(
890 $expectedResult,
891 ArrayUtility::removeByPath($array, $path)
892 );
893 }
894
895 ///////////////////////
896 // Tests concerning sortByKeyRecursive
897 ///////////////////////
898 /**
899 * @test
900 */
901 public function sortByKeyRecursiveCheckIfSortingIsCorrect()
902 {
903 $unsortedArray = [
904 'z' => null,
905 'a' => null,
906 'd' => [
907 'c' => null,
908 'b' => null,
909 'd' => null,
910 'a' => null
911 ]
912 ];
913 $expectedResult = [
914 'a' => null,
915 'd' => [
916 'a' => null,
917 'b' => null,
918 'c' => null,
919 'd' => null
920 ],
921 'z' => null
922 ];
923 $this->assertSame($expectedResult, ArrayUtility::sortByKeyRecursive($unsortedArray));
924 }
925
926 ///////////////////////
927 // Tests concerning sortArraysByKey
928 ///////////////////////
929 /**
930 * Data provider for sortArraysByKeyCheckIfSortingIsCorrect
931 */
932 public function sortArraysByKeyCheckIfSortingIsCorrectDataProvider()
933 {
934 return [
935 'assoc array index' => [
936 [
937 '22' => [
938 'uid' => '22',
939 'title' => 'c',
940 'dummy' => 2
941 ],
942 '24' => [
943 'uid' => '24',
944 'title' => 'a',
945 'dummy' => 3
946 ],
947 '23' => [
948 'uid' => '23',
949 'title' => 'b',
950 'dummy' => 4
951 ],
952 ],
953 'title',
954 true,
955 [
956 '24' => [
957 'uid' => '24',
958 'title' => 'a',
959 'dummy' => 3
960 ],
961 '23' => [
962 'uid' => '23',
963 'title' => 'b',
964 'dummy' => 4
965 ],
966 '22' => [
967 'uid' => '22',
968 'title' => 'c',
969 'dummy' => 2
970 ],
971 ],
972 ],
973 'numeric array index' => [
974 [
975 22 => [
976 'uid' => '22',
977 'title' => 'c',
978 'dummy' => 2
979 ],
980 24 => [
981 'uid' => '24',
982 'title' => 'a',
983 'dummy' => 3
984 ],
985 23 => [
986 'uid' => '23',
987 'title' => 'b',
988 'dummy' => 4
989 ],
990 ],
991 'title',
992 true,
993 [
994 24 => [
995 'uid' => '24',
996 'title' => 'a',
997 'dummy' => 3
998 ],
999 23 => [
1000 'uid' => '23',
1001 'title' => 'b',
1002 'dummy' => 4
1003 ],
1004 22 => [
1005 'uid' => '22',
1006 'title' => 'c',
1007 'dummy' => 2
1008 ],
1009 ],
1010 ],
1011 'numeric array index DESC' => [
1012 [
1013 23 => [
1014 'uid' => '23',
1015 'title' => 'b',
1016 'dummy' => 4
1017 ],
1018 22 => [
1019 'uid' => '22',
1020 'title' => 'c',
1021 'dummy' => 2
1022 ],
1023 24 => [
1024 'uid' => '24',
1025 'title' => 'a',
1026 'dummy' => 3
1027 ],
1028 ],
1029 'title',
1030 false,
1031 [
1032 22 => [
1033 'uid' => '22',
1034 'title' => 'c',
1035 'dummy' => 2
1036 ],
1037 23 => [
1038 'uid' => '23',
1039 'title' => 'b',
1040 'dummy' => 4
1041 ],
1042 24 => [
1043 'uid' => '24',
1044 'title' => 'a',
1045 'dummy' => 3
1046 ],
1047 ],
1048 ],
1049 ];
1050 }
1051
1052 /**
1053 * @test
1054 * @dataProvider sortArraysByKeyCheckIfSortingIsCorrectDataProvider
1055 * @param array $array
1056 * @param string $key
1057 * @param bool $ascending
1058 * @param array $expectedResult
1059 */
1060 public function sortArraysByKeyCheckIfSortingIsCorrect(array $array, $key, $ascending, $expectedResult)
1061 {
1062 $sortedArray = ArrayUtility::sortArraysByKey($array, $key, $ascending);
1063 $this->assertSame($expectedResult, $sortedArray);
1064 }
1065
1066 /**
1067 * @test
1068 */
1069 public function sortArraysByKeyThrowsExceptionForNonExistingKey()
1070 {
1071 $this->expectException(\RuntimeException::class);
1072 $this->expectExceptionCode(1373727309);
1073
1074 ArrayUtility::sortArraysByKey([['a'], ['a']], 'dummy');
1075 }
1076
1077 ///////////////////////
1078 // Tests concerning arrayExport
1079 ///////////////////////
1080 /**
1081 * @test
1082 */
1083 public function arrayExportReturnsFormattedMultidimensionalArray()
1084 {
1085 $array = [
1086 'foo' => [
1087 'bar' => 42,
1088 'bar2' => [
1089 'baz' => 'val\'ue',
1090 'baz2' => true,
1091 'baz3' => false,
1092 'baz4' => []
1093 ]
1094 ],
1095 'baz' => 23,
1096 'foobar' => null,
1097 'qux' => 0.1,
1098 'qux2' => 0.000000001,
1099 ];
1100 $expected =
1101 '[' . LF .
1102 ' \'foo\' => [' . LF .
1103 ' \'bar\' => 42,' . LF .
1104 ' \'bar2\' => [' . LF .
1105 ' \'baz\' => \'val\\\'ue\',' . LF .
1106 ' \'baz2\' => true,' . LF .
1107 ' \'baz3\' => false,' . LF .
1108 ' \'baz4\' => [],' . LF .
1109 ' ],' . LF .
1110 ' ],' . LF .
1111 ' \'baz\' => 23,' . LF .
1112 ' \'foobar\' => null,' . LF .
1113 ' \'qux\' => 0.1,' . LF .
1114 ' \'qux2\' => 1.0E-9,' . LF .
1115 ']';
1116 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1117 }
1118
1119 /**
1120 * @test
1121 */
1122 public function arrayExportThrowsExceptionIfObjectShouldBeExported()
1123 {
1124 $array = [
1125 'foo' => [
1126 'bar' => new \stdClass()
1127 ]
1128 ];
1129
1130 $this->expectException(\RuntimeException::class);
1131 $this->expectExceptionCode(1342294987);
1132
1133 ArrayUtility::arrayExport($array);
1134 }
1135
1136 /**
1137 * @test
1138 */
1139 public function arrayExportReturnsNumericArrayKeys()
1140 {
1141 $array = [
1142 'foo' => 'string key',
1143 23 => 'integer key',
1144 '42' => 'string key representing integer'
1145 ];
1146 $expected =
1147 '[' . LF .
1148 ' \'foo\' => \'string key\',' . LF .
1149 ' 23 => \'integer key\',' . LF .
1150 ' 42 => \'string key representing integer\',' . LF .
1151 ']';
1152 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1153 }
1154
1155 /**
1156 * @test
1157 */
1158 public function arrayExportReturnsNoKeyIndexForConsecutiveCountedArrays()
1159 {
1160 $array = [
1161 0 => 'zero',
1162 1 => 'one',
1163 2 => 'two'
1164 ];
1165 $expected =
1166 '[' . LF .
1167 ' \'zero\',' . LF .
1168 ' \'one\',' . LF .
1169 ' \'two\',' . LF .
1170 ']';
1171 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1172 }
1173
1174 /**
1175 * @test
1176 */
1177 public function arrayExportReturnsKeyIndexForNonConsecutiveCountedArrays()
1178 {
1179 $array = [
1180 0 => 'zero',
1181 1 => 'one',
1182 3 => 'three',
1183 4 => 'four'
1184 ];
1185 $expected =
1186 '[' . LF .
1187 ' 0 => \'zero\',' . LF .
1188 ' 1 => \'one\',' . LF .
1189 ' 3 => \'three\',' . LF .
1190 ' 4 => \'four\',' . LF .
1191 ']';
1192 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1193 }
1194
1195 ///////////////////////
1196 // Tests concerning flatten
1197 ///////////////////////
1198
1199 /**
1200 * @return array
1201 */
1202 public function flattenCalculatesExpectedResultDataProvider()
1203 {
1204 return [
1205 'plain array' => [
1206 [
1207 'first' => 1,
1208 'second' => 2
1209 ],
1210 [
1211 'first' => 1,
1212 'second' => 2
1213 ]
1214 ],
1215 'plain array with faulty dots' => [
1216 [
1217 'first.' => 1,
1218 'second.' => 2
1219 ],
1220 [
1221 'first' => 1,
1222 'second' => 2
1223 ]
1224 ],
1225 'nested array of 2 levels' => [
1226 [
1227 'first.' => [
1228 'firstSub' => 1
1229 ],
1230 'second.' => [
1231 'secondSub' => 2
1232 ]
1233 ],
1234 [
1235 'first.firstSub' => 1,
1236 'second.secondSub' => 2
1237 ]
1238 ],
1239 'nested array of 2 levels with faulty dots' => [
1240 [
1241 'first.' => [
1242 'firstSub.' => 1
1243 ],
1244 'second.' => [
1245 'secondSub.' => 2
1246 ]
1247 ],
1248 [
1249 'first.firstSub' => 1,
1250 'second.secondSub' => 2
1251 ]
1252 ],
1253 'nested array of 3 levels' => [
1254 [
1255 'first.' => [
1256 'firstSub.' => [
1257 'firstSubSub' => 1
1258 ]
1259 ],
1260 'second.' => [
1261 'secondSub.' => [
1262 'secondSubSub' => 2
1263 ]
1264 ]
1265 ],
1266 [
1267 'first.firstSub.firstSubSub' => 1,
1268 'second.secondSub.secondSubSub' => 2
1269 ]
1270 ],
1271 'nested array of 3 levels with faulty dots' => [
1272 [
1273 'first.' => [
1274 'firstSub.' => [
1275 'firstSubSub.' => 1
1276 ]
1277 ],
1278 'second.' => [
1279 'secondSub.' => [
1280 'secondSubSub.' => 2
1281 ]
1282 ]
1283 ],
1284 [
1285 'first.firstSub.firstSubSub' => 1,
1286 'second.secondSub.secondSubSub' => 2
1287 ]
1288 ]
1289 ];
1290 }
1291
1292 /**
1293 * @test
1294 * @param array $array
1295 * @param array $expected
1296 * @dataProvider flattenCalculatesExpectedResultDataProvider
1297 */
1298 public function flattenCalculatesExpectedResult(array $array, array $expected)
1299 {
1300 $this->assertEquals($expected, ArrayUtility::flatten($array));
1301 }
1302
1303 ///////////////////////
1304 // Tests concerning intersectRecursive
1305 ///////////////////////
1306
1307 /**
1308 * @return array
1309 */
1310 public function intersectRecursiveCalculatesExpectedResultDataProvider()
1311 {
1312 $sameObject = new \stdClass();
1313 return [
1314 // array($source, $mask, $expected)
1315 'empty array is returned if source is empty array' => [
1316 [],
1317 [
1318 'foo' => 'bar',
1319 ],
1320 [],
1321 ],
1322 'empty array is returned if mask is empty' => [
1323 [
1324 'foo' => 'bar',
1325 ],
1326 [],
1327 [],
1328 ],
1329 'key is kept on first level if exists in mask' => [
1330 [
1331 'foo' => 42,
1332 ],
1333 [
1334 'foo' => 42,
1335 ],
1336 [
1337 'foo' => 42,
1338 ],
1339 ],
1340 'value of key in source is kept if mask has different value' => [
1341 [
1342 'foo' => 42,
1343 ],
1344 [
1345 'foo' => new \stdClass(),
1346 ],
1347 [
1348 'foo' => 42,
1349 ],
1350 ],
1351 'key is kept on first level if according mask value is NULL' => [
1352 [
1353 'foo' => 42,
1354 ],
1355 [
1356 'foo' => null,
1357 ],
1358 [
1359 'foo' => 42,
1360 ],
1361 ],
1362 'null in source value is kept' => [
1363 [
1364 'foo' => null,
1365 ],
1366 [
1367 'foo' => 'bar',
1368 ],
1369 [
1370 'foo' => null,
1371 ]
1372 ],
1373 'mask does not add new keys' => [
1374 [
1375 'foo' => 42,
1376 ],
1377 [
1378 'foo' => 23,
1379 'bar' => [
1380 4711
1381 ],
1382 ],
1383 [
1384 'foo' => 42,
1385 ],
1386 ],
1387 'mask does not overwrite simple values with arrays' => [
1388 [
1389 'foo' => 42,
1390 ],
1391 [
1392 'foo' => [
1393 'bar' => 23,
1394 ],
1395 ],
1396 [
1397 'foo' => 42,
1398 ],
1399 ],
1400 'key is kept on first level if according mask value is array' => [
1401 [
1402 'foo' => 42,
1403 ],
1404 [
1405 'foo' => [
1406 'bar' => 23
1407 ],
1408 ],
1409 [
1410 'foo' => 42,
1411 ],
1412 ],
1413 'full array is kept if value is array and mask value is simple type' => [
1414 [
1415 'foo' => [
1416 'bar' => 23
1417 ],
1418 ],
1419 [
1420 'foo' => 42,
1421 ],
1422 [
1423 'foo' => [
1424 'bar' => 23
1425 ],
1426 ],
1427 ],
1428 'key handling is type agnostic' => [
1429 [
1430 42 => 'foo',
1431 ],
1432 [
1433 '42' => 'bar',
1434 ],
1435 [
1436 42 => 'foo',
1437 ],
1438 ],
1439 'value is same if value is object' => [
1440 [
1441 'foo' => $sameObject,
1442 ],
1443 [
1444 'foo' => 'something',
1445 ],
1446 [
1447 'foo' => $sameObject,
1448 ],
1449 ],
1450 'mask does not add simple value to result if key does not exist in source' => [
1451 [
1452 'foo' => '42',
1453 ],
1454 [
1455 'foo' => '42',
1456 'bar' => 23
1457 ],
1458 [
1459 'foo' => '42',
1460 ],
1461 ],
1462 'array of source is kept if value of mask key exists but is no array' => [
1463 [
1464 'foo' => '42',
1465 'bar' => [
1466 'baz' => 23
1467 ],
1468 ],
1469 [
1470 'foo' => 'value is not significant',
1471 'bar' => null,
1472 ],
1473 [
1474 'foo' => '42',
1475 'bar' => [
1476 'baz' => 23
1477 ],
1478 ],
1479 ],
1480 'sub arrays are kept if mask has according sub array key and is similar array' => [
1481 [
1482 'first1' => 42,
1483 'first2' => [
1484 'second1' => 23,
1485 'second2' => 4711,
1486 ],
1487 ],
1488 [
1489 'first1' => 42,
1490 'first2' => [
1491 'second1' => 'exists but different',
1492 ],
1493 ],
1494 [
1495 'first1' => 42,
1496 'first2' => [
1497 'second1' => 23,
1498 ],
1499 ],
1500 ],
1501 ];
1502 }
1503
1504 /**
1505 * @test
1506 * @param array $source
1507 * @param array $mask
1508 * @param array $expected
1509 * @dataProvider intersectRecursiveCalculatesExpectedResultDataProvider
1510 */
1511 public function intersectRecursiveCalculatesExpectedResult(array $source, array $mask, array $expected)
1512 {
1513 $this->assertSame($expected, ArrayUtility::intersectRecursive($source, $mask));
1514 }
1515
1516 ///////////////////////
1517 // Tests concerning renumberKeysToAvoidLeapsIfKeysAreAllNumeric
1518 ///////////////////////
1519 /**
1520 * @return array
1521 */
1522 public function renumberKeysToAvoidLeapsIfKeysAreAllNumericDataProvider()
1523 {
1524 return [
1525 'empty array is returned if source is empty array' => [
1526 [],
1527 []
1528 ],
1529 'returns self if array is already numerically keyed' => [
1530 [1, 2, 3],
1531 [1, 2, 3]
1532 ],
1533 'returns correctly if keys are numeric, but contains a leap' => [
1534 [0 => 'One', 1 => 'Two', 3 => 'Three'],
1535 [0 => 'One', 1 => 'Two', 2 => 'Three'],
1536 ],
1537 'returns correctly even though keys are strings but still numeric' => [
1538 ['0' => 'One', '1' => 'Two', '3' => 'Three'],
1539 [0 => 'One', 1 => 'Two', 2 => 'Three'],
1540 ],
1541 'returns correctly if just a single keys is not numeric' => [
1542 [0 => 'Zero', '1' => 'One', 'Two' => 'Two'],
1543 [0 => 'Zero', '1' => 'One', 'Two' => 'Two'],
1544 ],
1545 'returns unchanged if keys end with a dot' => [
1546 ['2.' => 'Two', '1.' => 'One', '0.' => 'Zero'],
1547 ['2.' => 'Two', '1.' => 'One', '0.' => 'Zero'],
1548 ],
1549 'return self with nested numerically keyed array' => [
1550 [
1551 'One',
1552 'Two',
1553 'Three',
1554 [
1555 'sub.One',
1556 'sub.Two',
1557 ]
1558 ],
1559 [
1560 'One',
1561 'Two',
1562 'Three',
1563 [
1564 'sub.One',
1565 'sub.Two',
1566 ]
1567 ]
1568 ],
1569 'returns correctly with nested numerically keyed array with leaps' => [
1570 [
1571 'One',
1572 'Two',
1573 'Three',
1574 [
1575 0 => 'sub.One',
1576 2 => 'sub.Two',
1577 ]
1578 ],
1579 [
1580 'One',
1581 'Two',
1582 'Three',
1583 [
1584 'sub.One',
1585 'sub.Two',
1586 ]
1587 ]
1588 ],
1589 'returns correctly with nested string-keyed array' => [
1590 [
1591 'One',
1592 'Two',
1593 'Three',
1594 [
1595 'one' => 'sub.One',
1596 'two' => 'sub.Two',
1597 ]
1598 ],
1599 [
1600 'One',
1601 'Two',
1602 'Three',
1603 [
1604 'one' => 'sub.One',
1605 'two' => 'sub.Two',
1606 ]
1607 ]
1608 ],
1609 'returns correctly with deeply nested arrays' => [
1610 [
1611 'One',
1612 'Two',
1613 [
1614 'one' => 1,
1615 'two' => 2,
1616 'three' => [
1617 2 => 'SubSubOne',
1618 5 => 'SubSubTwo',
1619 9 => [0, 1, 2],
1620 []
1621 ]
1622 ]
1623 ],
1624 [
1625 'One',
1626 'Two',
1627 [
1628 'one' => 1,
1629 'two' => 2,
1630 'three' => [
1631 'SubSubOne',
1632 'SubSubTwo',
1633 [0, 1, 2],
1634 []
1635 ]
1636 ]
1637 ]
1638 ]
1639 ];
1640 }
1641
1642 /**
1643 * @test
1644 * @param array $inputArray
1645 * @param array $expected
1646 * @dataProvider renumberKeysToAvoidLeapsIfKeysAreAllNumericDataProvider
1647 */
1648 public function renumberKeysToAvoidLeapsIfKeysAreAllNumericReturnsExpectedOrder(array $inputArray, array $expected)
1649 {
1650 $this->assertEquals($expected, ArrayUtility::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($inputArray));
1651 }
1652
1653 /**
1654 * @return array
1655 */
1656 public function mergeRecursiveWithOverruleCalculatesExpectedResultDataProvider()
1657 {
1658 return [
1659 'Override array can reset string to array' => [
1660 [
1661 'first' => [
1662 'second' => 'foo',
1663 ],
1664 ],
1665 [
1666 'first' => [
1667 'second' => ['third' => 'bar'],
1668 ],
1669 ],
1670 true,
1671 true,
1672 true,
1673 [
1674 'first' => [
1675 'second' => ['third' => 'bar'],
1676 ],
1677 ],
1678 ],
1679 'Override array does not reset array to string (weird!)' => [
1680 [
1681 'first' => [],
1682 ],
1683 [
1684 'first' => 'foo',
1685 ],
1686 true,
1687 true,
1688 true,
1689 [
1690 'first' => [], // This is rather unexpected, naive expectation: first => 'foo'
1691 ],
1692 ],
1693 'Override array does override string with null' => [
1694 [
1695 'first' => 'foo',
1696 ],
1697 [
1698 'first' => null,
1699 ],
1700 true,
1701 true,
1702 true,
1703 [
1704 'first' => null,
1705 ],
1706 ],
1707 'Override array does override null with string' => [
1708 [
1709 'first' => null,
1710 ],
1711 [
1712 'first' => 'foo',
1713 ],
1714 true,
1715 true,
1716 true,
1717 [
1718 'first' => 'foo',
1719 ],
1720 ],
1721 'Override array does override null with empty string' => [
1722 [
1723 'first' => null,
1724 ],
1725 [
1726 'first' => '',
1727 ],
1728 true,
1729 true,
1730 true,
1731 [
1732 'first' => '',
1733 ],
1734 ],
1735 'Override array does not override string with NULL if requested' => [
1736 [
1737 'first' => 'foo',
1738 ],
1739 [
1740 'first' => null,
1741 ],
1742 true,
1743 false, // no include empty values
1744 true,
1745 [
1746 'first' => 'foo',
1747 ],
1748 ],
1749 'Override array does override null with null' => [
1750 [
1751 'first' => null,
1752 ],
1753 [
1754 'first' => null,
1755 ],
1756 true,
1757 true,
1758 true,
1759 [
1760 'first' => '',
1761 ],
1762 ],
1763 'Override array can __UNSET values' => [
1764 [
1765 'first' => [
1766 'second' => 'second',
1767 'third' => 'third',
1768 ],
1769 'fifth' => [],
1770 ],
1771 [
1772 'first' => [
1773 'second' => 'overrule',
1774 'third' => '__UNSET',
1775 'fourth' => 'overrile',
1776 ],
1777 'fifth' => '__UNSET',
1778 ],
1779 true,
1780 true,
1781 true,
1782 [
1783 'first' => [
1784 'second' => 'overrule',
1785 'fourth' => 'overrile',
1786 ],
1787 ],
1788 ],
1789 'Override can add keys' => [
1790 [
1791 'first' => 'foo',
1792 ],
1793 [
1794 'second' => 'bar',
1795 ],
1796 true,
1797 true,
1798 true,
1799 [
1800 'first' => 'foo',
1801 'second' => 'bar',
1802 ],
1803 ],
1804 'Override does not add key if __UNSET' => [
1805 [
1806 'first' => 'foo',
1807 ],
1808 [
1809 'second' => '__UNSET',
1810 ],
1811 true,
1812 true,
1813 true,
1814 [
1815 'first' => 'foo',
1816 ],
1817 ],
1818 'Override does not add key if not requested' => [
1819 [
1820 'first' => 'foo',
1821 ],
1822 [
1823 'second' => 'bar',
1824 ],
1825 false, // no add keys
1826 true,
1827 true,
1828 [
1829 'first' => 'foo',
1830 ],
1831 ],
1832 'Override does not add key if not requested with add include empty values' => [
1833 [
1834 'first' => 'foo',
1835 ],
1836 [
1837 'second' => 'bar',
1838 ],
1839 false, // no add keys
1840 false, // no include empty values
1841 true,
1842 [
1843 'first' => 'foo',
1844 ],
1845 ],
1846 'Override does not override string with empty string if requested' => [
1847 [
1848 'first' => 'foo',
1849 ],
1850 [
1851 'first' => '',
1852 ],
1853 true,
1854 false, // no include empty values
1855 true,
1856 [
1857 'first' => 'foo',
1858 ],
1859 ],
1860 'Override array does merge instead of __UNSET if requested (weird!)' => [
1861 [
1862 'first' => [
1863 'second' => 'second',
1864 'third' => 'third',
1865 ],
1866 'fifth' => [],
1867 ],
1868 [
1869 'first' => [
1870 'second' => 'overrule',
1871 'third' => '__UNSET',
1872 'fourth' => 'overrile',
1873 ],
1874 'fifth' => '__UNSET',
1875 ],
1876 true,
1877 true,
1878 false,
1879 [
1880 'first' => [
1881 'second' => 'overrule',
1882 'third' => '__UNSET', // overruled
1883 'fourth' => 'overrile',
1884 ],
1885 'fifth' => [], // not overruled with string here, naive expectation: 'fifth' => '__UNSET'
1886 ],
1887 ],
1888 ];
1889 }
1890
1891 /**
1892 * @test
1893 * @dataProvider mergeRecursiveWithOverruleCalculatesExpectedResultDataProvider
1894 * @param array $input1 Input 1
1895 * @param array $input2 Input 2
1896 * @param bool $addKeys TRUE if should add keys, else FALSE
1897 * @param bool $includeEmptyValues TRUE if should include empty values, else FALSE
1898 * @param bool $enableUnsetFeature TRUE if should enable unset feature, else FALSE
1899 * @param array $expected expected array
1900 */
1901 public function mergeRecursiveWithOverruleCalculatesExpectedResult($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature, $expected)
1902 {
1903 ArrayUtility::mergeRecursiveWithOverrule($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature);
1904 $this->assertEquals($expected, $input1);
1905 }
1906
1907 //////////////////////////////////
1908 // Tests concerning removeArrayEntryByValue
1909 //////////////////////////////////
1910 /**
1911 * @test
1912 */
1913 public function checkRemoveArrayEntryByValueRemovesEntriesFromOneDimensionalArray()
1914 {
1915 $inputArray = [
1916 '0' => 'test1',
1917 '1' => 'test2',
1918 '2' => 'test3',
1919 '3' => 'test2'
1920 ];
1921 $compareValue = 'test2';
1922 $expectedResult = [
1923 '0' => 'test1',
1924 '2' => 'test3'
1925 ];
1926 $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1927 $this->assertEquals($expectedResult, $actualResult);
1928 }
1929
1930 /**
1931 * @test
1932 */
1933 public function checkRemoveArrayEntryByValueRemovesEntriesFromMultiDimensionalArray()
1934 {
1935 $inputArray = [
1936 '0' => 'foo',
1937 '1' => [
1938 '10' => 'bar'
1939 ],
1940 '2' => 'bar'
1941 ];
1942 $compareValue = 'bar';
1943 $expectedResult = [
1944 '0' => 'foo',
1945 '1' => []
1946 ];
1947 $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1948 $this->assertEquals($expectedResult, $actualResult);
1949 }
1950
1951 /**
1952 * @test
1953 */
1954 public function checkRemoveArrayEntryByValueRemovesEntryWithEmptyString()
1955 {
1956 $inputArray = [
1957 '0' => 'foo',
1958 '1' => '',
1959 '2' => 'bar'
1960 ];
1961 $compareValue = '';
1962 $expectedResult = [
1963 '0' => 'foo',
1964 '2' => 'bar'
1965 ];
1966 $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1967 $this->assertEquals($expectedResult, $actualResult);
1968 }
1969
1970 //////////////////////////////////
1971 // Tests concerning keepItemsInArray
1972 //////////////////////////////////
1973 /**
1974 * @test
1975 * @dataProvider keepItemsInArrayWorksWithOneArgumentDataProvider
1976 * @param mixed $search The items which are allowed/kept in the array
1977 * @param array $array target array
1978 * @param array $expected expected array
1979 */
1980 public function keepItemsInArrayWorksWithOneArgument($search, $array, $expected)
1981 {
1982 $this->assertEquals($expected, ArrayUtility::keepItemsInArray($array, $search));
1983 }
1984
1985 /**
1986 * Data provider for keepItemsInArrayWorksWithOneArgument
1987 *
1988 * @return array
1989 */
1990 public function keepItemsInArrayWorksWithOneArgumentDataProvider()
1991 {
1992 $array = [
1993 0 => 0,
1994 'one' => 'one',
1995 'two' => 'two',
1996 'three' => 'three'
1997 ];
1998 return [
1999 'Empty argument will match "all" elements' => [null, $array, $array],
2000 'No match' => ['four', $array, []],
2001 'One match' => ['two', $array, ['two' => 'two']],
2002 'Multiple matches' => ['two,one', $array, ['one' => 'one', 'two' => 'two']],
2003 'Argument can be an array' => [['three'], $array, ['three' => 'three']]
2004 ];
2005 }
2006
2007 /**
2008 * Shows the example from the doc comment where
2009 * a function is used to reduce the sub arrays to one item which
2010 * is then used for the matching.
2011 *
2012 * @test
2013 */
2014 public function keepItemsInArrayCanUseClosure()
2015 {
2016 $array = [
2017 'aa' => ['first', 'second'],
2018 'bb' => ['third', 'fourth'],
2019 'cc' => ['fifth', 'sixth']
2020 ];
2021 $expected = ['bb' => ['third', 'fourth']];
2022 $keepItems = 'third';
2023 $match = ArrayUtility::keepItemsInArray(
2024 $array,
2025 $keepItems,
2026 function ($value) {
2027 return $value[0];
2028 }
2029 );
2030 $this->assertEquals($expected, $match);
2031 }
2032
2033 //////////////////////////////////
2034 // Tests concerning remapArrayKeys
2035 //////////////////////////////////
2036 /**
2037 * @test
2038 */
2039 public function remapArrayKeysExchangesKeysWithGivenMapping()
2040 {
2041 $array = [
2042 'one' => 'one',
2043 'two' => 'two',
2044 'three' => 'three'
2045 ];
2046 $keyMapping = [
2047 'one' => '1',
2048 'two' => '2'
2049 ];
2050 $expected = [
2051 '1' => 'one',
2052 '2' => 'two',
2053 'three' => 'three'
2054 ];
2055 ArrayUtility::remapArrayKeys($array, $keyMapping);
2056 $this->assertEquals($expected, $array);
2057 }
2058
2059 //////////////////////////////////////
2060 // Tests concerning arrayDiffAssocRecursive
2061 //////////////////////////////////////
2062 /**
2063 * @test
2064 */
2065 public function arrayDiffAssocRecursiveHandlesOneDimensionalArrays()
2066 {
2067 $array1 = [
2068 'key1' => 'value1',
2069 'key2' => 'value2',
2070 'key3' => 'value3'
2071 ];
2072 $array2 = [
2073 'key1' => 'value1',
2074 'key3' => 'value3'
2075 ];
2076 $expectedResult = [
2077 'key2' => 'value2'
2078 ];
2079 $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2080 $this->assertEquals($expectedResult, $actualResult);
2081 }
2082
2083 /**
2084 * @test
2085 */
2086 public function arrayDiffAssocRecursiveHandlesMultiDimensionalArrays()
2087 {
2088 $array1 = [
2089 'key1' => 'value1',
2090 'key2' => [
2091 'key21' => 'value21',
2092 'key22' => 'value22',
2093 'key23' => [
2094 'key231' => 'value231',
2095 'key232' => 'value232'
2096 ]
2097 ]
2098 ];
2099 $array2 = [
2100 'key1' => 'value1',
2101 'key2' => [
2102 'key21' => 'value21',
2103 'key23' => [
2104 'key231' => 'value231'
2105 ]
2106 ]
2107 ];
2108 $expectedResult = [
2109 'key2' => [
2110 'key22' => 'value22',
2111 'key23' => [
2112 'key232' => 'value232'
2113 ]
2114 ]
2115 ];
2116 $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2117 $this->assertEquals($expectedResult, $actualResult);
2118 }
2119
2120 /**
2121 * @test
2122 */
2123 public function arrayDiffAssocRecursiveHandlesMixedArrays()
2124 {
2125 $array1 = [
2126 'key1' => [
2127 'key11' => 'value11',
2128 'key12' => 'value12'
2129 ],
2130 'key2' => 'value2',
2131 'key3' => 'value3'
2132 ];
2133 $array2 = [
2134 'key1' => 'value1',
2135 'key2' => [
2136 'key21' => 'value21'
2137 ]
2138 ];
2139 $expectedResult = [
2140 'key3' => 'value3'
2141 ];
2142 $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2143 $this->assertEquals($expectedResult, $actualResult);
2144 }
2145
2146 //////////////////////////////////////
2147 // Tests concerning naturalKeySortRecursive
2148 //////////////////////////////////////
2149
2150 /**
2151 * @test
2152 */
2153 public function naturalKeySortRecursiveSortsOneDimensionalArrayByNaturalOrder()
2154 {
2155 $testArray = [
2156 'bb' => 'bb',
2157 'ab' => 'ab',
2158 '123' => '123',
2159 'aaa' => 'aaa',
2160 'abc' => 'abc',
2161 '23' => '23',
2162 'ba' => 'ba',
2163 'bad' => 'bad',
2164 '2' => '2',
2165 'zap' => 'zap',
2166 '210' => '210'
2167 ];
2168 $expectedResult = [
2169 '2',
2170 '23',
2171 '123',
2172 '210',
2173 'aaa',
2174 'ab',
2175 'abc',
2176 'ba',
2177 'bad',
2178 'bb',
2179 'zap'
2180 ];
2181 ArrayUtility::naturalKeySortRecursive($testArray);
2182 $this->assertEquals($expectedResult, array_values($testArray));
2183 }
2184
2185 /**
2186 * @test
2187 */
2188 public function naturalKeySortRecursiveSortsMultiDimensionalArrayByNaturalOrder()
2189 {
2190 $testArray = [
2191 '2' => '2',
2192 'bb' => 'bb',
2193 'ab' => 'ab',
2194 '23' => '23',
2195 'aaa' => [
2196 'bb' => 'bb',
2197 'ab' => 'ab',
2198 '123' => '123',
2199 'aaa' => 'aaa',
2200 '2' => '2',
2201 'abc' => 'abc',
2202 'ba' => 'ba',
2203 '23' => '23',
2204 'bad' => [
2205 'bb' => 'bb',
2206 'ab' => 'ab',
2207 '123' => '123',
2208 'aaa' => 'aaa',
2209 'abc' => 'abc',
2210 '23' => '23',
2211 'ba' => 'ba',
2212 'bad' => 'bad',
2213 '2' => '2',
2214 'zap' => 'zap',
2215 '210' => '210'
2216 ],
2217 '210' => '210',
2218 'zap' => 'zap'
2219 ],
2220 'abc' => 'abc',
2221 'ba' => 'ba',
2222 '210' => '210',
2223 'bad' => 'bad',
2224 '123' => '123',
2225 'zap' => 'zap'
2226 ];
2227 $expectedResult = [
2228 '2',
2229 '23',
2230 '123',
2231 '210',
2232 'aaa',
2233 'ab',
2234 'abc',
2235 'ba',
2236 'bad',
2237 'bb',
2238 'zap'
2239 ];
2240 ArrayUtility::naturalKeySortRecursive($testArray);
2241 $this->assertEquals($expectedResult, array_values(array_keys($testArray['aaa']['bad'])));
2242 $this->assertEquals($expectedResult, array_values(array_keys($testArray['aaa'])));
2243 $this->assertEquals($expectedResult, array_values(array_keys($testArray)));
2244 }
2245
2246 /**
2247 * Data provider for filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsTrue
2248 *
2249 * @return array
2250 */
2251 public function filterAndSortByNumericKeysWithAcceptAnyKey()
2252 {
2253 return [
2254 'ordered list of plain numeric keys' => [
2255 'input' => [
2256 '10' => 'foo',
2257 '20' => 'bar',
2258 ],
2259 'expected' => [
2260 10,
2261 20,
2262 ],
2263 ],
2264 'unordered list of plain numeric keys' => [
2265 'input' => [
2266 '20' => 'bar',
2267 '10' => 'foo',
2268 ],
2269 'expected' => [
2270 10,
2271 20,
2272 ],
2273 ],
2274 'list of string keys' => [
2275 'input' => [
2276 '10.' => [
2277 'wrap' => 'foo',
2278 ],
2279 '20.' => [
2280 'wrap' => 'bar',
2281 ],
2282 ],
2283 'expected' => [
2284 10,
2285 20,
2286 ],
2287 ],
2288 'list of mixed keys' => [
2289 'input' => [
2290 '10' => 'foo',
2291 '20.' => [
2292 'wrap' => 'bar',
2293 ],
2294 ],
2295 'expected' => [
2296 10,
2297 20,
2298 ],
2299 ],
2300 'list of mixed keys with one not interpreted as integer' => [
2301 'input' => [
2302 '10' => 'foo',
2303 'bla20.' => [
2304 'wrap' => 'bar',
2305 ],
2306 ],
2307 'expected' => [
2308 0,
2309 10,
2310 ],
2311 ],
2312 'list of mixed keys with more than one not interpreted as integer' => [
2313 'input' => [
2314 '10' => 'foo',
2315 'bla20.' => [
2316 'wrap' => 'bar',
2317 ],
2318 'bla21.' => [
2319 'wrap' => 'foobar',
2320 ],
2321 ],
2322 'expected' => [
2323 0,
2324 10,
2325 ],
2326 ],
2327 ];
2328 }
2329
2330 /**
2331 * @test
2332 * @dataProvider filterAndSortByNumericKeysWithAcceptAnyKey
2333 *
2334 * @param array $input
2335 * @param array $expected
2336 */
2337 public function filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsTrue($input, $expected)
2338 {
2339 $result = ArrayUtility::filterAndSortByNumericKeys($input, true);
2340 $this->assertEquals($result, $expected);
2341 }
2342
2343 /**
2344 * Data provider for filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsFalse
2345 *
2346 * @return array
2347 */
2348 public function filterAndSortByNumericKeysWithoutAcceptAnyKey()
2349 {
2350 return [
2351 'ordered list of plain numeric keys' => [
2352 'input' => [
2353 '10' => 'foo',
2354 '20' => 'bar',
2355 ],
2356 'expected' => [
2357 10,
2358 20,
2359 ],
2360 ],
2361 'unordered list of plain numeric keys' => [
2362 'input' => [
2363 '20' => 'bar',
2364 '10' => 'foo',
2365 ],
2366 'expected' => [
2367 10,
2368 20,
2369 ],
2370 ],
2371 'list of string keys' => [
2372 'input' => [
2373 '10.' => [
2374 'wrap' => 'foo',
2375 ],
2376 '20.' => [
2377 'wrap' => 'bar',
2378 ],
2379 ],
2380 'expected' => [],
2381 ],
2382 'list of mixed keys' => [
2383 'input' => [
2384 '10' => 'foo',
2385 '20.' => [
2386 'wrap' => 'bar',
2387 ],
2388 ],
2389 'expected' => [
2390 10,
2391 ],
2392 ],
2393 ];
2394 }
2395
2396 /**
2397 * @test
2398 * @dataProvider filterAndSortByNumericKeysWithoutAcceptAnyKey
2399 *
2400 * @param array $input
2401 * @param array $expected
2402 */
2403 public function filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsFalse($input, $expected)
2404 {
2405 $result = ArrayUtility::filterAndSortByNumericKeys($input);
2406 $this->assertEquals($result, $expected);
2407 }
2408
2409 /**
2410 * dataProvider for sortArrayWithIntegerKeys
2411 *
2412 * @return array
2413 */
2414 public function sortArrayWithIntegerKeysDataProvider()
2415 {
2416 return [
2417 [
2418 [
2419 '20' => 'test1',
2420 '11' => 'test2',
2421 '16' => 'test3',
2422 ],
2423 [
2424 '11' => 'test2',
2425 '16' => 'test3',
2426 '20' => 'test1',
2427 ]
2428 ],
2429 [
2430 [
2431 '20' => 'test1',
2432 '16.5' => 'test2',
2433 '16' => 'test3',
2434 ],
2435 [
2436 '20' => 'test1',
2437 '16.5' => 'test2',
2438 '16' => 'test3',
2439 ]
2440 ],
2441 [
2442 [
2443 '20' => 'test20',
2444 'somestring' => 'teststring',
2445 '16' => 'test16',
2446 ],
2447 [
2448 '20' => 'test20',
2449 'somestring' => 'teststring',
2450 '16' => 'test16',
2451 ]
2452 ],
2453 ];
2454 }
2455
2456 /**
2457 * @test
2458 *
2459 * @param array $arrayToSort
2460 * @param array $expectedArray
2461 *
2462 * @dataProvider sortArrayWithIntegerKeysDataProvider
2463 */
2464 public function sortArrayWithIntegerKeysSortsNumericArrays(array $arrayToSort, array $expectedArray)
2465 {
2466 $sortedArray = ArrayUtility::sortArrayWithIntegerKeys($arrayToSort);
2467 $this->assertSame($sortedArray, $expectedArray);
2468 }
2469
2470 /**
2471 * @test
2472 */
2473 public function assertAllArrayKeysAreValidThrowsExceptionOnNotAllowedArrayKeys()
2474 {
2475 $this->expectException(\InvalidArgumentException::class);
2476 $this->expectExceptionCode(1325697085);
2477
2478 $arrayToTest = [
2479 'roger' => '',
2480 'francine' => '',
2481 'stan' => '',
2482 ];
2483
2484 $allowedArrayKeys = [
2485 'roger',
2486 'francine',
2487 ];
2488
2489 ArrayUtility::assertAllArrayKeysAreValid($arrayToTest, $allowedArrayKeys);
2490 }
2491
2492 /**
2493 * @test
2494 */
2495 public function assertAllArrayKeysAreValidReturnsNullOnAllowedArrayKeys()
2496 {
2497 $arrayToTest = [
2498 'roger' => '',
2499 'francine' => '',
2500 'stan' => '',
2501 ];
2502
2503 $allowedArrayKeys = [
2504 'roger',
2505 'francine',
2506 'stan',
2507 ];
2508
2509 ArrayUtility::assertAllArrayKeysAreValid($arrayToTest, $allowedArrayKeys);
2510 }
2511
2512 /**
2513 * @test
2514 */
2515 public function sortArrayWithIntegerKeysRecursiveExpectSorting()
2516 {
2517 $input = [
2518 20 => 'b',
2519 10 => 'a',
2520 40 => 'd',
2521 30 => 'c',
2522 50 => [
2523 20 => 'a',
2524 10 => 'b',
2525 ],
2526 ];
2527
2528 $expected = [
2529 10 => 'a',
2530 20 => 'b',
2531 30 => 'c',
2532 40 => 'd',
2533 50 => [
2534 10 => 'b',
2535 20 => 'a',
2536 ],
2537 ];
2538
2539 $this->assertSame($expected, ArrayUtility::sortArrayWithIntegerKeysRecursive($input));
2540 }
2541
2542 /**
2543 * @test
2544 */
2545 public function sortArrayWithIntegerKeysRecursiveExpectNoSorting()
2546 {
2547 $input = [
2548 'b' => 'b',
2549 10 => 'a',
2550 40 => 'd',
2551 30 => 'c',
2552 ];
2553
2554 $expected = [
2555 'b' => 'b',
2556 10 => 'a',
2557 40 => 'd',
2558 30 => 'c',
2559 ];
2560
2561 $this->assertSame($expected, ArrayUtility::sortArrayWithIntegerKeysRecursive($input));
2562 }
2563
2564 /**
2565 * @test
2566 */
2567 public function reIndexNumericArrayKeysRecursiveExpectReindexing()
2568 {
2569 $input = [
2570 20 => 'b',
2571 10 => 'a',
2572 40 => 'd',
2573 30 => 'c',
2574 50 => [
2575 20 => 'a',
2576 10 => 'b',
2577 ],
2578 ];
2579
2580 $expected = [
2581 0 => 'b',
2582 1 => 'a',
2583 2 => 'd',
2584 3 => 'c',
2585 4 => [
2586 0 => 'a',
2587 1 => 'b',
2588 ],
2589 ];
2590
2591 $this->assertSame($expected, ArrayUtility::reIndexNumericArrayKeysRecursive($input));
2592 }
2593
2594 /**
2595 * @test
2596 */
2597 public function reIndexNumericArrayKeysRecursiveExpectNoReindexing()
2598 {
2599 $input = [
2600 'a' => 'b',
2601 10 => 'a',
2602 40 => 'd',
2603 30 => 'c',
2604 50 => [
2605 20 => 'a',
2606 10 => 'b',
2607 ],
2608 ];
2609
2610 $expected = [
2611 'a' => 'b',
2612 10 => 'a',
2613 40 => 'd',
2614 30 => 'c',
2615 50 => [
2616 0 => 'a',
2617 1 => 'b',
2618 ],
2619 ];
2620
2621 $this->assertSame($expected, ArrayUtility::reIndexNumericArrayKeysRecursive($input));
2622 }
2623
2624 /**
2625 * @test
2626 */
2627 public function removeNullValuesRecursiveExpectRemoval()
2628 {
2629 $input = [
2630 'a' => 'a',
2631 'b' => [
2632 'c' => null,
2633 'd' => 'd',
2634 ],
2635 ];
2636
2637 $expected = [
2638 'a' => 'a',
2639 'b' => [
2640 'd' => 'd',
2641 ],
2642 ];
2643
2644 $this->assertSame($expected, ArrayUtility::removeNullValuesRecursive($input));
2645 }
2646
2647 /**
2648 * @test
2649 */
2650 public function stripTagsFromValuesRecursiveExpectRemoval()
2651 {
2652 $input = [
2653 'a' => 'a',
2654 'b' => [
2655 'c' => '<b>i am evil</b>',
2656 'd' => 'd',
2657 ],
2658 ];
2659
2660 $expected = [
2661 'a' => 'a',
2662 'b' => [
2663 'c' => 'i am evil',
2664 'd' => 'd',
2665 ],
2666 ];
2667
2668 $this->assertSame($expected, ArrayUtility::stripTagsFromValuesRecursive($input));
2669 }
2670
2671 /**
2672 * @test
2673 */
2674 public function convertBooleanStringsToBooleanRecursiveExpectConverting()
2675 {
2676 $input = [
2677 'a' => 'a',
2678 'b' => [
2679 'c' => 'true',
2680 'd' => 'd',
2681 ],
2682 ];
2683
2684 $expected = [
2685 'a' => 'a',
2686 'b' => [
2687 'c' => true,
2688 'd' => 'd',
2689 ],
2690 ];
2691
2692 $this->assertSame($expected, ArrayUtility::convertBooleanStringsToBooleanRecursive($input));
2693 }
2694
2695 /**
2696 * Data provider for arrayFilterRecursiveFiltersFalseElements
2697 * @return array
2698 */
2699 public function filterRecursiveFiltersFalseElementsDataProvider()
2700 {
2701 return [
2702 'filter all values which will be false when converted to boolean' => [
2703 // input
2704 [
2705 true,
2706 false,
2707 'foo1' => [
2708 'bar' => [
2709 'baz' => [
2710 '1',
2711 null,
2712 '',
2713 ],
2714 '' => 1,
2715 'bbd' => 0,
2716 ]
2717 ],
2718 'foo2' => 'foo',
2719 'foo3' => '',
2720 'foo4' => [
2721 'z' => 'bar',
2722 'bar' => 0,
2723 'baz' => [
2724 'foo' => [
2725 'bar' => '',
2726 'boo' => [],
2727 'bamboo' => 5,
2728 'fooAndBoo' => [0],
2729 ]
2730 ],
2731 ],
2732 ],
2733 // expected
2734 [
2735 true,
2736 'foo1' => [
2737 'bar' => [
2738 'baz' => [
2739 '1',
2740 ],
2741 '' => 1,
2742 ]
2743 ],
2744 'foo2' => 'foo',
2745 'foo4' => [
2746 'z' => 'bar',
2747 'baz' => [
2748 'foo' => [
2749 'bamboo' => 5,
2750 'fooAndBoo' => [],
2751 ]
2752 ],
2753 ],
2754 ],
2755 ],
2756 ];
2757 }
2758
2759 /**
2760 * @test
2761 * @dataProvider filterRecursiveFiltersFalseElementsDataProvider
2762 * @param array $input
2763 * @param array $expectedResult
2764 */
2765 public function filterRecursiveFiltersFalseElements(array $input, array $expectedResult)
2766 {
2767 // If no callback is supplied, all entries of array equal to FALSE (see converting to boolean) will be removed.
2768 $result = ArrayUtility::filterRecursive($input);
2769 $this->assertEquals($expectedResult, $result);
2770 }
2771
2772 /**
2773 * Data provider for filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerByCallback
2774 * @return array
2775 */
2776 public function filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerZeroByCallbackDataProvider()
2777 {
2778 return [
2779 'filter empty values, keep zero integers' => [
2780 // input
2781 [
2782 true,
2783 false,
2784 'foo1' => [
2785 'bar' => [
2786 'baz' => [
2787 '1',
2788 null,
2789 '',
2790 ],
2791 '' => 1,
2792 'bbd' => 0,
2793 ]
2794 ],
2795 'foo2' => 'foo',
2796 'foo3' => '',
2797 'foo4' => [
2798 'z' => 'bar',
2799 'bar' => 0,
2800 'baz' => [
2801 'foo' => [
2802 'bar' => '',
2803 'boo' => [],
2804 'bamboo' => 5,
2805 'fooAndBoo' => [0],
2806 ]
2807 ],
2808 ],
2809 ],
2810 // expected
2811 [
2812 true,
2813 false,
2814 'foo1' => [
2815 'bar' => [
2816 'baz' => [
2817 '1',
2818 ],
2819 '' => 1,
2820 'bbd' => 0,
2821 ]
2822 ],
2823 'foo2' => 'foo',
2824 'foo4' => [
2825 'z' => 'bar',
2826 'bar' => 0,
2827 'baz' => [
2828 'foo' => [
2829 'bamboo' => 5,
2830 'fooAndBoo' => [0],
2831 ]
2832 ],
2833 ],
2834 ],
2835 ],
2836 ];
2837 }
2838
2839 /**
2840 * @test
2841 * @dataProvider filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerZeroByCallbackDataProvider
2842 * @param array $input
2843 * @param array $expectedResult
2844 */
2845 public function filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerByCallback(array $input, array $expectedResult)
2846 {
2847 // callback filters empty strings, array and null but keeps zero integers
2848 $result = ArrayUtility::filterRecursive(
2849 $input,
2850 function ($item) {
2851 return $item !== '' && $item !== [] && $item !== null;
2852 }
2853 );
2854 $this->assertEquals($expectedResult, $result);
2855 }
2856 }