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: Move Field

Practical PHP Refactoring: Move Field

Giorgio Sironi user avatar by
Giorgio Sironi
·
Jul. 18, 11 · Interview
Like (1)
Save
Tweet
Share
6.72K Views

Join the DZone community and get the full member experience.

Join For Free

Object-oriented programming is based on the encapsulation of state and behavior associated with that state in decoupled items called objects. In the previous issue of this series, we saw how to move behavior to match existing state, while today we'll see the inverse: how to move state representations between classes to better match the methods defined on them. This adjustment of state variables is composed by many Move Field refactorings.

Why should I Move a Field?

Fields should follow the methods referencing them: there are some reason to perform this kind of refactoring which are identical to the ones for Move Method. For example, you may avoid getters and setters since the code working on the fields gets closer to them. Moreover, in the case of classes extraction, moving fields is necessary since the new class is initially empty: this refactoring become a step in a larger process.

There are also a few technological reasons to influence you: for example, you may need to keep the field out or in the session. It happened to me that a PDO was reached by a variable stored in $_SESSION, and thus causing great explosion (PDO connections cannot be serialized.) In these scenarios, changing the location of the problematic field is the simplest step.

A more philosophical question is why this field is in the wrong place? Because of new design decisions that update the old picture. For example, the multiplicity of a field may change (like in our code sample); in any case, fields and methodsget in the wrong place all the time without actually moving. The important part is to acknowledge that a bit of refactoring is needed.

Steps

These steps let you move a field from a source class to a target one.

Step 0 is check that the field is private: in case it's not, encapsulation is required before starting.

  1. Create a field in the target class, accessible for now with a getter and a setter.
  2. Temporarily reference the target object from the source class.
  3. Replace references to the old field with references to the new field. This should comprehend also initialization, of course.
  4. Remove the old field on the source class, since it is now unused.

Throughout all these steps, you can run tests at the functional level, being these an inter-class refactoring. Unit tests for the two classes involved should be modified accordingly: the initialization of the field should move to the target class tests; if encapsulation is working well, tests shouldn't shift from one class to the other, otherwise you're moving a method, not just a field.

This refactoring is perfectly suited to your case when the final version of the source class has just a setter/getter contract over the field with the target one. In this case, the field has been totalle encapsulated and the code can furtherly be simplified by eliminating the reference as we'll see in the example.

Example

We continue with the TagCloud and Link classes example. A new requirement which has been satisfied is to place an attribute on links: rel="archives". We imagine that originally this requirement was defined on a per-link basis, but now we simplify the design by considering only one value for rel in the whole tag cloud.

In the initial state, there is a getter which exposes the field.

<?php
class MoveMethodTest extends PHPUnit_Framework_TestCase
{
    public function testDisplayItsLinksInShortForm()
    {
        $tagCloud = new TagCloud(array(
            new Link('http://giorgiosironi.blogspot.com/search/label/productivity', 'archives'),
            new Link('http://giorgiosironi.blogspot.com/search/label/software%20development', 'archives')
        ));
        $html = $tagCloud->toHtml();
        $this->assertEquals(
            "<a href=\"http://giorgiosironi.blogspot.com/search/label/productivity\" rel=\"archives\">productivity</a>\n"
          . "<a href=\"http://giorgiosironi.blogspot.com/search/label/software%20development\" rel=\"archives\">software development</a>\n",
            $html
        );
    }
}

class TagCloud
{
    private $links;

    public function __construct(array $links)
    {
        $this->links = $links;
    }

    public function toHtml()
    {
        $html = '';
        foreach ($this->links as $link) {
            $text = $link->text();
            $rel = $link->getRel();
            $html .= "<a href=\"$link\" rel=\"$rel\">$text</a>\n";
        }
        return $html;
    }
}

class Link
{
    private $url;
    private $rel;

    public function __construct($url, $rel = null)
    {
        $this->url = $url;
        $this->rel = $rel;
    }

    public function __toString()
    {
        return $this->url;
    }

    public function getRel()
    {
        return $this->rel;
    }

    public function text()
    {
        $lastFragment = substr(strrchr($this, '/'), 1);
        return str_replace('%20', ' ', $lastFragment);
    }
}

But TagCloud is generating the HTML, while Link is a conceptual link, not an anchor. We introduce two parallel designs: the field continues to exist temporarily. Note that this time we move the field instead of moving the behavior to follow the field, since the field is related to HTML generation, and thus better suited to TagCloud's current responsibility.

<?php
class MoveMethodTest extends PHPUnit_Framework_TestCase
{
    public function testDisplayItsLinksInShortForm()
    {
        $tagCloud = new TagCloud(array(
            new Link('http://giorgiosironi.blogspot.com/search/label/productivity', 'archives'),
            new Link('http://giorgiosironi.blogspot.com/search/label/software%20development', 'archives')
        ), 'archives');
        $html = $tagCloud->toHtml();
        $this->assertEquals(
            "<a href=\"http://giorgiosironi.blogspot.com/search/label/productivity\" rel=\"archives\">productivity</a>\n"
          . "<a href=\"http://giorgiosironi.blogspot.com/search/label/software%20development\" rel=\"archives\">software development</a>\n",
            $html
        );
    }
}

class TagCloud
{
    private $links;
    private $rel;

    public function __construct(array $links, $rel)
    {
        $this->links = $links;
        $this->rel = $rel;
    }

    public function toHtml()
    {
        $html = '';
        foreach ($this->links as $link) {
            $text = $link->text();
            $rel = $link->getRel();
            $html .= "<a href=\"$link\" rel=\"$rel\">$text</a>\n";
        }
        return $html;
    }
}

We can now switch to the usage of the new field. If the source class needed to access the field, the switch would have to provide a reference to TagCloud to the Link. In this particular case, I probably would write a Factory Method on TagCloud to address the construction of Link and avoid exposing this complication.

    public function toHtml()
    {
        $html = '';
        foreach ($this->links as $link) {
            $text = $link->text();
            $html .= "<a href=\"$link\" rel=\"$this->rel\">$text</a>\n";
        }
        return $html;
    }

To get to the final state, we can now delete the field from Link, along with its getter. We do not have to initialize pass it anymore to the Link constructor in the tests.

class Link
{
    private $url;

    public function __construct($url)
    {
        $this->url = $url;
    }

    public function __toString()
    {
        return $this->url;
    }

    public function text()
    {
        $lastFragment = substr(strrchr($this, '/'), 1);
        return str_replace('%20', ' ', $lastFragment);
    }
}
PHP

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Create a CLI Chatbot With the ChatGPT API and Node.js
  • What “The Rings of Power” Taught Me About a Career in Tech
  • Unlock the Full Potential of Git
  • Best Practices for Setting up Monitoring Operations for Your AI Team

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: