Practical PHP Refactoring: Change Unidirectional Association to Bidirectional
Join the DZone community and get the full member experience.
Join For FreeObject graphs are built by maintaining references to other objects inside an object's fields (usually private). These associations are persisted and stored by an ORM or another mechanism, or are simply built by PHP code calling constructors and setter during the instantiation time of your application.
Every time you have a reference to another object in a private field, you may navigate this reference and call a method on the associated object, or pass it to other ones.
Most of the time, a unidirectional association is enough to qualify object relationship. This refactoring is about changing the existing association to a bidirectional one - where both objects have references to each other. The technique used is the simplest one, and is called back pointers: two unidirectional relationship are established, each one with one of the two objects as source.
Note that the multiplicity the objects are participating to the association with is not necessarily 1: we may have a reference to a single B object on the class A and a reference to an array of A objects on the class B. Indeed, many-to-one associations are the most common case.
Why should I introduce a bidirectional association?
Let's not hide the fact that bidirectional associations are inherently more complex than unidirectional one. In fact, Fowler advises to set up tests for the construction phases in case it's the first time this refactoring is implemented.
Bidirectional associations are however necessary in the following scenarios:
- calling a method on a general stateful object (hidden by an interface) which composes the current one. For example, a Front Controller is an highest-level object that can be referred by back lower-level controllers in order to change the state of the application (forwarding to other controllers or redirecting immediately).
- Entities with many to one associations commonly need to maintain the association on the one side (owning side) in present-day ORMs, in order to mimic the foreign key and simplify implementation. Thus a User composing N Phonenumbers will find that Phonenumbers must maintain a reference to him, even if they never call back User in their code.
I may have introduced one bidirectional association in stateless objects in the last year. This refactoring is really common instead in stateful classes like Entities, both for technical and semantic reasons.
Steps
- Introduce a private field for the back pointer.
- Choose which class is the controlling side, which means it hosts the method that initiates the creation or the update of the association.
- Create an "internal" method on the noncontrolling side. We actually use the prefix internal (e.g. internalSetUser(User $u)) to show that this method should never be called by client code.
- Update the existing modifier: if it is in the controlling side, it should call our new internal method. If it is in the non controlling side, it should be moved in the controlling one.
In the example, we will start from a User containing an addPhonenumber(Phonenumber $number) method and we will add an internalSetUser(User $u) method on the Phonenumber class, tying it to the first method.
Example
In the initial state, User is composing an array of Phonenumber objects. It can easily produce a list of its phonenumbers complete of name.
<?php class ChangeUnidirectionalAssociationToBidirectional extends PHPUnit_Framework_TestCase { public function testPhonumbersWillReferBackToUsers() { $user = new User('Giorgio'); $user->addPhonenumber(new Phonenumber('012345')); $this->assertEquals("Giorgio: 012345\n", $user->phonenumbersList()); } } class User { private $name; private $phonenumbers; public function __construct($name) { $this->name = $name; } public function addPhonenumber(Phonenumber $phonenumber) { $this->phonenumbers[] = $phonenumber; } public function phonenumbersList() { $list = ''; foreach ($this->phonenumbers as $number) { $list .= "$this->name: $number\n"; } return $list; } } class Phonenumber { private $number; public function __construct($number) { $this->number = $number; } public function __toString() { return $this->number; } }
We want to move the responsibility for building each string element of the list onto Phonenumber.
We choose User as the controlling side and thus we create the internal method in Phonenumber. We may have used the constructor for establishing the back pointer, but since the noncontrolling objects may have in general a completely independent lifecycle we can't just always recreate them everytime the association change.
class Phonenumber { private $number; /** * @var User */ private $user; public function __construct($number) { $this->number = $number; } public function internalSetUser(User $u) { $this->user = $u; } public function __toString() { return $this->number; } }
We add the call to the noncontrolling side into the controlling side's method. the visibility of the internal method must be public.
class User { private $name; private $phonenumbers; public function __construct($name) { $this->name = $name; } public function addPhonenumber(Phonenumber $phonenumber) { $this->phonenumbers[] = $phonenumber; $phonenumber->internalSetUser($this); } public function phonenumbersList() { $list = ''; foreach ($this->phonenumbers as $number) { $list .= "$this->name: $number\n"; } return $list; } }
We can now move the logic onto Phonenumber, as we can refer back to User everytime we need it.
class User { ... public function getName() { return $this->name; } } class Phonenumber { ... public function __toString() { return $this->user->getName() . ': ' . $this->number; } }
Opinions expressed by DZone contributors are their own.
Comments