[TASK] Add more fixers for php-cs-fixer
[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\Tests\UnitTestCase;
18 use TYPO3\CMS\Core\Utility\ArrayUtility;
19
20 /**
21 * Test case
22 */
23 class ArrayUtilityTest extends UnitTestCase
24 {
25 ///////////////////////
26 // Tests concerning filterByValueRecursive
27 ///////////////////////
28 /**
29 * Data provider for filterByValueRecursiveCorrectlyFiltersArray
30 *
31 * Every array splits into:
32 * - String value to search for
33 * - Input array
34 * - Expected result array
35 */
36 public function filterByValueRecursive()
37 {
38 return [
39 'empty search array' => [
40 'banana',
41 [],
42 []
43 ],
44 'empty string as needle' => [
45 '',
46 [
47 '',
48 'apple'
49 ],
50 [
51 ''
52 ]
53 ],
54 'flat array searching for string' => [
55 'banana',
56 [
57 'apple',
58 'banana'
59 ],
60 [
61 1 => 'banana'
62 ]
63 ],
64 'flat array searching for string with two matches' => [
65 'banana',
66 [
67 'foo' => 'apple',
68 'firstbanana' => 'banana',
69 'secondbanana' => 'banana'
70 ],
71 [
72 'firstbanana' => 'banana',
73 'secondbanana' => 'banana'
74 ]
75 ],
76 'multi dimensional array searching for string with multiple matches' => [
77 'banana',
78 [
79 'foo' => 'apple',
80 'firstbanana' => 'banana',
81 'grape' => [
82 'foo2' => 'apple2',
83 'secondbanana' => 'banana',
84 'foo3' => []
85 ],
86 'bar' => 'orange'
87 ],
88 [
89 'firstbanana' => 'banana',
90 'grape' => [
91 'secondbanana' => 'banana'
92 ]
93 ]
94 ],
95 'multi dimensional array searching for integer with multiple matches' => [
96 42,
97 [
98 'foo' => 23,
99 'bar' => 42,
100 [
101 'foo' => 23,
102 'bar' => 42
103 ]
104 ],
105 [
106 'bar' => 42,
107 [
108 'bar' => 42
109 ]
110 ]
111 ],
112 'flat array searching for boolean TRUE' => [
113 true,
114 [
115 23 => false,
116 42 => true
117 ],
118 [
119 42 => true
120 ]
121 ],
122 'multi dimensional array searching for boolean FALSE' => [
123 false,
124 [
125 23 => false,
126 42 => true,
127 'foo' => [
128 23 => false,
129 42 => true
130 ]
131 ],
132 [
133 23 => false,
134 'foo' => [
135 23 => false
136 ]
137 ]
138 ],
139 'flat array searching for array' => [
140 [
141 'foo' => 'bar'
142 ],
143 [
144 'foo' => 'bar',
145 'foobar' => [
146 'foo' => 'bar'
147 ]
148 ],
149 [
150 'foobar' => [
151 'foo' => 'bar'
152 ]
153 ]
154 ]
155 ];
156 }
157
158 /**
159 * @test
160 * @dataProvider filterByValueRecursive
161 * @param array $needle
162 * @param array $haystack
163 * @param array $expectedResult
164 */
165 public function filterByValueRecursiveCorrectlyFiltersArray($needle, $haystack, $expectedResult)
166 {
167 $this->assertEquals(
168 $expectedResult,
169 ArrayUtility::filterByValueRecursive($needle, $haystack)
170 );
171 }
172
173 /**
174 * @test
175 */
176 public function filterByValueRecursiveMatchesReferencesToSameObject()
177 {
178 $instance = new \stdClass();
179 $this->assertEquals(
180 [$instance],
181 ArrayUtility::filterByValueRecursive($instance, [$instance])
182 );
183 }
184
185 /**
186 * @test
187 */
188 public function filterByValueRecursiveDoesNotMatchDifferentInstancesOfSameClass()
189 {
190 $this->assertEquals(
191 [],
192 ArrayUtility::filterByValueRecursive(new \stdClass(), [new \stdClass()])
193 );
194 }
195
196 ///////////////////////
197 // Tests concerning isValidPath
198 ///////////////////////
199 /**
200 * Mock the class under test, isValidPath() (method under test), calls
201 * static getValuePath() internally, which is mocked here to return a specific
202 * result. This works because of 'static' keyword' instead of 'self'
203 * for getValueByPath() call, using late static binding in PHP 5.3
204 *
205 * @test
206 */
207 public function isValidPathReturnsTrueIfPathExists()
208 {
209 $this->assertTrue(ArrayUtility::isValidPath(['foo' => 'bar'], 'foo'));
210 }
211
212 /**
213 * @test
214 */
215 public function isValidPathReturnsFalseIfPathDoesNotExist()
216 {
217 $this->assertFalse(ArrayUtility::isValidPath(['foo' => 'bar'], 'bar'));
218 }
219
220 ///////////////////////
221 // Tests concerning getValueByPath
222 ///////////////////////
223 /**
224 * @test
225 */
226 public function getValueByPathThrowsExceptionIfPathIsEmpty()
227 {
228 $this->expectException(\RuntimeException::class);
229 $this->expectExceptionCode(1341397767);
230
231 ArrayUtility::getValueByPath([], '');
232 }
233
234 /**
235 * Data provider for getValueByPathThrowsExceptionIfPathNotExists
236 * Every array splits into:
237 * - Array to get value from
238 * - String path
239 * - Expected result
240 * @return array
241 */
242 public function getValueByPathInvalidPathDataProvider()
243 {
244 return [
245 'not existing path 1' => [
246 [
247 'foo' => []
248 ],
249 'foo/bar/baz',
250 false
251 ],
252 'not existing path 2' => [
253 [
254 'foo' => [
255 'baz' => 42
256 ],
257 'bar' => []
258 ],
259 'foo/bar/baz',
260 false
261 ],
262 // Negative test: This could be improved and the test moved to
263 // the valid data provider if the method supports this
264 'doubletick encapsulated quoted doubletick does not work' => [
265 [
266 '"foo"bar"' => [
267 'baz' => 42
268 ],
269 'bar' => []
270 ],
271 '"foo\\"bar"/baz',
272 42
273 ],
274 // Negative test: Method could be improved here
275 'path with doubletick does not work' => [
276 [
277 'fo"o' => [
278 'bar' => 42
279 ]
280 ],
281 'fo"o/foobar',
282 42
283 ]
284 ];
285 }
286
287 /**
288 * @test
289 * @dataProvider getValueByPathInvalidPathDataProvider
290 * @param array $array
291 * @param string $path
292 */
293 public function getValueByPathThrowsExceptionIfPathNotExists(array $array, $path)
294 {
295 $this->expectException(\RuntimeException::class);
296 $this->expectExceptionCode(1341397869);
297
298 ArrayUtility::getValueByPath($array, $path);
299 }
300
301 /**
302 * Data provider for getValueByPathReturnsCorrectValue
303 * Every array splits into:
304 * - Array to get value from
305 * - String path
306 * - Expected result
307 */
308 public function getValueByPathValidDataProvider()
309 {
310 $testObject = new \stdClass();
311 $testObject->foo = 'foo';
312 $testObject->bar = 'bar';
313 return [
314 'integer in multi level array' => [
315 [
316 'foo' => [
317 'bar' => [
318 'baz' => 42
319 ],
320 'bar2' => []
321 ]
322 ],
323 'foo/bar/baz',
324 42
325 ],
326 'zero integer in multi level array' => [
327 [
328 'foo' => [
329 'bar' => [
330 'baz' => 0
331 ]
332 ]
333 ],
334 'foo/bar/baz',
335 0
336 ],
337 'NULL value in multi level array' => [
338 [
339 'foo' => [
340 'baz' => null
341 ]
342 ],
343 'foo/baz',
344 null
345 ],
346 'get string value' => [
347 [
348 'foo' => [
349 'baz' => 'this is a test string'
350 ]
351 ],
352 'foo/baz',
353 'this is a test string'
354 ],
355 'get boolean value: FALSE' => [
356 [
357 'foo' => [
358 'baz' => false
359 ]
360 ],
361 'foo/baz',
362 false
363 ],
364 'get boolean value: TRUE' => [
365 [
366 'foo' => [
367 'baz' => true
368 ]
369 ],
370 'foo/baz',
371 true
372 ],
373 'get object value' => [
374 [
375 'foo' => [
376 'baz' => $testObject
377 ]
378 ],
379 'foo/baz',
380 $testObject
381 ],
382 'enclosed path' => [
383 [
384 'foo/bar' => [
385 'foobar' => 42
386 ]
387 ],
388 '"foo/bar"/foobar',
389 42
390 ]
391 ];
392 }
393
394 /**
395 * @test
396 * @dataProvider getValueByPathValidDataProvider
397 * @param array $array
398 * @param string $path
399 * @param mixed $expectedResult
400 */
401 public function getValueByPathGetsCorrectValue(array $array, $path, $expectedResult)
402 {
403 $this->assertEquals($expectedResult, ArrayUtility::getValueByPath($array, $path));
404 }
405
406 /**
407 * @test
408 */
409 public function getValueByPathAcceptsDifferentDelimiter()
410 {
411 $input = [
412 'foo' => [
413 'bar' => [
414 'baz' => 42
415 ],
416 'bar2' => []
417 ]
418 ];
419 $searchPath = 'foo%bar%baz';
420 $expected = 42;
421 $delimiter = '%';
422 $this->assertEquals(
423 $expected,
424 ArrayUtility::getValueByPath($input, $searchPath, $delimiter)
425 );
426 }
427
428 ///////////////////////
429 // Tests concerning setValueByPath
430 ///////////////////////
431 /**
432 * @test
433 */
434 public function setValueByPathThrowsExceptionIfPathIsEmpty()
435 {
436 $this->expectException(\RuntimeException::class);
437 $this->expectExceptionCode(1341406194);
438
439 ArrayUtility::setValueByPath([], '', null);
440 }
441
442 /**
443 * @test
444 */
445 public function setValueByPathThrowsExceptionIfPathIsNotAString()
446 {
447 $this->expectException(\RuntimeException::class);
448 $this->expectExceptionCode(1341406402);
449
450 ArrayUtility::setValueByPath([], ['foo'], null);
451 }
452
453 /**
454 * Data provider for setValueByPathSetsCorrectValueDataProvider
455 *
456 * Every array splits into:
457 * - Array to set value in
458 * - String path
459 * - Value to set
460 * - Expected result
461 */
462 public function setValueByPathSetsCorrectValueDataProvider()
463 {
464 $testObject = new \stdClass();
465 $testObject->foo = 'foo';
466 $testObject->bar = 'bar';
467 return [
468 'set integer value: 42' => [
469 [
470 'foo' => [
471 'bar' => [
472 'baz' => 0
473 ]
474 ]
475 ],
476 'foo/bar/baz',
477 42,
478 [
479 'foo' => [
480 'bar' => [
481 'baz' => 42
482 ]
483 ]
484 ]
485 ],
486 'set integer value: 0' => [
487 [
488 'foo' => [
489 'bar' => [
490 'baz' => 42
491 ]
492 ]
493 ],
494 'foo/bar/baz',
495 0,
496 [
497 'foo' => [
498 'bar' => [
499 'baz' => 0
500 ]
501 ]
502 ]
503 ],
504 'set null value' => [
505 [
506 'foo' => [
507 'bar' => [
508 'baz' => 42
509 ]
510 ]
511 ],
512 'foo/bar/baz',
513 null,
514 [
515 'foo' => [
516 'bar' => [
517 'baz' => null
518 ]
519 ]
520 ]
521 ],
522 'set array value' => [
523 [
524 'foo' => [
525 'bar' => [
526 'baz' => 42
527 ]
528 ]
529 ],
530 'foo/bar/baz',
531 [
532 'foo' => 123
533 ],
534 [
535 'foo' => [
536 'bar' => [
537 'baz' => [
538 'foo' => 123
539 ]
540 ]
541 ]
542 ]
543 ],
544 'set boolean value: FALSE' => [
545 [
546 'foo' => [
547 'bar' => [
548 'baz' => true
549 ]
550 ]
551 ],
552 'foo/bar/baz',
553 false,
554 [
555 'foo' => [
556 'bar' => [
557 'baz' => false
558 ]
559 ]
560 ]
561 ],
562 'set boolean value: TRUE' => [
563 [
564 'foo' => [
565 'bar' => [
566 'baz' => null
567 ]
568 ]
569 ],
570 'foo/bar/baz',
571 true,
572 [
573 'foo' => [
574 'bar' => [
575 'baz' => true
576 ]
577 ]
578 ]
579 ],
580 'set object value' => [
581 [
582 'foo' => [
583 'bar' => [
584 'baz' => null
585 ]
586 ]
587 ],
588 'foo/bar/baz',
589 $testObject,
590 [
591 'foo' => [
592 'bar' => [
593 'baz' => $testObject
594 ]
595 ]
596 ]
597 ],
598 'multi keys in array' => [
599 [
600 'foo' => [
601 'bar' => [
602 'baz' => 'value'
603 ],
604 'bar2' => [
605 'baz' => 'value'
606 ]
607 ]
608 ],
609 'foo/bar2/baz',
610 'newValue',
611 [
612 'foo' => [
613 'bar' => [
614 'baz' => 'value'
615 ],
616 'bar2' => [
617 'baz' => 'newValue'
618 ]
619 ]
620 ]
621 ]
622 ];
623 }
624
625 /**
626 * @test
627 * @dataProvider setValueByPathSetsCorrectValueDataProvider
628 * @param array $array
629 * @param string $path
630 * @param string $value
631 * @param array $expectedResult
632 */
633 public function setValueByPathSetsCorrectValue(array $array, $path, $value, $expectedResult)
634 {
635 $this->assertEquals(
636 $expectedResult,
637 ArrayUtility::setValueByPath($array, $path, $value)
638 );
639 }
640
641 /**********************
642 /* Tests concerning removeByPath
643 ***********************/
644
645 /**
646 * @test
647 */
648 public function removeByPathThrowsExceptionIfPathIsEmpty()
649 {
650 $this->expectException(\RuntimeException::class);
651 $this->expectExceptionCode(1371757718);
652
653 ArrayUtility::removeByPath([], '');
654 }
655
656 /**
657 * @test
658 */
659 public function removeByPathThrowsExceptionIfPathIsNotAString()
660 {
661 $this->expectException(\RuntimeException::class);
662 $this->expectExceptionCode(1371757719);
663
664 ArrayUtility::removeByPath([], ['foo']);
665 }
666
667 /**
668 * @test
669 */
670 public function removeByPathThrowsExceptionWithEmptyPathSegment()
671 {
672 $inputArray = [
673 'foo' => [
674 'bar' => 42,
675 ]
676 ];
677
678 $this->expectException(\RuntimeException::class);
679 $this->expectExceptionCode(1371757720);
680
681 ArrayUtility::removeByPath($inputArray, 'foo//bar');
682 }
683
684 /**
685 * @test
686 */
687 public function removeByPathThrowsExceptionIfPathDoesNotExistInArray()
688 {
689 $inputArray = [
690 'foo' => [
691 'bar' => 42,
692 ]
693 ];
694
695 $this->expectException(\RuntimeException::class);
696 $this->expectExceptionCode(1371758436);
697
698 ArrayUtility::removeByPath($inputArray, 'foo/baz');
699 }
700
701 /**
702 * @test
703 */
704 public function removeByPathAcceptsGivenDelimiter()
705 {
706 $inputArray = [
707 'foo' => [
708 'toRemove' => 42,
709 'keep' => 23
710 ],
711 ];
712 $path = 'foo.toRemove';
713 $expected = [
714 'foo' => [
715 'keep' => 23,
716 ],
717 ];
718 $this->assertEquals(
719 $expected,
720 ArrayUtility::removeByPath($inputArray, $path, '.')
721 );
722 }
723
724 /**
725 * Data provider for removeByPathRemovesCorrectPath
726 */
727 public function removeByPathRemovesCorrectPathDataProvider()
728 {
729 return [
730 'single value' => [
731 [
732 'foo' => [
733 'toRemove' => 42,
734 'keep' => 23
735 ],
736 ],
737 'foo/toRemove',
738 [
739 'foo' => [
740 'keep' => 23,
741 ],
742 ],
743 ],
744 'whole array' => [
745 [
746 'foo' => [
747 'bar' => 42
748 ],
749 ],
750 'foo',
751 [],
752 ],
753 'sub array' => [
754 [
755 'foo' => [
756 'keep' => 23,
757 'toRemove' => [
758 'foo' => 'bar',
759 ],
760 ],
761 ],
762 'foo/toRemove',
763 [
764 'foo' => [
765 'keep' => 23,
766 ],
767 ],
768 ],
769 ];
770 }
771
772 /**
773 * @test
774 * @dataProvider removeByPathRemovesCorrectPathDataProvider
775 * @param array $array
776 * @param string $path
777 * @param array $expectedResult
778 */
779 public function removeByPathRemovesCorrectPath(array $array, $path, $expectedResult)
780 {
781 $this->assertEquals(
782 $expectedResult,
783 ArrayUtility::removeByPath($array, $path)
784 );
785 }
786
787 ///////////////////////
788 // Tests concerning sortByKeyRecursive
789 ///////////////////////
790 /**
791 * @test
792 */
793 public function sortByKeyRecursiveCheckIfSortingIsCorrect()
794 {
795 $unsortedArray = [
796 'z' => null,
797 'a' => null,
798 'd' => [
799 'c' => null,
800 'b' => null,
801 'd' => null,
802 'a' => null
803 ]
804 ];
805 $expectedResult = [
806 'a' => null,
807 'd' => [
808 'a' => null,
809 'b' => null,
810 'c' => null,
811 'd' => null
812 ],
813 'z' => null
814 ];
815 $this->assertSame($expectedResult, ArrayUtility::sortByKeyRecursive($unsortedArray));
816 }
817
818 ///////////////////////
819 // Tests concerning sortArraysByKey
820 ///////////////////////
821 /**
822 * Data provider for sortArraysByKeyCheckIfSortingIsCorrect
823 */
824 public function sortArraysByKeyCheckIfSortingIsCorrectDataProvider()
825 {
826 return [
827 'assoc array index' => [
828 [
829 '22' => [
830 'uid' => '22',
831 'title' => 'c',
832 'dummy' => 2
833 ],
834 '24' => [
835 'uid' => '24',
836 'title' => 'a',
837 'dummy' => 3
838 ],
839 '23' => [
840 'uid' => '23',
841 'title' => 'b',
842 'dummy' => 4
843 ],
844 ],
845 'title',
846 true,
847 [
848 '24' => [
849 'uid' => '24',
850 'title' => 'a',
851 'dummy' => 3
852 ],
853 '23' => [
854 'uid' => '23',
855 'title' => 'b',
856 'dummy' => 4
857 ],
858 '22' => [
859 'uid' => '22',
860 'title' => 'c',
861 'dummy' => 2
862 ],
863 ],
864 ],
865 'numeric array index' => [
866 [
867 22 => [
868 'uid' => '22',
869 'title' => 'c',
870 'dummy' => 2
871 ],
872 24 => [
873 'uid' => '24',
874 'title' => 'a',
875 'dummy' => 3
876 ],
877 23 => [
878 'uid' => '23',
879 'title' => 'b',
880 'dummy' => 4
881 ],
882 ],
883 'title',
884 true,
885 [
886 24 => [
887 'uid' => '24',
888 'title' => 'a',
889 'dummy' => 3
890 ],
891 23 => [
892 'uid' => '23',
893 'title' => 'b',
894 'dummy' => 4
895 ],
896 22 => [
897 'uid' => '22',
898 'title' => 'c',
899 'dummy' => 2
900 ],
901 ],
902 ],
903 'numeric array index DESC' => [
904 [
905 23 => [
906 'uid' => '23',
907 'title' => 'b',
908 'dummy' => 4
909 ],
910 22 => [
911 'uid' => '22',
912 'title' => 'c',
913 'dummy' => 2
914 ],
915 24 => [
916 'uid' => '24',
917 'title' => 'a',
918 'dummy' => 3
919 ],
920 ],
921 'title',
922 false,
923 [
924 22 => [
925 'uid' => '22',
926 'title' => 'c',
927 'dummy' => 2
928 ],
929 23 => [
930 'uid' => '23',
931 'title' => 'b',
932 'dummy' => 4
933 ],
934 24 => [
935 'uid' => '24',
936 'title' => 'a',
937 'dummy' => 3
938 ],
939 ],
940 ],
941 ];
942 }
943
944 /**
945 * @test
946 * @dataProvider sortArraysByKeyCheckIfSortingIsCorrectDataProvider
947 * @param array $array
948 * @param string $key
949 * @param bool $ascending
950 * @param array $expectedResult
951 */
952 public function sortArraysByKeyCheckIfSortingIsCorrect(array $array, $key, $ascending, $expectedResult)
953 {
954 $sortedArray = ArrayUtility::sortArraysByKey($array, $key, $ascending);
955 $this->assertSame($expectedResult, $sortedArray);
956 }
957
958 /**
959 * @test
960 */
961 public function sortArraysByKeyThrowsExceptionForNonExistingKey()
962 {
963 $this->expectException(\RuntimeException::class);
964 $this->expectExceptionCode(1373727309);
965
966 ArrayUtility::sortArraysByKey([['a'], ['a']], 'dummy');
967 }
968
969 ///////////////////////
970 // Tests concerning arrayExport
971 ///////////////////////
972 /**
973 * @test
974 */
975 public function arrayExportReturnsFormattedMultidimensionalArray()
976 {
977 $array = [
978 'foo' => [
979 'bar' => 42,
980 'bar2' => [
981 'baz' => 'val\'ue',
982 'baz2' => true,
983 'baz3' => false,
984 'baz4' => []
985 ]
986 ],
987 'baz' => 23,
988 'foobar' => null,
989 'qux' => 0.1,
990 'qux2' => 0.000000001,
991 ];
992 $expected =
993 '[' . LF .
994 ' \'foo\' => [' . LF .
995 ' \'bar\' => 42,' . LF .
996 ' \'bar2\' => [' . LF .
997 ' \'baz\' => \'val\\\'ue\',' . LF .
998 ' \'baz2\' => true,' . LF .
999 ' \'baz3\' => false,' . LF .
1000 ' \'baz4\' => [],' . LF .
1001 ' ],' . LF .
1002 ' ],' . LF .
1003 ' \'baz\' => 23,' . LF .
1004 ' \'foobar\' => null,' . LF .
1005 ' \'qux\' => 0.1,' . LF .
1006 ' \'qux2\' => 1.0E-9,' . LF .
1007 ']';
1008 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1009 }
1010
1011 /**
1012 * @test
1013 */
1014 public function arrayExportThrowsExceptionIfObjectShouldBeExported()
1015 {
1016 $array = [
1017 'foo' => [
1018 'bar' => new \stdClass()
1019 ]
1020 ];
1021
1022 $this->expectException(\RuntimeException::class);
1023 $this->expectExceptionCode(1342294987);
1024
1025 ArrayUtility::arrayExport($array);
1026 }
1027
1028 /**
1029 * @test
1030 */
1031 public function arrayExportReturnsNumericArrayKeys()
1032 {
1033 $array = [
1034 'foo' => 'string key',
1035 23 => 'integer key',
1036 '42' => 'string key representing integer'
1037 ];
1038 $expected =
1039 '[' . LF .
1040 ' \'foo\' => \'string key\',' . LF .
1041 ' 23 => \'integer key\',' . LF .
1042 ' 42 => \'string key representing integer\',' . LF .
1043 ']';
1044 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1045 }
1046
1047 /**
1048 * @test
1049 */
1050 public function arrayExportReturnsNoKeyIndexForConsecutiveCountedArrays()
1051 {
1052 $array = [
1053 0 => 'zero',
1054 1 => 'one',
1055 2 => 'two'
1056 ];
1057 $expected =
1058 '[' . LF .
1059 ' \'zero\',' . LF .
1060 ' \'one\',' . LF .
1061 ' \'two\',' . LF .
1062 ']';
1063 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1064 }
1065
1066 /**
1067 * @test
1068 */
1069 public function arrayExportReturnsKeyIndexForNonConsecutiveCountedArrays()
1070 {
1071 $array = [
1072 0 => 'zero',
1073 1 => 'one',
1074 3 => 'three',
1075 4 => 'four'
1076 ];
1077 $expected =
1078 '[' . LF .
1079 ' 0 => \'zero\',' . LF .
1080 ' 1 => \'one\',' . LF .
1081 ' 3 => \'three\',' . LF .
1082 ' 4 => \'four\',' . LF .
1083 ']';
1084 $this->assertSame($expected, ArrayUtility::arrayExport($array));
1085 }
1086
1087 ///////////////////////
1088 // Tests concerning flatten
1089 ///////////////////////
1090
1091 /**
1092 * @return array
1093 */
1094 public function flattenCalculatesExpectedResultDataProvider()
1095 {
1096 return [
1097 'plain array' => [
1098 [
1099 'first' => 1,
1100 'second' => 2
1101 ],
1102 [
1103 'first' => 1,
1104 'second' => 2
1105 ]
1106 ],
1107 'plain array with faulty dots' => [
1108 [
1109 'first.' => 1,
1110 'second.' => 2
1111 ],
1112 [
1113 'first' => 1,
1114 'second' => 2
1115 ]
1116 ],
1117 'nested array of 2 levels' => [
1118 [
1119 'first.' => [
1120 'firstSub' => 1
1121 ],
1122 'second.' => [
1123 'secondSub' => 2
1124 ]
1125 ],
1126 [
1127 'first.firstSub' => 1,
1128 'second.secondSub' => 2
1129 ]
1130 ],
1131 'nested array of 2 levels with faulty dots' => [
1132 [
1133 'first.' => [
1134 'firstSub.' => 1
1135 ],
1136 'second.' => [
1137 'secondSub.' => 2
1138 ]
1139 ],
1140 [
1141 'first.firstSub' => 1,
1142 'second.secondSub' => 2
1143 ]
1144 ],
1145 'nested array of 3 levels' => [
1146 [
1147 'first.' => [
1148 'firstSub.' => [
1149 'firstSubSub' => 1
1150 ]
1151 ],
1152 'second.' => [
1153 'secondSub.' => [
1154 'secondSubSub' => 2
1155 ]
1156 ]
1157 ],
1158 [
1159 'first.firstSub.firstSubSub' => 1,
1160 'second.secondSub.secondSubSub' => 2
1161 ]
1162 ],
1163 'nested array of 3 levels with faulty dots' => [
1164 [
1165 'first.' => [
1166 'firstSub.' => [
1167 'firstSubSub.' => 1
1168 ]
1169 ],
1170 'second.' => [
1171 'secondSub.' => [
1172 'secondSubSub.' => 2
1173 ]
1174 ]
1175 ],
1176 [
1177 'first.firstSub.firstSubSub' => 1,
1178 'second.secondSub.secondSubSub' => 2
1179 ]
1180 ]
1181 ];
1182 }
1183
1184 /**
1185 * @test
1186 * @param array $array
1187 * @param array $expected
1188 * @dataProvider flattenCalculatesExpectedResultDataProvider
1189 */
1190 public function flattenCalculatesExpectedResult(array $array, array $expected)
1191 {
1192 $this->assertEquals($expected, ArrayUtility::flatten($array));
1193 }
1194
1195 ///////////////////////
1196 // Tests concerning intersectRecursive
1197 ///////////////////////
1198
1199 /**
1200 * @return array
1201 */
1202 public function intersectRecursiveCalculatesExpectedResultDataProvider()
1203 {
1204 $sameObject = new \stdClass();
1205 return [
1206 // array($source, $mask, $expected)
1207 'empty array is returned if source is empty array' => [
1208 [],
1209 [
1210 'foo' => 'bar',
1211 ],
1212 [],
1213 ],
1214 'empty array is returned if mask is empty' => [
1215 [
1216 'foo' => 'bar',
1217 ],
1218 [],
1219 [],
1220 ],
1221 'key is kept on first level if exists in mask' => [
1222 [
1223 'foo' => 42,
1224 ],
1225 [
1226 'foo' => 42,
1227 ],
1228 [
1229 'foo' => 42,
1230 ],
1231 ],
1232 'value of key in source is kept if mask has different value' => [
1233 [
1234 'foo' => 42,
1235 ],
1236 [
1237 'foo' => new \stdClass(),
1238 ],
1239 [
1240 'foo' => 42,
1241 ],
1242 ],
1243 'key is kept on first level if according mask value is NULL' => [
1244 [
1245 'foo' => 42,
1246 ],
1247 [
1248 'foo' => null,
1249 ],
1250 [
1251 'foo' => 42,
1252 ],
1253 ],
1254 'null in source value is kept' => [
1255 [
1256 'foo' => null,
1257 ],
1258 [
1259 'foo' => 'bar',
1260 ],
1261 [
1262 'foo' => null,
1263 ]
1264 ],
1265 'mask does not add new keys' => [
1266 [
1267 'foo' => 42,
1268 ],
1269 [
1270 'foo' => 23,
1271 'bar' => [
1272 4711
1273 ],
1274 ],
1275 [
1276 'foo' => 42,
1277 ],
1278 ],
1279 'mask does not overwrite simple values with arrays' => [
1280 [
1281 'foo' => 42,
1282 ],
1283 [
1284 'foo' => [
1285 'bar' => 23,
1286 ],
1287 ],
1288 [
1289 'foo' => 42,
1290 ],
1291 ],
1292 'key is kept on first level if according mask value is array' => [
1293 [
1294 'foo' => 42,
1295 ],
1296 [
1297 'foo' => [
1298 'bar' => 23
1299 ],
1300 ],
1301 [
1302 'foo' => 42,
1303 ],
1304 ],
1305 'full array is kept if value is array and mask value is simple type' => [
1306 [
1307 'foo' => [
1308 'bar' => 23
1309 ],
1310 ],
1311 [
1312 'foo' => 42,
1313 ],
1314 [
1315 'foo' => [
1316 'bar' => 23
1317 ],
1318 ],
1319 ],
1320 'key handling is type agnostic' => [
1321 [
1322 42 => 'foo',
1323 ],
1324 [
1325 '42' => 'bar',
1326 ],
1327 [
1328 42 => 'foo',
1329 ],
1330 ],
1331 'value is same if value is object' => [
1332 [
1333 'foo' => $sameObject,
1334 ],
1335 [
1336 'foo' => 'something',
1337 ],
1338 [
1339 'foo' => $sameObject,
1340 ],
1341 ],
1342 'mask does not add simple value to result if key does not exist in source' => [
1343 [
1344 'foo' => '42',
1345 ],
1346 [
1347 'foo' => '42',
1348 'bar' => 23
1349 ],
1350 [
1351 'foo' => '42',
1352 ],
1353 ],
1354 'array of source is kept if value of mask key exists but is no array' => [
1355 [
1356 'foo' => '42',
1357 'bar' => [
1358 'baz' => 23
1359 ],
1360 ],
1361 [
1362 'foo' => 'value is not significant',
1363 'bar' => null,
1364 ],
1365 [
1366 'foo' => '42',
1367 'bar' => [
1368 'baz' => 23
1369 ],
1370 ],
1371 ],
1372 'sub arrays are kept if mask has according sub array key and is similar array' => [
1373 [
1374 'first1' => 42,
1375 'first2' => [
1376 'second1' => 23,
1377 'second2' => 4711,
1378 ],
1379 ],
1380 [
1381 'first1' => 42,
1382 'first2' => [
1383 'second1' => 'exists but different',
1384 ],
1385 ],
1386 [
1387 'first1' => 42,
1388 'first2' => [
1389 'second1' => 23,
1390 ],
1391 ],
1392 ],
1393 ];
1394 }
1395
1396 /**
1397 * @test
1398 * @param array $source
1399 * @param array $mask
1400 * @param array $expected
1401 * @dataProvider intersectRecursiveCalculatesExpectedResultDataProvider
1402 */
1403 public function intersectRecursiveCalculatesExpectedResult(array $source, array $mask, array $expected)
1404 {
1405 $this->assertSame($expected, ArrayUtility::intersectRecursive($source, $mask));
1406 }
1407
1408 ///////////////////////
1409 // Tests concerning renumberKeysToAvoidLeapsIfKeysAreAllNumeric
1410 ///////////////////////
1411 /**
1412 * @return array
1413 */
1414 public function renumberKeysToAvoidLeapsIfKeysAreAllNumericDataProvider()
1415 {
1416 return [
1417 'empty array is returned if source is empty array' => [
1418 [],
1419 []
1420 ],
1421 'returns self if array is already numerically keyed' => [
1422 [1, 2, 3],
1423 [1, 2, 3]
1424 ],
1425 'returns correctly if keys are numeric, but contains a leap' => [
1426 [0 => 'One', 1 => 'Two', 3 => 'Three'],
1427 [0 => 'One', 1 => 'Two', 2 => 'Three'],
1428 ],
1429 'returns correctly even though keys are strings but still numeric' => [
1430 ['0' => 'One', '1' => 'Two', '3' => 'Three'],
1431 [0 => 'One', 1 => 'Two', 2 => 'Three'],
1432 ],
1433 'returns correctly if just a single keys is not numeric' => [
1434 [0 => 'Zero', '1' => 'One', 'Two' => 'Two'],
1435 [0 => 'Zero', '1' => 'One', 'Two' => 'Two'],
1436 ],
1437 'return self with nested numerically keyed array' => [
1438 [
1439 'One',
1440 'Two',
1441 'Three',
1442 [
1443 'sub.One',
1444 'sub.Two',
1445 ]
1446 ],
1447 [
1448 'One',
1449 'Two',
1450 'Three',
1451 [
1452 'sub.One',
1453 'sub.Two',
1454 ]
1455 ]
1456 ],
1457 'returns correctly with nested numerically keyed array with leaps' => [
1458 [
1459 'One',
1460 'Two',
1461 'Three',
1462 [
1463 0 => 'sub.One',
1464 2 => 'sub.Two',
1465 ]
1466 ],
1467 [
1468 'One',
1469 'Two',
1470 'Three',
1471 [
1472 'sub.One',
1473 'sub.Two',
1474 ]
1475 ]
1476 ],
1477 'returns correctly with nested string-keyed array' => [
1478 [
1479 'One',
1480 'Two',
1481 'Three',
1482 [
1483 'one' => 'sub.One',
1484 'two' => 'sub.Two',
1485 ]
1486 ],
1487 [
1488 'One',
1489 'Two',
1490 'Three',
1491 [
1492 'one' => 'sub.One',
1493 'two' => 'sub.Two',
1494 ]
1495 ]
1496 ],
1497 'returns correctly with deeply nested arrays' => [
1498 [
1499 'One',
1500 'Two',
1501 [
1502 'one' => 1,
1503 'two' => 2,
1504 'three' => [
1505 2 => 'SubSubOne',
1506 5 => 'SubSubTwo',
1507 9 => [0, 1, 2],
1508 []
1509 ]
1510 ]
1511 ],
1512 [
1513 'One',
1514 'Two',
1515 [
1516 'one' => 1,
1517 'two' => 2,
1518 'three' => [
1519 'SubSubOne',
1520 'SubSubTwo',
1521 [0, 1, 2],
1522 []
1523 ]
1524 ]
1525 ]
1526 ]
1527 ];
1528 }
1529
1530 /**
1531 * @test
1532 * @param array $inputArray
1533 * @param array $expected
1534 * @dataProvider renumberKeysToAvoidLeapsIfKeysAreAllNumericDataProvider
1535 */
1536 public function renumberKeysToAvoidLeapsIfKeysAreAllNumericReturnsExpectedOrder(array $inputArray, array $expected)
1537 {
1538 $this->assertEquals($expected, ArrayUtility::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($inputArray));
1539 }
1540
1541 /**
1542 * @return array
1543 */
1544 public function mergeRecursiveWithOverruleCalculatesExpectedResultDataProvider()
1545 {
1546 return [
1547 'Override array can reset string to array' => [
1548 [
1549 'first' => [
1550 'second' => 'foo',
1551 ],
1552 ],
1553 [
1554 'first' => [
1555 'second' => ['third' => 'bar'],
1556 ],
1557 ],
1558 true,
1559 true,
1560 true,
1561 [
1562 'first' => [
1563 'second' => ['third' => 'bar'],
1564 ],
1565 ],
1566 ],
1567 'Override array does not reset array to string (weird!)' => [
1568 [
1569 'first' => [],
1570 ],
1571 [
1572 'first' => 'foo',
1573 ],
1574 true,
1575 true,
1576 true,
1577 [
1578 'first' => [], // This is rather unexpected, naive expectation: first => 'foo'
1579 ],
1580 ],
1581 'Override array does override string with null' => [
1582 [
1583 'first' => 'foo',
1584 ],
1585 [
1586 'first' => null,
1587 ],
1588 true,
1589 true,
1590 true,
1591 [
1592 'first' => null,
1593 ],
1594 ],
1595 'Override array does override null with string' => [
1596 [
1597 'first' => null,
1598 ],
1599 [
1600 'first' => 'foo',
1601 ],
1602 true,
1603 true,
1604 true,
1605 [
1606 'first' => 'foo',
1607 ],
1608 ],
1609 'Override array does override null with empty string' => [
1610 [
1611 'first' => null,
1612 ],
1613 [
1614 'first' => '',
1615 ],
1616 true,
1617 true,
1618 true,
1619 [
1620 'first' => '',
1621 ],
1622 ],
1623 'Override array does not override string with NULL if requested' => [
1624 [
1625 'first' => 'foo',
1626 ],
1627 [
1628 'first' => null,
1629 ],
1630 true,
1631 false, // no include empty values
1632 true,
1633 [
1634 'first' => 'foo',
1635 ],
1636 ],
1637 'Override array does override null with null' => [
1638 [
1639 'first' => null,
1640 ],
1641 [
1642 'first' => null,
1643 ],
1644 true,
1645 true,
1646 true,
1647 [
1648 'first' => '',
1649 ],
1650 ],
1651 'Override array can __UNSET values' => [
1652 [
1653 'first' => [
1654 'second' => 'second',
1655 'third' => 'third',
1656 ],
1657 'fifth' => [],
1658 ],
1659 [
1660 'first' => [
1661 'second' => 'overrule',
1662 'third' => '__UNSET',
1663 'fourth' => 'overrile',
1664 ],
1665 'fifth' => '__UNSET',
1666 ],
1667 true,
1668 true,
1669 true,
1670 [
1671 'first' => [
1672 'second' => 'overrule',
1673 'fourth' => 'overrile',
1674 ],
1675 ],
1676 ],
1677 'Override can add keys' => [
1678 [
1679 'first' => 'foo',
1680 ],
1681 [
1682 'second' => 'bar',
1683 ],
1684 true,
1685 true,
1686 true,
1687 [
1688 'first' => 'foo',
1689 'second' => 'bar',
1690 ],
1691 ],
1692 'Override does not add key if __UNSET' => [
1693 [
1694 'first' => 'foo',
1695 ],
1696 [
1697 'second' => '__UNSET',
1698 ],
1699 true,
1700 true,
1701 true,
1702 [
1703 'first' => 'foo',
1704 ],
1705 ],
1706 'Override does not add key if not requested' => [
1707 [
1708 'first' => 'foo',
1709 ],
1710 [
1711 'second' => 'bar',
1712 ],
1713 false, // no add keys
1714 true,
1715 true,
1716 [
1717 'first' => 'foo',
1718 ],
1719 ],
1720 'Override does not add key if not requested with add include empty values' => [
1721 [
1722 'first' => 'foo',
1723 ],
1724 [
1725 'second' => 'bar',
1726 ],
1727 false, // no add keys
1728 false, // no include empty values
1729 true,
1730 [
1731 'first' => 'foo',
1732 ],
1733 ],
1734 'Override does not override string with empty string if requested' => [
1735 [
1736 'first' => 'foo',
1737 ],
1738 [
1739 'first' => '',
1740 ],
1741 true,
1742 false, // no include empty values
1743 true,
1744 [
1745 'first' => 'foo',
1746 ],
1747 ],
1748 'Override array does merge instead of __UNSET if requested (weird!)' => [
1749 [
1750 'first' => [
1751 'second' => 'second',
1752 'third' => 'third',
1753 ],
1754 'fifth' => [],
1755 ],
1756 [
1757 'first' => [
1758 'second' => 'overrule',
1759 'third' => '__UNSET',
1760 'fourth' => 'overrile',
1761 ],
1762 'fifth' => '__UNSET',
1763 ],
1764 true,
1765 true,
1766 false,
1767 [
1768 'first' => [
1769 'second' => 'overrule',
1770 'third' => '__UNSET', // overruled
1771 'fourth' => 'overrile',
1772 ],
1773 'fifth' => [], // not overruled with string here, naive expectation: 'fifth' => '__UNSET'
1774 ],
1775 ],
1776 ];
1777 }
1778
1779 /**
1780 * @test
1781 * @dataProvider mergeRecursiveWithOverruleCalculatesExpectedResultDataProvider
1782 * @param array $input1 Input 1
1783 * @param array $input2 Input 2
1784 * @param bool $addKeys TRUE if should add keys, else FALSE
1785 * @param bool $includeEmptyValues TRUE if should include empty values, else FALSE
1786 * @param bool $enableUnsetFeature TRUE if should enable unset feature, else FALSE
1787 * @param array $expected expected array
1788 */
1789 public function mergeRecursiveWithOverruleCalculatesExpectedResult($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature, $expected)
1790 {
1791 ArrayUtility::mergeRecursiveWithOverrule($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature);
1792 $this->assertEquals($expected, $input1);
1793 }
1794
1795 //////////////////////////////////
1796 // Tests concerning inArray
1797 //////////////////////////////////
1798 /**
1799 * @test
1800 * @dataProvider inArrayDataProvider
1801 * @param array $array target array
1802 * @param string $item search string
1803 * @param bool $expected expected value
1804 */
1805 public function inArrayChecksStringExistenceWithinArray($array, $item, $expected)
1806 {
1807 $this->assertEquals($expected, ArrayUtility::inArray($array, $item));
1808 }
1809
1810 /**
1811 * Data provider for inArrayChecksStringExistenceWithinArray
1812 *
1813 * @return array
1814 */
1815 public function inArrayDataProvider()
1816 {
1817 return [
1818 'Empty array' => [[], 'search', false],
1819 'One item array no match' => [['one'], 'two', false],
1820 'One item array match' => [['one'], 'one', true],
1821 'Multiple items array no match' => [['one', 2, 'three', 4], 'four', false],
1822 'Multiple items array match' => [['one', 2, 'three', 4], 'three', true],
1823 'Integer search items can match string values' => [['0', '1', '2'], 1, true],
1824 'Search item is not casted to integer for a match' => [[4], '4a', false],
1825 'Empty item won\'t match - in contrast to the php-builtin ' => [[0, 1, 2], '', false]
1826 ];
1827 }
1828
1829 //////////////////////////////////
1830 // Tests concerning removeArrayEntryByValue
1831 //////////////////////////////////
1832 /**
1833 * @test
1834 */
1835 public function checkRemoveArrayEntryByValueRemovesEntriesFromOneDimensionalArray()
1836 {
1837 $inputArray = [
1838 '0' => 'test1',
1839 '1' => 'test2',
1840 '2' => 'test3',
1841 '3' => 'test2'
1842 ];
1843 $compareValue = 'test2';
1844 $expectedResult = [
1845 '0' => 'test1',
1846 '2' => 'test3'
1847 ];
1848 $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1849 $this->assertEquals($expectedResult, $actualResult);
1850 }
1851
1852 /**
1853 * @test
1854 */
1855 public function checkRemoveArrayEntryByValueRemovesEntriesFromMultiDimensionalArray()
1856 {
1857 $inputArray = [
1858 '0' => 'foo',
1859 '1' => [
1860 '10' => 'bar'
1861 ],
1862 '2' => 'bar'
1863 ];
1864 $compareValue = 'bar';
1865 $expectedResult = [
1866 '0' => 'foo',
1867 '1' => []
1868 ];
1869 $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1870 $this->assertEquals($expectedResult, $actualResult);
1871 }
1872
1873 /**
1874 * @test
1875 */
1876 public function checkRemoveArrayEntryByValueRemovesEntryWithEmptyString()
1877 {
1878 $inputArray = [
1879 '0' => 'foo',
1880 '1' => '',
1881 '2' => 'bar'
1882 ];
1883 $compareValue = '';
1884 $expectedResult = [
1885 '0' => 'foo',
1886 '2' => 'bar'
1887 ];
1888 $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1889 $this->assertEquals($expectedResult, $actualResult);
1890 }
1891
1892 //////////////////////////////////
1893 // Tests concerning keepItemsInArray
1894 //////////////////////////////////
1895 /**
1896 * @test
1897 * @dataProvider keepItemsInArrayWorksWithOneArgumentDataProvider
1898 * @param mixed $search The items which are allowed/kept in the array
1899 * @param array $array target array
1900 * @param array $expected expected array
1901 */
1902 public function keepItemsInArrayWorksWithOneArgument($search, $array, $expected)
1903 {
1904 $this->assertEquals($expected, ArrayUtility::keepItemsInArray($array, $search));
1905 }
1906
1907 /**
1908 * Data provider for keepItemsInArrayWorksWithOneArgument
1909 *
1910 * @return array
1911 */
1912 public function keepItemsInArrayWorksWithOneArgumentDataProvider()
1913 {
1914 $array = [
1915 'one' => 'one',
1916 'two' => 'two',
1917 'three' => 'three'
1918 ];
1919 return [
1920 'Empty argument will match "all" elements' => [null, $array, $array],
1921 'No match' => ['four', $array, []],
1922 'One match' => ['two', $array, ['two' => 'two']],
1923 'Multiple matches' => ['two,one', $array, ['one' => 'one', 'two' => 'two']],
1924 'Argument can be an array' => [['three'], $array, ['three' => 'three']]
1925 ];
1926 }
1927
1928 /**
1929 * Shows the example from the doc comment where
1930 * a function is used to reduce the sub arrays to one item which
1931 * is then used for the matching.
1932 *
1933 * @test
1934 */
1935 public function keepItemsInArrayCanUseClosure()
1936 {
1937 $array = [
1938 'aa' => ['first', 'second'],
1939 'bb' => ['third', 'fourth'],
1940 'cc' => ['fifth', 'sixth']
1941 ];
1942 $expected = ['bb' => ['third', 'fourth']];
1943 $keepItems = 'third';
1944 $match = ArrayUtility::keepItemsInArray(
1945 $array,
1946 $keepItems,
1947 function ($value) {
1948 return $value[0];
1949 }
1950 );
1951 $this->assertEquals($expected, $match);
1952 }
1953
1954 //////////////////////////////////
1955 // Tests concerning remapArrayKeys
1956 //////////////////////////////////
1957 /**
1958 * @test
1959 */
1960 public function remapArrayKeysExchangesKeysWithGivenMapping()
1961 {
1962 $array = [
1963 'one' => 'one',
1964 'two' => 'two',
1965 'three' => 'three'
1966 ];
1967 $keyMapping = [
1968 'one' => '1',
1969 'two' => '2'
1970 ];
1971 $expected = [
1972 '1' => 'one',
1973 '2' => 'two',
1974 'three' => 'three'
1975 ];
1976 ArrayUtility::remapArrayKeys($array, $keyMapping);
1977 $this->assertEquals($expected, $array);
1978 }
1979
1980 //////////////////////////////////////
1981 // Tests concerning arrayDiffAssocRecursive
1982 //////////////////////////////////////
1983 /**
1984 * @test
1985 */
1986 public function arrayDiffAssocRecursiveHandlesOneDimensionalArrays()
1987 {
1988 $array1 = [
1989 'key1' => 'value1',
1990 'key2' => 'value2',
1991 'key3' => 'value3'
1992 ];
1993 $array2 = [
1994 'key1' => 'value1',
1995 'key3' => 'value3'
1996 ];
1997 $expectedResult = [
1998 'key2' => 'value2'
1999 ];
2000 $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2001 $this->assertEquals($expectedResult, $actualResult);
2002 }
2003
2004 /**
2005 * @test
2006 */
2007 public function arrayDiffAssocRecursiveHandlesMultiDimensionalArrays()
2008 {
2009 $array1 = [
2010 'key1' => 'value1',
2011 'key2' => [
2012 'key21' => 'value21',
2013 'key22' => 'value22',
2014 'key23' => [
2015 'key231' => 'value231',
2016 'key232' => 'value232'
2017 ]
2018 ]
2019 ];
2020 $array2 = [
2021 'key1' => 'value1',
2022 'key2' => [
2023 'key21' => 'value21',
2024 'key23' => [
2025 'key231' => 'value231'
2026 ]
2027 ]
2028 ];
2029 $expectedResult = [
2030 'key2' => [
2031 'key22' => 'value22',
2032 'key23' => [
2033 'key232' => 'value232'
2034 ]
2035 ]
2036 ];
2037 $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2038 $this->assertEquals($expectedResult, $actualResult);
2039 }
2040
2041 /**
2042 * @test
2043 */
2044 public function arrayDiffAssocRecursiveHandlesMixedArrays()
2045 {
2046 $array1 = [
2047 'key1' => [
2048 'key11' => 'value11',
2049 'key12' => 'value12'
2050 ],
2051 'key2' => 'value2',
2052 'key3' => 'value3'
2053 ];
2054 $array2 = [
2055 'key1' => 'value1',
2056 'key2' => [
2057 'key21' => 'value21'
2058 ]
2059 ];
2060 $expectedResult = [
2061 'key3' => 'value3'
2062 ];
2063 $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2064 $this->assertEquals($expectedResult, $actualResult);
2065 }
2066
2067 //////////////////////////////////////
2068 // Tests concerning naturalKeySortRecursive
2069 //////////////////////////////////////
2070
2071 /**
2072 * @test
2073 */
2074 public function naturalKeySortRecursiveSortsOneDimensionalArrayByNaturalOrder()
2075 {
2076 $testArray = [
2077 'bb' => 'bb',
2078 'ab' => 'ab',
2079 '123' => '123',
2080 'aaa' => 'aaa',
2081 'abc' => 'abc',
2082 '23' => '23',
2083 'ba' => 'ba',
2084 'bad' => 'bad',
2085 '2' => '2',
2086 'zap' => 'zap',
2087 '210' => '210'
2088 ];
2089 $expectedResult = [
2090 '2',
2091 '23',
2092 '123',
2093 '210',
2094 'aaa',
2095 'ab',
2096 'abc',
2097 'ba',
2098 'bad',
2099 'bb',
2100 'zap'
2101 ];
2102 ArrayUtility::naturalKeySortRecursive($testArray);
2103 $this->assertEquals($expectedResult, array_values($testArray));
2104 }
2105
2106 /**
2107 * @test
2108 */
2109 public function naturalKeySortRecursiveSortsMultiDimensionalArrayByNaturalOrder()
2110 {
2111 $testArray = [
2112 '2' => '2',
2113 'bb' => 'bb',
2114 'ab' => 'ab',
2115 '23' => '23',
2116 'aaa' => [
2117 'bb' => 'bb',
2118 'ab' => 'ab',
2119 '123' => '123',
2120 'aaa' => 'aaa',
2121 '2' => '2',
2122 'abc' => 'abc',
2123 'ba' => 'ba',
2124 '23' => '23',
2125 'bad' => [
2126 'bb' => 'bb',
2127 'ab' => 'ab',
2128 '123' => '123',
2129 'aaa' => 'aaa',
2130 'abc' => 'abc',
2131 '23' => '23',
2132 'ba' => 'ba',
2133 'bad' => 'bad',
2134 '2' => '2',
2135 'zap' => 'zap',
2136 '210' => '210'
2137 ],
2138 '210' => '210',
2139 'zap' => 'zap'
2140 ],
2141 'abc' => 'abc',
2142 'ba' => 'ba',
2143 '210' => '210',
2144 'bad' => 'bad',
2145 '123' => '123',
2146 'zap' => 'zap'
2147 ];
2148 $expectedResult = [
2149 '2',
2150 '23',
2151 '123',
2152 '210',
2153 'aaa',
2154 'ab',
2155 'abc',
2156 'ba',
2157 'bad',
2158 'bb',
2159 'zap'
2160 ];
2161 ArrayUtility::naturalKeySortRecursive($testArray);
2162 $this->assertEquals($expectedResult, array_values(array_keys($testArray['aaa']['bad'])));
2163 $this->assertEquals($expectedResult, array_values(array_keys($testArray['aaa'])));
2164 $this->assertEquals($expectedResult, array_values(array_keys($testArray)));
2165 }
2166
2167 /**
2168 * Data provider for filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsTrue
2169 *
2170 * @return array
2171 */
2172 public function filterAndSortByNumericKeysWithAcceptAnyKey()
2173 {
2174 return [
2175 'ordered list of plain numeric keys' => [
2176 'input' => [
2177 '10' => 'foo',
2178 '20' => 'bar',
2179 ],
2180 'expected' => [
2181 10,
2182 20,
2183 ],
2184 ],
2185 'unordered list of plain numeric keys' => [
2186 'input' => [
2187 '20' => 'bar',
2188 '10' => 'foo',
2189 ],
2190 'expected' => [
2191 10,
2192 20,
2193 ],
2194 ],
2195 'list of string keys' => [
2196 'input' => [
2197 '10.' => [
2198 'wrap' => 'foo',
2199 ],
2200 '20.' => [
2201 'wrap' => 'bar',
2202 ],
2203 ],
2204 'expected' => [
2205 10,
2206 20,
2207 ],
2208 ],
2209 'list of mixed keys' => [
2210 'input' => [
2211 '10' => 'foo',
2212 '20.' => [
2213 'wrap' => 'bar',
2214 ],
2215 ],
2216 'expected' => [
2217 10,
2218 20,
2219 ],
2220 ],
2221 'list of mixed keys with one not interpreted as integer' => [
2222 'input' => [
2223 '10' => 'foo',
2224 'bla20.' => [
2225 'wrap' => 'bar',
2226 ],
2227 ],
2228 'expected' => [
2229 0,
2230 10,
2231 ],
2232 ],
2233 'list of mixed keys with more than one not interpreted as integer' => [
2234 'input' => [
2235 '10' => 'foo',
2236 'bla20.' => [
2237 'wrap' => 'bar',
2238 ],
2239 'bla21.' => [
2240 'wrap' => 'foobar',
2241 ],
2242 ],
2243 'expected' => [
2244 0,
2245 10,
2246 ],
2247 ],
2248 ];
2249 }
2250
2251 /**
2252 * @test
2253 * @dataProvider filterAndSortByNumericKeysWithAcceptAnyKey
2254 *
2255 * @param array $input
2256 * @param array $expected
2257 */
2258 public function filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsTrue($input, $expected)
2259 {
2260 $result = ArrayUtility::filterAndSortByNumericKeys($input, true);
2261 $this->assertEquals($result, $expected);
2262 }
2263
2264 /**
2265 * Data provider for filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsFalse
2266 *
2267 * @return array
2268 */
2269 public function filterAndSortByNumericKeysWithoutAcceptAnyKey()
2270 {
2271 return [
2272 'ordered list of plain numeric keys' => [
2273 'input' => [
2274 '10' => 'foo',
2275 '20' => 'bar',
2276 ],
2277 'expected' => [
2278 10,
2279 20,
2280 ],
2281 ],
2282 'unordered list of plain numeric keys' => [
2283 'input' => [
2284 '20' => 'bar',
2285 '10' => 'foo',
2286 ],
2287 'expected' => [
2288 10,
2289 20,
2290 ],
2291 ],
2292 'list of string keys' => [
2293 'input' => [
2294 '10.' => [
2295 'wrap' => 'foo',
2296 ],
2297 '20.' => [
2298 'wrap' => 'bar',
2299 ],
2300 ],
2301 'expected' => [],
2302 ],
2303 'list of mixed keys' => [
2304 'input' => [
2305 '10' => 'foo',
2306 '20.' => [
2307 'wrap' => 'bar',
2308 ],
2309 ],
2310 'expected' => [
2311 10,
2312 ],
2313 ],
2314 ];
2315 }
2316
2317 /**
2318 * @test
2319 * @dataProvider filterAndSortByNumericKeysWithoutAcceptAnyKey
2320 *
2321 * @param array $input
2322 * @param array $expected
2323 */
2324 public function filterAndSortByNumericKeysBehavesCorrectlyForAcceptAnyKeysIsFalse($input, $expected)
2325 {
2326 $result = ArrayUtility::filterAndSortByNumericKeys($input);
2327 $this->assertEquals($result, $expected);
2328 }
2329 }