Skip to content

Commit

Permalink
Merge pull request #35 from Hi-Folks/feat/34-groupbyfunction
Browse files Browse the repository at this point in the history
groupByFunction
  • Loading branch information
roberto-butti authored Oct 15, 2024
2 parents 3fba8b7 + c3ece53 commit 30f196c
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 42 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
79 changes: 70 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
PHP Package for Managing Nested Data
</i>
</p>
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:

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:

Expand All @@ -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 ("").

Expand Down Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion src/Traits/FormattableBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 20 additions & 1 deletion src/Traits/QueryableBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down
100 changes: 70 additions & 30 deletions tests/Unit/Traits/QueryableBlockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,31 @@
use HiFolks\DataType\Enums\Operator;

$fruitsArray = [
"avocado" =>
[
'avocado' => [
'name' => 'Avocado',
'fruit' => '🥑',
'wikipedia' => 'https://en.wikipedia.org/wiki/Avocado',
'color' => 'green',
'rating' => 8,
'tags' => ['healthy', 'creamy', 'green'],
],
"apple" =>
[
'apple' => [
'name' => 'Apple',
'fruit' => '🍎',
'wikipedia' => 'https://en.wikipedia.org/wiki/Apple',
'color' => 'red',
'rating' => 7,
'tags' => ['classic', 'crunchy', 'juicy', 'red', 'sweet'],
],
"banana" =>
[
'banana' => [
'name' => 'Banana',
'fruit' => '🍌',
'wikipedia' => 'https://en.wikipedia.org/wiki/Banana',
'color' => 'yellow',
'rating' => 8.5,
'tags' => ['sweet', 'soft', 'yellow'],
],
"cherry" =>
[
'cherry' => [
'name' => 'Cherry',
'fruit' => '🍒',
'wikipedia' => 'https://en.wikipedia.org/wiki/Cherry',
Expand All @@ -46,24 +42,23 @@
'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);


},
);

test(
'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);
},
);

Expand All @@ -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);
},
);
Expand All @@ -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);

},
Expand Down

0 comments on commit 30f196c

Please sign in to comment.