diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php new file mode 100644 index 00000000..1bde4bad --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php @@ -0,0 +1,402 @@ + [ + 'name' => 'John', + 'age' => 25 + // missing 'email' key + ], + 'item2' => [ + 'name' => 'Jane', + 'age' => 30, + 'email' => 'jane@example.com' + ], + 'item3' => [ + 'name' => 'John', // same value as item1 + 'age' => 35, + 'email' => 'john2@example.com' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'John', 'email'); + + $this->assertCount(1, $result); + $this->assertEquals($array['item1'], $result[0]['content']); + $this->assertEquals('item1', $result[0]['path']); + $this->assertEquals(['email'], $result[0]['missing_key']); + } + + /** + * Test finding missing single key when searching by specific key-value pair + */ + public function testFindMissingSingleKeyWithKeyValueSearch() + { + $array = [ + 'user1' => [ + 'id' => 1, + 'name' => 'Alice' + // missing 'status' key + ], + 'user2' => [ + 'id' => 2, + 'name' => 'Bob', + 'status' => 'active' + ], + 'user3' => [ + 'id' => 1, // same id as user1 + 'name' => 'Charlie', + 'status' => 'inactive' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 1, 'status', 'id'); + + $this->assertCount(1, $result); + $this->assertEquals($array['user1'], $result[0]['content']); + $this->assertEquals('user1', $result[0]['path']); + $this->assertEquals(['status'], $result[0]['missing_key']); + } + + /** + * Test finding missing multiple keys + */ + public function testFindMissingMultipleKeys() + { + $array = [ + 'record1' => [ + 'name' => 'Test', + 'value' => 100 + // missing both 'date' and 'status' keys + ], + 'record2' => [ + 'name' => 'Test', + 'value' => 200, + 'date' => '2023-01-01' + // missing 'status' key + ], + 'record3' => [ + 'name' => 'Test', + 'value' => 300, + 'date' => '2023-01-02', + 'status' => 'complete' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Test', ['date', 'status']); + + $this->assertCount(2, $result); + + // First result should be record1 missing both keys + $this->assertEquals($array['record1'], $result[0]['content']); + $this->assertEquals('record1', $result[0]['path']); + $this->assertContains('date', $result[0]['missing_key']); + $this->assertContains('status', $result[0]['missing_key']); + $this->assertCount(2, $result[0]['missing_key']); + + // Second result should be record2 missing status key + $this->assertEquals($array['record2'], $result[1]['content']); + $this->assertEquals('record2', $result[1]['path']); + $this->assertEquals(['status'], $result[1]['missing_key']); + } + + /** + * Test with nested arrays + */ + public function testFindMissingKeyInNestedArrays() + { + $array = [ + 'section1' => [ + 'items' => [ + 'item1' => [ + 'name' => 'Product A', + 'price' => 99.99 + // missing 'category' key + ], + 'item2' => [ + 'name' => 'Product B', + 'price' => 149.99, + 'category' => 'electronics' + ] + ] + ], + 'section2' => [ + 'data' => [ + 'name' => 'Product A', // same name as nested item + 'category' => 'books' + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Product A', 'category'); + + $this->assertCount(1, $result); + $this->assertEquals($array['section1']['items']['item1'], $result[0]['content']); + $this->assertEquals('section1:items:item1', $result[0]['path']); + $this->assertEquals(['category'], $result[0]['missing_key']); + } + + /** + * Test when no arrays are missing the required key + */ + public function testNoMissingKeys() + { + $array = [ + 'item1' => [ + 'name' => 'John', + 'email' => 'john@example.com' + ], + 'item2' => [ + 'name' => 'Jane', + 'email' => 'jane@example.com' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'John', 'email'); + + $this->assertEmpty($result); + } + + /** + * Test when search value is not found in any array + */ + public function testSearchValueNotFound() + { + $array = [ + 'item1' => [ + 'name' => 'John', + 'age' => 25 + ], + 'item2' => [ + 'name' => 'Jane', + 'age' => 30 + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Bob', 'email'); + + $this->assertEmpty($result); + } + + /** + * Test with different data types for search value + */ + public function testDifferentSearchValueTypes() + { + $array = [ + 'item1' => [ + 'active' => true, + 'count' => 5 + // missing 'label' key + ], + 'item2' => [ + 'active' => false, + 'count' => 10, + 'label' => 'test' + ], + 'item3' => [ + 'active' => true, // same boolean as item1 + 'count' => 15, + 'label' => 'another' + ] + ]; + + // Test with boolean + $result = ArrayHandler::findArraysMissingKey($array, true, 'label', 'active'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + + // Test with integer + $result = ArrayHandler::findArraysMissingKey($array, 5, 'label', 'count'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + } + + /** + * Test with empty array + */ + public function testEmptyArray() + { + $array = []; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'key'); + + $this->assertEmpty($result); + } + + /** + * Test with array containing non-array values + */ + public function testMixedArrayTypes() + { + $array = [ + 'string_value' => 'hello', + 'numeric_value' => 123, + 'array_value' => [ + 'name' => 'test', + // missing 'type' key + ], + 'another_array' => [ + 'name' => 'test', + 'type' => 'example' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'type'); + + $this->assertCount(1, $result); + $this->assertEquals($array['array_value'], $result[0]['content']); + $this->assertEquals('array_value', $result[0]['path']); + $this->assertEquals(['type'], $result[0]['missing_key']); + } + + /** + * Test path building with deeper nesting + */ + public function testDeepNestingPathBuilding() + { + $array = [ + 'level1' => [ + 'level2' => [ + 'level3' => [ + 'items' => [ + 'target_item' => [ + 'name' => 'deep_test', + // missing 'required_field' + ] + ] + ] + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'deep_test', 'required_field'); + + $this->assertCount(1, $result); + $this->assertEquals('level1:level2:level3:items:target_item', $result[0]['path']); + } + + /** + * Test with custom path separator + */ + public function testCustomPathSeparator() + { + $array = [ + 'level1' => [ + 'level2' => [ + 'item' => [ + 'name' => 'test', + // missing 'type' key + ] + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'type', null, '/'); + + $this->assertCount(1, $result); + $this->assertEquals('level1/level2/item', $result[0]['path']); + } + + /** + * Test default path separator behavior + */ + public function testDefaultPathSeparator() + { + $array = [ + 'parent' => [ + 'child' => [ + 'name' => 'test', + // missing 'value' key + ] + ] + ]; + + // Using default separator (should be ':') + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'value'); + + $this->assertCount(1, $result); + $this->assertEquals('parent:child', $result[0]['path']); + } + + /** + * Test different path separators don't affect search logic + */ + public function testPathSeparatorDoesNotAffectSearchLogic() + { + $array = [ + 'section' => [ + 'data' => [ + 'id' => 123, + 'name' => 'item' + // missing 'status' + ] + ] + ]; + + // Test with different separators - results should be identical except for path + $result1 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', ':'); + $result2 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '.'); + $result3 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '/'); + + $this->assertCount(1, $result1); + $this->assertCount(1, $result2); + $this->assertCount(1, $result3); + + // Content and missing_key should be the same + $this->assertEquals($result1[0]['content'], $result2[0]['content']); + $this->assertEquals($result1[0]['content'], $result3[0]['content']); + $this->assertEquals($result1[0]['missing_key'], $result2[0]['missing_key']); + $this->assertEquals($result1[0]['missing_key'], $result3[0]['missing_key']); + + // Paths should be different based on separator + $this->assertEquals('section:data', $result1[0]['path']); + $this->assertEquals('section.data', $result2[0]['path']); + $this->assertEquals('section/data', $result3[0]['path']); + } + + /** + * test type checking + */ + public function testStrictTypeChecking() + { + $array = [ + 'item1' => [ + 'id' => '123', // string + 'name' => 'test' + // missing 'status' + ], + 'item2' => [ + 'id' => 123, // integer + 'name' => 'test2', + 'status' => 'active' + ] + ]; + + // Search for integer 123 - should only match item2 + $result = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id'); + $this->assertEmpty($result); // item2 has the status key + + // Search for string '123' - should only match item1 + $result = ArrayHandler::findArraysMissingKey($array, '123', 'status', 'id'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php new file mode 100644 index 00000000..9560fadc --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php @@ -0,0 +1,333 @@ + 'value1', + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4', + 'zebra' => 'value1' + ]; + + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test descending sort with reverse=true + */ + public function testKsortArrayDescending(): void + { + $input = [ + 'zebra' => 'value1', + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'zebra' => 'value1', + 'cherry' => 'value4', + 'banana' => 'value3', + 'apple' => 'value2' + ]; + + $result = ArrayHandler::ksortArray($input, false, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test case-insensitive ascending sort + */ + public function testKsortArrayCaseInsensitiveAscending(): void + { + $input = [ + 'Zebra' => 'value1', + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4', + 'Zebra' => 'value1' + ]; + + $result = ArrayHandler::ksortArray($input, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test case-insensitive descending sort + */ + public function testKsortArrayCaseInsensitiveDescending(): void + { + $input = [ + 'Zebra' => 'value1', + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'Zebra' => 'value1', + 'cherry' => 'value4', + 'Banana' => 'value3', + 'apple' => 'value2' + ]; + + $result = ArrayHandler::ksortArray($input, true, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test with mixed case keys to verify case sensitivity behavior + */ + public function testKsortArrayCaseSensitivityComparison(): void + { + $input = [ + 'B' => 'value1', + 'a' => 'value2', + 'C' => 'value3', + 'b' => 'value4' + ]; + + // Case-sensitive sort (uppercase comes before lowercase in ASCII) + $expectedCaseSensitive = [ + 'B' => 'value1', + 'C' => 'value3', + 'a' => 'value2', + 'b' => 'value4' + ]; + + // Case-insensitive sort + $expectedCaseInsensitive = [ + 'a' => 'value2', + 'B' => 'value1', + 'b' => 'value4', + 'C' => 'value3' + ]; + + $resultCaseSensitive = ArrayHandler::ksortArray($input, false); + $resultCaseInsensitive = ArrayHandler::ksortArray($input, true); + + $this->assertEquals($expectedCaseSensitive, $resultCaseSensitive); + $this->assertEquals($expectedCaseInsensitive, $resultCaseInsensitive); + } + + /** + * Test with numeric string keys + */ + public function testKsortArrayNumericStringKeys(): void + { + $input = [ + '10' => 'value1', + '2' => 'value2', + '1' => 'value3', + '20' => 'value4' + ]; + + // String comparison, not numeric + $expected = [ + '1' => 'value3', + '10' => 'value1', + '2' => 'value2', + '20' => 'value4' + ]; + + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($expected, $result); + } + + /** + * Test with special characters in keys + */ + public function testKsortArraySpecialCharacters(): void + { + $input = [ + 'key_with_underscore' => 'value1', + 'key-with-dash' => 'value2', + 'key.with.dot' => 'value3', + 'key with space' => 'value4', + 'keyWithCamelCase' => 'value5' + ]; + + $result = ArrayHandler::ksortArray($input); + + // Verify it doesn't throw an error and maintains all keys + $this->assertCount(5, $result); + $this->assertArrayHasKey('key_with_underscore', $result); + $this->assertArrayHasKey('key-with-dash', $result); + $this->assertArrayHasKey('key.with.dot', $result); + $this->assertArrayHasKey('key with space', $result); + $this->assertArrayHasKey('keyWithCamelCase', $result); + } + + /** + * Test with empty array + */ + public function testKsortArrayEmpty(): void + { + $input = []; + $result = ArrayHandler::ksortArray($input); + $this->assertEquals([], $result); + $this->assertIsArray($result); + } + + /** + * Test with single element array + */ + public function testKsortArraySingleElement(): void + { + $input = ['onlykey' => 'onlyvalue']; + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($input, $result); + } + + /** + * Test that original array is not modified (function returns new array) + */ + public function testKsortArrayDoesNotModifyOriginal(): void + { + $original = [ + 'zebra' => 'value1', + 'apple' => 'value2', + 'banana' => 'value3' + ]; + + $originalCopy = $original; // Keep a copy for comparison + $result = ArrayHandler::ksortArray($original); + + // Original array should remain unchanged + $this->assertEquals($originalCopy, $original); + $this->assertNotEquals(array_keys($original), array_keys($result)); + } + + /** + * Test with complex mixed data types as values + */ + public function testKsortArrayMixedValueTypes(): void + { + $input = [ + 'string_key' => 'string_value', + 'array_key' => ['nested', 'array'], + 'int_key' => 42, + 'bool_key' => true, + 'null_key' => null + ]; + + $result = ArrayHandler::ksortArray($input); + + // Check that all keys are preserved and sorted + $expectedKeys = ['array_key', 'bool_key', 'int_key', 'null_key', 'string_key']; + $this->assertEquals($expectedKeys, array_keys($result)); + + // Check that values are preserved correctly + $this->assertEquals('string_value', $result['string_key']); + $this->assertEquals(['nested', 'array'], $result['array_key']); + $this->assertEquals(42, $result['int_key']); + $this->assertTrue($result['bool_key']); + $this->assertNull($result['null_key']); + } + + /** + * Test all parameter combinations + */ + public function testKsortArrayAllParameterCombinations(): void + { + $input = [ + 'Delta' => 'value1', + 'alpha' => 'value2', + 'Charlie' => 'value3', + 'bravo' => 'value4' + ]; + + // Test all 4 combinations + $result1 = ArrayHandler::ksortArray($input, false, false); // default + $result2 = ArrayHandler::ksortArray($input, false, true); // reverse only + $result3 = ArrayHandler::ksortArray($input, true, false); // lowercase only + $result4 = ArrayHandler::ksortArray($input, true, true); // both + + // Each should produce different ordering + $this->assertNotEquals(array_keys($result1), array_keys($result2)); + $this->assertNotEquals(array_keys($result1), array_keys($result3)); + $this->assertNotEquals(array_keys($result1), array_keys($result4)); + $this->assertNotEquals(array_keys($result2), array_keys($result3)); + $this->assertNotEquals(array_keys($result2), array_keys($result4)); + $this->assertNotEquals(array_keys($result3), array_keys($result4)); + + // But all should have same keys and values, just different order + $this->assertEqualsCanonicalizing(array_values($input), array_values($result1)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result2)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result3)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result4)); + } + + /** + * Data provider for comprehensive testing + */ + public function sortingParametersProvider(): array + { + return [ + 'default' => [false, false], + 'reverse' => [false, true], + 'lowercase' => [true, false], + 'lowercase_reverse' => [true, true], + ]; + } + + /** + * Test that function works with all parameter combinations using data provider + * + * @dataProvider sortingParametersProvider + */ + public function testKsortArrayWithDataProvider(bool $lowerCase, bool $reverse): void + { + $input = [ + 'Zebra' => 'animal1', + 'apple' => 'fruit1', + 'Banana' => 'fruit2', + 'cat' => 'animal2' + ]; + + $result = ArrayHandler::ksortArray($input, $lowerCase, $reverse); + + // Basic assertions that apply to all combinations + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('Zebra', $result); + $this->assertArrayHasKey('apple', $result); + $this->assertArrayHasKey('Banana', $result); + $this->assertArrayHasKey('cat', $result); + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php new file mode 100644 index 00000000..b8055986 --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php @@ -0,0 +1,383 @@ +testData = [ + 'item1' => [ + 'name' => 'John', + 'age' => 25, + 'status' => 'active', + 'score' => 85.5 + ], + 'item2' => [ + 'name' => 'jane', + 'age' => 30, + 'status' => 'inactive', + 'score' => 92.0 + ], + 'item3' => [ + 'name' => 'Bob', + 'age' => 25, + 'status' => 'active', + 'score' => 78.3 + ], + 'item4' => [ + 'name' => 'Alice', + 'age' => 35, + 'status' => 'pending', + 'score' => 88.7 + ] + ]; + + $this->nestedTestData = [ + 'level1_a' => [ + 'name' => 'Level1A', + 'type' => 'parent', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'type' => 'child', + 'active' => true + ], + 'child2' => [ + 'name' => 'Child2', + 'type' => 'child', + 'active' => false + ] + ] + ], + 'level1_b' => [ + 'name' => 'Level1B', + 'type' => 'parent', + 'children' => [ + 'child3' => [ + 'name' => 'Child3', + 'type' => 'child', + 'active' => true, + 'nested' => [ + 'deep1' => [ + 'name' => 'Deep1', + 'type' => 'deep', + 'active' => true + ] + ] + ] + ] + ], + 'item5' => [ + 'name' => 'Direct', + 'type' => 'child', + 'active' => false + ] + ]; + } + + public function testEmptyArrayReturnsEmpty(): void + { + $result = ArrayHandler::selectArrayFromOption([], 'name', 'John'); + $this->assertEmpty($result); + } + + public function testBasicStringSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'John'); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertEquals('John', $result['item1']['name']); + } + + public function testBasicIntegerSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'age', 25); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testBasicFloatSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'score', 85.5); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertEquals(85.5, $result['item1']['score']); + } + + public function testBasicBooleanSearch(): void + { + $data = [ + 'item1' => ['enabled' => true, 'name' => 'Test1'], + 'item2' => ['enabled' => false, 'name' => 'Test2'], + 'item3' => ['enabled' => true, 'name' => 'Test3'] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'enabled', true); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testStrictComparison(): void + { + $data = [ + 'item1' => ['value' => '25', 'name' => 'String25'], + 'item2' => ['value' => 25, 'name' => 'Int25'], + 'item3' => ['value' => 25.0, 'name' => 'Float25'] + ]; + + // Non-strict should match all + $nonStrictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, false); + $this->assertCount(3, $nonStrictResult); + + // Strict should only match exact type + $strictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, true); + $this->assertCount(1, $strictResult); + $this->assertArrayHasKey('item2', $strictResult); + } + + public function testCaseInsensitiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, true); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item2', $result); + $this->assertEquals('jane', $result['item2']['name']); + } + + public function testCaseSensitiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, false); + + $this->assertEmpty($result); + } + + public function testRecursiveSearchWithFlatResult(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + ':*' + ); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('level1_a:*children:*child1', $result); + $this->assertArrayHasKey('level1_a:*children:*child2', $result); + $this->assertArrayHasKey('level1_b:*children:*child3', $result); + $this->assertArrayHasKey('item5', $result); + } + + public function testRecursiveSearchWithNestedResult(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + false + ); + + $this->assertCount(3, $result); + $this->assertArrayHasKey('level1_a', $result); + $this->assertArrayHasKey('level1_b', $result); + $this->assertArrayHasKey('item5', $result); + + // Check nested structure is preserved + $this->assertArrayHasKey('children', $result['level1_a']); + $this->assertArrayHasKey('child1', $result['level1_a']['children']); + $this->assertArrayHasKey('child2', $result['level1_a']['children']); + } + + public function testRecursiveSearchDeepNesting(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'deep', + false, + false, + true, + true, + ':*' + ); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('level1_b:*children:*child3:*nested:*deep1', $result); + $this->assertEquals('Deep1', $result['level1_b:*children:*child3:*nested:*deep1']['name']); + } + + public function testCustomFlatSeparator(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + '|' + ); + + $this->assertArrayHasKey('level1_a|children|child1', $result); + $this->assertArrayHasKey('level1_a|children|child2', $result); + $this->assertArrayHasKey('level1_b|children|child3', $result); + } + + public function testNonRecursiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + false + ); + + // Should only find direct matches, not nested ones + $this->assertCount(1, $result); + $this->assertArrayHasKey('item5', $result); + } + + public function testNoMatchesFound(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'NonExistent'); + + $this->assertEmpty($result); + } + + public function testMissingLookupKey(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'nonexistent_key', 'value'); + + $this->assertEmpty($result); + } + + public function testCombinedStrictAndCaseInsensitive(): void + { + $data = [ + 'item1' => ['name' => 'Test', 'id' => '123'], + 'item2' => ['name' => 'test', 'id' => 123], + 'item3' => ['name' => 'TEST', 'id' => '123'] + ]; + + // Case insensitive but strict type matching + $result = ArrayHandler::selectArrayFromOption($data, 'id', '123', true, true); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testBooleanWithCaseInsensitive(): void + { + $data = [ + 'item1' => ['active' => true, 'name' => 'Test1'], + 'item2' => ['active' => false, 'name' => 'Test2'] + ]; + + // Case insensitive flag should not affect boolean comparison + $result = ArrayHandler::selectArrayFromOption($data, 'active', true, false, true); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + } + + public function testArrayWithNumericKeys(): void + { + $data = [ + 0 => ['name' => 'First', 'type' => 'test'], + 1 => ['name' => 'Second', 'type' => 'test'], + 2 => ['name' => 'Third', 'type' => 'other'] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'type', 'test'); + + $this->assertCount(2, $result); + $this->assertArrayHasKey(0, $result); + $this->assertArrayHasKey(1, $result); + } + + public function testRecursiveWithMixedKeyTypes(): void + { + $data = [ + 'string_key' => [ + 'name' => 'Parent', + 'type' => 'parent', + 0 => [ + 'name' => 'Child0', + 'type' => 'child' + ], + 'child_key' => [ + 'name' => 'ChildKey', + 'type' => 'child' + ] + ] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'type', 'child', false, false, true, true, ':*'); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('string_key:*0', $result); + $this->assertArrayHasKey('string_key:*child_key', $result); + } + + public function testAllParametersCombined(): void + { + $data = [ + 'parent1' => [ + 'name' => 'Parent1', + 'status' => 'ACTIVE', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'status' => 'active' + ] + ] + ] + ]; + + $result = ArrayHandler::selectArrayFromOption( + $data, + 'status', + 'active', + false, // not strict + true, // case insensitive + true, // recursive + true, // flat result + '|' // custom separator + ); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('parent1', $result); + $this->assertArrayHasKey('parent1|children|child1', $result); + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php new file mode 100644 index 00000000..3572c115 --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php @@ -0,0 +1,328 @@ +assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test basic descending sort without maintaining keys + */ + public function testBasicDescendingSort() + { + $input = [3, 1, 4, 1, 5, 9]; + $expected = [9, 5, 4, 3, 1, 1]; + + $result = ArrayHandler::sortArray($input, false, true); + + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test ascending sort with key maintenance + */ + public function testAscendingSortWithKeyMaintenance() + { + $input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5]; + $expected = ['a' => 1, 'b' => 1, 'c' => 3, 'd' => 4, 'e' => 5]; + + $result = ArrayHandler::sortArray($input, false, false, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test descending sort with key maintenance + */ + public function testDescendingSortWithKeyMaintenance() + { + $input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5]; + $expected = ['e' => 5, 'd' => 4, 'c' => 3, 'a' => 1, 'b' => 1]; + + $result = ArrayHandler::sortArray($input, false, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion + */ + public function testStringLowerCaseSort() + { + $input = ['Banana', 'apple', 'Cherry', 'date']; + $expected = ['apple', 'Banana', 'Cherry', 'date']; + + $result = ArrayHandler::sortArray($input, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion in reverse + */ + public function testStringLowerCaseSortReverse() + { + $input = ['Banana', 'apple', 'Cherry', 'date']; + $expected = ['date', 'Cherry', 'Banana', 'apple']; + + $result = ArrayHandler::sortArray($input, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion and key maintenance + */ + public function testStringLowerCaseSortWithKeys() + { + $input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date']; + $expected = ['a' => 'apple', 'b' => 'Banana', 'c' => 'Cherry', 'd' => 'date']; + + $result = ArrayHandler::sortArray($input, true, false, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion, reverse, and key maintenance + */ + public function testStringLowerCaseSortReverseWithKeys() + { + $input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date']; + $expected = ['d' => 'date', 'c' => 'Cherry', 'b' => 'Banana', 'a' => 'apple']; + + $result = ArrayHandler::sortArray($input, true, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test numeric string sorting with SORT_NUMERIC flag + */ + public function testNumericStringSorting() + { + $input = ['10', '2', '1', '20']; + $expected = ['1', '2', '10', '20']; + + $result = ArrayHandler::sortArray($input, false, false, false, SORT_NUMERIC); + + $this->assertEquals($expected, $result); + } + + /** + * Test natural string sorting with SORT_NATURAL flag + */ + public function testNaturalStringSorting() + { + $input = ['img1.png', 'img10.png', 'img2.png', 'img20.png']; + $expected = ['img1.png', 'img2.png', 'img10.png', 'img20.png']; + + $result = ArrayHandler::sortArray($input, false, false, false, SORT_NATURAL); + + $this->assertEquals($expected, $result); + } + + /** + * Test with empty array + */ + public function testEmptyArray() + { + $input = []; + $expected = []; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with single element array + */ + public function testSingleElementArray() + { + $input = [42]; + $expected = [42]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with array containing null values + */ + public function testArrayWithNullValues() + { + $input = [3, null, 1, null, 2]; + $expected = [null, null, 1, 2, 3]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with mixed data types + */ + public function testMixedDataTypes() + { + $input = [3, '1', 4.5, '2', 1]; + + $result = ArrayHandler::sortArray($input); + + // Should sort according to PHP's natural comparison rules + $this->assertIsArray($result); + $this->assertCount(5, $result); + } + + /** + * Test that original array is not modified (immutability) + */ + public function testOriginalArrayNotModified() + { + $original = [3, 1, 4, 1, 5, 9]; + $input = $original; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($original, $input); + $this->assertNotEquals($input, $result); + } + + /** + * Test case sensitivity without lowercase flag + */ + public function testCaseSensitivityWithoutLowercase() + { + $input = ['Banana', 'apple', 'Cherry']; + + $result = ArrayHandler::sortArray($input); + + // Capital letters should come before lowercase in ASCII sort + $this->assertEquals('Banana', $result[0]); + $this->assertEquals('Cherry', $result[1]); + $this->assertEquals('apple', $result[2]); + } + + /** + * Test all parameters combination + */ + public function testAllParametersCombination() + { + $input = ['z' => 'Zebra', 'a' => 'apple', 'b' => 'Banana']; + + $result = ArrayHandler::sortArray($input, true, true, true, SORT_REGULAR); + + // Should be sorted by lowercase, reversed, with keys maintained + $keys = array_keys($result); + $values = array_values($result); + + $this->assertEquals(['z', 'b', 'a'], $keys); + $this->assertEquals(['Zebra', 'Banana', 'apple'], $values); + } + + /** + * Test floating point numbers + */ + public function testFloatingPointNumbers() + { + $input = [3.14, 2.71, 1.41, 1.73]; + $expected = [1.41, 1.73, 2.71, 3.14]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with duplicate values and key maintenance + */ + public function testDuplicateValuesWithKeyMaintenance() + { + $input = ['first' => 1, 'second' => 2, 'third' => 1, 'fourth' => 2]; + + $result = ArrayHandler::sortArray($input, false, false, true); + + $this->assertCount(4, $result); + $this->assertEquals([1, 1, 2, 2], array_values($result)); + // Keys should be preserved + $this->assertArrayHasKey('first', $result); + $this->assertArrayHasKey('second', $result); + $this->assertArrayHasKey('third', $result); + $this->assertArrayHasKey('fourth', $result); + } + + /** + * Data provider for comprehensive parameter testing + */ + public function sortParameterProvider(): array + { + return [ + 'basic_ascending' => [ + [3, 1, 4, 2], + false, false, false, SORT_REGULAR, + [1, 2, 3, 4] + ], + 'basic_descending' => [ + [3, 1, 4, 2], + false, true, false, SORT_REGULAR, + [4, 3, 2, 1] + ], + 'lowercase_ascending' => [ + ['Banana', 'apple', 'Cherry'], + true, false, false, SORT_REGULAR, + ['apple', 'Banana', 'Cherry'] + ], + 'lowercase_descending' => [ + ['Banana', 'apple', 'Cherry'], + true, true, false, SORT_REGULAR, + ['Cherry', 'Banana', 'apple'] + ] + ]; + } + + /** + * Test various parameter combinations using data provider + * + * @dataProvider sortParameterProvider + */ + public function testSortParameterCombinations( + array $input, + bool $lowercase, + bool $reverse, + bool $maintainKeys, + int $params, + array $expected + ) { + $result = ArrayHandler::sortArray($input, $lowercase, $reverse, $maintainKeys, $params); + + if (!$maintainKeys) { + $this->assertEquals($expected, $result); + } else { + $this->assertEquals($expected, array_values($result)); + } + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php index e11aab8e..8843393c 100644 --- a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace tests; -use Exception; use PHPUnit\Framework\TestCase; /** @@ -25,7 +24,11 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'same' => 'same', 3 => 'foobar', 'foobar' => 4, + 'barbaz' => 5, + 'zapzap' => '6', + 'zipzip' => 7, 'true' => true, + 'more' => 'other', ], 'd', 4, @@ -37,7 +40,10 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'sub' => [ 'nested' => 'bar', 'same' => 'same', - 'more' => 'test' + 'more' => 'test', + 'barbaz' => '5', + 'zapzap' => '6', + 'zipzip' => 7, ] ] ]; @@ -184,7 +190,7 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 0: array $input, 1: $key, 2: $value, - 3: bool $flag, + 3: bool $strict, 4: bool $expected */ return [ @@ -286,6 +292,91 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 3 => true, 4 => false, ], + // array tyep search + 'array type, both exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['other', 'test'], + 3 => false, + 4 => true, + ], + 'array type, one exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['other', 'not'], + 3 => false, + 4 => true, + ], + 'array type, none exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['never', 'not'], + 3 => false, + 4 => false, + ], + 'array type, both exist, not strict, int and string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, '5'], + 3 => false, + 4 => true, + ], + 'array type, both exist, not strict, both string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => ['5', '5'], + 3 => false, + 4 => true, + ], + 'array type, both exist, not strict, int and int' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, 5], + 3 => false, + 4 => true, + ], + 'array type, both exist, strict, int and string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, '5'], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, both string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => ['5', '5'], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, int and int' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, 5], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, int and int to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => [6, 6], + 3 => true, + 4 => false, + ], + 'array type, both exist, strict, string and string to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => ['6', '6'], + 3 => true, + 4 => true, + ], + 'array type, both exist, not strict, int and int to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => [6, 6], + 3 => false, + 4 => true, + ], ]; } @@ -842,13 +933,13 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * @dataProvider arraySearchRecursiveAllProvider * @testdox arraySearchRecursiveAll $needle (key $key_search_for) in $input and will be $expected (old: $flag) [$_dataName] * - * @param string|null $needle + * @param string|int|null $needle * @param array $input - * @param string|null $key_search_for + * @param string|int|null $key_search_for * @param bool $flag * @return void */ - public function testArraySearchRecursiveAll($needle, array $input, ?string $key_search_for, bool $flag, array $expected): void + public function testArraySearchRecursiveAll(string|int|null $needle, array $input, string|int|null $key_search_for, bool $flag, array $expected): void { $this->assertEquals( $expected, @@ -865,15 +956,16 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * * @param array $input * @param string|int $key - * @param string|int|bool $value + * @param string|int|bool|array $value + * @param bool $strict * @param bool $expected * @return void */ - public function testArraySearchSimple(array $input, $key, $value, bool $flag, bool $expected): void + public function testArraySearchSimple(array $input, string|int $key, string|int|bool|array $value, bool $strict, bool $expected): void { $this->assertEquals( $expected, - \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $flag) + \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $strict) ); } @@ -1394,8 +1486,896 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase array $expected ): void { $this->assertEquals( - \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix), - $expected + $expected, + \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix) + ); + } + + /** + * sort + * + * @return array + */ + public function providerSortArray(): array + { + $unsorted = [9, 5, 'A', 4, 'B', 6, 'c', 'C', 'a']; + // for lower case the initial order of the elmenet is important: + // A, a => A, a + // d, D => d, D + $unsorted_keys = ['A' => 9, 'B' => 5, 'C' => 'A', 'D' => 4, 'E' => 'B', 'F' => 6, 'G' => 'c', 'H' => 'C', 'I' => 'a']; + return [ + // sort default + 'sort default' => [ + $unsorted, + null, + null, + null, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort param set + 'sort param set' => [ + $unsorted, + false, + false, + false, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort lower case + 'sort lower case' => [ + $unsorted, + true, + false, + false, + [4, 5, 6, 9, 'A', 'a', 'B', 'c', 'C'], + ], + // sort reverse + 'sort reverse' => [ + $unsorted, + false, + true, + false, + ['c', 'a', 'C', 'B', 'A', 9, 6, 5, 4], + ], + // sort lower case + reverse + 'sort lower case + reverse' => [ + $unsorted, + true, + true, + false, + ['c', 'C', 'B', 'A', 'a', 9, 6, 5, 4], + ], + // keys, do not maintain, default + 'keys, do not maintain, default' => [ + $unsorted_keys, + false, + false, + false, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort maintain keys + 'sort maintain keys' => [ + $unsorted_keys, + false, + false, + true, + [ + 'D' => 4, + 'B' => 5, + 'F' => 6, + 'A' => 9, + 'C' => 'A', + 'E' => 'B', + 'H' => 'C', + 'I' => 'a', + 'G' => 'c' + ], + ], + // sort maintain keys + lower case + 'sort maintain keys + lower case' => [ + $unsorted_keys, + true, + false, + true, + [ + 'D' => 4, + 'B' => 5, + 'F' => 6, + 'A' => 9, + 'C' => 'A', + 'I' => 'a', + 'E' => 'B', + 'H' => 'C', + 'G' => 'c' + ], + ], + // sort maintain keys + reverse + 'sort maintain keys + reverse' => [ + $unsorted_keys, + false, + true, + true, + [ + 'G' => 'c', + 'H' => 'C', + 'E' => 'B', + 'I' => 'a', + 'C' => 'A', + 'A' => 9, + 'F' => 6, + 'B' => 5, + 'D' => 4, + ], + ], + // sort maintain keys + lower case + reverse + 'sort maintain keys + lower case + reverse' => [ + $unsorted_keys, + true, + true, + true, + [ + 'G' => 'c', + 'H' => 'C', + 'E' => 'B', + 'I' => 'a', + 'C' => 'A', + 'A' => 9, + 'F' => 6, + 'B' => 5, + 'D' => 4, + ], + ], + // emtpy + 'empty' => [ + [], + false, + false, + false, + [] + ], + // with nulls + 'null entries' => [ + ['d', null, 'a', null, 1], + false, + false, + false, + [null, null, 1, 'a', 'd'], + ], + // double entries + 'double entries' => [ + [1, 2, 2, 1, 'B', 'A', 'a', 'b', 'A', 'B', 'b', 'a'], + false, + false, + false, + [1, 1, 2, 2, 'A', 'A', 'B', 'B', 'a', 'a', 'b', 'b'], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::sortArray + * @dataProvider providerSortArray + * @testdox sortArray sort $input with lower case $lower_case, reverse $reverse, maintain keys $maintain_keys with expeted $expected [$_dataName] + * + * @param array $input + * @param ?bool $lower_case + * @param ?bool $reverse + * @param ?bool $maintain_keys + * @param array $expected + * @return void + */ + public function testSortArray(array $input, ?bool $lower_case, ?bool $reverse, ?bool $maintain_keys, array $expected): void + { + $original = $input; + if ($lower_case === null && $reverse === null && $maintain_keys === null) { + $sorted_array = \CoreLibs\Combined\ArrayHandler::sortArray($input); + } else { + $sorted_array = \CoreLibs\Combined\ArrayHandler::sortArray($input, $lower_case, $reverse, $maintain_keys); + } + $expected_count = count($expected); + $this->assertIsArray( + $sorted_array, + 'sortArray: result not array' + ); + $this->assertCount( + $expected_count, + $sorted_array, + 'sortArray: count not matching' + ); + $this->assertEquals( + $expected, + $sorted_array, + 'sortArray: result not matching' + ); + $this->assertEquals( + $original, + $input, + 'sortArray: original - input was modified' + ); + if ($maintain_keys) { + $this->assertEqualsCanonicalizing( + array_keys($input), + array_keys($sorted_array), + 'sortArray: keys are not modified', + ); + } + if ($input != []) { + // we only care about array values + $this->assertNotEquals( + array_values($input), + array_values($sorted_array), + 'sortArray: output - input was modified' + ); + } + } + +/** + * sort + * + * @return array + */ + public function providerKsortArray(): array + { + // for lower case the initial order of the elmenet is important: + // A, a => A, a + // d, D => d, D + $unsorted_keys = [ + 9 => 'A', + 5 => 'B', + 'A' => 'C', + 4 => 'D', + 'B' => 'E', + 6 => 'F', + 'c' => 'G', + 'C' => 'H', + 'a' => 'I', + ]; + return [ + // sort keys + 'sort keys' => [ + $unsorted_keys, + false, + false, + [ + 4 => 'D', + 5 => 'B', + 6 => 'F', + 9 => 'A', + 'A' => 'C', + 'B' => 'E', + 'C' => 'H', + 'a' => 'I', + 'c' => 'G', + ], + ], + // sort keys + lower case + 'sort keys + lower case' => [ + $unsorted_keys, + true, + false, + [ + 4 => 'D', + 5 => 'B', + 6 => 'F', + 9 => 'A', + 'A' => 'C', + 'a' => 'I', + 'B' => 'E', + 'c' => 'G', + 'C' => 'H', + ], + ], + // sort keys + reverse + 'sort keys + reverse' => [ + $unsorted_keys, + false, + true, + [ + 'c' => 'G', + 'a' => 'I', + 'C' => 'H', + 'B' => 'E', + 'A' => 'C', + 9 => 'A', + 6 => 'F', + 5 => 'B', + 4 => 'D', + ], + ], + // sort keys + lower case + reverse + 'sort keys + lower case + reverse' => [ + $unsorted_keys, + true, + true, + [ + 'C' => 'H', + 'c' => 'G', + 'B' => 'E', + 'a' => 'I', + 'A' => 'C', + 9 => 'A', + 6 => 'F', + 5 => 'B', + 4 => 'D', + ], + ], + // emtpy + 'empty' => [ + [], + false, + false, + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::ksortArray + * @dataProvider providerKsortArray + * @testdox ksortArray sort $input with lower case $lower_case, reverse $reverse with expeted $expected [$_dataName] + * + * @param array $input + * @param ?bool $lower_case + * @param ?bool $reverse + * @param array $expected + * @return void + */ + public function testKsortArray(array $input, ?bool $lower_case, ?bool $reverse, array $expected): void + { + $original = $input; + if ($lower_case === null && $reverse === null) { + $sorted_array = \CoreLibs\Combined\ArrayHandler::ksortArray($input); + } else { + $sorted_array = \CoreLibs\Combined\ArrayHandler::ksortArray($input, $lower_case, $reverse); + } + $expected_count = count($expected); + $this->assertIsArray( + $sorted_array, + 'ksortArray: result not array' + ); + $this->assertCount( + $expected_count, + $sorted_array, + 'ksortArray: count not matching' + ); + $this->assertEquals( + $expected, + $sorted_array, + 'ksortArray: result not matching' + ); + $this->assertEquals( + $original, + $input, + 'ksortArray: original - input was modified' + ); + $this->assertEqualsCanonicalizing( + array_values($original), + array_values($sorted_array), + 'ksortArray: values are not modified' + ); + if ($input != []) { + // we only care about array keys + $this->assertNotEquals( + array_keys($input), + array_keys($sorted_array), + 'sortArray: output - input was modified' + ); + } + } + + /** + * Undocumented function + * + * @return array + */ + public function providerFindArraysMissingKey(): array + { + $search_array = [ + 'table_lookup' => [ + 'match' => [ + ['param' => 'access_d_cd', 'data' => 'a_cd', 'time_validation' => 'on_load',], + ['param' => 'other_block', 'data' => 'b_cd'], + ['pflaume' => 'other_block', 'data' => 'c_cd'], + ['param' => 'third_block', 'data' => 'd_cd', 'time_validation' => 'cool'], + ['special' => 'other_block', 'data' => 'e_cd', 'time_validation' => 'other'], + ] + ] + ]; + return [ + 'find, no key set' => [ + $search_array, + 'other_block', + 'time_validation', + null, + null, + [ + [ + 'content' => [ + 'param' => 'other_block', + 'data' => 'b_cd', + ], + 'path' => 'table_lookup:match:1', + 'missing_key' => ['time_validation'], + ], + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set' => [ + $search_array, + 'other_block', + 'time_validation', + 'pflaume', + null, + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set, different separator' => [ + $search_array, + 'other_block', + 'time_validation', + 'pflaume', + '#', + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup#match#2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set, multiple check' => [ + $search_array, + 'other_block', + ['data', 'time_validation'], + 'pflaume', + null, + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'has set' => [ + $search_array, + 'access_d_cd', + 'time_validation', + null, + null, + [] + ], + 'not found' => [ + $search_array, + 'not_found', + 'value', + null, + null, + [] + ], + 'empty' => [ + [], + 'something', + 'other', + null, + null, + [] + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::findArraysMissingKey + * @dataProvider providerFindArraysMissingKey + * @testdox findArraysMissingKey $input find $search_value with $search_key and missing $required_key [$_dataName] + * + * @param array $input + * @param string|int|float|bool $search_value + * @param string|array $required_key + * @param string|null $search_key + * @param string|null $path_separator + * @param array $expected + * @return void + */ + public function testFindArraysMissingKey( + array $input, + string|int|float|bool $search_value, + string|array $required_key, + ?string $search_key, + ?string $path_separator, + array $expected + ): void { + if ($path_separator === null) { + $result = \CoreLibs\Combined\ArrayHandler::findArraysMissingKey( + $input, + $search_value, + $required_key, + $search_key + ); + } else { + $result = \CoreLibs\Combined\ArrayHandler::findArraysMissingKey( + $input, + $search_value, + $required_key, + $search_key, + $path_separator + ); + } + $this->assertEquals( + $expected, + $result + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerSelectArrayFromOption(): array + { + $search_array = [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd-2' => [ + 'lookup' => 0, + 'value' => 'D SUB 2', + 'other' => 'Other B', + ], + 'more' => [ + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + 'd-more-2' => [ + 'lookup' => 0, + 'value' => 'D MORE SUB 0', + 'other' => 'Other C', + ], + ] + ] + ]; + /* + 0: input + 1: lookup + 2: search + 3: strict [false] + 4: case insensitive [false] + 5: recursive [false] + 6: flat_result [true] + 7: flat_separator [:] + 8: expected + */ + return [ + 'search, flat with found' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'search, recusrive with found' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd:d-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd:more:d-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ] + ], + 'search, recusrive with found, other separator' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => true, + 'flag_separator' => '+', + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd+d-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd+more+d-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ] + ], + 'search, recusrive with found, not flat result' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => false, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'more' => [ + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ], + ], + ], + ], + 'search case insensitive' => [ + $search_array, + 'lookup' => 'other', + 'search' => 'Other', + 'strict' => false, + 'case_insenstivie' => true, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + ] + ], + 'search case sensitiv' => [ + $search_array, + 'lookup' => 'other', + 'search' => 'Other', + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'search strict' => [ + $search_array, + 'lookup' => 'strict', + 'search' => '2', + 'strict' => true, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + ] + ], + 'search not strict' => [ + $search_array, + 'lookup' => 'strict', + 'search' => '2', + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'empty' => [ + [], + 'something', + 'NOT_SET_AT_ALL', + false, + false, + false, + true, + null, + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::selectArrayFromOption + * @dataProvider providerSelectArrayFromOption + * @testdox selectArrayFromOption $input find $lookup with $search, strict: $strict, case sensitive: $case_sensitive, recursive: $recursive, flag result: $flag_result, flag separator: $flat_separator amd expected $expected [$_dataName] + * + * @param array $input + * @param string $lookup + * @param int|string|float|bool $search + * @param bool $strict + * @param bool $case_sensitive + * @param bool $recursive + * @param bool $flat_result + * @param string|null $flat_separator + * @param array $expected + * @return void + */ + public function testSelectArrayFromOption( + array $input, + string $lookup, + int|string|float|bool $search, + bool $strict, + bool $case_sensitive, + bool $recursive, + bool $flat_result, + ?string $flat_separator, + array $expected + ): void { + if ($flat_separator === null) { + $result = \CoreLibs\Combined\ArrayHandler::selectArrayFromOption( + $input, + $lookup, + $search, + $strict, + $case_sensitive, + $recursive, + $flat_result + ); + } else { + $result = \CoreLibs\Combined\ArrayHandler::selectArrayFromOption( + $input, + $lookup, + $search, + $strict, + $case_sensitive, + $recursive, + $flat_result, + $flat_separator + ); + } + $this->assertEquals( + $expected, + $result ); } } diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index c292355e..87146eaf 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -51,6 +51,9 @@ $test_array = [ 'element_c' => [ 'type' => 'email' ], + 'element_d' => [ + 'type' => 'butter' + ], ], ]; @@ -70,7 +73,15 @@ echo "ARRAYSEARCHRECURSIVEALL(email, [array], type): " // simple search echo "ARRAYSEARCHSIMPLE([array], type, email): " - . (string)ArrayHandler::arraySearchSimple($test_array, 'type', 'email') . "
"; + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', 'email')) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, not): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', 'not')) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, [email,butter]): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', ['email', 'butter'])) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, [email,not]): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', ['email', 'not'])) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, [never,not]): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', ['never', 'not'])) . "
"; $array_1 = [ 'foo' => 'bar' @@ -292,6 +303,231 @@ print "array intersect key: " . DgS::printAr($keys) . ": " . DgS::printAr($out) print "array + suffix: " . DgS::printAr(ArrayHandler::arrayModifyKey($array, key_mod_suffix:'_attached')) . "
"; +print "
"; +$unsorted = [9, 5, 'A', 4, 'B', 6, 'c', 'C', 'a']; +$unsorted_keys = [ + 'A' => 9, 'B' => 5, 'C' => 'A', 'D' => 4, 'E' => 'B', 'F' => 6, 'G' => 'c', + 'H1' => 'D', 'B1' => 'd', 'H' => 'C', 'I' => 'a' +]; +print "Unsorted: " . DgS::printAr($unsorted) . "
"; +print "(sort): " . DgS::printAr(ArrayHandler::sortArray($unsorted)) . "
"; +print "(sort, lower): " . DgS::printAr(ArrayHandler::sortArray($unsorted, case_insensitive:true)) . "
"; +print "(sort, reverse): " . DgS::printAr(ArrayHandler::sortArray($unsorted, reverse:true)) . "
"; +print "(sort, lower, reverse): " + . DgS::printAr(ArrayHandler::sortArray($unsorted, case_insensitive:true, reverse:true)) . "
"; +print "(sort, keys): " . DgS::printAr(ArrayHandler::sortArray($unsorted_keys, maintain_keys:true)) . "
"; +print "(sort, keys, lower): " + . DgS::printAr(ArrayHandler::sortArray($unsorted_keys, maintain_keys:true, case_insensitive:true)) . "
"; + +print "
"; +$unsorted = [9 => 'A', 5 => 'B', 'A' => 'C', 4 => 'D', 'B' => 'E', 6 => 'F', 'c' => 'G', 'C' => 'H', 'a' => 'I']; +print "Unsorted Keys: " . DgS::printAr($unsorted) . "
"; +print "(sort): " . DgS::printAr(ArrayHandler::sortArray($unsorted)) . "
"; +print "(sort, keys): " . DgS::printAr(ArrayHandler::sortArray($unsorted, maintain_keys:true)) . "
"; +print "(kosrt): " . DgS::printAr(ArrayHandler::ksortArray($unsorted)) . "
"; +print "(kosrt, reverse): " . DgS::printAr(ArrayHandler::ksortArray($unsorted, reverse:true)) . "
"; +print "(kosrt, lower case, reverse): " + . DgS::printAr(ArrayHandler::ksortArray($unsorted, case_insensitive:true, reverse:true)) . "
"; + + +print "
"; +$nested = [ + 'B' => 'foo', 'a', '0', 9, + 1 => ['z', 'b', 'a'], + 'd' => ['zaip', 'bar', 'baz'] +]; +print "Nested: " . DgS::printAr($nested) . "
"; +print "(sort): " . DgS::printAr(ArrayHandler::sortArray($nested)) . "
"; +print "(ksort): " . DgS::printAr(ArrayHandler::ksortArray($nested)) . "
"; + +print "
"; + +$search_array = [ + 'table_lookup' => [ + 'match' => [ + ['param' => 'access_d_cd', 'data' => 'a_cd', 'time_validation' => 'on_load',], + ['param' => 'other_block', 'data' => 'b_cd'], + ['pflaume' => 'other_block', 'data' => 'c_cd'], + ['param' => 'third_block', 'data' => 'd_cd', 'time_validation' => 'cool'], + ['special' => 'other_block', 'data' => 'e_cd', 'time_validation' => 'other'], + ] + ] +]; +print "Search: " . DgS::printAr($search_array) . "
"; +print "Result (all): " . Dgs::printAr(ArrayHandler::findArraysMissingKey( + $search_array, + 'other_block', + 'time_validation' +)) . "
"; +print "Result (key): " . Dgs::printAr(ArrayHandler::findArraysMissingKey( + $search_array, + 'other_block', + 'time_validation', + 'pflaume' +)) . "
"; +print "Result (key): " . Dgs::printAr(ArrayHandler::findArraysMissingKey( + $search_array, + 'other_block', + ['data', 'time_validation'], + 'pflaume' +)) . "
"; + +print "
"; + +$search_array = [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd-2' => [ + 'lookup' => 0, + 'value' => 'D SUB 2', + 'other' => 'Other C', + ], + 'more' => [ + 'lookup' => 1, + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + 'd-more-2' => [ + 'lookup' => 0, + 'value' => 'D MORE SUB 0', + 'other' => 'Other C', + ], + ] + ] +]; + +print "Search: " . DgS::printAr($search_array) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, + recursive:true +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, + recursive:true, + flat_separator:'-=-' +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, + recursive:true, + flat_result:false +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'other', + 'Other', + case_insensitive:false, +)) . "
"; + +$nestedTestData = [ + 'level1_a' => [ + 'name' => 'Level1A', + 'type' => 'parent', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'type' => 'child', + 'active' => true + ], + 'child2' => [ + 'name' => 'Child2', + 'type' => 'child', + 'active' => false + ] + ] + ], + 'level1_b' => [ + 'name' => 'Level1B', + 'type' => 'parent', + 'children' => [ + 'child3' => [ + 'name' => 'Child3', + 'type' => 'child', + 'active' => true, + 'nested' => [ + 'deep1' => [ + 'name' => 'Deep1', + 'type' => 'deep', + 'active' => true + ] + ] + ] + ] + ], + 'item5' => [ + 'name' => 'Direct', + 'type' => 'child', + 'active' => false + ] +]; + +$result = ArrayHandler::selectArrayFromOption( + $nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + ':*' +); +print "*1*Result: " . DgS::printAr($result) . "
"; +$data = [ + 'parent1' => [ + 'name' => 'Parent1', + 'status' => 'ACTIVE', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'status' => 'active' + ] + ] + ] +]; + +$result = ArrayHandler::selectArrayFromOption( + $data, + 'status', + 'active', + false, // not strict + true, // case insensitive + true, // recursive + true, // flat result + '|' // custom separator +); +print "*2*Result: " . DgS::printAr($result) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index fc2e468e..16d2c3ab 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -10,7 +10,7 @@ namespace CoreLibs\Combined; class ArrayHandler { - public const string DATA_SEPARATOR = '#'; + public const string DATA_SEPARATOR = ':'; /** * searches key = value in an array / array @@ -151,7 +151,7 @@ class ArrayHandler * on default does not strict check, so string '4' will match int 4 and vica versa * * @param array $array search in as array - * @param string|int $key key (key to search in) + * @param string|int $key key (key to search in) * @param string|int|bool|array $value values list (what to find) * @param bool $strict [false], if set to true, will strict check key/value * @return bool true on found, false on not found @@ -243,45 +243,73 @@ class ArrayHandler } /** - * TODO: move to CoreLibs - * Search in an array for value and check in the same array block for the required key + * Search in an array for value with or without key and + * check in the same array block for the required key * If not found return an array with the array block there the required key is missing, * the path as string with seperator block set and the missing key entry * * @param array $array * @param string|int|float|bool $search_value - * @param string $required_key + * @param string|array $required_key + * @param ?string $serach_key [null] + * @param string $path_separator [DATA_SEPARATOR] * @param string $current_path * @return array,path?:string,missing_key?:string}> */ public static function findArraysMissingKey( array $array, string|int|float|bool $search_value, - string $required_key, + string|array $required_key, + ?string $search_key = null, + string $path_separator = self::DATA_SEPARATOR, string $current_path = '' ): array { $results = []; - foreach ($array as $key => $value) { - $path = $current_path ? $current_path . self::DATA_SEPARATOR . $key : $key; + $path = $current_path ? $current_path . $path_separator . $key : $key; if (is_array($value)) { // Check if this array contains the search value - $containsValue = in_array($search_value, $value, true); + // either any value match or with key + if ($search_key === null) { + $containsValue = in_array($search_value, $value, true); + } else { + $containsValue = array_key_exists($search_key, $value) && $value[$search_key] === $search_value; + } // If it contains the value but doesn't have the required key - if ($containsValue && !array_key_exists($required_key, $value)) { + if ( + $containsValue && + ( + ( + is_string($required_key) && + !array_key_exists($required_key, $value) + ) || ( + is_array($required_key) && + count(array_intersect($required_key, array_keys($value))) !== count($required_key) + ) + ) + ) { $results[] = [ 'content' => $value, 'path' => $path, - 'missing_key' => $required_key + 'missing_key' => is_array($required_key) ? + array_values(array_diff($required_key, array_keys($value))) : + [$required_key] ]; } // Recursively search nested arrays $results = array_merge( $results, - self::findArraysMissingKey($value, $search_value, $required_key, $path) + self::findArraysMissingKey( + $value, + $search_value, + $required_key, + $search_key, + $path_separator, + $path + ) ); } } @@ -290,15 +318,17 @@ class ArrayHandler } /** - * TODO: move to CoreLibs - * currenly only over one level, find key => value entry and return set with key - * for all matching + * Find key => value entry and return set with key for all matching + * Can search recursively through nested arrays if recursive flag is set * - * @param array $array - * @param string $lookup + * @param array $array + * @param string $lookup * @param int|string|float|bool $search - * @param bool $strict [default=false] - * @param bool $case_senstivie [default=false] + * @param bool $strict [false] + * @param bool $case_insensitive [false] + * @param bool $recursive [false] + * @param bool $flat_result [true] If set to false and recursive is on the result is a nested array + * @param string $flat_separator [DATA_SEPARATOR] if flat result is true, can be any string * @return array */ public static function selectArrayFromOption( @@ -306,28 +336,63 @@ class ArrayHandler string $lookup, int|string|float|bool $search, bool $strict = false, - bool $case_senstivie = false, + bool $case_insensitive = false, + bool $recursive = false, + bool $flat_result = true, + string $flat_separator = self::DATA_SEPARATOR ): array { + // skip on empty + if ($array == []) { + return []; + } + // init return result $result = []; - if ($case_senstivie && is_string($search)) { + // case sensitive convert if string + if ($case_insensitive && is_string($search)) { $search = strtolower($search); } + foreach ($array as $key => $value) { - // skip on not set - if (!isset($value[$lookup])) { - continue; + // Handle current level search + if (isset($value[$lookup])) { + $compareValue = $value[$lookup]; + + if ($case_insensitive && is_string($compareValue)) { + $compareValue = strtolower($compareValue); + } + + if ( + ($strict && $search === $compareValue) || + (!$strict && $search == $compareValue) + ) { + $result[$key] = $value; + } } - if ($case_senstivie && is_string($value[$value])) { - $value[$lookup] = strtolower($value[$value]); - } - if ( - ($strict && $search === $value[$lookup]) || - (!$strict && $search == $value[$lookup]) - ) { - $result[$key] = $value; - continue; + // Handle recursive search if flag is set + if ($recursive && is_array($value)) { + $recursiveResults = self::selectArrayFromOption( + $value, + $lookup, + $search, + $strict, + $case_insensitive, + true, + $flat_result, + $flat_separator + ); + + // Merge recursive results with current results + // Preserve keys by using array_merge with string keys or + operator + foreach ($recursiveResults as $recKey => $recValue) { + if ($flat_result) { + $result[$key . $flat_separator . $recKey] = $recValue; + } else { + $result[$key][$recKey] = $recValue; + } + } } } + return $result; } @@ -653,8 +718,8 @@ class ArrayHandler * does not change order in array * * @param array $in_array - * @param string $key_mod_prefix [default=''] key prefix string - * @param string $key_mod_suffix [default=''] key suffix string + * @param string $key_mod_prefix [''] key prefix string + * @param string $key_mod_suffix [''] key suffix string * @return array */ public static function arrayModifyKey( @@ -680,35 +745,66 @@ class ArrayHandler /** * sort array and return in same call - * sort ascending, value + * sort ascending or descending with or without lower case convert + * value only, will loose key connections unless preserve_keys is set to true * * @param array $array array to sort by values * @param int $params sort flags * @return array */ - public function sortArray(array $array, int $params = SORT_REGULAR): array - { - return sort($array, $params) ? $array : $array; + public static function sortArray( + array $array, + bool $case_insensitive = false, + bool $reverse = false, + bool $maintain_keys = false, + int $params = SORT_REGULAR + ): array { + $fk_sort_lower_case = function (string $a, string $b): int { + return strtolower($a) <=> strtolower($b); + }; + $fk_sort_lower_case_reverse = function (string $a, string $b): int { + return strtolower($b) <=> strtolower($a); + }; + $case_insensitive ? ( + $maintain_keys ? + (uasort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) : + (usort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) + ) : + ( + $maintain_keys ? + ($reverse ? arsort($array, $params) : asort($array, $params)) : + ($reverse ? rsort($array, $params) : sort($array, $params)) + ); + return $array; } /** - * sort by key ascending and return + * sort by key ascending or descending and return * * @param array $array - * @param bool $lower_case [default=false] + * @param bool $case_insensitive [false] + * @param bool $reverse [false] * @return array */ - public static function ksortArray(array $array, bool $lower_case = false): array + public static function ksortArray(array $array, bool $case_insensitive = false, bool $reverse = false): array { $fk_sort_lower_case = function (string $a, string $b): int { return strtolower($a) <=> strtolower($b); }; + $fk_sort_lower_case_reverse = function (string $a, string $b): int { + return strtolower($b) <=> strtolower($a); + }; $fk_sort = function (string $a, string $b): int { return $a <=> $b; }; + $fk_sort_reverse = function (string $a, string $b): int { + return $b <=> $a; + }; uksort( $array, - $lower_case ? $fk_sort_lower_case : $fk_sort + $case_insensitive ? + ($reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case) : + ($reverse ? $fk_sort_reverse : $fk_sort) ); return $array; }