Skip to content

Commit 8cbaf9c

Browse files
soyukaclaude
andcommitted
feat: add ensureContainerExists with recursive parent creation
Adds ensureContainerExists() which HEADs the target URL and, if it returns 404, recursively ensures parent containers exist before creating the target via PUT with LDP BasicContainer type. Stops recursion at the authority level. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 99a3c4a commit 8cbaf9c

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

src/SolidClient.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,58 @@ public function getContainerContents(string $url, array $options = []): array
135135
return $entries;
136136
}
137137

138+
/**
139+
* Ensures that an LDP container exists at the given URL, creating it (and any missing parents) if necessary.
140+
*/
141+
public function ensureContainerExists(string $url, array $options = []): void
142+
{
143+
// Normalize: ensure trailing slash
144+
if (!str_ends_with($url, '/')) {
145+
$url .= '/';
146+
}
147+
148+
try {
149+
$response = $this->head($url, $options);
150+
$statusCode = $response->getStatusCode();
151+
if ($statusCode >= 200 && $statusCode < 300) {
152+
return;
153+
}
154+
} catch (\Throwable) {
155+
// Fall through to creation
156+
}
157+
158+
// Ensure parent exists first
159+
$parentUrl = self::getParentContainerUrl($url);
160+
if (null !== $parentUrl && $parentUrl !== $url) {
161+
$this->ensureContainerExists($parentUrl, $options);
162+
}
163+
164+
$this->put($url, null, true, $options);
165+
}
166+
138167
public function getResourceMetadata(string $url, array $options = []): ResourceMetadata
139168
{
140169
$response = $this->head($url, $options);
141170

142171
return ResourceMetadata::fromResponseHeaders($response->getHeaders(false));
143172
}
144173

174+
private static function getParentContainerUrl(string $url): ?string
175+
{
176+
$trimmed = rtrim($url, '/');
177+
$lastSlash = strrpos($trimmed, '/');
178+
if (false === $lastSlash) {
179+
return null;
180+
}
181+
182+
$parent = substr($trimmed, 0, $lastSlash + 1);
183+
if (preg_match('#^https?://[^/]+/$#', $parent)) {
184+
return null;
185+
}
186+
187+
return $parent;
188+
}
189+
145190
public function request(string $method, string $url, array $options = []): ResponseInterface
146191
{
147192
if ($accessToken = $this->oidcClient?->getAccessToken()) {

tests/ContainerOperationsTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,42 @@ public function testGetContainerContentsWithRelativeIds(): void
9898
$this->assertSame('http://pod.example/data/file.txt', $entries[0]->url);
9999
$this->assertSame('http://pod.example/data/sub/', $entries[1]->url);
100100
}
101+
102+
public function testEnsureContainerExistsAlreadyExists(): void
103+
{
104+
$responses = [
105+
new MockResponse('', ['http_code' => 200]),
106+
];
107+
$httpClient = new MockHttpClient($responses);
108+
$client = new SolidClient($httpClient);
109+
110+
$client->ensureContainerExists('http://pod.example/existing/');
111+
112+
$this->assertSame(1, $httpClient->getRequestsCount());
113+
}
114+
115+
public function testEnsureContainerExistsCreates(): void
116+
{
117+
$requests = [];
118+
$httpClient = new MockHttpClient(static function (string $method, string $url) use (&$requests): MockResponse {
119+
$requests[] = [$method, $url];
120+
if ('HEAD' === $method && 'http://pod.example/parent/' === $url) {
121+
return new MockResponse('', ['http_code' => 200]);
122+
}
123+
if ('HEAD' === $method) {
124+
return new MockResponse('', ['http_code' => 404]);
125+
}
126+
if ('PUT' === $method) {
127+
return new MockResponse('', ['http_code' => 201]);
128+
}
129+
130+
return new MockResponse('', ['http_code' => 500]);
131+
});
132+
$client = new SolidClient($httpClient);
133+
134+
$client->ensureContainerExists('http://pod.example/parent/child/');
135+
136+
$methods = array_column($requests, 0);
137+
$this->assertContains('PUT', $methods);
138+
}
101139
}

0 commit comments

Comments
 (0)