Skip to content

Conversation

@innocenzi
Copy link
Member

@innocenzi innocenzi commented Dec 8, 2025

Depends on #1789
Closes #1522

This pull requests adds the concept of contexts to casters and serializers. It works by adding the ability to the mapper to use a context (string, enum or Context class that might hold data).

The mapper will inject the context to serializers/casters/mappers when resolved from the container, and will use serializers/casters registered for that context (or fall back to the default context).

This allows, for instance, the database to use dedicated serializers/casters. Before this pull request, we had to manually add a condition for boolean values to be transformed properly depending on the current database dialect. After this PR, this functionality works through a serializer:

#[Context(DatabaseContext::class)]
final class BooleanSerializer implements Serializer
{
    public function __construct(
        private DatabaseContext $context,
    ) {}

    public static function for(): array
    {
        return ['bool', 'boolean'];
    }

    public function serialize(mixed $input): string
    {
        if (! is_bool($input)) {
            throw new ValueCouldNotBeSerialized('boolean');
        }

        return match ($this->context->dialect) {
            DatabaseDialect::POSTGRESQL => $input ? 'true' : 'false',
            default => $input ? '1' : '0',
        };
    }
}

Serializers and casters are now discovered by Discovery instead of being registered through an initializer. Since ordering is important for them, they support priority ordering through Tempest\Core\Priority.

It's also possible for userland to register their own casters and serializers this way. To use a specific context, we use the in method on ObjectFactory (or SerializerFactory or CasterFactory):

$array = map($object)
    ->in('inertia')
    ->toArray();

To pass data to a caster/serializer/mapper, an object extending Context may be used instead of a string/enum. The database package now uses a DatabaseContext under the hood:

return map($query->fetch())
    ->with(SelectModelMapper::class)
    ->in(new DatabaseContext(dialect: $this->dialect))
    ->to($this->model->getName());

As seen above, the BooleanSerializer expects a DatabaseContext in its constructor. Note that for now, the injection works by parameter name, not parameter type.

@innocenzi innocenzi force-pushed the feat/contextual-mapper branch from 1ee7bde to a026a63 Compare December 8, 2025 13:02
@innocenzi innocenzi marked this pull request as ready for review December 9, 2025 17:22
@innocenzi innocenzi requested a review from brendt December 9, 2025 17:22
@innocenzi
Copy link
Member Author

This is ready for an initial review! What do we think about this approach?

@innocenzi innocenzi requested a review from brendt December 10, 2025 14:39
@innocenzi
Copy link
Member Author

@brendt ready for another review!

@innocenzi innocenzi force-pushed the feat/contextual-mapper branch from 9b83f8f to c848c21 Compare December 10, 2025 17:31
@innocenzi innocenzi force-pushed the feat/contextual-mapper branch from c848c21 to 23a8db6 Compare December 10, 2025 20:06
@innocenzi innocenzi force-pushed the feat/contextual-mapper branch from 23a8db6 to 55fd009 Compare December 10, 2025 22:08
@innocenzi innocenzi merged commit 7d5af17 into 3.x Dec 11, 2025
74 checks passed
@innocenzi innocenzi deleted the feat/contextual-mapper branch December 11, 2025 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants