Skip to content

Commit 398ef06

Browse files
koriymclaude
andcommitted
feat: add ToArray functionality for flattening Input objects
- Add ToArrayInterface for converting Input objects to flat arrays - Add ToArray implementation with recursive object flattening - Handle property name conflicts (later values overwrite earlier ones) - Preserve arrays for SQL IN clause compatibility - Ignore private/protected properties (public only) - Add comprehensive test suite covering all edge cases This enables SQL parameter binding from nested Input objects: $params = $toArray($orderInput); // Flat array for SQL queries 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3f5f77b commit 398ef06

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed

src/ToArray.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Ray\InputQuery;
6+
7+
use Override;
8+
use ReflectionClass;
9+
use ReflectionProperty;
10+
11+
use function is_object;
12+
13+
final class ToArray implements ToArrayInterface
14+
{
15+
/**
16+
* {@inheritDoc}
17+
*/
18+
#[Override]
19+
public function __invoke(object $input): array
20+
{
21+
return $this->extractProperties($input);
22+
}
23+
24+
/** @return array<string, mixed> */
25+
private function extractProperties(object $object): array
26+
{
27+
$result = [];
28+
$reflection = new ReflectionClass($object);
29+
30+
foreach ($reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
31+
/** @var mixed $value */
32+
$value = $property->getValue($object);
33+
$name = $property->getName();
34+
35+
if (is_object($value)) {
36+
// Recursively extract nested objects
37+
$nestedProperties = $this->extractProperties($value);
38+
/** @var mixed $nestedValue */
39+
foreach ($nestedProperties as $nestedName => $nestedValue) {
40+
/** @psalm-suppress MixedAssignment */
41+
$result[$nestedName] = $nestedValue;
42+
}
43+
44+
continue;
45+
}
46+
47+
// Keep arrays and scalar values as-is
48+
/** @psalm-suppress MixedAssignment */
49+
$result[$name] = $value;
50+
}
51+
52+
return $result;
53+
}
54+
}

src/ToArrayInterface.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Ray\InputQuery;
6+
7+
interface ToArrayInterface
8+
{
9+
/**
10+
* Convert Input object to flat associative array
11+
*
12+
* @param object $input Input object with #[Input] attributes
13+
*
14+
* @return array<string, mixed> Flat associative array
15+
*/
16+
public function __invoke(object $input): array;
17+
}

tests/ToArrayTest.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Ray\InputQuery;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Ray\InputQuery\Attribute\Input;
9+
10+
final class ToArrayTest extends TestCase
11+
{
12+
private ToArrayInterface $toArray;
13+
14+
protected function setUp(): void
15+
{
16+
$this->toArray = new ToArray();
17+
}
18+
19+
public function testSimpleObject(): void
20+
{
21+
$input = new class {
22+
public function __construct(
23+
#[Input]
24+
public readonly string $name = 'John',
25+
#[Input]
26+
public readonly int $age = 30,
27+
) {
28+
}
29+
};
30+
31+
$result = ($this->toArray)($input);
32+
33+
$this->assertSame(['name' => 'John', 'age' => 30], $result);
34+
}
35+
36+
public function testNestedObject(): void
37+
{
38+
$author = new class {
39+
public function __construct(
40+
#[Input]
41+
public readonly string $name = 'John',
42+
#[Input]
43+
public readonly string $email = 'john@example.com',
44+
) {
45+
}
46+
};
47+
48+
$article = new class {
49+
#[Input]
50+
public readonly string $title;
51+
52+
#[Input]
53+
public readonly object $author;
54+
55+
public function __construct()
56+
{
57+
$this->title = 'Hello World';
58+
$this->author = new class {
59+
public function __construct(
60+
#[Input]
61+
public readonly string $name = 'John',
62+
#[Input]
63+
public readonly string $email = 'john@example.com',
64+
) {
65+
}
66+
};
67+
}
68+
};
69+
70+
$result = ($this->toArray)($article);
71+
72+
$this->assertSame([
73+
'title' => 'Hello World',
74+
'name' => 'John',
75+
'email' => 'john@example.com',
76+
], $result);
77+
}
78+
79+
public function testArrayProperty(): void
80+
{
81+
$input = new class {
82+
/** @param array<int> $userIds */
83+
public function __construct(
84+
#[Input]
85+
public readonly string $status = 'active',
86+
#[Input]
87+
public readonly array $userIds = [1, 2, 3],
88+
) {
89+
}
90+
};
91+
92+
$result = ($this->toArray)($input);
93+
94+
$this->assertSame([
95+
'status' => 'active',
96+
'userIds' => [1, 2, 3],
97+
], $result);
98+
}
99+
100+
public function testPropertyNameConflict(): void
101+
{
102+
$order = new class {
103+
#[Input]
104+
public readonly string $id;
105+
106+
#[Input]
107+
public readonly object $customer;
108+
109+
public function __construct()
110+
{
111+
$this->id = 'order-456';
112+
$this->customer = new class {
113+
public function __construct(
114+
#[Input]
115+
public readonly string $id = 'customer-123',
116+
#[Input]
117+
public readonly string $name = 'John',
118+
) {
119+
}
120+
};
121+
}
122+
};
123+
124+
$result = ($this->toArray)($order);
125+
126+
// Later property overwrites earlier one
127+
$this->assertSame([
128+
'id' => 'customer-123',
129+
'name' => 'John',
130+
], $result);
131+
}
132+
133+
public function testPrivatePropertiesIgnored(): void
134+
{
135+
$input = new class {
136+
public function __construct(
137+
#[Input]
138+
public readonly string $public = 'visible',
139+
#[Input]
140+
private readonly string $private = 'hidden',
141+
) {
142+
}
143+
};
144+
145+
$result = ($this->toArray)($input);
146+
147+
$this->assertSame(['public' => 'visible'], $result);
148+
$this->assertArrayNotHasKey('private', $result);
149+
}
150+
151+
public function testNullValues(): void
152+
{
153+
$input = new class {
154+
public function __construct(
155+
#[Input]
156+
public readonly string $name = 'John',
157+
#[Input]
158+
public readonly string|null $email = null,
159+
#[Input]
160+
public readonly object|null $address = null,
161+
) {
162+
}
163+
};
164+
165+
$result = ($this->toArray)($input);
166+
167+
$this->assertSame([
168+
'name' => 'John',
169+
'email' => null,
170+
'address' => null,
171+
], $result);
172+
}
173+
}

0 commit comments

Comments
 (0)