Skip to content

Commit beea124

Browse files
soyukaclaude
andcommitted
feat: add recursive container traversal via walkContainer generator
Adds walkContainer() which recursively descends an LDP container tree using getContainerContents(), yielding ContainerEntry objects. Supports configurable depth limits: -1 for unlimited, 0 for current level only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8cbaf9c commit beea124

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

src/SolidClient.php

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

167+
/**
168+
* Recursively walks an LDP container tree, yielding ContainerEntry objects.
169+
*
170+
* @param int $maxDepth -1 for unlimited, 0 for current level only
171+
*
172+
* @return \Generator<int, ContainerEntry>
173+
*/
174+
public function walkContainer(string $url, int $maxDepth = -1, array $options = []): \Generator
175+
{
176+
$entries = $this->getContainerContents($url, $options);
177+
178+
foreach ($entries as $entry) {
179+
yield $entry;
180+
181+
if ($entry->isContainer && 0 !== $maxDepth) {
182+
yield from $this->walkContainer(
183+
$entry->url,
184+
-1 === $maxDepth ? -1 : $maxDepth - 1,
185+
$options,
186+
);
187+
}
188+
}
189+
}
190+
167191
public function getResourceMetadata(string $url, array $options = []): ResourceMetadata
168192
{
169193
$response = $this->head($url, $options);

tests/ContainerOperationsTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,73 @@ public function testEnsureContainerExistsCreates(): void
136136
$methods = array_column($requests, 0);
137137
$this->assertContains('PUT', $methods);
138138
}
139+
140+
public function testWalkContainer(): void
141+
{
142+
$rootJson = json_encode([
143+
'@id' => 'http://pod.example/root/',
144+
'@type' => ['http://www.w3.org/ns/ldp#BasicContainer'],
145+
'http://www.w3.org/ns/ldp#contains' => [
146+
['@id' => 'http://pod.example/root/file.txt'],
147+
['@id' => 'http://pod.example/root/sub/', '@type' => ['http://www.w3.org/ns/ldp#BasicContainer']],
148+
],
149+
]);
150+
151+
$subJson = json_encode([
152+
'@id' => 'http://pod.example/root/sub/',
153+
'@type' => ['http://www.w3.org/ns/ldp#BasicContainer'],
154+
'http://www.w3.org/ns/ldp#contains' => [
155+
['@id' => 'http://pod.example/root/sub/nested.ttl'],
156+
],
157+
]);
158+
159+
$httpClient = new MockHttpClient(static function (string $method, string $url) use ($rootJson, $subJson): MockResponse {
160+
if ('http://pod.example/root/' === $url) {
161+
return new MockResponse($rootJson, [
162+
'http_code' => 200,
163+
'response_headers' => ['Content-Type' => 'application/ld+json'],
164+
]);
165+
}
166+
if ('http://pod.example/root/sub/' === $url) {
167+
return new MockResponse($subJson, [
168+
'http_code' => 200,
169+
'response_headers' => ['Content-Type' => 'application/ld+json'],
170+
]);
171+
}
172+
173+
return new MockResponse('', ['http_code' => 404]);
174+
});
175+
$client = new SolidClient($httpClient);
176+
177+
$entries = iterator_to_array($client->walkContainer('http://pod.example/root/'), false);
178+
179+
$this->assertCount(3, $entries);
180+
$this->assertSame('http://pod.example/root/file.txt', $entries[0]->url);
181+
$this->assertSame('http://pod.example/root/sub/', $entries[1]->url);
182+
$this->assertSame('http://pod.example/root/sub/nested.ttl', $entries[2]->url);
183+
}
184+
185+
public function testWalkContainerWithMaxDepth(): void
186+
{
187+
$rootJson = json_encode([
188+
'@id' => 'http://pod.example/root/',
189+
'@type' => ['http://www.w3.org/ns/ldp#BasicContainer'],
190+
'http://www.w3.org/ns/ldp#contains' => [
191+
['@id' => 'http://pod.example/root/sub/', '@type' => ['http://www.w3.org/ns/ldp#BasicContainer']],
192+
],
193+
]);
194+
195+
$httpClient = new MockHttpClient(static function (string $method, string $url) use ($rootJson): MockResponse {
196+
return new MockResponse($rootJson, [
197+
'http_code' => 200,
198+
'response_headers' => ['Content-Type' => 'application/ld+json'],
199+
]);
200+
});
201+
$client = new SolidClient($httpClient);
202+
203+
$entries = iterator_to_array($client->walkContainer('http://pod.example/root/', 0), false);
204+
205+
$this->assertCount(1, $entries);
206+
$this->assertSame('http://pod.example/root/sub/', $entries[0]->url);
207+
}
139208
}

0 commit comments

Comments
 (0)