Embedded Value Objects

Value objects represent values that are made up of more than one element, such as addresses or prices. A value object is different from an entity, because an entity has an identity separate from its properties - you can change the value of a property on an entity, but it is still regarded as the same entity. Value objects are essentially immutable - if you were to change the value of a property, the whole object would be regarded as a brand new value. For example, changing the house number of an address object turns it into a completely different address - even if no other properties change. Likewise, changing the currency of a price turns it into a completely different price, even if the numerical part stays the same.

As value objects have no identity separate from their properties, they cannot have a primary key or any single value identifier. Of course you could add an address ID or whatever, and turn it into an entity, but we are assuming here that you don't want to do that, and that your value objects have no identity of their own, and belong to a parent entity as a property. So a Customer entity might have an address property, which is an Address value object that has no ID. It's even possible that Customer has both homeAddress and businessAddress properties, and each of those are different instances of the Address value object class.

In cases like this, the value object fields are typically stored in the same table as the parent entity. If more than one instance of the same type of value object can appear on the parent, each would typically have a prefix on its column names. So an entity like this:

Customer
  ├id
  ├name
  ├homeAddress
  │   ├houseNumber
  │   ├street
  │   ├town
  │   └postcode
  └businessAddress
      ├houseNumber
      ├street
      ├town
      └postcode

...might be stored in a database like this:

customer table:
id
name
home_address_house_number
home_address_street
home_address_town
home_address_postcode
business_address_house_number
business_address_street
business_address_town
business_address_postcode

ie. There is no separate table for storing the address information - it is 'embedded' in the parent.

To tell Objectiphy that a property holds an embedded value object, set the isEmbedded attribute to true on the Relationship mapping. If there is a prefix for the column name, you can specify that on the embeddedColumnPrefix attribute of the Relationship mapping. For example, our customer entity might look something like this:

class Customer
{
    #[Column(isPrimaryKey: true)]
    public int $id;
    
    #[Column]
    public string $name;
    
    #[Relationship(
        childClassName: Address::class,
        relationshipType: 'one_to_one',
        isEmbedded: true,
        embeddedColumnPrefix: 'home_address_'
    )]
    public Address $homeAddress;
    
    #[Relationship(
        childClassName: Address:class,
        relationshipType: 'one_to_one',
        isEmbedded: true,
        embeddedColumnPrefix: 'business_address_'
    )]
    public Address $businessAddress;
}

... and the Address object might look like this (an embedded value object would not have a table defined, as it uses the table of its parent class):

class Address
{
    #[Column]
    public $houseNumber;
    
    #[Column]
    public $street;
    
    #[Column]
    public $town;
    
    #[Column]
    public $postcode;
}

Last updated