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