TYPO3 CMS  TYPO3_7-6
ArrayUtilityTest.php
Go to the documentation of this file.
1 <?php
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 
19 
24 {
26  // Tests concerning filterByValueRecursive
28 
36  public function filterByValueRecursive()
37  {
38  return [
39  'empty search array' => [
40  'banana',
41  [],
42  []
43  ],
44  'empty string as needle' => [
45  '',
46  [
47  '',
48  'apple'
49  ],
50  [
51  ''
52  ]
53  ],
54  'flat array searching for string' => [
55  'banana',
56  [
57  'apple',
58  'banana'
59  ],
60  [
61  1 => 'banana'
62  ]
63  ],
64  'flat array searching for string with two matches' => [
65  'banana',
66  [
67  'foo' => 'apple',
68  'firstbanana' => 'banana',
69  'secondbanana' => 'banana'
70  ],
71  [
72  'firstbanana' => 'banana',
73  'secondbanana' => 'banana'
74  ]
75  ],
76  'multi dimensional array searching for string with multiple matches' => [
77  'banana',
78  [
79  'foo' => 'apple',
80  'firstbanana' => 'banana',
81  'grape' => [
82  'foo2' => 'apple2',
83  'secondbanana' => 'banana',
84  'foo3' => []
85  ],
86  'bar' => 'orange'
87  ],
88  [
89  'firstbanana' => 'banana',
90  'grape' => [
91  'secondbanana' => 'banana'
92  ]
93  ]
94  ],
95  'multi dimensional array searching for integer with multiple matches' => [
96  42,
97  [
98  'foo' => 23,
99  'bar' => 42,
100  [
101  'foo' => 23,
102  'bar' => 42
103  ]
104  ],
105  [
106  'bar' => 42,
107  [
108  'bar' => 42
109  ]
110  ]
111  ],
112  'flat array searching for boolean TRUE' => [
113  true,
114  [
115  23 => false,
116  42 => true
117  ],
118  [
119  42 => true
120  ]
121  ],
122  'multi dimensional array searching for boolean FALSE' => [
123  false,
124  [
125  23 => false,
126  42 => true,
127  'foo' => [
128  23 => false,
129  42 => true
130  ]
131  ],
132  [
133  23 => false,
134  'foo' => [
135  23 => false
136  ]
137  ]
138  ],
139  'flat array searching for array' => [
140  [
141  'foo' => 'bar'
142  ],
143  [
144  'foo' => 'bar',
145  'foobar' => [
146  'foo' => 'bar'
147  ]
148  ],
149  [
150  'foobar' => [
151  'foo' => 'bar'
152  ]
153  ]
154  ]
155  ];
156  }
157 
165  public function filterByValueRecursiveCorrectlyFiltersArray($needle, $haystack, $expectedResult)
166  {
167  $this->assertEquals(
168  $expectedResult,
169  ArrayUtility::filterByValueRecursive($needle, $haystack)
170  );
171  }
172 
177  {
178  $instance = new \stdClass();
179  $this->assertEquals(
180  [$instance],
181  ArrayUtility::filterByValueRecursive($instance, [$instance])
182  );
183  }
184 
189  {
190  $this->assertEquals(
191  [],
192  ArrayUtility::filterByValueRecursive(new \stdClass(), [new \stdClass()])
193  );
194  }
195 
197  // Tests concerning isValidPath
199 
208  {
209  $this->assertTrue(ArrayUtility::isValidPath(['foo' => 'bar'], 'foo'));
210  }
211 
216  {
217  $this->assertFalse(ArrayUtility::isValidPath(['foo' => 'bar'], 'bar'));
218  }
219 
221  // Tests concerning getValueByPath
223 
229  {
231  }
232 
239  {
241  }
242 
247  {
248  $this->assertSame('foo', ArrayUtility::getValueByPath(['foo'], '0'));
249  }
250 
255  {
256  $this->assertSame('bar', ArrayUtility::getValueByPath(['foo' => ['bar']], 'foo/0'));
257  }
258 
268  {
269  return [
270  'not existing index' => [
271  [
272  'foo' => ['foo']
273  ],
274  'foo/1',
275  false
276  ],
277  'not existing path 1' => [
278  [
279  'foo' => []
280  ],
281  'foo/bar/baz',
282  false
283  ],
284  'not existing path 2' => [
285  [
286  'foo' => [
287  'baz' => 42
288  ],
289  'bar' => []
290  ],
291  'foo/bar/baz',
292  false
293  ],
294  // Negative test: This could be improved and the test moved to
295  // the valid data provider if the method supports this
296  'doubletick encapsulated quoted doubletick does not work' => [
297  [
298  '"foo"bar"' => [
299  'baz' => 42
300  ],
301  'bar' => []
302  ],
303  '"foo\\"bar"/baz',
304  42
305  ],
306  // Negative test: Method could be improved here
307  'path with doubletick does not work' => [
308  [
309  'fo"o' => [
310  'bar' => 42
311  ]
312  ],
313  'fo"o/foobar',
314  42
315  ]
316  ];
317  }
318 
327  public function getValueByPathThrowsExceptionIfPathNotExists(array $array, $path)
328  {
329  ArrayUtility::getValueByPath($array, $path);
330  }
331 
340  {
341  $testObject = new \StdClass();
342  $testObject->foo = 'foo';
343  $testObject->bar = 'bar';
344  return [
345  'integer in multi level array' => [
346  [
347  'foo' => [
348  'bar' => [
349  'baz' => 42
350  ],
351  'bar2' => []
352  ]
353  ],
354  'foo/bar/baz',
355  42
356  ],
357  'zero integer in multi level array' => [
358  [
359  'foo' => [
360  'bar' => [
361  'baz' => 0
362  ]
363  ]
364  ],
365  'foo/bar/baz',
366  0
367  ],
368  'NULL value in multi level array' => [
369  [
370  'foo' => [
371  'baz' => null
372  ]
373  ],
374  'foo/baz',
375  null
376  ],
377  'get string value' => [
378  [
379  'foo' => [
380  'baz' => 'this is a test string'
381  ]
382  ],
383  'foo/baz',
384  'this is a test string'
385  ],
386  'get boolean value: FALSE' => [
387  [
388  'foo' => [
389  'baz' => false
390  ]
391  ],
392  'foo/baz',
393  false
394  ],
395  'get boolean value: TRUE' => [
396  [
397  'foo' => [
398  'baz' => true
399  ]
400  ],
401  'foo/baz',
402  true
403  ],
404  'get object value' => [
405  [
406  'foo' => [
407  'baz' => $testObject
408  ]
409  ],
410  'foo/baz',
411  $testObject
412  ],
413  'enclosed path' => [
414  [
415  'foo/bar' => [
416  'foobar' => 42
417  ]
418  ],
419  '"foo/bar"/foobar',
420  42
421  ]
422  ];
423  }
424 
432  public function getValueByPathGetsCorrectValue(array $array, $path, $expectedResult)
433  {
434  $this->assertEquals($expectedResult, ArrayUtility::getValueByPath($array, $path));
435  }
436 
441  {
442  $input = [
443  'foo' => [
444  'bar' => [
445  'baz' => 42
446  ],
447  'bar2' => []
448  ]
449  ];
450  $searchPath = 'foo%bar%baz';
451  $expected = 42;
452  $delimiter = '%';
453  $this->assertEquals(
454  $expected,
455  ArrayUtility::getValueByPath($input, $searchPath, $delimiter)
456  );
457  }
458 
460  // Tests concerning setValueByPath
462 
468  {
469  ArrayUtility::setValueByPath([], '', null);
470  }
471 
478  {
479  ArrayUtility::setValueByPath([], ['foo'], null);
480  }
481 
488  {
489  ArrayUtility::setValueByPath(['foo' => 'bar'], '/foo', 'value');
490  }
491 
496  {
497  $this->assertSame(['foo' => ['value']], ArrayUtility::setValueByPath(['foo' => []], 'foo/0', 'value'));
498  }
499 
504  {
505  $this->assertSame(['value', 'bar'], ArrayUtility::setValueByPath(['foo', 'bar'], '0', 'value'));
506  }
507 
518  {
519  $testObject = new \StdClass();
520  $testObject->foo = 'foo';
521  $testObject->bar = 'bar';
522  return [
523  'set integer value: 42' => [
524  [
525  'foo' => [
526  'bar' => [
527  'baz' => 0
528  ]
529  ]
530  ],
531  'foo/bar/baz',
532  42,
533  [
534  'foo' => [
535  'bar' => [
536  'baz' => 42
537  ]
538  ]
539  ]
540  ],
541  'set integer value: 0' => [
542  [
543  'foo' => [
544  'bar' => [
545  'baz' => 42
546  ]
547  ]
548  ],
549  'foo/bar/baz',
550  0,
551  [
552  'foo' => [
553  'bar' => [
554  'baz' => 0
555  ]
556  ]
557  ]
558  ],
559  'set null value' => [
560  [
561  'foo' => [
562  'bar' => [
563  'baz' => 42
564  ]
565  ]
566  ],
567  'foo/bar/baz',
568  null,
569  [
570  'foo' => [
571  'bar' => [
572  'baz' => null
573  ]
574  ]
575  ]
576  ],
577  'set array value' => [
578  [
579  'foo' => [
580  'bar' => [
581  'baz' => 42
582  ]
583  ]
584  ],
585  'foo/bar/baz',
586  [
587  'foo' => 123
588  ],
589  [
590  'foo' => [
591  'bar' => [
592  'baz' => [
593  'foo' => 123
594  ]
595  ]
596  ]
597  ]
598  ],
599  'set boolean value: FALSE' => [
600  [
601  'foo' => [
602  'bar' => [
603  'baz' => true
604  ]
605  ]
606  ],
607  'foo/bar/baz',
608  false,
609  [
610  'foo' => [
611  'bar' => [
612  'baz' => false
613  ]
614  ]
615  ]
616  ],
617  'set boolean value: TRUE' => [
618  [
619  'foo' => [
620  'bar' => [
621  'baz' => null
622  ]
623  ]
624  ],
625  'foo/bar/baz',
626  true,
627  [
628  'foo' => [
629  'bar' => [
630  'baz' => true
631  ]
632  ]
633  ]
634  ],
635  'set object value' => [
636  [
637  'foo' => [
638  'bar' => [
639  'baz' => null
640  ]
641  ]
642  ],
643  'foo/bar/baz',
644  $testObject,
645  [
646  'foo' => [
647  'bar' => [
648  'baz' => $testObject
649  ]
650  ]
651  ]
652  ],
653  'multi keys in array' => [
654  [
655  'foo' => [
656  'bar' => [
657  'baz' => 'value'
658  ],
659  'bar2' => [
660  'baz' => 'value'
661  ]
662  ]
663  ],
664  'foo/bar2/baz',
665  'newValue',
666  [
667  'foo' => [
668  'bar' => [
669  'baz' => 'value'
670  ],
671  'bar2' => [
672  'baz' => 'newValue'
673  ]
674  ]
675  ]
676  ]
677  ];
678  }
679 
688  public function setValueByPathSetsCorrectValue(array $array, $path, $value, $expectedResult)
689  {
690  $this->assertEquals(
691  $expectedResult,
692  ArrayUtility::setValueByPath($array, $path, $value)
693  );
694  }
695 
696  /**********************
697  /* Tests concerning removeByPath
698  ***********************/
699 
706  {
708  }
709 
716  {
717  ArrayUtility::removeByPath([], ['foo']);
718  }
719 
726  {
727  $inputArray = [
728  'foo' => [
729  'bar' => 42,
730  ],
731  ];
732  ArrayUtility::removeByPath($inputArray, 'foo//bar');
733  }
734 
739  {
740  $inputArray = [
741  'foo' => ['bar']
742  ];
743  $this->assertSame(['foo' => []], ArrayUtility::removeByPath($inputArray, 'foo/0'));
744  }
745 
750  {
751  $inputArray = ['bar'];
752 
753  $this->assertSame([], ArrayUtility::removeByPath($inputArray, '0'));
754  }
755 
762  {
763  $inputArray = [
764  'foo' => [
765  'bar' => 42,
766  ],
767  ];
768  ArrayUtility::removeByPath($inputArray, 'foo/baz');
769  }
770 
775  {
776  $inputArray = [
777  'foo' => [
778  'toRemove' => 42,
779  'keep' => 23
780  ],
781  ];
782  $path = 'foo.toRemove';
783  $expected = [
784  'foo' => [
785  'keep' => 23,
786  ],
787  ];
788  $this->assertEquals(
789  $expected,
790  ArrayUtility::removeByPath($inputArray, $path, '.')
791  );
792  }
793 
798  {
799  return [
800  'single value' => [
801  [
802  'foo' => [
803  'toRemove' => 42,
804  'keep' => 23
805  ],
806  ],
807  'foo/toRemove',
808  [
809  'foo' => [
810  'keep' => 23,
811  ],
812  ],
813  ],
814  'whole array' => [
815  [
816  'foo' => [
817  'bar' => 42
818  ],
819  ],
820  'foo',
821  [],
822  ],
823  'sub array' => [
824  [
825  'foo' => [
826  'keep' => 23,
827  'toRemove' => [
828  'foo' => 'bar',
829  ],
830  ],
831  ],
832  'foo/toRemove',
833  [
834  'foo' => [
835  'keep' => 23,
836  ],
837  ],
838  ],
839  ];
840  }
841 
849  public function removeByPathRemovesCorrectPath(array $array, $path, $expectedResult)
850  {
851  $this->assertEquals(
852  $expectedResult,
853  ArrayUtility::removeByPath($array, $path)
854  );
855  }
856 
858  // Tests concerning sortByKeyRecursive
860 
864  {
865  $unsortedArray = [
866  'z' => null,
867  'a' => null,
868  'd' => [
869  'c' => null,
870  'b' => null,
871  'd' => null,
872  'a' => null
873  ]
874  ];
875  $expectedResult = [
876  'a' => null,
877  'd' => [
878  'a' => null,
879  'b' => null,
880  'c' => null,
881  'd' => null
882  ],
883  'z' => null
884  ];
885  $this->assertSame($expectedResult, ArrayUtility::sortByKeyRecursive($unsortedArray));
886  }
887 
889  // Tests concerning sortArraysByKey
891 
895  {
896  return [
897  'assoc array index' => [
898  [
899  '22' => [
900  'uid' => '22',
901  'title' => 'c',
902  'dummy' => 2
903  ],
904  '24' => [
905  'uid' => '24',
906  'title' => 'a',
907  'dummy' => 3
908  ],
909  '23' => [
910  'uid' => '23',
911  'title' => 'b',
912  'dummy' => 4
913  ],
914  ],
915  'title',
916  true,
917  [
918  '24' => [
919  'uid' => '24',
920  'title' => 'a',
921  'dummy' => 3
922  ],
923  '23' => [
924  'uid' => '23',
925  'title' => 'b',
926  'dummy' => 4
927  ],
928  '22' => [
929  'uid' => '22',
930  'title' => 'c',
931  'dummy' => 2
932  ],
933  ],
934  ],
935  'numeric array index' => [
936  [
937  22 => [
938  'uid' => '22',
939  'title' => 'c',
940  'dummy' => 2
941  ],
942  24 => [
943  'uid' => '24',
944  'title' => 'a',
945  'dummy' => 3
946  ],
947  23 => [
948  'uid' => '23',
949  'title' => 'b',
950  'dummy' => 4
951  ],
952  ],
953  'title',
954  true,
955  [
956  24 => [
957  'uid' => '24',
958  'title' => 'a',
959  'dummy' => 3
960  ],
961  23 => [
962  'uid' => '23',
963  'title' => 'b',
964  'dummy' => 4
965  ],
966  22 => [
967  'uid' => '22',
968  'title' => 'c',
969  'dummy' => 2
970  ],
971  ],
972  ],
973  'numeric array index DESC' => [
974  [
975  23 => [
976  'uid' => '23',
977  'title' => 'b',
978  'dummy' => 4
979  ],
980  22 => [
981  'uid' => '22',
982  'title' => 'c',
983  'dummy' => 2
984  ],
985  24 => [
986  'uid' => '24',
987  'title' => 'a',
988  'dummy' => 3
989  ],
990  ],
991  'title',
992  false,
993  [
994  22 => [
995  'uid' => '22',
996  'title' => 'c',
997  'dummy' => 2
998  ],
999  23 => [
1000  'uid' => '23',
1001  'title' => 'b',
1002  'dummy' => 4
1003  ],
1004  24 => [
1005  'uid' => '24',
1006  'title' => 'a',
1007  'dummy' => 3
1008  ],
1009  ],
1010  ],
1011  ];
1012  }
1013 
1022  public function sortArraysByKeyCheckIfSortingIsCorrect(array $array, $key, $ascending, $expectedResult)
1023  {
1024  $sortedArray = ArrayUtility::sortArraysByKey($array, $key, $ascending);
1025  $this->assertSame($expectedResult, $sortedArray);
1026  }
1027 
1034  {
1035  ArrayUtility::sortArraysByKey([['a'], ['a']], 'dummy');
1036  }
1037 
1039  // Tests concerning arrayExport
1041 
1045  {
1046  $array = [
1047  'foo' => [
1048  'bar' => 42,
1049  'bar2' => [
1050  'baz' => 'val\'ue',
1051  'baz2' => true,
1052  'baz3' => false,
1053  'baz4' => []
1054  ]
1055  ],
1056  'baz' => 23,
1057  'foobar' => null,
1058  'qux' => 0.1,
1059  'qux2' => 0.000000001,
1060  ];
1061  $expected =
1062  '[' . LF .
1063  ' \'foo\' => [' . LF .
1064  ' \'bar\' => 42,' . LF .
1065  ' \'bar2\' => [' . LF .
1066  ' \'baz\' => \'val\\\'ue\',' . LF .
1067  ' \'baz2\' => true,' . LF .
1068  ' \'baz3\' => false,' . LF .
1069  ' \'baz4\' => [],' . LF .
1070  ' ],' . LF .
1071  ' ],' . LF .
1072  ' \'baz\' => 23,' . LF .
1073  ' \'foobar\' => null,' . LF .
1074  ' \'qux\' => 0.1,' . LF .
1075  ' \'qux2\' => 1.0E-9,' . LF .
1076  ']';
1077  $this->assertSame($expected, ArrayUtility::arrayExport($array));
1078  }
1079 
1086  {
1087  $array = [
1088  'foo' => [
1089  'bar' => new \stdClass()
1090  ]
1091  ];
1092  ArrayUtility::arrayExport($array);
1093  }
1094 
1099  {
1100  $array = [
1101  'foo' => 'string key',
1102  23 => 'integer key',
1103  '42' => 'string key representing integer'
1104  ];
1105  $expected =
1106  '[' . LF .
1107  ' \'foo\' => \'string key\',' . LF .
1108  ' 23 => \'integer key\',' . LF .
1109  ' 42 => \'string key representing integer\',' . LF .
1110  ']';
1111  $this->assertSame($expected, ArrayUtility::arrayExport($array));
1112  }
1113 
1118  {
1119  $array = [
1120  0 => 'zero',
1121  1 => 'one',
1122  2 => 'two'
1123  ];
1124  $expected =
1125  '[' . LF .
1126  ' \'zero\',' . LF .
1127  ' \'one\',' . LF .
1128  ' \'two\',' . LF .
1129  ']';
1130  $this->assertSame($expected, ArrayUtility::arrayExport($array));
1131  }
1132 
1137  {
1138  $array = [
1139  0 => 'zero',
1140  1 => 'one',
1141  3 => 'three',
1142  4 => 'four'
1143  ];
1144  $expected =
1145  '[' . LF .
1146  ' 0 => \'zero\',' . LF .
1147  ' 1 => \'one\',' . LF .
1148  ' 3 => \'three\',' . LF .
1149  ' 4 => \'four\',' . LF .
1150  ']';
1151  $this->assertSame($expected, ArrayUtility::arrayExport($array));
1152  }
1153 
1155  // Tests concerning flatten
1157 
1162  {
1163  return [
1164  'plain array' => [
1165  [
1166  'first' => 1,
1167  'second' => 2
1168  ],
1169  [
1170  'first' => 1,
1171  'second' => 2
1172  ]
1173  ],
1174  'plain array with faulty dots' => [
1175  [
1176  'first.' => 1,
1177  'second.' => 2
1178  ],
1179  [
1180  'first' => 1,
1181  'second' => 2
1182  ]
1183  ],
1184  'nested array of 2 levels' => [
1185  [
1186  'first.' => [
1187  'firstSub' => 1
1188  ],
1189  'second.' => [
1190  'secondSub' => 2
1191  ]
1192  ],
1193  [
1194  'first.firstSub' => 1,
1195  'second.secondSub' => 2
1196  ]
1197  ],
1198  'nested array of 2 levels with faulty dots' => [
1199  [
1200  'first.' => [
1201  'firstSub.' => 1
1202  ],
1203  'second.' => [
1204  'secondSub.' => 2
1205  ]
1206  ],
1207  [
1208  'first.firstSub' => 1,
1209  'second.secondSub' => 2
1210  ]
1211  ],
1212  'nested array of 3 levels' => [
1213  [
1214  'first.' => [
1215  'firstSub.' => [
1216  'firstSubSub' => 1
1217  ]
1218  ],
1219  'second.' => [
1220  'secondSub.' => [
1221  'secondSubSub' => 2
1222  ]
1223  ]
1224  ],
1225  [
1226  'first.firstSub.firstSubSub' => 1,
1227  'second.secondSub.secondSubSub' => 2
1228  ]
1229  ],
1230  'nested array of 3 levels with faulty dots' => [
1231  [
1232  'first.' => [
1233  'firstSub.' => [
1234  'firstSubSub.' => 1
1235  ]
1236  ],
1237  'second.' => [
1238  'secondSub.' => [
1239  'secondSubSub.' => 2
1240  ]
1241  ]
1242  ],
1243  [
1244  'first.firstSub.firstSubSub' => 1,
1245  'second.secondSub.secondSubSub' => 2
1246  ]
1247  ]
1248  ];
1249  }
1250 
1257  public function flattenCalculatesExpectedResult(array $array, array $expected)
1258  {
1259  $this->assertEquals($expected, ArrayUtility::flatten($array));
1260  }
1261 
1263  // Tests concerning intersectRecursive
1265 
1270  {
1271  $sameObject = new \stdClass();
1272  return [
1273  // array($source, $mask, $expected)
1274  'empty array is returned if source is empty array' => [
1275  [],
1276  [
1277  'foo' => 'bar',
1278  ],
1279  [],
1280  ],
1281  'empty array is returned if mask is empty' => [
1282  [
1283  'foo' => 'bar',
1284  ],
1285  [],
1286  [],
1287  ],
1288  'key is kept on first level if exists in mask' => [
1289  [
1290  'foo' => 42,
1291  ],
1292  [
1293  'foo' => 42,
1294  ],
1295  [
1296  'foo' => 42,
1297  ],
1298  ],
1299  'value of key in source is kept if mask has different value' => [
1300  [
1301  'foo' => 42,
1302  ],
1303  [
1304  'foo' => new \stdClass(),
1305  ],
1306  [
1307  'foo' => 42,
1308  ],
1309  ],
1310  'key is kept on first level if according mask value is NULL' => [
1311  [
1312  'foo' => 42,
1313  ],
1314  [
1315  'foo' => null,
1316  ],
1317  [
1318  'foo' => 42,
1319  ],
1320  ],
1321  'null in source value is kept' => [
1322  [
1323  'foo' => null,
1324  ],
1325  [
1326  'foo' => 'bar',
1327  ],
1328  [
1329  'foo' => null,
1330  ]
1331  ],
1332  'mask does not add new keys' => [
1333  [
1334  'foo' => 42,
1335  ],
1336  [
1337  'foo' => 23,
1338  'bar' => [
1339  4711
1340  ],
1341  ],
1342  [
1343  'foo' => 42,
1344  ],
1345  ],
1346  'mask does not overwrite simple values with arrays' => [
1347  [
1348  'foo' => 42,
1349  ],
1350  [
1351  'foo' => [
1352  'bar' => 23,
1353  ],
1354  ],
1355  [
1356  'foo' => 42,
1357  ],
1358  ],
1359  'key is kept on first level if according mask value is array' => [
1360  [
1361  'foo' => 42,
1362  ],
1363  [
1364  'foo' => [
1365  'bar' => 23
1366  ],
1367  ],
1368  [
1369  'foo' => 42,
1370  ],
1371  ],
1372  'full array is kept if value is array and mask value is simple type' => [
1373  [
1374  'foo' => [
1375  'bar' => 23
1376  ],
1377  ],
1378  [
1379  'foo' => 42,
1380  ],
1381  [
1382  'foo' => [
1383  'bar' => 23
1384  ],
1385  ],
1386  ],
1387  'key handling is type agnostic' => [
1388  [
1389  42 => 'foo',
1390  ],
1391  [
1392  '42' => 'bar',
1393  ],
1394  [
1395  42 => 'foo',
1396  ],
1397  ],
1398  'value is same if value is object' => [
1399  [
1400  'foo' => $sameObject,
1401  ],
1402  [
1403  'foo' => 'something',
1404  ],
1405  [
1406  'foo' => $sameObject,
1407  ],
1408  ],
1409  'mask does not add simple value to result if key does not exist in source' => [
1410  [
1411  'foo' => '42',
1412  ],
1413  [
1414  'foo' => '42',
1415  'bar' => 23
1416  ],
1417  [
1418  'foo' => '42',
1419  ],
1420  ],
1421  'array of source is kept if value of mask key exists but is no array' => [
1422  [
1423  'foo' => '42',
1424  'bar' => [
1425  'baz' => 23
1426  ],
1427  ],
1428  [
1429  'foo' => 'value is not significant',
1430  'bar' => null,
1431  ],
1432  [
1433  'foo' => '42',
1434  'bar' => [
1435  'baz' => 23
1436  ],
1437  ],
1438  ],
1439  'sub arrays are kept if mask has according sub array key and is similar array' => [
1440  [
1441  'first1' => 42,
1442  'first2' => [
1443  'second1' => 23,
1444  'second2' => 4711,
1445  ],
1446  ],
1447  [
1448  'first1' => 42,
1449  'first2' => [
1450  'second1' => 'exists but different',
1451  ],
1452  ],
1453  [
1454  'first1' => 42,
1455  'first2' => [
1456  'second1' => 23,
1457  ],
1458  ],
1459  ],
1460  ];
1461  }
1462 
1470  public function intersectRecursiveCalculatesExpectedResult(array $source, array $mask, array $expected)
1471  {
1472  $this->assertSame($expected, ArrayUtility::intersectRecursive($source, $mask));
1473  }
1474 
1476  // Tests concerning renumberKeysToAvoidLeapsIfKeysAreAllNumeric
1478 
1482  {
1483  return [
1484  'empty array is returned if source is empty array' => [
1485  [],
1486  []
1487  ],
1488  'returns self if array is already numerically keyed' => [
1489  [1, 2, 3],
1490  [1, 2, 3]
1491  ],
1492  'returns correctly if keys are numeric, but contains a leap' => [
1493  [0 => 'One', 1 => 'Two', 3 => 'Three'],
1494  [0 => 'One', 1 => 'Two', 2 => 'Three'],
1495  ],
1496  'returns correctly even though keys are strings but still numeric' => [
1497  ['0' => 'One', '1' => 'Two', '3' => 'Three'],
1498  [0 => 'One', 1 => 'Two', 2 => 'Three'],
1499  ],
1500  'returns correctly if just a single keys is not numeric' => [
1501  [0 => 'Zero', '1' => 'One', 'Two' => 'Two'],
1502  [0 => 'Zero', '1' => 'One', 'Two' => 'Two'],
1503  ],
1504  'return self with nested numerically keyed array' => [
1505  [
1506  'One',
1507  'Two',
1508  'Three',
1509  [
1510  'sub.One',
1511  'sub.Two',
1512  ]
1513  ],
1514  [
1515  'One',
1516  'Two',
1517  'Three',
1518  [
1519  'sub.One',
1520  'sub.Two',
1521  ]
1522  ]
1523  ],
1524  'returns correctly with nested numerically keyed array with leaps' => [
1525  [
1526  'One',
1527  'Two',
1528  'Three',
1529  [
1530  0 => 'sub.One',
1531  2 => 'sub.Two',
1532  ]
1533  ],
1534  [
1535  'One',
1536  'Two',
1537  'Three',
1538  [
1539  'sub.One',
1540  'sub.Two',
1541  ]
1542  ]
1543  ],
1544  'returns correctly with nested string-keyed array' => [
1545  [
1546  'One',
1547  'Two',
1548  'Three',
1549  [
1550  'one' => 'sub.One',
1551  'two' => 'sub.Two',
1552  ]
1553  ],
1554  [
1555  'One',
1556  'Two',
1557  'Three',
1558  [
1559  'one' => 'sub.One',
1560  'two' => 'sub.Two',
1561  ]
1562  ]
1563  ],
1564  'returns correctly with deeply nested arrays' => [
1565  [
1566  'One',
1567  'Two',
1568  [
1569  'one' => 1,
1570  'two' => 2,
1571  'three' => [
1572  2 => 'SubSubOne',
1573  5 => 'SubSubTwo',
1574  9 => [0, 1, 2],
1575  []
1576  ]
1577  ]
1578  ],
1579  [
1580  'One',
1581  'Two',
1582  [
1583  'one' => 1,
1584  'two' => 2,
1585  'three' => [
1586  'SubSubOne',
1587  'SubSubTwo',
1588  [0, 1, 2],
1589  []
1590  ]
1591  ]
1592  ]
1593  ]
1594  ];
1595  }
1596 
1603  public function renumberKeysToAvoidLeapsIfKeysAreAllNumericReturnsExpectedOrder(array $inputArray, array $expected)
1604  {
1605  $this->assertEquals($expected, ArrayUtility::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($inputArray));
1606  }
1607 
1612  {
1613  return [
1614  'Override array can reset string to array' => [
1615  [
1616  'first' => [
1617  'second' => 'foo',
1618  ],
1619  ],
1620  [
1621  'first' => [
1622  'second' => ['third' => 'bar'],
1623  ],
1624  ],
1625  true,
1626  true,
1627  true,
1628  [
1629  'first' => [
1630  'second' => ['third' => 'bar'],
1631  ],
1632  ],
1633  ],
1634  'Override array does not reset array to string (weird!)' => [
1635  [
1636  'first' => [],
1637  ],
1638  [
1639  'first' => 'foo',
1640  ],
1641  true,
1642  true,
1643  true,
1644  [
1645  'first' => [], // This is rather unexpected, naive expectation: first => 'foo'
1646  ],
1647  ],
1648  'Override array does override string with null' => [
1649  [
1650  'first' => 'foo',
1651  ],
1652  [
1653  'first' => null,
1654  ],
1655  true,
1656  true,
1657  true,
1658  [
1659  'first' => null,
1660  ],
1661  ],
1662  'Override array does override null with string' => [
1663  [
1664  'first' => null,
1665  ],
1666  [
1667  'first' => 'foo',
1668  ],
1669  true,
1670  true,
1671  true,
1672  [
1673  'first' => 'foo',
1674  ],
1675  ],
1676  'Override array does override null with empty string' => [
1677  [
1678  'first' => null,
1679  ],
1680  [
1681  'first' => '',
1682  ],
1683  true,
1684  true,
1685  true,
1686  [
1687  'first' => '',
1688  ],
1689  ],
1690  'Override array does not override string with NULL if requested' => [
1691  [
1692  'first' => 'foo',
1693  ],
1694  [
1695  'first' => null,
1696  ],
1697  true,
1698  false, // no include empty values
1699  true,
1700  [
1701  'first' => 'foo',
1702  ],
1703  ],
1704  'Override array does override null with null' => [
1705  [
1706  'first' => null,
1707  ],
1708  [
1709  'first' => null,
1710  ],
1711  true,
1712  true,
1713  true,
1714  [
1715  'first' => '',
1716  ],
1717  ],
1718  'Override array can __UNSET values' => [
1719  [
1720  'first' => [
1721  'second' => 'second',
1722  'third' => 'third',
1723  ],
1724  'fifth' => [],
1725  ],
1726  [
1727  'first' => [
1728  'second' => 'overrule',
1729  'third' => '__UNSET',
1730  'fourth' => 'overrile',
1731  ],
1732  'fifth' => '__UNSET',
1733  ],
1734  true,
1735  true,
1736  true,
1737  [
1738  'first' => [
1739  'second' => 'overrule',
1740  'fourth' => 'overrile',
1741  ],
1742  ],
1743  ],
1744  'Override can add keys' => [
1745  [
1746  'first' => 'foo',
1747  ],
1748  [
1749  'second' => 'bar',
1750  ],
1751  true,
1752  true,
1753  true,
1754  [
1755  'first' => 'foo',
1756  'second' => 'bar',
1757  ],
1758  ],
1759  'Override does not add key if __UNSET' => [
1760  [
1761  'first' => 'foo',
1762  ],
1763  [
1764  'second' => '__UNSET',
1765  ],
1766  true,
1767  true,
1768  true,
1769  [
1770  'first' => 'foo',
1771  ],
1772  ],
1773  'Override does not add key if not requested' => [
1774  [
1775  'first' => 'foo',
1776  ],
1777  [
1778  'second' => 'bar',
1779  ],
1780  false, // no add keys
1781  true,
1782  true,
1783  [
1784  'first' => 'foo',
1785  ],
1786  ],
1787  'Override does not add key if not requested with add include empty values' => [
1788  [
1789  'first' => 'foo',
1790  ],
1791  [
1792  'second' => 'bar',
1793  ],
1794  false, // no add keys
1795  false, // no include empty values
1796  true,
1797  [
1798  'first' => 'foo',
1799  ],
1800  ],
1801  'Override does not override string with empty string if requested' => [
1802  [
1803  'first' => 'foo',
1804  ],
1805  [
1806  'first' => '',
1807  ],
1808  true,
1809  false, // no include empty values
1810  true,
1811  [
1812  'first' => 'foo',
1813  ],
1814  ],
1815  'Override array does merge instead of __UNSET if requested (weird!)' => [
1816  [
1817  'first' => [
1818  'second' => 'second',
1819  'third' => 'third',
1820  ],
1821  'fifth' => [],
1822  ],
1823  [
1824  'first' => [
1825  'second' => 'overrule',
1826  'third' => '__UNSET',
1827  'fourth' => 'overrile',
1828  ],
1829  'fifth' => '__UNSET',
1830  ],
1831  true,
1832  true,
1833  false,
1834  [
1835  'first' => [
1836  'second' => 'overrule',
1837  'third' => '__UNSET', // overruled
1838  'fourth' => 'overrile',
1839  ],
1840  'fifth' => [], // not overruled with string here, naive expectation: 'fifth' => '__UNSET'
1841  ],
1842  ],
1843  ];
1844  }
1845 
1856  public function mergeRecursiveWithOverruleCalculatesExpectedResult($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature, $expected)
1857  {
1858  ArrayUtility::mergeRecursiveWithOverrule($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature);
1859  $this->assertEquals($expected, $input1);
1860  }
1861 
1863  // Tests concerning inArray
1865 
1872  public function inArrayChecksStringExistenceWithinArray($array, $item, $expected)
1873  {
1874  $this->assertEquals($expected, ArrayUtility::inArray($array, $item));
1875  }
1876 
1882  public function inArrayDataProvider()
1883  {
1884  return [
1885  'Empty array' => [[], 'search', false],
1886  'One item array no match' => [['one'], 'two', false],
1887  'One item array match' => [['one'], 'one', true],
1888  'Multiple items array no match' => [['one', 2, 'three', 4], 'four', false],
1889  'Multiple items array match' => [['one', 2, 'three', 4], 'three', true],
1890  'Integer search items can match string values' => [['0', '1', '2'], 1, true],
1891  'Search item is not casted to integer for a match' => [[4], '4a', false],
1892  'Empty item won\'t match - in contrast to the php-builtin ' => [[0, 1, 2], '', false]
1893  ];
1894  }
1895 
1897  // Tests concerning removeArrayEntryByValue
1899 
1903  {
1904  $inputArray = [
1905  '0' => 'test1',
1906  '1' => 'test2',
1907  '2' => 'test3',
1908  '3' => 'test2'
1909  ];
1910  $compareValue = 'test2';
1911  $expectedResult = [
1912  '0' => 'test1',
1913  '2' => 'test3'
1914  ];
1915  $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1916  $this->assertEquals($expectedResult, $actualResult);
1917  }
1918 
1923  {
1924  $inputArray = [
1925  '0' => 'foo',
1926  '1' => [
1927  '10' => 'bar'
1928  ],
1929  '2' => 'bar'
1930  ];
1931  $compareValue = 'bar';
1932  $expectedResult = [
1933  '0' => 'foo',
1934  '1' => []
1935  ];
1936  $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1937  $this->assertEquals($expectedResult, $actualResult);
1938  }
1939 
1944  {
1945  $inputArray = [
1946  '0' => 'foo',
1947  '1' => '',
1948  '2' => 'bar'
1949  ];
1950  $compareValue = '';
1951  $expectedResult = [
1952  '0' => 'foo',
1953  '2' => 'bar'
1954  ];
1955  $actualResult = ArrayUtility::removeArrayEntryByValue($inputArray, $compareValue);
1956  $this->assertEquals($expectedResult, $actualResult);
1957  }
1958 
1960  // Tests concerning keepItemsInArray
1962 
1969  public function keepItemsInArrayWorksWithOneArgument($search, $array, $expected)
1970  {
1971  $this->assertEquals($expected, ArrayUtility::keepItemsInArray($array, $search));
1972  }
1973 
1980  {
1981  $array = [
1982  'one' => 'one',
1983  'two' => 'two',
1984  'three' => 'three'
1985  ];
1986  return [
1987  'Empty argument will match "all" elements' => [null, $array, $array],
1988  'No match' => ['four', $array, []],
1989  'One match' => ['two', $array, ['two' => 'two']],
1990  'Multiple matches' => ['two,one', $array, ['one' => 'one', 'two' => 'two']],
1991  'Argument can be an array' => [['three'], $array, ['three' => 'three']]
1992  ];
1993  }
1994 
2003  {
2004  $array = [
2005  'aa' => ['first', 'second'],
2006  'bb' => ['third', 'fourth'],
2007  'cc' => ['fifth', 'sixth']
2008  ];
2009  $expected = ['bb' => ['third', 'fourth']];
2010  $keepItems = 'third';
2012  $array,
2013  $keepItems,
2014  function ($value) {
2015  return $value[0];
2016  }
2017  );
2018  $this->assertEquals($expected, $match);
2019  }
2020 
2022  // Tests concerning remapArrayKeys
2024 
2028  {
2029  $array = [
2030  'one' => 'one',
2031  'two' => 'two',
2032  'three' => 'three'
2033  ];
2034  $keyMapping = [
2035  'one' => '1',
2036  'two' => '2'
2037  ];
2038  $expected = [
2039  '1' => 'one',
2040  '2' => 'two',
2041  'three' => 'three'
2042  ];
2043  ArrayUtility::remapArrayKeys($array, $keyMapping);
2044  $this->assertEquals($expected, $array);
2045  }
2046 
2048  // Tests concerning arrayDiffAssocRecursive
2050 
2054  {
2055  $array1 = [
2056  'key1' => 'value1',
2057  'key2' => 'value2',
2058  'key3' => 'value3'
2059  ];
2060  $array2 = [
2061  'key1' => 'value1',
2062  'key3' => 'value3'
2063  ];
2064  $expectedResult = [
2065  'key2' => 'value2'
2066  ];
2067  $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2068  $this->assertEquals($expectedResult, $actualResult);
2069  }
2070 
2075  {
2076  $array1 = [
2077  'key1' => 'value1',
2078  'key2' => [
2079  'key21' => 'value21',
2080  'key22' => 'value22',
2081  'key23' => [
2082  'key231' => 'value231',
2083  'key232' => 'value232'
2084  ]
2085  ]
2086  ];
2087  $array2 = [
2088  'key1' => 'valueDoesNotMatter',
2089  'key2' => [
2090  'key21' => 'value21',
2091  'key23' => [
2092  'key231' => 'value231'
2093  ]
2094  ]
2095  ];
2096  $expectedResult = [
2097  'key2' => [
2098  'key22' => 'value22',
2099  'key23' => [
2100  'key232' => 'value232'
2101  ]
2102  ]
2103  ];
2104  $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2105  $this->assertEquals($expectedResult, $actualResult);
2106  }
2107 
2112  {
2113  $array1 = [
2114  'key1' => [
2115  'key11' => 'value11',
2116  'key12' => 'value12'
2117  ],
2118  'key2' => 'value2',
2119  'key3' => 'value3'
2120  ];
2121  $array2 = [
2122  'key1' => 'value1',
2123  'key2' => [
2124  'key21' => 'valueDoesNotMatter'
2125  ]
2126  ];
2127  $expectedResult = [
2128  'key3' => 'value3'
2129  ];
2130  $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2131  $this->assertEquals($expectedResult, $actualResult);
2132  }
2133 
2138  {
2139  $array1 = [
2140  'key1' => [
2141  'key11' => 'value11',
2142  'key12' => 'value12'
2143  ],
2144  'key2' => 'value2',
2145  'key3' => 'value3'
2146  ];
2147  $array2 = [
2148  'key1' => [
2149  'key11' => 'valueDoesNotMatter',
2150  'key12' => 'value12'
2151  ],
2152  'key2' => 'value2',
2153  'key3' => 'value3'
2154  ];
2155  $expectedResult = [];
2156  $actualResult = ArrayUtility::arrayDiffAssocRecursive($array1, $array2);
2157  $this->assertEquals($expectedResult, $actualResult);
2158  }
2159 
2161  // Tests concerning naturalKeySortRecursive
2163 
2168  {
2169  $testArray = [
2170  'bb' => 'bb',
2171  'ab' => 'ab',
2172  '123' => '123',
2173  'aaa' => 'aaa',
2174  'abc' => 'abc',
2175  '23' => '23',
2176  'ba' => 'ba',
2177  'bad' => 'bad',
2178  '2' => '2',
2179  'zap' => 'zap',
2180  '210' => '210'
2181  ];
2182  $expectedResult = [
2183  '2',
2184  '23',
2185  '123',
2186  '210',
2187  'aaa',
2188  'ab',
2189  'abc',
2190  'ba',
2191  'bad',
2192  'bb',
2193  'zap'
2194  ];
2196  $this->assertEquals($expectedResult, array_values($testArray));
2197  }
2198 
2203  {
2204  $testArray = [
2205  '2' => '2',
2206  'bb' => 'bb',
2207  'ab' => 'ab',
2208  '23' => '23',
2209  'aaa' => [
2210  'bb' => 'bb',
2211  'ab' => 'ab',
2212  '123' => '123',
2213  'aaa' => 'aaa',
2214  '2' => '2',
2215  'abc' => 'abc',
2216  'ba' => 'ba',
2217  '23' => '23',
2218  'bad' => [
2219  'bb' => 'bb',
2220  'ab' => 'ab',
2221  '123' => '123',
2222  'aaa' => 'aaa',
2223  'abc' => 'abc',
2224  '23' => '23',
2225  'ba' => 'ba',
2226  'bad' => 'bad',
2227  '2' => '2',
2228  'zap' => 'zap',
2229  '210' => '210'
2230  ],
2231  '210' => '210',
2232  'zap' => 'zap'
2233  ],
2234  'abc' => 'abc',
2235  'ba' => 'ba',
2236  '210' => '210',
2237  'bad' => 'bad',
2238  '123' => '123',
2239  'zap' => 'zap'
2240  ];
2241  $expectedResult = [
2242  '2',
2243  '23',
2244  '123',
2245  '210',
2246  'aaa',
2247  'ab',
2248  'abc',
2249  'ba',
2250  'bad',
2251  'bb',
2252  'zap'
2253  ];
2255  $this->assertEquals($expectedResult, array_values(array_keys($testArray['aaa']['bad'])));
2256  $this->assertEquals($expectedResult, array_values(array_keys($testArray['aaa'])));
2257  $this->assertEquals($expectedResult, array_values(array_keys($testArray)));
2258  }
2259 }
sortArraysByKeyCheckIfSortingIsCorrect(array $array, $key, $ascending, $expectedResult)
setValueByPathSetsCorrectValue(array $array, $path, $value, $expectedResult)
renumberKeysToAvoidLeapsIfKeysAreAllNumericReturnsExpectedOrder(array $inputArray, array $expected)
static filterByValueRecursive($needle='', array $haystack=[])
static setValueByPath(array $array, $path, $value, $delimiter='/')
static getValueByPath(array $array, $path, $delimiter='/')
inArrayChecksStringExistenceWithinArray($array, $item, $expected)
static intersectRecursive(array $source, array $mask=[])
intersectRecursiveCalculatesExpectedResult(array $source, array $mask, array $expected)
static flatten(array $array, $prefix='')
static remapArrayKeys(array &$array, array $mappingTable)
keepItemsInArrayWorksWithOneArgument($search, $array, $expected)
removeByPathRemovesCorrectPath(array $array, $path, $expectedResult)
getValueByPathGetsCorrectValue(array $array, $path, $expectedResult)
static arrayExport(array $array=[], $level=0)
static naturalKeySortRecursive(array &$array)
static inArray(array $in_array, $item)
static removeByPath(array $array, $path, $delimiter='/')
static sortByKeyRecursive(array $array)
static renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array=[], $level=0)
mergeRecursiveWithOverruleCalculatesExpectedResult($input1, $input2, $addKeys, $includeEmptyValues, $enableUnsetFeature, $expected)
static isValidPath(array $array, $path, $delimiter='/')
static removeArrayEntryByValue(array $array, $cmpValue)
static keepItemsInArray(array $array, $keepItems, $getValueFunc=null)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
flattenCalculatesExpectedResult(array $array, array $expected)
static sortArraysByKey(array $arrays, $key, $ascending=true)
static arrayDiffAssocRecursive(array $array1, array $array2)
filterByValueRecursiveCorrectlyFiltersArray($needle, $haystack, $expectedResult)