Skip to content

Commit 0bcac0a

Browse files
committed
Add extractWhere() method
1 parent e614085 commit 0bcac0a

4 files changed

Lines changed: 111 additions & 60 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
@@ -679,6 +679,31 @@ The `in` operator filters elements by matching a field's value against an array
679679

680680
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.
681681

682+
### The `extractWhere()` method
683+
The `extractWhere()` method allows you to recursively query data elements and extract all elements that match a given property/value pair.
684+
685+
It is especially useful when working with deeply nested data structures (for example JSON content trees), where matching items may appear at any depth.
686+
687+
The implementation:
688+
- Recursively scans the entire Block
689+
- Finds all elements that:
690+
- Contain the given $property
691+
- Have a value strictly equal (===) to $value
692+
- Returns a new Block instance containing only the matched items
693+
- The original datablock is not modified
694+
695+
```php
696+
$jsonString = file_get_contents('./tests/data/story.json');
697+
698+
$story = Block::fromJsonString($jsonString);
699+
700+
// Extract all items where "fieldtype" === "asset"
701+
$assets = $story->extractWhere('fieldtype', 'asset');
702+
703+
// Debug output
704+
$assets->dump();
705+
```
706+
682707
### The `orderBy()` method
683708
You can order or sort data for a specific key.
684709
For example, if you want to retrieve the data at `story.content.body` key and sort them by `component` key:

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: 56 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,37 @@
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+
$assets->dump();
138+
expect($assets)->toHaveCount(16);
139+
expect($assets->get("3.filename"))->toStartWith("https://a.story");
140+
});

0 commit comments

Comments
 (0)