Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 635 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Refactoring: Self Encapsulate Field

08.10.2011
| 4536 views |
  • submit to reddit

We are entering the data organization refactorings section of Fowler's book: these methods apply to object which maintain state, like entities, records, value objects and parameter objects. Sometimes these objects are persisted, while sometimes they remain transient: the refactoring techniques are equivalent for both cases.

We start with a simple refactoring, Self Encapsulate Field, which introduce a bit of indirection over private fields in a class. This refactoring leads to the usage of getters instead of private fields directly, even in the class's own code. Remember the bidirectionality of refactorings: this step can be performed in reverse for simplification.

Why another abstraction?

The reason to access fields indirectly is that subclasses are then allowed to override the getters. Getters and setters introduced by this refactoring do not need to be public (they should remain at least protected however.)

Remember why getters are introduced over public fields? Not for encapsulation (there is a 1-to-1 correspondence with fields), but to prevent modification and allow substitution with a calculated value in the future without affecting the client code.

In this case, the client code is the class itself: you prevent accidental modification, ensuring an immutable class; you also can substitute the body of getX() with a computed value in the future, or perform the substitution only in some subclasses.

Steps

  1. Introduce a getter (and an optional setter) methods, with protected or public visibility.
  2. Find references to the field, and replace them with calls to getX() or setX().
  3. Run tests at the unit level.

You can do a find/replace operation to ensure the field is not referenced anymore outside of the getter or setter, and the constructor. The constructor may in fact need to perform special initialization, or maybe the only place where to populate the field in absence of a setter.

Example

In the code sample, we start from a Paragraph object with a $class field containing the CSS class to display.

<?php
class SelfEncapsulateField extends PHPUnit_Framework_TestCase
{
    public function testParagraphIsDisplayed()
    {
        $p = new Paragraph('Lorem ipsum', 'important');
        $expected = '<p class="important">Lorem ipsum</p>';
        $this->assertEquals($expected, $p->__toString());
    }
}

class Paragraph
{
    private $text;
    private $class;

    public function __construct($text, $class)
    {
        $this->text = $text;
        $this->class = $class;
    }

    public function __toString()
    {
        return "<p class=\"$this->class\">$this->text</p>";
    }
}

A new requirement is introduced: a fixed class for some kind of paragraphs:

    public function testParagraphIsDisplayedAsAlwaysImportant()
    {
        $this->markTestSkipped();
        $p = new ImportantParagraph('Lorem ipsum');
        $expected = '<p class="important">Lorem ipsum</p>';
        $this->assertEquals($expected, $p->__toString());
    }

The usage of the field in the class is encapsulated by a getter:

class Paragraph
{
    private $text;
    private $class;

    public function __construct($text, $class)
    {
        $this->text = $text;
        $this->class = $class;
    }

    public function __toString()
    {
        return "<p class=\"" . $this->getClass() . "\">$this->text</p>";
    }

    protected function getClass()
    {
        return $this->class;
    }
}

And the subclass can finally be created:

class ImportantParagraph extends Paragraph
{
    /**
     * This constructor is an hack, but at least the Liskov Substitution
     * Principle is respected in the external interface, which only
     * consists of __toString().
     * If we want to improve this constructor, we can extract a common
     * abstract class between Paragraph and ImportantParagraph, so that
     * both can have their own constructors.
     */
    public function __construct($text)
    {
        parent::__construct($text, null);
    }
   
    protected function getClass()
    {
        return 'important';
    }
}
Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)