Skip to content

Commit 35890d8

Browse files
authored
Merge pull request #116: Added the ability to specify typecasts in the Embeddable attribute
2 parents b1b9c4a + 306033f commit 35890d8

8 files changed

Lines changed: 209 additions & 1 deletion

File tree

‎src/Annotation/Embeddable.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ class Embeddable
2020
* @param class-string|null $mapper Mapper class name. Defaults to {@see \Cycle\ORM\Mapper\Mapper}.
2121
* @param string $columnPrefix Custom prefix for embeddable entity columns.
2222
* @param Column[] $columns Embedded entity columns.
23+
* @param non-empty-string|non-empty-string[]|null $typecast Typecast handlers for entity columns.
2324
*/
2425
public function __construct(
2526
protected ?string $role = null,
2627
protected ?string $mapper = null,
2728
protected string $columnPrefix = '',
2829
protected array $columns = [],
30+
protected array|string|null $typecast = null,
2931
) {}
3032

3133
/**
@@ -56,4 +58,12 @@ public function getColumns(): array
5658
{
5759
return $this->columns;
5860
}
61+
62+
/**
63+
* @return non-empty-string|non-empty-string[]|null
64+
*/
65+
public function getTypecast(): array|string|null
66+
{
67+
return $this->typecast;
68+
}
5969
}

‎src/Annotation/Entity.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Entity
2929
* @param non-empty-string|null $database Database name. Defaults to null (default database).
3030
* @param class-string<Source>|null $source Entity source class (internal).
3131
* Defaults to {@see \Cycle\ORM\Select\Source}
32-
* @param non-empty-string|non-empty-string[]|null $typecast
32+
* @param non-empty-string|non-empty-string[]|null $typecast Typecast handlers for entity columns.
3333
* @param class-string<Scope>|null $scope Class name of constraint to be applied to every entity query.
3434
* @param Column[] $columns Entity columns.
3535
* @param ForeignKey[] $foreignKeys Entity foreign keys.

‎src/Configurator.php‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ public function initEmbedding(Embeddable $emb, \ReflectionClass $class): EntityS
9191
// representing classes
9292
$e->setMapper($this->resolveName($emb->getMapper(), $class));
9393

94+
$typecast = $emb->getTypecast();
95+
96+
if (\is_array($typecast)) {
97+
/** @var non-empty-string[] $typecast */
98+
$typecast = \array_map(fn(string $value): string => $this->resolveName($value, $class), $typecast);
99+
} else {
100+
/** @var non-empty-string|null $typecast */
101+
$typecast = $this->resolveName($typecast, $class);
102+
}
103+
104+
$e->setTypecast($typecast);
105+
94106
return $e;
95107
}
96108

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Fixtures\Fixtures26;
6+
7+
use Cycle\Annotated\Annotation\Column;
8+
use Cycle\Annotated\Annotation\Embeddable;
9+
10+
/**
11+
* @Embeddable(
12+
* role="address",
13+
* columnPrefix="address_",
14+
* typecast={"Cycle\Annotated\Tests\Fixtures\Fixtures26\CityTypecast"}
15+
* )
16+
*/
17+
#[Embeddable(
18+
role: 'address',
19+
columnPrefix: 'address_',
20+
typecast: [
21+
CityTypecast::class,
22+
],
23+
)]
24+
class Address
25+
{
26+
/** @Column(type="string", typecast="city") */
27+
#[Column(type: 'string', typecast: 'city')]
28+
protected City $city;
29+
30+
/** @Column(type="string") */
31+
#[Column(type: 'string')]
32+
protected $country;
33+
34+
/** @Column(type="string") */
35+
#[Column(type: 'string')]
36+
protected $address;
37+
38+
/** @Column(type="int") */
39+
#[Column(type: 'integer')]
40+
protected $zipcode;
41+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Fixtures\Fixtures26;
6+
7+
final class City
8+
{
9+
private string $value;
10+
11+
private function __construct(string $value)
12+
{
13+
$this->value = $value;
14+
}
15+
16+
public function __toString(): string
17+
{
18+
return $this->value;
19+
}
20+
21+
public static function fromString(string $value): self
22+
{
23+
return new self($value);
24+
}
25+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Fixtures\Fixtures26;
6+
7+
use Cycle\ORM\Parser\CastableInterface;
8+
use Cycle\ORM\Parser\UncastableInterface;
9+
10+
final class CityTypecast implements CastableInterface, UncastableInterface
11+
{
12+
private array $rules = [];
13+
14+
public function cast(array $data): array
15+
{
16+
foreach (array_keys($this->rules) as $column) {
17+
if (!isset($data[$column])) {
18+
continue;
19+
}
20+
21+
if (!\is_string($data[$column]) || $data[$column] === '') {
22+
$data[$column] = null;
23+
24+
continue;
25+
}
26+
27+
$data[$column] = City::fromString($data[$column]);
28+
}
29+
30+
return $data;
31+
}
32+
33+
public function uncast(array $data): array
34+
{
35+
foreach (array_keys($this->rules) as $column) {
36+
if (!isset($data[$column])) {
37+
continue;
38+
}
39+
40+
$value = $data[$column];
41+
42+
if (!$value instanceof City) {
43+
continue;
44+
}
45+
46+
$data[$column] = (string)$value;
47+
}
48+
49+
return $data;
50+
}
51+
52+
public function setRules(array $rules): array
53+
{
54+
/** @var non-empty-string $rule */
55+
foreach ($rules as $key => $rule) {
56+
if ($rule !== 'city') {
57+
continue;
58+
}
59+
60+
unset($rules[$key]);
61+
62+
$this->rules[$key] = $rule;
63+
}
64+
65+
return $rules;
66+
}
67+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Fixtures\Fixtures26;
6+
7+
use Cycle\Annotated\Annotation\Column;
8+
use Cycle\Annotated\Annotation\Entity;
9+
use Cycle\Annotated\Annotation\Relation\Embedded;
10+
11+
/**
12+
* @Entity()
13+
*/
14+
#[Entity]
15+
class User
16+
{
17+
/** @Column(type="primary") */
18+
#[Column(type: 'primary')]
19+
protected $id;
20+
21+
/** @Embedded(target=Address::class) */
22+
#[Embedded(target: Address::class)]
23+
protected $address;
24+
}

‎tests/Annotated/Functional/Driver/Common/Relation/EmbeddedTestCase.php‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Cycle\Annotated\Locator\TokenizerEntityLocator;
1111
use Cycle\Annotated\MergeColumns;
1212
use Cycle\Annotated\MergeIndexes;
13+
use Cycle\Annotated\Tests\Fixtures\Fixtures26\CityTypecast;
1314
use Cycle\Annotated\Tests\Functional\Driver\Common\BaseTestCase;
1415
use Cycle\ORM\Relation;
1516
use Cycle\ORM\Schema;
@@ -137,4 +138,32 @@ public function testEmbeddedPrefix(ReaderInterface $reader): void
137138
$this->assertSame($address, $schema['user:address:address'][Schema::COLUMNS]);
138139
$this->assertSame($workAddress, $schema['user:address:workAddress'][Schema::COLUMNS]);
139140
}
141+
142+
#[DataProvider('allReadersProvider')]
143+
public function testEmbeddedTypecast(ReaderInterface $reader): void
144+
{
145+
$tokenizer = new Tokenizer(new TokenizerConfig([
146+
'directories' => [__DIR__ . '/../../../../Fixtures/Fixtures26'],
147+
'exclude' => [],
148+
]));
149+
150+
$locator = $tokenizer->classLocator();
151+
152+
$r = new Registry($this->dbal);
153+
154+
$schema = (new Compiler())->compile($r, [
155+
new Embeddings(new TokenizerEmbeddingLocator($locator, $reader), $reader),
156+
new Entities(new TokenizerEntityLocator($locator, $reader), $reader),
157+
new ResetTables(),
158+
new MergeColumns($reader),
159+
new GenerateRelations(),
160+
new RenderTables(),
161+
new RenderRelations(),
162+
new MergeIndexes($reader),
163+
new SyncTables(),
164+
new GenerateTypecast(),
165+
]);
166+
167+
$this->assertSame(CityTypecast::class, $schema['user:address:address'][Schema::TYPECAST_HANDLER][0]);
168+
}
140169
}

0 commit comments

Comments
 (0)