Skip to content

Commit 2e5b734

Browse files
Fix object-backed entity hydration (#358)
1 parent 37fbcdf commit 2e5b734

7 files changed

Lines changed: 383 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGE LOG
66

77
* Add PHP 8.5 support
88
* Fixed the `Droplet::create` SSH keys parameter type documentation
9+
* Fixed hydration of object-backed App Platform and project resource fields
910

1011

1112
## 5.0.5 (03/05/2025)

src/Entity/App.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,33 @@ final class App extends AbstractEntity
4949
public string $liveDomain;
5050

5151
public array $domains;
52+
53+
public function build(array $parameters): void
54+
{
55+
foreach ($parameters as $property => $value) {
56+
if (
57+
\in_array(static::convertToCamelCase($property), ['spec', 'activeDeployment', 'inProgressDeployment', 'region', 'domains'], true) &&
58+
($value instanceof \stdClass || \is_array($value))
59+
) {
60+
$parameters[$property] = self::normalizeArray($value);
61+
}
62+
}
63+
64+
parent::build($parameters);
65+
}
66+
67+
private static function normalizeArray(array|\stdClass $value): array
68+
{
69+
if ($value instanceof \stdClass) {
70+
$value = \get_object_vars($value);
71+
}
72+
73+
foreach ($value as $key => $subValue) {
74+
if ($subValue instanceof \stdClass || \is_array($subValue)) {
75+
$value[$key] = self::normalizeArray($subValue);
76+
}
77+
}
78+
79+
return $value;
80+
}
5281
}

src/Entity/AppDeployment.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,33 @@ final class AppDeployment extends AbstractEntity
4747
public string $phase;
4848

4949
public string $tierSlug;
50+
51+
public function build(array $parameters): void
52+
{
53+
foreach ($parameters as $property => $value) {
54+
if (
55+
\in_array(static::convertToCamelCase($property), ['spec', 'services', 'staticSites', 'workers', 'jobs', 'progress'], true) &&
56+
($value instanceof \stdClass || \is_array($value))
57+
) {
58+
$parameters[$property] = self::normalizeArray($value);
59+
}
60+
}
61+
62+
parent::build($parameters);
63+
}
64+
65+
private static function normalizeArray(array|\stdClass $value): array
66+
{
67+
if ($value instanceof \stdClass) {
68+
$value = \get_object_vars($value);
69+
}
70+
71+
foreach ($value as $key => $subValue) {
72+
if ($subValue instanceof \stdClass || \is_array($subValue)) {
73+
$value[$key] = self::normalizeArray($subValue);
74+
}
75+
}
76+
77+
return $value;
78+
}
5079
}

src/Entity/ProjectResource.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,15 @@ final class ProjectResource extends AbstractEntity
2727
public array $links;
2828

2929
public string $status;
30+
31+
public function build(array $parameters): void
32+
{
33+
foreach ($parameters as $property => $value) {
34+
if ('links' === static::convertToCamelCase($property) && $value instanceof \stdClass) {
35+
$parameters[$property] = \get_object_vars($value);
36+
}
37+
}
38+
39+
parent::build($parameters);
40+
}
3041
}

tests/Entity/AppDeploymentTest.php

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public function testConstructor(): void
3232
'staticSites' => [],
3333
'workers' => [],
3434
'jobs' => [],
35+
'phaseLastUpdatedAt' => '2021-02-10T17:05:30Z',
3536
'createdAt' => '2021-02-10T17:05:30Z',
3637
'updatedAt' => '2021-02-10T17:05:30Z',
3738
'cause' => 'cause',
@@ -51,6 +52,7 @@ public function testConstructor(): void
5152
self::assertSame($values['staticSites'], $entity->staticSites);
5253
self::assertSame($values['workers'], $entity->workers);
5354
self::assertSame($values['jobs'], $entity->jobs);
55+
self::assertSame($values['phaseLastUpdatedAt'], $entity->phaseLastUpdatedAt);
5456
self::assertSame($values['createdAt'], $entity->createdAt);
5557
self::assertSame($values['updatedAt'], $entity->updatedAt);
5658
self::assertSame($values['cause'], $entity->cause);
@@ -60,9 +62,172 @@ public function testConstructor(): void
6062
self::assertSame($values['tierSlug'], $entity->tierSlug);
6163

6264
self::assertSame($values['staticSites'], $entity->static_sites);
65+
self::assertSame($values['phaseLastUpdatedAt'], $entity->phase_last_updated_at);
6366
self::assertSame($values['createdAt'], $entity->created_at);
6467
self::assertSame($values['updatedAt'], $entity->updated_at);
6568
self::assertSame($values['clonedFrom'], $entity->cloned_from);
6669
self::assertSame($values['tierSlug'], $entity->tier_slug);
6770
}
71+
72+
public function testConstructorAcceptsApiShapedObjects(): void
73+
{
74+
$payload = \json_decode(<<<'JSON'
75+
{
76+
"id": "b6bdf840-2854-4f87-a36c-5f231c617c84",
77+
"spec": {
78+
"name": "sample-golang",
79+
"services": [
80+
{
81+
"name": "web",
82+
"github": {
83+
"repo": "digitalocean/sample-golang",
84+
"branch": "main"
85+
},
86+
"routes": [
87+
{
88+
"path": "/"
89+
}
90+
]
91+
}
92+
]
93+
},
94+
"services": [
95+
{
96+
"name": "web",
97+
"source_commit_hash": "abc123",
98+
"alerts": [
99+
{
100+
"rule": "CPU_UTILIZATION"
101+
}
102+
]
103+
}
104+
],
105+
"static_sites": [
106+
{
107+
"name": "docs",
108+
"routes": [
109+
{
110+
"path": "/docs"
111+
}
112+
]
113+
}
114+
],
115+
"workers": [
116+
{
117+
"name": "queue",
118+
"instance_count": 1
119+
}
120+
],
121+
"jobs": [
122+
{
123+
"name": "migrate",
124+
"kind": "POST_DEPLOY"
125+
}
126+
],
127+
"phase_last_updated_at": "2024-01-02T00:03:00Z",
128+
"created_at": "2024-01-02T00:00:00Z",
129+
"updated_at": "2024-01-02T00:04:00Z",
130+
"cause": "manual",
131+
"cloned_from": "dep-0",
132+
"progress": {
133+
"success_steps": 1,
134+
"steps": [
135+
{
136+
"name": "build",
137+
"components": [
138+
{
139+
"name": "web"
140+
}
141+
]
142+
}
143+
]
144+
},
145+
"phase": "ACTIVE",
146+
"tier_slug": "basic"
147+
}
148+
JSON, false, 512, \JSON_THROW_ON_ERROR);
149+
150+
$expectedSpec = [
151+
'name' => 'sample-golang',
152+
'services' => [
153+
[
154+
'name' => 'web',
155+
'github' => [
156+
'repo' => 'digitalocean/sample-golang',
157+
'branch' => 'main',
158+
],
159+
'routes' => [
160+
[
161+
'path' => '/',
162+
],
163+
],
164+
],
165+
],
166+
];
167+
$expectedServices = [
168+
[
169+
'name' => 'web',
170+
'source_commit_hash' => 'abc123',
171+
'alerts' => [
172+
[
173+
'rule' => 'CPU_UTILIZATION',
174+
],
175+
],
176+
],
177+
];
178+
$expectedStaticSites = [
179+
[
180+
'name' => 'docs',
181+
'routes' => [
182+
[
183+
'path' => '/docs',
184+
],
185+
],
186+
],
187+
];
188+
$expectedWorkers = [
189+
[
190+
'name' => 'queue',
191+
'instance_count' => 1,
192+
],
193+
];
194+
$expectedJobs = [
195+
[
196+
'name' => 'migrate',
197+
'kind' => 'POST_DEPLOY',
198+
],
199+
];
200+
$expectedProgress = [
201+
'success_steps' => 1,
202+
'steps' => [
203+
[
204+
'name' => 'build',
205+
'components' => [
206+
[
207+
'name' => 'web',
208+
],
209+
],
210+
],
211+
],
212+
];
213+
214+
$entity = new AppDeployment($payload);
215+
216+
self::assertInstanceOf(AbstractEntity::class, $entity);
217+
self::assertInstanceOf(AppDeployment::class, $entity);
218+
self::assertSame('b6bdf840-2854-4f87-a36c-5f231c617c84', $entity->id);
219+
self::assertSame($expectedSpec, $entity->spec);
220+
self::assertSame($expectedServices, $entity->services);
221+
self::assertSame($expectedStaticSites, $entity->staticSites);
222+
self::assertSame($expectedWorkers, $entity->workers);
223+
self::assertSame($expectedJobs, $entity->jobs);
224+
self::assertSame($expectedProgress, $entity->progress);
225+
self::assertSame('2024-01-02T00:03:00Z', $entity->phaseLastUpdatedAt);
226+
self::assertSame('dep-0', $entity->clonedFrom);
227+
self::assertSame('basic', $entity->tierSlug);
228+
self::assertSame($expectedStaticSites, $entity->static_sites);
229+
self::assertSame('2024-01-02T00:03:00Z', $entity->phase_last_updated_at);
230+
self::assertSame('dep-0', $entity->cloned_from);
231+
self::assertSame('basic', $entity->tier_slug);
232+
}
68233
}

0 commit comments

Comments
 (0)