From 47c9036147b1fd730f0ebe4da672b227fea39b74 Mon Sep 17 00:00:00 2001
From: Roberto Butti
Date: Tue, 15 Oct 2024 19:42:25 +0200
Subject: [PATCH 1/2] groupByFunction
---
README.md | 79 ++++++++++++++++--
src/Traits/FormattableBlock.php | 1 -
src/Traits/QueryableBlock.php | 21 ++++-
tests/Unit/Traits/QueryableBlockTest.php | 100 ++++++++++++++++-------
4 files changed, 160 insertions(+), 41 deletions(-)
diff --git a/README.md b/README.md
index d473a1f..a37fc53 100644
--- a/README.md
+++ b/README.md
@@ -28,13 +28,14 @@
PHP Package for Managing Nested Data
+This PHP package provides classes and methods for easily managing, querying, filtering, and setting nested data structures.
+The PHP Data Block package offers a streamlined approach to handling nested data, whether you're working with complex JSON data, hierarchical configurations, or deeply nested arrays.
-This PHP package provides classes and methods for managing, querying, filtering, and setting nested data structures with ease.
-Whether you're working with complex JSON data, hierarchical configurations, or deeply nested arrays, this package offers a streamlined approach to handling nested data easly.
+One "core" element of the PHP Data Block package is the Block PHP class.
## The Block class
-The **Block** class offers a comprehensive set of methods to create, manage, and access nested data structures.
+The **Block** class offers comprehensive methods to create, manage, and access nested data structures.
The **Block** class provides various methods, including:
@@ -45,15 +46,15 @@ The **Block** class provides various methods, including:
## Installing and using the Block class
-For adding to your projects, the Block class with its method and helper you can run the `composer require` command:
+For adding to your projects, the Block class with its methods and helpers, you can run the `composer require` command:
```php
composer require hi-folks/data-block
```
-> For supporting the development you can star the repository: https://github.com/Hi-Folks/data-block
+> To support the development, you can "star" ⭐ the repository: https://github.com/Hi-Folks/data-block
-Then in your PHP files, you can import the `HiFolks\DataType\Block` Namespace:
+Then, in your PHP files, you can import the `HiFolks\DataType\Block` Namespace:
```php
use HiFolks\DataType\Block;
@@ -124,7 +125,7 @@ $data->get('avocado.color'); // returns the string "green"
For example, with the `$fruitsArray` sample data, the `$data->get("avocado")` is:
- an array;
-- has 5 elements;
+- has five elements;
For example, the `$data->get("avocado.color")` is:
- a string;
@@ -186,7 +187,7 @@ The `getFormattedByte()` method retrieves and formats a byte value from a specif
Parameters:
- `$path` (string): The path to the field containing the byte value (e.g., "assets.0.total_bytes").
-- `$precision` (int): (Optional) Number of decimal places to include in the formatted result. Default is 2.
+- `$precision` (int): (Optional) Number of decimal places to include in the formatted result. The default is 2.
Example usage:
@@ -199,12 +200,13 @@ $data1->getFormattedByte("assets.1.total_bytes", 0); // Returns "2 GB"
Key Features:
- Automatic unit conversion: converts bytes into appropriate units (e.g., KB, MB, GB) based on the size.
-- customizable precision: you can specify the number of decimal places for the output, making it flexible for various use cases.
+- Customizable precision: you can specify the number of decimal places for the output, making it flexible for various use cases.
### The `getString()` method
The `getString()` method retrieves the value of a specified field as a string from a data block. If the field does not exist or is null, it returns a default value, which can be customized.
Parameters:
+
- `$path` (string): The path to the field (e.g., "0.commit.author.date").
- `$default` (string): (Optional) The default value to return if the field doesn't exist. Defaults to an empty string ("").
@@ -653,6 +655,65 @@ $grouped->dumpJson();
*/
```
+### The `groupByFunction()` method
+
+The `groupByFunction()` method allows you to group items from an Block based on a grouping logic provided by a callback function (closure). The function returns an associative array where the keys represent groupings defined by the callback, and the values are arrays of elements that belong to each group.
+
+```php
+$fruits = [
+ ['name' => 'Apple', 'type' => 'Citrus', 'quantity' => 15],
+ ['name' => 'Banana', 'type' => 'Tropical', 'quantity' => 10],
+ ['name' => 'Orange', 'type' => 'Citrus', 'quantity' => 8],
+ ['name' => 'Mango', 'type' => 'Tropical', 'quantity' => 5],
+ ['name' => 'Lemon', 'type' => 'Citrus', 'quantity' => 12]
+];
+$fruitsBlock = Block::make($fruits);
+$groupedByQuantityRange = $fruitsBlock->groupByFunction(
+ fn($fruit): string =>
+ match (true) {
+ $fruit['quantity'] < 10 => 'Low',
+ $fruit['quantity'] < 15 => 'Medium',
+ default => 'High',
+ },
+);
+// It returns:
+/*
+{
+ "High": [
+ {
+ "name": "Apple",
+ "type": "Citrus",
+ "quantity": 15
+ }
+ ],
+ "Medium": [
+ {
+ "name": "Banana",
+ "type": "Tropical",
+ "quantity": 10
+ },
+ {
+ "name": "Lemon",
+ "type": "Citrus",
+ "quantity": 12
+ }
+ ],
+ "Low": [
+ {
+ "name": "Orange",
+ "type": "Citrus",
+ "quantity": 8
+ },
+ {
+ "name": "Mango",
+ "type": "Tropical",
+ "quantity": 5
+ }
+ ]
+}
+*/
+```
+
### The `exists()` method
You can use the `exists()` method to check if an element that meets a certain condition exists. This method is a convenient way to determine if any records match your query without needing to count them explicitly.
diff --git a/src/Traits/FormattableBlock.php b/src/Traits/FormattableBlock.php
index 2ae4f15..c1a0a51 100644
--- a/src/Traits/FormattableBlock.php
+++ b/src/Traits/FormattableBlock.php
@@ -75,7 +75,6 @@ public function getFormattedByte(
* @param mixed $key the filed key , can be nested for example "commits.0.name"
* @param string|null $defaultValue the default value returned if no value is found
* @param non-empty-string $charNestedKey for nested field the . character is the default
- * @return string
*/
public function getString(
mixed $key,
diff --git a/src/Traits/QueryableBlock.php b/src/Traits/QueryableBlock.php
index 9271ad6..5812e83 100644
--- a/src/Traits/QueryableBlock.php
+++ b/src/Traits/QueryableBlock.php
@@ -59,7 +59,7 @@ public function where(
Operator::STRICT_NOT_EQUAL => $elementToCheck->get($field) !== $value,
Operator::IN => in_array($elementToCheck->get($field), $value),
Operator::HAS => in_array($value, $elementToCheck->get($field)),
- Operator::LIKE => str_contains($elementToCheck->get($field), $value),
+ Operator::LIKE => str_contains($elementToCheck->get($field), (string) $value),
default => $elementToCheck->get($field) === $value,
};
if ($found) {
@@ -152,6 +152,25 @@ public function groupBy(string|int $field): self
return self::make($result);
}
+ public function groupByFunction(callable $groupFunction): self
+ {
+ $result = [];
+
+ foreach ($this->data as $item) {
+ // Call the closure to determine the group key
+ $groupKey = $groupFunction($item);
+
+ // Group items under the same key
+ if (!array_key_exists($groupKey, $result)) {
+ $result[$groupKey] = [];
+ }
+
+ $result[$groupKey][] = $item;
+ }
+ return self::make($result);
+ }
+
+
private static function castVariableForStrval(mixed $property): bool|float|int|string|null
{
return match (gettype($property)) {
diff --git a/tests/Unit/Traits/QueryableBlockTest.php b/tests/Unit/Traits/QueryableBlockTest.php
index ba4d3b7..5e1cba3 100644
--- a/tests/Unit/Traits/QueryableBlockTest.php
+++ b/tests/Unit/Traits/QueryableBlockTest.php
@@ -4,8 +4,7 @@
use HiFolks\DataType\Enums\Operator;
$fruitsArray = [
- "avocado" =>
- [
+ 'avocado' => [
'name' => 'Avocado',
'fruit' => '🥑',
'wikipedia' => 'https://en.wikipedia.org/wiki/Avocado',
@@ -13,8 +12,7 @@
'rating' => 8,
'tags' => ['healthy', 'creamy', 'green'],
],
- "apple" =>
- [
+ 'apple' => [
'name' => 'Apple',
'fruit' => '🍎',
'wikipedia' => 'https://en.wikipedia.org/wiki/Apple',
@@ -22,8 +20,7 @@
'rating' => 7,
'tags' => ['classic', 'crunchy', 'juicy', 'red', 'sweet'],
],
- "banana" =>
- [
+ 'banana' => [
'name' => 'Banana',
'fruit' => '🍌',
'wikipedia' => 'https://en.wikipedia.org/wiki/Banana',
@@ -31,8 +28,7 @@
'rating' => 8.5,
'tags' => ['sweet', 'soft', 'yellow'],
],
- "cherry" =>
- [
+ 'cherry' => [
'name' => 'Cherry',
'fruit' => '🍒',
'wikipedia' => 'https://en.wikipedia.org/wiki/Cherry',
@@ -46,12 +42,11 @@
'Test query greater than x',
function () use ($fruitsArray): void {
$data = Block::make($fruitsArray);
- $highRated = $data->where("rating", Operator::GREATER_THAN, 8);
+ $highRated = $data->where('rating', Operator::GREATER_THAN, 8);
expect($highRated)->tohaveCount(2);
- $sorted = $data->where("rating", Operator::GREATER_THAN, 8)->orderBy("rating", "desc");
+ $sorted = $data->where('rating', Operator::GREATER_THAN, 8)->orderBy('rating', 'desc');
expect($sorted)->tohaveCount(2);
-
},
);
@@ -59,11 +54,11 @@ function () use ($fruitsArray): void {
'group by',
function () use ($fruitsArray): void {
$table = Block::make($fruitsArray);
- $grouped = $table->groupBy("color");
- expect($grouped->getBlock("red"))->tohaveCount(2);
- expect($grouped->getBlock("yellow"))->tohaveCount(1);
- expect($grouped->getBlock("NotExists"))->tohaveCount(0);
-
+ $grouped = $table->groupBy('color');
+ expect($grouped->getBlock('red'))
+ ->tohaveCount(2)
+ ->and($grouped->getBlock('yellow'))->tohaveCount(1)
+ ->and($grouped->getBlock('NotExists'))->tohaveCount(0);
},
);
@@ -76,23 +71,69 @@ function (): void {
['type' => 'vegetable', 'name' => 'carrot'],
]);
$grouped = $data->groupBy('type');
- expect($grouped->getBlock("fruit"))->tohaveCount(2);
- expect($grouped->getBlock("vegetable"))->tohaveCount(1);
- expect($grouped->getBlock("NotExists"))->tohaveCount(0);
+ expect($grouped->getBlock('fruit'))->tohaveCount(2);
+ expect($grouped->getBlock('vegetable'))->tohaveCount(1);
+ expect($grouped->getBlock('NotExists'))->tohaveCount(0);
},
);
+test(
+ 'group by function',
+ function (): void {
+ $fruits = [
+ ['name' => 'Apple', 'type' => 'Citrus', 'quantity' => 15],
+ ['name' => 'Banana', 'type' => 'Tropical', 'quantity' => 10],
+ ['name' => 'Orange', 'type' => 'Citrus', 'quantity' => 8],
+ ['name' => 'Mango', 'type' => 'Tropical', 'quantity' => 5],
+ ['name' => 'Lemon', 'type' => 'Citrus', 'quantity' => 12],
+ ];
+ $fruitsBlock = Block::make($fruits);
+ $groupedByQuantityRange = $fruitsBlock->groupByFunction(
+ fn($fruit): string =>
+ match (true) {
+ $fruit['quantity'] < 10 => 'Low',
+ $fruit['quantity'] < 15 => 'Medium',
+ default => 'High',
+ },
+ );
+ $groupedByQuantityRange->dumpJson();
+ expect($groupedByQuantityRange)
+ ->tohaveCount(3)
+ ->and($groupedByQuantityRange->getBlock('Low'))
+ ->tohaveCount(2)
+ ->and($groupedByQuantityRange->getBlock('Medium'))
+ ->tohaveCount(2)
+ ->and($groupedByQuantityRange->getBlock('High'))
+ ->tohaveCount(1);
+
+ $groupedByNameLenght = $fruitsBlock->groupByFunction(
+ fn($fruit): int => strlen((string) $fruit['name']),
+ );
+
+ expect($groupedByNameLenght)
+ ->tohaveCount(2)
+ ->and($groupedByNameLenght->hasKey(5))
+ ->toBeTrue()
+ ->and($groupedByNameLenght->hasKey("6"))
+ ->toBeTrue()
+ ->and($groupedByNameLenght->get("5"))
+ ->tohaveCount(3)
+ ->and($groupedByNameLenght->get("6"))
+ ->tohaveCount(2);
+ },
+);
+
test(
'where method, in operator',
function () use ($fruitsArray): void {
$data = Block::make($fruitsArray);
- $greenOrBlack = $data->where("color", Operator::IN, ["green", "black"]);
+ $greenOrBlack = $data->where('color', Operator::IN, ['green', 'black']);
expect($greenOrBlack)->tohaveCount(1);
- $noResult = $data->where("color", Operator::IN, []);
+ $noResult = $data->where('color', Operator::IN, []);
expect($noResult)->tohaveCount(0);
- $greenOrRed = $data->where("color", Operator::IN, ["green", "red"]);
+ $greenOrRed = $data->where('color', Operator::IN, ['green', 'red']);
expect($greenOrRed)->tohaveCount(3);
},
);
@@ -101,26 +142,25 @@ function () use ($fruitsArray): void {
'where method, has operator',
function () use ($fruitsArray): void {
$data = Block::make($fruitsArray);
- $sweet = $data->where("tags", Operator::HAS, "sweet");
+ $sweet = $data->where('tags', Operator::HAS, 'sweet');
expect($sweet)->tohaveCount(2);
- $noResult = $data->where("tags", Operator::HAS, "not-existent");
+ $noResult = $data->where('tags', Operator::HAS, 'not-existent');
expect($noResult)->tohaveCount(0);
- $softFruit = $data->where("tags", Operator::HAS, "soft");
+ $softFruit = $data->where('tags', Operator::HAS, 'soft');
expect($softFruit)->tohaveCount(1);
},
);
-
test(
'query with operators',
function (): void {
- $data1 = Block::fromJsonFile(__DIR__ . "/../../data/commits-json/commits-10-p1.json");
- $data2 = Block::fromJsonFile(__DIR__ . "/../../data/commits-json/commits-10-p2.json");
- $data3 = Block::fromJsonFile(__DIR__ . "/../../data/commits-json/commits-10-p3.json");
+ $data1 = Block::fromJsonFile(__DIR__ . '/../../data/commits-json/commits-10-p1.json');
+ $data2 = Block::fromJsonFile(__DIR__ . '/../../data/commits-json/commits-10-p2.json');
+ $data3 = Block::fromJsonFile(__DIR__ . '/../../data/commits-json/commits-10-p3.json');
$data1->append($data2)->append($data3);
expect($data1)->toHaveCount(30);
expect($data2)->toHaveCount(10);
- $block = $data1->where("commit.author.name", Operator::LIKE, "Roberto");
+ $block = $data1->where('commit.author.name', Operator::LIKE, 'Roberto');
expect($block->count())->toEqual(29);
},
From c3ece53d61a2c8926f78579a009454e68661b26f Mon Sep 17 00:00:00 2001
From: Roberto Butti
Date: Tue, 15 Oct 2024 19:45:21 +0200
Subject: [PATCH 2/2] Update CHANGELOG.md
---
CHANGELOG.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e07f8a..4eba45d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
-## 0.4.2 - WIP
+## 0.4.2 - 2024-10-15
+- Implementing the `groupByFunction()` to group elements based on a specific function.
- Implementing `getString()` for returning a string
- Implementing Operator Class with constants like Operator::EQUAL, Operator::GREATER_THAN ...
- Implementing LIKE Operator