Skip to content

Commit 9b802b8

Browse files
authored
feat(database): support dto fields (#1305)
1 parent 3892651 commit 9b802b8

21 files changed

+384
-10
lines changed

packages/database/src/Builder/ModelInspector.php

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use Tempest\Database\Relation;
1212
use Tempest\Database\Table;
1313
use Tempest\Database\Virtual;
14+
use Tempest\Mapper\CastWith;
15+
use Tempest\Mapper\SerializeWith;
1416
use Tempest\Reflection\ClassReflector;
1517
use Tempest\Reflection\PropertyReflector;
1618
use Tempest\Support\Arr\ImmutableArray;
@@ -130,10 +132,18 @@ public function getBelongsTo(string $name): ?BelongsTo
130132
return $belongsTo;
131133
}
132134

135+
if ($property->hasAttribute(Virtual::class)) {
136+
return null;
137+
}
138+
133139
if (! $property->getType()->isRelation()) {
134140
return null;
135141
}
136142

143+
if ($property->hasAttribute(SerializeWith::class) || $property->getType()->asClass()->haSAttribute(SerializeWith::class)) {
144+
return null;
145+
}
146+
137147
if ($property->hasAttribute(HasOne::class)) {
138148
return null;
139149
}
@@ -189,6 +199,10 @@ public function getHasMany(string $name): ?HasMany
189199
return $hasMany;
190200
}
191201

202+
if ($property->hasAttribute(Virtual::class)) {
203+
return null;
204+
}
205+
192206
if (! $property->getIterableType()?->isRelation()) {
193207
return null;
194208
}
@@ -199,6 +213,20 @@ public function getHasMany(string $name): ?HasMany
199213
return $hasMany;
200214
}
201215

216+
public function isRelation(string|PropertyReflector $name): bool
217+
{
218+
$name = ($name instanceof PropertyReflector) ? $name->getName() : $name;
219+
220+
return $this->getBelongsTo($name) !== null || $this->getHasOne($name) !== null || $this->getHasMany($name) !== null;
221+
}
222+
223+
public function getRelation(string|PropertyReflector $name): ?Relation
224+
{
225+
$name = ($name instanceof PropertyReflector) ? $name->getName() : $name;
226+
227+
return $this->getBelongsTo($name) ?? $this->getHasOne($name) ?? $this->getHasMany($name);
228+
}
229+
202230
public function getSelectFields(): ImmutableArray
203231
{
204232
if (! $this->isObjectModel()) {
@@ -228,13 +256,6 @@ public function getSelectFields(): ImmutableArray
228256
return $selectFields;
229257
}
230258

231-
public function getRelation(string|PropertyReflector $name): ?Relation
232-
{
233-
$name = ($name instanceof PropertyReflector) ? $name->getName() : $name;
234-
235-
return $this->getBelongsTo($name) ?? $this->getHasOne($name) ?? $this->getHasMany($name);
236-
}
237-
238259
public function resolveRelations(string $relationString, string $parent = ''): array
239260
{
240261
if ($relationString === '') {

packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private function resolveData(): array
119119
$value = $property->getValue($model);
120120

121121
// BelongsTo and reverse HasMany relations are included
122-
if ($property->getType()->isRelation()) {
122+
if ($definition->isRelation($property)) {
123123
$column .= '_id';
124124

125125
$value = match (true) {

packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private function resolveValues(): ImmutableArray
114114
throw new CannotUpdateHasOneRelation($modelClass->getName(), $property->getName());
115115
}
116116

117-
if ($property->getType()->isRelation()) {
117+
if ($modelDefinition->isRelation($property)) {
118118
$column .= '_id';
119119

120120
$value = match (true) {

packages/database/src/IsDatabaseModel.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,12 @@ public static function all(array $relations = []): array
4545
->all();
4646
}
4747

48-
public static function get(Id $id, array $relations = []): ?self
48+
public static function get(string|int|Id $id, array $relations = []): ?self
4949
{
50+
if (! ($id instanceof Id)) {
51+
$id = new Id($id);
52+
}
53+
5054
return self::select()
5155
->with(...$relations)
5256
->get($id);

packages/database/src/QueryStatements/CreateTableStatement.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,20 @@ public function json(
189189
return $this;
190190
}
191191

192+
public function dto(
193+
string $name,
194+
bool $nullable = false,
195+
?string $default = null,
196+
): self {
197+
$this->statements[] = new JsonStatement(
198+
name: $name,
199+
nullable: $nullable,
200+
default: $default,
201+
);
202+
203+
return $this;
204+
}
205+
192206
public function array(
193207
string $name,
194208
bool $nullable = false,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Tempest\Mapper\Casters;
4+
5+
use Tempest\Mapper\Caster;
6+
use Tempest\Mapper\Exceptions\CannotCastValue;
7+
8+
use function Tempest\map;
9+
10+
final class DtoCaster implements Caster
11+
{
12+
public function cast(mixed $input): mixed
13+
{
14+
if (! json_validate($input)) {
15+
throw new CannotCastValue('json string');
16+
}
17+
18+
['type' => $type, 'data' => $data] = json_decode($input, true);
19+
20+
return map($data)->to($type);
21+
}
22+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Mapper\Exceptions;
6+
7+
use Exception;
8+
9+
final class CannotCastValue extends Exception
10+
{
11+
public function __construct(string $expectedType)
12+
{
13+
parent::__construct('Could not cast value, input should be of type ' . $expectedType);
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Tempest\Mapper\Serializers;
4+
5+
use Tempest\Mapper\Exceptions\CannotSerializeValue;
6+
use Tempest\Mapper\Serializer;
7+
8+
use function Tempest\map;
9+
10+
final class DtoSerializer implements Serializer
11+
{
12+
public function serialize(mixed $input): array|string
13+
{
14+
if (! is_object($input)) {
15+
throw new CannotSerializeValue('object');
16+
}
17+
18+
$data = map($input)->toArray();
19+
20+
return json_encode([
21+
'type' => get_class($input),
22+
'data' => $data,
23+
]);
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Database\Fixtures;
4+
5+
use Tempest\Mapper\Casters\DtoCaster;
6+
use Tempest\Mapper\CastWith;
7+
use Tempest\Mapper\Serializers\DtoSerializer;
8+
use Tempest\Mapper\SerializeWith;
9+
10+
#[CastWith(DtoCaster::class)]
11+
#[SerializeWith(DtoSerializer::class)]
12+
final class DtoForModelWithSerializer
13+
{
14+
public function __construct(
15+
public string $data,
16+
) {}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Integration\Database\Fixtures;
4+
5+
final class DtoForModelWithSerializerOnProperty
6+
{
7+
public function __construct(
8+
public string $data,
9+
) {}
10+
}

0 commit comments

Comments
 (0)