You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/1-essentials/03-database.md
+26-19Lines changed: 26 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -308,34 +308,41 @@ final class User
308
308
309
309
The encryption key is taken from the `SIGNING_KEY` environment variable.
310
310
311
-
### DTO properties
311
+
### Data transfer object properties
312
312
313
-
Sometimes, you might want to store data objects as-is in a table, without there needing to be a relation to another table. To do so, it's enough to add a serializer and caster to the data object's class, and Tempest will know that these objects aren't meant to be treated as database models. Next, you can store the object's data as a json field on the table (see [migrations](#migrations) for more info).
313
+
You can store arbitrary objects directly in a `json` column when they don’t need to be part of the relational schema.
314
+
315
+
To do this, annotate the class with `#[Tempest\Mapper\SerializeAs]` and provide a unique identifier for the object’s serialized form. The identifier must map to a single, distinct class.
314
316
315
317
```php
316
-
use Tempest\Database\IsDatabaseModel;
317
-
use Tempest\Mapper\CastWith;
318
-
use Tempest\Mapper\SerializeWith;
319
-
use Tempest\Mapper\Casters\DtoCaster;
320
-
use Tempest\Mapper\Serializers\DtoSerializer;
318
+
use Tempest\Mapper\SerializeAs;
321
319
322
-
final class DebugItem
320
+
final class User implements Authenticatable
323
321
{
324
-
use IsDatabaseModel;
325
-
326
-
/* … */
327
-
328
-
public Backtrace $backtrace,
322
+
public PrimaryKey $id;
323
+
324
+
public function __construct(
325
+
public string $email,
326
+
#[Hashed, SensitiveParameter]
327
+
public ?string $password,
328
+
public Settings $settings,
329
+
) {}
329
330
}
330
331
331
-
#[CastWith(DtoCaster::class)]
332
-
#[SerializeWith(DtoSerializer::class)]
333
-
final class Backtrace
332
+
#[SerializeAs('user_settings')]
333
+
final class Settings
334
334
{
335
-
// This object won't be considered a relation,
336
-
// but rather serialized and stored in a JSON column.
@@ -202,6 +202,38 @@ final readonly class AddressSerializer implements Serializer
202
202
203
203
Of course, Tempest provides casters and serializers for the most common data types, including arrays, booleans, dates, enumerations, integers and value objects.
204
204
205
+
### Registering casters and serializers globally
206
+
207
+
You may register casters and serializers globally, so you don't have to specify them for every property. This is useful for value objects that are used frequently. To do so, you may implement the {`\Tempest\Mapper\DynamicCaster`} or {`\Tempest\Mapper\DynamicSerializer`} interface, which require an `accepts` method:
208
+
209
+
```php app/AddressSerializer.php
210
+
use Tempest\Mapper\Serializer;
211
+
use Tempest\Mapper\DynamicSerializer;
212
+
213
+
final readonly class AddressSerializer implements Serializer, DynamicSerializer
214
+
{
215
+
public static function accepts(PropertyReflector|TypeReflector $input): bool
216
+
{
217
+
$type = $input instanceof PropertyReflector
218
+
? $input->getType()
219
+
: $input;
220
+
221
+
return $type->matches(Address::class);
222
+
}
223
+
224
+
public function serialize(mixed $input): array|string
225
+
{
226
+
if (! $input instanceof Address) {
227
+
throw new CannotSerializeValue(Address::class);
228
+
}
229
+
230
+
return $input->toArray();
231
+
}
232
+
}
233
+
```
234
+
235
+
Dynamic serializers and casters will automatically be discovered by Tempest.
236
+
205
237
### Specifying casters or serializers for properties
206
238
207
239
You may use a specific caster or serializer for a property by using the {b`#[Tempest\Mapper\CastWith]`} or {b`#[Tempest\Mapper\SerializeWith]`} attribute, respectively.
@@ -218,21 +250,147 @@ final class User
218
250
219
251
You may of course use {b`#[Tempest\Mapper\CastWith]`} and {b`#[Tempest\Mapper\SerializeWith]`} together.
220
252
221
-
### Registering casters and serializers globally
253
+
##Mapping contexts
222
254
223
-
You may register casters and serializers globally, so you don't have to specify them for every property. This is useful for value objects that are used frequently.
255
+
Contexts allow you to use different casters, serializers, and mappers depending on the situation. For example, you might want to serialize dates differently for an API response versus database storage, or apply different validation rules for different contexts.
256
+
257
+
### Using contexts
258
+
259
+
You may specify a context when mapping by using the `in()` method. Contexts can be provided as a string, an enum, or a {b`\Tempest\Mapper\Context`} object.
final readonly class ApiDateSerializer implements Serializer, DynamicSerializer
281
+
{
282
+
public static function accepts(PropertyReflector|TypeReflector $input): bool
283
+
{
284
+
$type = $input instanceof PropertyReflector
285
+
? $input->getType()
286
+
: $input;
287
+
288
+
return $type->matches(DateTime::class);
289
+
}
290
+
291
+
public function serialize(mixed $input): string
292
+
{
293
+
return $input->format(FormatPattern::ISO8601);
294
+
}
295
+
}
236
296
```
237
297
238
-
If you're looking for the right place where to put this logic, [provider classes](/docs/extra-topics/package-development#provider-classes) is our recommendation.
298
+
This serializer will only be used when mapping with `->in(SerializationContext::API)`. Without a context specified, or in other contexts, the default serializers will be used.
299
+
300
+
### Injecting context into casters and serializers
301
+
302
+
You may inject the current context into your caster or serializer constructor to adapt behavior dynamically. Note that the context property has to be named `$context`. You may also inject any other dependency from the container.
303
+
304
+
```php
305
+
use Tempest\Mapper\Context;
306
+
use Tempest\Mapper\Serializer;
307
+
308
+
#[Context(DatabaseContext::class)]
309
+
final class BooleanSerializer implements Serializer, DynamicSerializer
310
+
{
311
+
public function __construct(
312
+
private DatabaseContext $context,
313
+
) {}
314
+
315
+
public static function accepts(PropertyReflector|TypeReflector $type): bool
Sometimes, a caster or serializer needs to be configured based on the property it's applied to. For example, an enum caster needs to know which enum class to use, or an object caster needs to know the target type.
337
+
338
+
Implement the {b`\Tempest\Mapper\ConfigurableCaster`} or {b`\Tempest\Mapper\ConfigurableSerializer`} interface to create casters/serializers that are configured per property:
339
+
340
+
```php
341
+
use Tempest\Mapper\Caster;
342
+
use Tempest\Mapper\ConfigurableCaster;
343
+
use Tempest\Mapper\Context;
344
+
use Tempest\Mapper\DynamicCaster;
345
+
use Tempest\Reflection\PropertyReflector;
346
+
347
+
final readonly class EnumCaster implements Caster, DynamicCaster, ConfigurableCaster
348
+
{
349
+
/**
350
+
* @param class-string<UnitEnum> $enum
351
+
*/
352
+
public function __construct(
353
+
private string $enum,
354
+
) {}
355
+
356
+
public static function accepts(PropertyReflector|TypeReflector $input): bool
357
+
{
358
+
$type = $input instanceof PropertyReflector
359
+
? $input->getType()
360
+
: $input;
361
+
362
+
return $type->matches(UnitEnum::class);
363
+
}
364
+
365
+
public static function configure(PropertyReflector $property, Context $context): self
366
+
{
367
+
// Create a new instance configured for this specific property
368
+
return new self(enum: $property->getType()->getName());
369
+
}
370
+
371
+
public function cast(mixed $input): ?object
372
+
{
373
+
if ($input === null) {
374
+
return null;
375
+
}
376
+
377
+
// Use the configured enum class
378
+
return $this->enum::from($input);
379
+
}
380
+
}
381
+
```
382
+
383
+
The `configure()` method receives the property being mapped and the current context, allowing you to create a caster instance tailored to that specific property.
384
+
385
+
Note that `ConfigurableSerializer::configure()` can receive either a `PropertyReflector`, `TypeReflector`, or `string`, depending on whether it's being used for property mapping or value serialization.
386
+
387
+
### When to use configurable casters and serializers
388
+
389
+
Use configurable casters and serializers when:
390
+
391
+
- The caster/serializer behavior depends on the specific property type (e.g., enum class, object class)
392
+
- You need access to property attributes or metadata
393
+
- Different properties of the same base type require different handling
394
+
- You want to avoid creating many similar caster/serializer classes
395
+
396
+
For simple, static behavior that doesn't depend on property information, regular casters and serializers are sufficient.
0 commit comments