DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Change Unidirectional Association to Bidirectional

Practical PHP Refactoring: Change Unidirectional Association to Bidirectional

Giorgio Sironi user avatar by
Giorgio Sironi
·
Aug. 31, 11 · Interview
Like (0)
Save
Tweet
Share
7.56K Views

Join the DZone community and get the full member experience.

Join For Free

Object 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

  1. Introduce a private field for the back pointer.
  2. Choose which class is the controlling side, which means it hosts the method that initiates the creation or the update of the association.
  3. 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.
  4. 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;
    }
}
PHP Object (computer science)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 5 Steps for Getting Started in Deep Learning
  • OpenVPN With Radius and Multi-Factor Authentication
  • Choosing the Right Framework for Your Project
  • Host Hack Attempt Detection Using ELK

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: