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