Allow to create Custom Type with metadata accessible from convertToPHPValue #7576

Open
opened 2026-01-22 15:53:43 +01:00 by admin · 5 comments
Owner

Originally created by @nfacciolo on GitHub (Nov 19, 2025).

Feature Request

What

Allow Custom Types to access field mapping metadata (like options or a new class parameter) in convertToPHPValue() and convertToDatabaseValue().

Example usage:

#[ORM\Column(type: 'string_vo', class: Firstname::class)]
private Firstname $firstName;

#[ORM\Column(type: 'string_vo', class: Email::class)]
private Email $email;

Why

Currently, creating Value Objects requires one Custom Type per class, even when the conversion logic is identical (string ↔ VO with __toString()).

The enumType parameter solves this elegantly for enums, but there's no equivalent for custom objects.

This leads to:

  • Boilerplate: dozens of nearly identical Type classes
  • Configuration overhead: one YAML entry per VO
  • Maintenance burden: any change must be replicated across all types

A generic approach would allow a single StringValueObjectType to handle all string-based Value Objects, similar to how enumType works.

How

Pass field mapping metadata to Type methods:

public function convertToPHPValue(
    mixed $value, 
    AbstractPlatform $platform,
    array $fieldMapping = []  // New optional parameter
): mixed {
    $class = $fieldMapping['class'] ?? null;
    if ($class && $value !== null) {
        return new $class($value);
    }
    return $value;
}

Alternatively, introduce a dedicated parameter like valueObjectClass similar to enumType.

This would maintain backward compatibility while enabling generic Type implementations for Value Objects.

Originally created by @nfacciolo on GitHub (Nov 19, 2025). ### Feature Request #### What Allow Custom Types to access field mapping metadata (like `options` or a new `class` parameter) in `convertToPHPValue()` and `convertToDatabaseValue()`. Example usage: ```php #[ORM\Column(type: 'string_vo', class: Firstname::class)] private Firstname $firstName; #[ORM\Column(type: 'string_vo', class: Email::class)] private Email $email; ``` #### Why Currently, creating Value Objects requires one Custom Type per class, even when the conversion logic is identical (string ↔ VO with `__toString()`). The `enumType` parameter solves this elegantly for enums, but there's no equivalent for custom objects. This leads to: - Boilerplate: dozens of nearly identical Type classes - Configuration overhead: one YAML entry per VO - Maintenance burden: any change must be replicated across all types A generic approach would allow a single `StringValueObjectType` to handle all string-based Value Objects, similar to how `enumType` works. #### How Pass field mapping metadata to Type methods: ```php public function convertToPHPValue( mixed $value, AbstractPlatform $platform, array $fieldMapping = [] // New optional parameter ): mixed { $class = $fieldMapping['class'] ?? null; if ($class && $value !== null) { return new $class($value); } return $value; } ``` Alternatively, introduce a dedicated parameter like `valueObjectClass` similar to `enumType`. This would maintain backward compatibility while enabling generic Type implementations for Value Objects.
admin added the New Feature label 2026-01-22 15:53:43 +01:00
Author
Owner

@derrabus commented on GitHub (Nov 23, 2025):

Not sure if the DBAL type system is a good fit for this feature. Maybe we should implement this in the ORM instead, like we did with enumType.

@derrabus commented on GitHub (Nov 23, 2025): Not sure if the DBAL type system is a good fit for this feature. Maybe we should implement this in the ORM instead, like we did with `enumType`.
Author
Owner

@nfacciolo commented on GitHub (Nov 24, 2025):

Thank you for the feedback! I understand your point about the DBAL type system potentially not being the right fit.
I mentioned enumType precisely because I see it as a precedent for this kind of flexibility - if implementing this at the ORM level (similar to enumType) would be the better architectural approach, I'm completely open to that solution.
My core need is straightforward: I want to avoid creating multiple Type classes with identical conversion logic, differing only in the target Value Object class. Whether this is achieved through:

Field mapping metadata passed to DBAL Type methods, or
An ORM-level implementation like enumType

...either approach would solve my use case.
If the ORM layer is indeed the more appropriate place for this feature, I'd be happy to have the issue transferred there or to open a new one in the ORM repository. Just let me know what works best for the project architecture.

@nfacciolo commented on GitHub (Nov 24, 2025): Thank you for the feedback! I understand your point about the DBAL type system potentially not being the right fit. I mentioned enumType precisely because I see it as a precedent for this kind of flexibility - if implementing this at the ORM level (similar to enumType) would be the better architectural approach, I'm completely open to that solution. My core need is straightforward: I want to avoid creating multiple Type classes with identical conversion logic, differing only in the target Value Object class. Whether this is achieved through: Field mapping metadata passed to DBAL Type methods, or An ORM-level implementation like enumType ...either approach would solve my use case. If the ORM layer is indeed the more appropriate place for this feature, I'd be happy to have the issue transferred there or to open a new one in the ORM repository. Just let me know what works best for the project architecture.
Author
Owner

@derrabus commented on GitHub (Nov 29, 2025):

If the ORM layer is indeed the more appropriate place for this feature, I'd be happy to have the issue transferred there

Done.

@derrabus commented on GitHub (Nov 29, 2025): > If the ORM layer is indeed the more appropriate place for this feature, I'd be happy to have the issue transferred there Done.
Author
Owner

@beberlei commented on GitHub (Nov 30, 2025):

Why not use embeddebales?

@beberlei commented on GitHub (Nov 30, 2025): Why not use embeddebales?
Author
Owner

@nfacciolo commented on GitHub (Nov 30, 2025):

Thanks for the suggestion! Embeddables are great for certain use cases, but they don't fit well here for a few reasons:

  1. Loss of flexibility for database constraints
    With embeddables, the Doctrine attributes (#[Column]) must be defined inside the Value Object class itself:
class Email {
    #[Column(type: 'string', unique: true)] // ⚠️ Fixed for ALL uses
    public function __construct(private string $value) {}
}

This creates a major problem: sometimes email must be unique, sometimes not, depending on the entity context. With embeddables, you're forced to choose one behavior for all cases, or create multiple Value Object classes for the same logical type.
With a custom Type, each entity controls its own constraints:

// In User entity
#[Column(type: 'string_vo', unique: true, class: 'Email::class')]
private Email $email;

// In ContactForm entity  
#[Column(type: 'string_vo', unique: false, class: 'Email::class')]
private Email $secondEmail;
  1. Mapping boilerplate With embeddables, I need to explicitly map every property and each property:
#[Embedded(class: Email::class)]
private Email $email;

With a custom Type, the mapping is simpler and the same for each vo:

#[Column(type: 'string_vo', class: 'Name::class')]
private Name $name;


#[Column(type: 'string_vo', class: 'Email::class')]
private Email $email;
  1. Value Objects should stay persistence-agnostic
    Value Objects are domain concepts that shouldn't know about database concerns. With embeddables, you're forced to pollute your Value Objects with Doctrine-specific attributes:
use Doctrine\ORM\Mapping as ORM;

class Email {
    #[ORM\Column(type: 'string', length: 255)] // ❌ Domain layer knows about persistence
    public function __construct(private string $value) {
        // validation logic
    }
}

This creates tight coupling between the domain layer and the persistence layer.
With custom Types, the Value Object stays clean:

  1. My actual need

I have dozens of single-value Value Objects that each need:

  • Identical conversion logic (validate → instantiate)
  • Different target classes

Currently, I must create one Type class per Value Object, duplicating the same conversion code. I'm looking for a way to have one reusable Type that can handle multiple Value Object classes based on field metadata - similar to how enumType works.
Embeddables solve a different problem (multi-property value objects), not the "avoid duplicating Type classes" problem.

@nfacciolo commented on GitHub (Nov 30, 2025): Thanks for the suggestion! Embeddables are great for certain use cases, but they don't fit well here for a few reasons: 1. Loss of flexibility for database constraints With embeddables, the Doctrine attributes (#[Column]) must be defined inside the Value Object class itself: ```php class Email { #[Column(type: 'string', unique: true)] // ⚠️ Fixed for ALL uses public function __construct(private string $value) {} } ``` This creates a major problem: sometimes email must be unique, sometimes not, depending on the entity context. With embeddables, you're forced to choose one behavior for all cases, or create multiple Value Object classes for the same logical type. With a custom Type, each entity controls its own constraints: ```php // In User entity #[Column(type: 'string_vo', unique: true, class: 'Email::class')] private Email $email; // In ContactForm entity #[Column(type: 'string_vo', unique: false, class: 'Email::class')] private Email $secondEmail; ``` 2. Mapping boilerplate With embeddables, I need to explicitly map every property and each property: ```php #[Embedded(class: Email::class)] private Email $email; ``` With a custom Type, the mapping is simpler and the same for each vo: ```php #[Column(type: 'string_vo', class: 'Name::class')] private Name $name; #[Column(type: 'string_vo', class: 'Email::class')] private Email $email; ``` 3. Value Objects should stay persistence-agnostic Value Objects are domain concepts that shouldn't know about database concerns. With embeddables, you're forced to pollute your Value Objects with Doctrine-specific attributes: ```php use Doctrine\ORM\Mapping as ORM; class Email { #[ORM\Column(type: 'string', length: 255)] // ❌ Domain layer knows about persistence public function __construct(private string $value) { // validation logic } } ``` This creates tight coupling between the domain layer and the persistence layer. With custom Types, the Value Object stays clean: 4. My actual need I have dozens of single-value Value Objects that each need: - Identical conversion logic (validate → instantiate) - Different target classes Currently, I must create one Type class per Value Object, duplicating the same conversion code. I'm looking for a way to have one reusable Type that can handle multiple Value Object classes based on field metadata - similar to how enumType works. Embeddables solve a different problem (multi-property value objects), not the "avoid duplicating Type classes" problem.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7576