Skip to content

Commit b1bdc9a

Browse files
soyukaclaude
andcommitted
feat: add walkContainer() for recursive container traversal
Generator-based recursive descent over LDP container trees using getContainerContents(). Supports maxDepth parameter: -1 for unlimited, 0 for current level only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6e80b78 commit b1bdc9a

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

src/SolidClient.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,30 @@ public function ensureContainerExists(string $url, array $options = []): void
168168
$this->put($url, null, true, $options);
169169
}
170170

171+
/**
172+
* Recursively walks an LDP container tree, yielding ContainerEntry objects.
173+
*
174+
* @param int $maxDepth -1 for unlimited, 0 for current level only
175+
*
176+
* @return \Generator<int, ContainerEntry>
177+
*/
178+
public function walkContainer(string $url, int $maxDepth = -1, array $options = []): \Generator
179+
{
180+
$entries = $this->getContainerContents($url, $options);
181+
182+
foreach ($entries as $entry) {
183+
yield $entry;
184+
185+
if ($entry->isContainer && 0 !== $maxDepth) {
186+
yield from $this->walkContainer(
187+
$entry->url,
188+
-1 === $maxDepth ? -1 : $maxDepth - 1,
189+
$options,
190+
);
191+
}
192+
}
193+
}
194+
171195
private static function getParentContainerUrl(string $url): ?string
172196
{
173197
$trimmed = rtrim($url, '/');

tests/ContainerOperationsTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,73 @@ public function testEnsureContainerExistsCreates(): void
139139
$this->assertContains('PUT', $methods);
140140
}
141141

142+
public function testWalkContainer(): void
143+
{
144+
$rootJson = json_encode([
145+
'@id' => 'http://pod.example/root/',
146+
'@type' => ['http://www.w3.org/ns/ldp#BasicContainer'],
147+
'http://www.w3.org/ns/ldp#contains' => [
148+
['@id' => 'http://pod.example/root/file.txt'],
149+
['@id' => 'http://pod.example/root/sub/', '@type' => ['http://www.w3.org/ns/ldp#BasicContainer']],
150+
],
151+
]);
152+
153+
$subJson = json_encode([
154+
'@id' => 'http://pod.example/root/sub/',
155+
'@type' => ['http://www.w3.org/ns/ldp#BasicContainer'],
156+
'http://www.w3.org/ns/ldp#contains' => [
157+
['@id' => 'http://pod.example/root/sub/nested.ttl'],
158+
],
159+
]);
160+
161+
$httpClient = new MockHttpClient(static function (string $method, string $url) use ($rootJson, $subJson): MockResponse {
162+
if ('http://pod.example/root/' === $url) {
163+
return new MockResponse($rootJson, [
164+
'http_code' => 200,
165+
'response_headers' => ['Content-Type' => 'application/ld+json'],
166+
]);
167+
}
168+
if ('http://pod.example/root/sub/' === $url) {
169+
return new MockResponse($subJson, [
170+
'http_code' => 200,
171+
'response_headers' => ['Content-Type' => 'application/ld+json'],
172+
]);
173+
}
174+
175+
return new MockResponse('', ['http_code' => 404]);
176+
});
177+
$client = new SolidClient($httpClient);
178+
179+
$entries = iterator_to_array($client->walkContainer('http://pod.example/root/'), false);
180+
181+
$this->assertCount(3, $entries);
182+
$this->assertSame('http://pod.example/root/file.txt', $entries[0]->url);
183+
$this->assertSame('http://pod.example/root/sub/', $entries[1]->url);
184+
$this->assertSame('http://pod.example/root/sub/nested.ttl', $entries[2]->url);
185+
}
186+
187+
public function testWalkContainerWithMaxDepth(): void
188+
{
189+
$rootJson = json_encode([
190+
'@id' => 'http://pod.example/root/',
191+
'@type' => ['http://www.w3.org/ns/ldp#BasicContainer'],
192+
'http://www.w3.org/ns/ldp#contains' => [
193+
['@id' => 'http://pod.example/root/sub/', '@type' => ['http://www.w3.org/ns/ldp#BasicContainer']],
194+
],
195+
]);
196+
197+
$httpClient = new MockHttpClient(static function (string $method, string $url) use ($rootJson): MockResponse {
198+
return new MockResponse($rootJson, [
199+
'http_code' => 200,
200+
'response_headers' => ['Content-Type' => 'application/ld+json'],
201+
]);
202+
});
203+
$client = new SolidClient($httpClient);
204+
205+
$entries = iterator_to_array($client->walkContainer('http://pod.example/root/', 0), false);
206+
207+
// maxDepth=0 should only return root level entries, no recursion
208+
$this->assertCount(1, $entries);
209+
$this->assertSame('http://pod.example/root/sub/', $entries[0]->url);
210+
}
142211
}

0 commit comments

Comments
 (0)