Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Dependent Mapping

DZone's Guide to

Practical PHP Patterns: Dependent Mapping

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

The Dependent Mapping pattern consists in making a root entity manage the persistence of its children, which are usually encapsulated by it. The relationship between the root and these objects is composition, and there are several use cases where making the root handle the whole subgraph is a good solution to simplify the Vietnam of object-relational mapping.

These use cases are one-to-many or one-to-one associations, of the unidirectional type from the root towards the related object. Whe using Dependent Mapping, you want to encapsulate them since they are not important to the rest of the object graph, and you want the data access mechanism to reflect this choice.

Domain-Driven Design to the rescue

The concept of Dependent Mapping is related to the persistence of Aggregates via their Aggregate Roots from Domain-Driven Design. When using Aggregate Roots, only transient references to children objects are handed out of the root object; for example they are passed on the stack and returned via method calls but not maintained in the fields of some external entity. The Aggregate Root is the Facade of this sub-object graph, and Dependent Mapping is a way to implement the persistence of an Aggregate Root that preserving the decoupling of client code from the internal objects.

I will refer to the Facade as the root object however, since this mapping is not specific to an Aggregate, the internals can be exposed as well but it's still Dependent Mapping as long as the persistence mechanism of the root entity takes care of the children instead of them having their own Repository or find() method somewhere. In this sense, Dependent Mapping is a necessary but not sufficient condition to design an Aggregate.

Relationships between entities

From the relational point of view instead, the children are weak entities, so if you're also in database design (which you do a little with ORMs metadata even if they generate the schema for you), they usually import the primary key of the root object as they do not make sense in the overall state of the application without a root object. You can pass around a Phonenumber, but would you save it as-is, without a referencing User?

In the case of one-to-one relationships from the root to a children, the semantic of the Domain Model is improved by using Dependent Mapping because the separate object is preserved, but the data can still be kept in the same table or handled the way the root feels more comfortable.

In the case of one-to-many relationships, performance can be improved with Dependent Mapping because the root entity automatically loads the children, which are always accessed together since the Aggregate is a cohesive graph.

For example, you can have a Repository for the root entity which joins via DQL the children in every query it performs. This is not a pure Dependent Mapping (DQL), since for now there is no strong composition in Doctrine 2 and related objects are always independent ones, kept in the Identity Map, etc.

Custom mapping vs. Doctrine 2

A property of pure Dependent Mapping is that children do not have an Identity Field (in their object representation; the table should still have a primary key). For example, the primary key is imported from the parent, or it is compounded with a progressive number for children collections.

And as you may have guessed, ideally the dependent entity is also not in the Identity Map. This is ideal for Value Objects, immutable objects whose function is to contain data such as Date, Money, or PhoneNumber objects. If the dependent objects are immutable, their handling is simplified.

The issues is in the PHP landscape there is still little support for Value Objects, which make great candidates for the Dependent Mapping and Embedded Value patterns. For example, Doctrine 2 supports a DateTime object as an entity field, but not a custom related object to map into the same table of its owner or by custom mapping rules (but it may out of the scope of this ORM.)

Thus the Dependent Mapping pattern simplify greatly custom mapping, when you have a mapper for every entity you want to save, and it is a great solution in this environment. In this use case, only root entities must have a mapper, while their composed objects are managed by the mapper of their root. Since you're writing the mapper by hand instead of using a generic one like Doctrine 2, Dependent Mapping saves you many lines of code that would be needed for the children mappers: since independent storage and retrieval via queries or finders of children objects is not required, they can just be managed together with their root.

When using a Unit of Work like Doctrine 2, Dependent Mapping is commonly emulated by normal mapping, by not exposing via getters, adders, or removers the children objects. The tables are still forced to be separated from each other, but the relational model cleverness is not of importance to ORM-based solution, because it only needs to be practical and performant.

The object model semantic is also preserved, since the client code sees an Aggregate and its methods, while the ORM has the best of both worlds: it accesses the private fields that references children via reflection bypassing the visibility rules, but hides these details and in general the relational model from the client code.

Examples

The sample code is taken from the Doctrine 2 test suite and modified a bit, and shows how to implement a simple Aggregate root while still taking advantage of common mapping metadata like @OneToOne annotations. We can argue that this is a different implementation of Dependent Mapping from the custom-mapping ones, and the intent of giving persistence to an Aggregate Root is still present. In the example, CmsUser is the Aggregate Root and CmsAddress is the composed and encapsulated entity.

This solution is not perfect: via the Entity Manager you can still access children via find*() methods, even if they should be encapsulated. But in a DDD approach, client code ideally shouldn't have knowledge of the Entity Manager, but only of the Repositories that compose it, and that are one for each Aggregate Root. The children objects also have an identity that they shouldn't need, but this is necessary to take advantage of the Unit of Work to manage changesets.

<?php

namespace Doctrine\Tests\Models\CMS;

use Doctrine\Common\Collections\ArrayCollection;

/**
* @Entity
* @Table(name="cms_users")
*/
class CmsUser
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
public $id;

/**
* @OneToOne(targetEntity="CmsAddress", cascade={"persist"})
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
public $address;

public function __toString()
{
return "#" . $this->id . "; lives at " . $this->address;
}
}

/**
* CmsAddress
*
* @author Roman S. Borschel
* @Entity
* @Table(name="cms_addresses")
*/
class CmsAddress
{
/**
* @Column(type="integer")
* @Id @GeneratedValue
*/
public $id;

/**
* @Column(length=50)
*/
public $city;

/**
* @Column(length=50)
*/
public $street;

public function __construct($city, $street) {
$this->city = $city;
$this->street = $street;
}

public function __toString()
{
return $this->city . ", " . $this->street;
}
}

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}