Skip to content

Commit a73f026

Browse files
Merge pull request #52 from Hi-Folks/feat/extract-where
Add extractWhere() method
2 parents a621531 + e9dad23 commit a73f026

5 files changed

Lines changed: 115 additions & 61 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.0.3 - WIP
4+
- Adding the `extractWhere()` method that allows you to recursively query data elements and extract all elements that match a given property/value pair.
5+
- Fix `offsetAccess.invalidOffset` phpstan warning
6+
37
## 1.0.2 - 2025-10-05
48
- Adding `getIntStrict()` method for returning strict int.
59
- Adding `getStringStrict()` method for returning strict string.

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,31 @@ The `in` operator filters elements by matching a field's value against an array
707707

708708
The `has` operator filters elements by checking if a specific value exists within a field (usually an array or a collection). If the value exists, the element is included in the result. Non-existent values return no matches.
709709

710+
### The `extractWhere()` method
711+
The `extractWhere()` method allows you to recursively query data elements and extract all elements that match a given property/value pair.
712+
713+
It is especially useful when working with deeply nested data structures (for example JSON content trees), where matching items may appear at any depth.
714+
715+
The implementation:
716+
- Recursively scans the entire Block
717+
- Finds all elements that:
718+
- Contain the given $property
719+
- Have a value strictly equal (===) to $value
720+
- Returns a new Block instance containing only the matched items
721+
- The original datablock is not modified
722+
723+
```php
724+
$jsonString = file_get_contents('./tests/data/story.json');
725+
726+
$story = Block::fromJsonString($jsonString);
727+
728+
// Extract all items where "fieldtype" === "asset"
729+
$assets = $story->extractWhere('fieldtype', 'asset');
730+
731+
// Debug output
732+
$assets->dump();
733+
```
734+
710735
### The `orderBy()` method
711736
You can order or sort data for a specific key.
712737
For example, if you want to retrieve the data at `story.content.body` key and sort them by `component` key:

src/Block.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,12 @@ public function set(int|string $key, mixed $value, string $charNestedKey = "."):
165165

166166
$array = &$array[$key];
167167
}
168+
$key = array_shift($keys);
169+
170+
if (!is_null($key)) {
171+
$array[$key] = $value;
172+
}
168173

169-
$array[array_shift($keys)] = $value;
170174
return $this;
171175
}
172176
$this->data[$key] = $value;

src/Traits/QueryableBlock.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,32 @@ public function groupByFunction(callable $groupFunction): self
175175
return self::make($result);
176176
}
177177

178+
public function extractWhere(string $property, mixed $value): self
179+
{
180+
$results = [];
181+
182+
$scan = function ($item) use (&$results, &$scan, $property, $value): void {
183+
if (is_array($item)) {
184+
// Match the property/value pair
185+
if (
186+
array_key_exists($property, $item)
187+
&& $item[$property] === $value
188+
) {
189+
$results[] = $item;
190+
}
191+
192+
// Scan deeper
193+
foreach ($item as $subItem) {
194+
$scan($subItem);
195+
}
196+
}
197+
};
198+
199+
$scan($this->data);
200+
201+
return self::make($results);
202+
}
203+
178204
private static function castVariableForStrval(
179205
mixed $property,
180206
): bool|float|int|string|null {

tests/Feature/QueryBlockTest.php

Lines changed: 55 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,84 +3,73 @@
33
use HiFolks\DataType\Block;
44
use HiFolks\DataType\Enums\Operator;
55

6-
test('Query Block', function (): void {
6+
test("Query Block", function (): void {
77
$jsonString = file_get_contents("./tests/data/story.json");
88

99
$composerContent = Block::fromJsonString($jsonString);
10-
$banners = $composerContent->getBlock("story.content.body")->where(
11-
"component",
12-
"==",
13-
"banner",
14-
);
10+
$banners = $composerContent
11+
->getBlock("story.content.body")
12+
->where("component", "==", "banner");
1513
expect($banners)->toHaveCount(2);
1614
expect($banners->get("0.headline"))->toBe("New banner");
17-
expect($banners->get("4.headline"))->toBe("Top Five Discoveries, Curiosity Rover at Mars");
15+
expect($banners->get("4.headline"))->toBe(
16+
"Top Five Discoveries, Curiosity Rover at Mars",
17+
);
1818

1919
$composerContent = Block::fromJsonString($jsonString);
20-
$banners = $composerContent->getBlock("story.content.body")->where(
21-
"component",
22-
Operator::NOT_EQUAL,
23-
"banner",
24-
false,
25-
);
20+
$banners = $composerContent
21+
->getBlock("story.content.body")
22+
->where("component", Operator::NOT_EQUAL, "banner", false);
2623
expect($banners)->toHaveCount(8);
2724
expect($banners->get("0.component"))->toBe("hero-section");
2825
expect($banners->get("4.component"))->toBe("grid-section");
29-
3026
});
3127

32-
test('Query and select Block', function (): void {
28+
test("Query and select Block", function (): void {
3329
$jsonString = file_get_contents("./tests/data/story.json");
3430

3531
$composerContent = Block::fromJsonString($jsonString);
36-
$banners = $composerContent->getBlock("story.content.body")->where(
37-
"component",
38-
Operator::EQUAL,
39-
"banner",
40-
)->select("headline");
32+
$banners = $composerContent
33+
->getBlock("story.content.body")
34+
->where("component", Operator::EQUAL, "banner")
35+
->select("headline");
4136
expect($banners)->toHaveCount(2);
4237
expect($banners->get("0"))->toHaveCount(1);
4338
expect($banners->get("0.headline"))->toBe("New banner");
4439
expect($banners->get("1"))->toHaveCount(1);
45-
expect($banners->get("1.headline"))->toBe("Top Five Discoveries, Curiosity Rover at Mars");
40+
expect($banners->get("1.headline"))->toBe(
41+
"Top Five Discoveries, Curiosity Rover at Mars",
42+
);
4643

4744
$composerContent = Block::fromJsonString($jsonString);
48-
$banners = $composerContent->getBlock("story.content.body")->where(
49-
"component",
50-
Operator::NOT_EQUAL,
51-
"banner",
52-
false,
53-
);
45+
$banners = $composerContent
46+
->getBlock("story.content.body")
47+
->where("component", Operator::NOT_EQUAL, "banner", false);
5448
expect($banners)->toHaveCount(8);
5549
expect($banners->get("0.component"))->toBe("hero-section");
5650
expect($banners->get("4.component"))->toBe("grid-section");
5751
});
5852

59-
test('Order Block', function (): void {
53+
test("Order Block", function (): void {
6054
$jsonString = file_get_contents("./tests/data/story.json");
6155

6256
$composerContent = Block::fromJsonString($jsonString);
63-
$bodyComponents = $composerContent->getBlock("story.content.body")->orderBy(
64-
"component",
65-
"asc",
66-
);
57+
$bodyComponents = $composerContent
58+
->getBlock("story.content.body")
59+
->orderBy("component", "asc");
6760
expect($bodyComponents)->toHaveCount(10);
6861
expect($bodyComponents->get("0.component"))->toBe("banner");
6962
expect($bodyComponents->get("9.component"))->toBe("text-section");
7063

71-
$bodyComponents = $composerContent->getBlock("story.content.body")->orderBy(
72-
"component",
73-
"desc",
74-
);
64+
$bodyComponents = $composerContent
65+
->getBlock("story.content.body")
66+
->orderBy("component", "desc");
7567
expect($bodyComponents)->toHaveCount(10);
7668
expect($bodyComponents->get("9.component"))->toBe("banner");
7769
expect($bodyComponents->get("0.component"))->toBe("text-section");
78-
7970
});
8071

81-
82-
it('local dummyjson post', function (): void {
83-
72+
it("local dummyjson post", function (): void {
8473
$response = Block::fromJsonFile("./tests/data/dummy-posts-30.json");
8574
expect($response)->toBeInstanceOf(Block::class);
8675
expect($response)->toHaveCount(4);
@@ -115,30 +104,36 @@
115104
expect($posts->get("0.reactions.likes"))->toBe(192);
116105
});
117106

118-
test('Query Block with has', function (): void {
107+
test("Query Block with has", function (): void {
119108
$jsonString = file_get_contents("./tests/data/story.json");
120109
$composerContent = Block::fromJsonString($jsonString);
121-
$has = $composerContent->getBlock("story.content.body")->where(
122-
"component",
123-
Operator::EQUAL,
124-
"banner",
125-
)->exists();
110+
$has = $composerContent
111+
->getBlock("story.content.body")
112+
->where("component", Operator::EQUAL, "banner")
113+
->exists();
126114
expect($has)->toBeTrue();
127-
$has = $composerContent->getBlock("story.content.body")->where(
128-
"component",
129-
Operator::NOT_EQUAL,
130-
"banner",
131-
)->exists();
115+
$has = $composerContent
116+
->getBlock("story.content.body")
117+
->where("component", Operator::NOT_EQUAL, "banner")
118+
->exists();
132119
expect($has)->toBeTrue();
133-
$has = $composerContent->getBlock("story.content.body")->where(
134-
"component",
135-
Operator::EQUAL,
136-
"bannerXXX",
137-
)->exists();
120+
$has = $composerContent
121+
->getBlock("story.content.body")
122+
->where("component", Operator::EQUAL, "bannerXXX")
123+
->exists();
138124
expect($has)->toBeFalse();
139-
$has = $composerContent->getBlock("story.content.body")->where(
140-
"component",
141-
"banner",
142-
)->exists();
125+
$has = $composerContent
126+
->getBlock("story.content.body")
127+
->where("component", "banner")
128+
->exists();
143129
expect($has)->toBeTrue();
144130
});
131+
132+
test("Query Block extractWhere", function (): void {
133+
$jsonString = file_get_contents("./tests/data/story.json");
134+
135+
$story = Block::fromJsonString($jsonString);
136+
$assets = $story->extractWhere("fieldtype", "asset");
137+
expect($assets)->toHaveCount(16);
138+
expect($assets->get("3.filename"))->toStartWith("https://a.story");
139+
});

0 commit comments

Comments
 (0)