Practical PHP Patterns: Embedded Value
Join the DZone community and get the full member experience.
Join For FreeThe Embedded Value is an understated object-relational structure pattern which maps a collaborator object's state into the table of another object, which usually has a direct reference to it. The state of the collaborator merged in the table of the original object is in the form of fields, while compounding relationships of an external object is rarer.
The Embedded Value pattern is a particular case of the Dependent Mapping one, where it is realized with a single table. It is a further departure from the naive mapping one table, one class and one object, one row that is not really necessary but it is pushed by the simpler Active Record pattern. Of course advanced Data Mapper patterns, like Embedded Value, add complexity to the object-relational mapper. Despite this, an ORM is usually an external library (custom mappers do not scale well), and provided that it is willing to grow in features and size it is not a great problem to shift some of the complexity of a software solution to it.
I know this last phrase sounds like encoded in marketese, but it simply means that complexity in an external, well-tested and decoupled open source product comes ideally for free (unless it becomes bloated and not cohesive, like an ORM that does mapping also towards document-oriented databases). I'll neve say that of an Active Record solution.
Value Objects
This pattern is employed in conjunction with Value Objects, and it is very useful when having a 1:1 relationship from an Entity towards a Value Object. This kind of objects trascend Domain-Driven Design, and is quite common in every domain model, although it is not always recognized.
A 0:1 association often indicates instead that the target is not a Value Object, while also a n:1 probably is a sign of a different type of target. Sharing an object in the graph because it is immutable is one thing, but if it is not conceptually the same entity we have still a 1:1 relationship and not a n:1. For example, if two User have the same Money object which represents a value of $12.34, they have not the same money shared between them, but only the same Flyweight object. With an Embedded Value mapping, this amount of money will reside in the same row of the User that owns it, as an additional column.
Actually, when there is a limited number of choices for the fields (see Category, or Role classes) and this choices are part of a set of rarely changed data, you may want to promote them to an Entity class to prevent duplication and give them their separate table.
In this case, they will gain an identity, so consider this option wisely: day of the weeks are Value Objects, while articles categories are probably entities. Consider if an hypothetical field can be added to them, even if it's not needed in your design: if it can be added, they're not Value Objects and Embedded Value does not apply. If you consider the Category class example, a description or icon field can probably be added to this class: thus, it is an Entity and not a Value Object.
Using the Embedded Value pattern with Entities would lead to the duplication of their additional fields in tables where they are, in fact, embedded, and that would not respect the third normal form. Only when we have basic 1:1 relationships, again, we can embed them without hassle, but what if we want to access them independently? Entities are usually better off in their own table, a trait which becomes a requirement when they are Aggregate Roots.
Another limitation resides in the fact that Value objects can have relationships towards other objects (towards Aggregate Roots), but when persisted with Embedded Value they can become problematic.
Object model
In its Patterns of Enterprise Application Architecture masterpiece, which we are following in this series, Fowler makes the classic example of a Money object: would you create a money table where every row represents a money amount, and a foreign key maps it to the original domain object that has a Money object? It will be kind of strange, at least for someone who sees only the relational model.
A simplification of this relational model would be handy. For example who needs to call a find() on a table full of Money values? The definition of Value Objects talks about objects without identity, and thus no primary key on an external table.
Still, a separate Money object is useful for encapsulating business logic like validation, or conversion, or displaying. In general, to outsource some of the work from the source class to a specific one, which is the central repository for this logic, instead of scattering it around the codebase by using string fields for monetary values. It makes sense to detach our design from the table-class bijection.
Examples
Although you probably can get away with a custom data type for the mapping of single-field Value Objects, the full support for them in Doctrine 2 will probably come further down the 2.x series. When I was contributing some time ago, Roman said we may see Value Objects mapped with the JPA annotations in the table of their source entity in Doctrine 2.1 or 2.2 (it is quite a bit of additional work to implement it.)
In this code sample, we'll see an object model which involves a Money Value Object and a getState() method on the Entity classes which substitute the application of reflection-based data access for simplicity's sake.
<?php /** * Main class, which in modelling would be called an Aggregate Root. */ class User { private $name; private $money; public function __construct($name) { $this->name = $name; } public function setMoney(Money $money) { $this->money = $money; } /** * Suppose this function is what the Data Mapper calls to * obtain the state of the object and store it in a database table * as a single row. * Exercise for the reader: write setState(array $state) * @return array keys are column names */ public function getState() { return array( 'name' => $this->name, 'money' => $this->money->getAmount() ); } } /** * This class has no getState() method because its state is managed * with its related User, since this is also an instance of Dependent Mapping. */ final class Money { private $amount; /** * let's ignore for simplicity that float and doubles are inadequate * for storing monetary values, and decimals should be used. * Validation is managed application-wide in the Money class, but * only one table (User) will be used for persistence. */ public function __construct($amount) { if (!is_numeric($amount) or $amount < 0) { throw new InvalidArgumentException("Amount of money should be a positive real value."); } $this->amount = $amount; } public function getAmount() { return $this->amount; } } // client code $user = new User('Giorgio'); $user->setMoney(new Money(42)); // Data Mapper's code var_dump($user->getState());
Opinions expressed by DZone contributors are their own.
Comments