Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Value Object Bundle Laravel Package

upscale/doctrine-value-object-bundle

A Symfony bundle that simplifies persisting value objects with Doctrine. It ships DBAL types and mapping helpers so value objects can be stored directly in entities, eliminating repetitive primitive conversion code and keeping domain models expressive, type-safe, and clean.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the bundle (Symfony Flex will register it automatically; otherwise add Upscale\DoctrineValueObjectBundle\DoctrineValueObjectBundle to config/bundles.php):

    composer require upscale/doctrine-value-object-bundle
    
  2. Implement the underlying contract. The bundle builds on yokai/doctrine-value-object, so your value objects implement Yokai\DoctrineValueObject\ValueObject:

    final class Email implements ValueObject
    {
        private function __construct(private string $value) {}
    
        public static function fromValue($value): self
        {
            return new self((string) $value);
        }
    
        public function toValue(): string
        {
            return $this->value;
        }
    }
    
  3. Define a DBAL type. Extend the base type from the library and tell it which value object it manages:

    final class EmailType extends AbstractValueObjectType
    {
        public function getName(): string { return 'email'; }
        protected function getValueObjectClass(): string { return Email::class; }
    }
    
  4. Register the type. Either let the bundle autoconfigure the service (tag it, or rely on the bundle's _instanceof rules), or list it explicitly under doctrine.dbal.types:

    doctrine:
        dbal:
            types:
                email: App\Doctrine\Type\EmailType
    
  5. Use it in an entity:

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

Where to look first: the bundle's DependencyInjection/ and config/ directories (to see how types are auto-registered and tagged), then the yokai/doctrine-value-object README for the exact ValueObject / base-type contract. The bundle README is usually a short "zero-config" example.

First use case: a Email, Uuid, or Money column you currently model as a primitive string/int, but want typed everywhere — request payloads, service layer, and entity — without a getter/setter pair and without a custom hydrator.

Implementation Patterns

  • Single-scalar value objects are the sweet spot. One value object → one DBAL type → one column. No embeddables, no life-cycle callbacks.
  • Auto-registration via service tag. Make your type services public (or tagged with the bundle's tag) and let the bundle register them on boot. This is the day-to-day workflow: add a class, run cache:clear, done.
  • Explicit registration in doctrine.dbal.types is worth it when you need deterministic ordering, use types in tests, or ship a library that other apps will include.
  • DQL and query builders. You can pass the value object directly and specify the type name:
    $qb->andWhere('u.email = :email')
       ->setParameter('email', new Email('[email protected]'), 'email');
    
  • Repository finders. Keep the value-object comparison in the query, not in PHP, so indexes are still used.
  • Nullable columns. The base type usually handles null round-tripping; if you write your own convertTo* methods, handle null in both directions.
  • Multi-field value objects stay embeddables. A DBAL type maps to a single column; do not try to shoe-horn a Money(amount, currency) into a type. Use #[ORM\Embeddable] instead.
  • Testing. Register the types in your bootstrap.php / test kernel before the connection is created. In-memory SQLite works fine and is the cheapest way to prove a type round-trips.

Gotchas and Tips

  • Registration timing. DBAL requires types to be present before a connection is instantiated. Registering a type after the first query throws Unknown column type 'email'. Use doctrine.dbal.types or a boot()-time / compiler-pass registration — never a controller or runtime listener.
  • Cache invalidation. After changing type mappings, run bin/console cache:clear. A stale compiled container is the #1 cause of "type suddenly not found".
  • Global type names. getName() must be unique and stable. Avoid collisions with built-ins (string, json, uuid) and prefer a namespace prefix (app_email) if you might ever shadow a third-party type.
  • Equality vs identity. Value objects have no identity. Doctrine's unit of work uses ===; two equal Email instances from different request flows won't match. Either compare via toValue() in your entity logic, or rely on Doctrine's equals/compare support.
  • Immutability. Keep value objects immutable and give the entity a single replacing setter. A misbehaving mutable value object will silently corrupt change-tracking.
  • Serialization. JSON output of entities won't automatically expose the value. Implement JsonSerializable on the value object or add an accessor; otherwise serializers render an empty object.
  • Fixtures and factories. Foundry/Alice need to construct via fromValue() or the canonical constructor. Do not rely on reflection-based instantiation — it bypasses your validation.
  • Debugging. When a type misbehaves, use bin/console doctrine:schema:validate and bin/console debug:container --tag=<your-type-tag> to confirm the bundle actually picked the service up. The bundle is thin; most bugs live in the base type's convertTo* methods.
  • Low-usage package. 13 stars and zero dependents in the wild — pin the version in composer.json and read the type's convertToDatabaseValue / convertToPHPValue before it lands on a production schema.
  • Extension point. If you need bulk/derived registration (e.g. auto-naming types from the value object class), the bundle's compiler pass is where to hook in — not an event listener.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource