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