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
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Extract Interface

Practical PHP Refactoring: Extract Interface

Giorgio Sironi user avatar by
Giorgio Sironi
·
Jan. 18, 12 · Interview
Like (1)
Save
Tweet
Share
6.55K Views

Join the DZone community and get the full member experience.

Join For Free

A concrete class still defines an implicit interface by itself, as the set of its public methods. When the called interface is a subset of this, or it is depended upon in multiple places, it is interesting to make it explicit.

The Extract Interface refactoring creates an interface from an existing concrete class.

Why?

Interfaces are not strictly necessary for computation: PHP is already Turing-complete without them. Interfaces describe what a collaborator should accomplish, while classes describe how, with all the necessary code. However, apart from documenting a contract between two classes, they have several benefits.

In the realm of TDD, they enable outside-in testing with mocks: this means you can test-driver a class together with the interfaces it depends upon, even if the real implementation of its collaborators do not exist, or do not implement them, yet.

Outside-in TDD helps shaping interfaces from the point of view of the caller instead of the callee. As such, these interfaces should reside in a separate folder/namespace from the implementing code (for the Dependency Inversion Principle).

What if a class has multiple calling points? It can possibly expose multiple interfaces, where every client depends exactly at most one of them and not on any additional methods.

Steps

  1. Create an empty interface. If you can only think of a name such as Set and ISet for a class and interface, start by saving the good name for the interface: Set and TreeBasedSet is better than the former case.
  2. Declare common operations in the interface, with method signatures identical to the original. In case only some operations are called by the client, only this subset should be copied in the interface.
  3. Add implements keywords to tie existing concrete classes to the interface.
  4. Simplify the client code by making it dependent on the interface where possible.

Examples of the last step are multiple:

  • the tests can now use a mock or a stub easily (even coded by hand instead of a generated one), since starting from interface you'll have a small set of methods to override.
  • Type hints can be written referring to the interface name whenever the client code calls only the methods listed in the interface.
There are also many possible subsequent refactorings, enabled by the presence of the interface:
  • renaming the interface and the concrete classes to reflect a role (in the interface) and implementation peculiarities (in the concrete classes).
  • Add or drop methods in the contract to fit the desires of the caller.
  • Extract some functionality into a Decorator or a Composite, which are multiple implementation of an interface.

Example

In the initial state, the Money presenter object is depending on a concrete class, EuroLocale.

<?php
class ExtractInterface extends PHPUnit_Framework_TestCase
{
    public function testShouldDisplayAMoneyAmount()
    {
        $locale = new EuroLocale();
        $money = new Money("42");
        $this->assertEquals("42 €", $money->display($locale));
    }
}

class EuroLocale
{
    public function format($amount)
    {
        return $amount . ' €';
    }
}

class Money
{
    private $amount;

    /**
     * @param string $amount    to keep precision
     */
    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function display(EuroLocale $locale)
    {
        return $locale->format($this->amount);
    }
}

We create an interface, Locale, and just the single public method is extracted. Anything more would not be shown in this example, and won't be brought up into the interface.

interface Locale
{
    /**
     * @return string
     */
    public function format($amount);
}

We add an implements keyword, and simplify the type hint dependency to target just Locale.

interface Locale
{
    /**
     * @return string
     */
    public function format($amount);
}

class EuroLocale implements Locale
{
    public function format($amount)
    {
        return $amount . ' €';
    }
}

class Money
{
    private $amount;

    /**
     * @param string $amount    to keep precision
     */
    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function display(Locale $locale)
    {
        return $locale->format($this->amount);
    }
}

Usually a dependency on a concrete class, which may have a dozen different methods, does not let us refactor tests introducing Test Doubles. This happens because we are unsure about which methods we should redefine: which are called in this test method? And by Money objects in general?

Now that we have an interface, we define explicitly that only format() is called, even if EuroLocale may have many others. So we can break the tests in two, one targeting Money and the other EuroLocale. Note the order of the unit tests: they are completely independent, so we can test (and thus develop) Money first.

<?php
class ExtractInterface extends PHPUnit_Framework_TestCase
{
    public function testShouldFormatItsAmountBeforeDisplayingIt()
    {
        $locale = $this->getMock('Locale');
        $locale->expects($this->once())->method('format')->with("42")->will($this->returnValue('42 SIMBOL'));
        $money = new Money("42");
        $this->assertEquals("42 SIMBOL", $money->display($locale));
    }

    public function testShouldFormatAnAmountWithTheEuroSighn()
    {
        $locale = new EuroLocale();
        $this->assertEquals("42 €", $locale->format("42"));
    }
}

In the real world, there would have been dozens of tests involving both objects, with a lot of setup code and machinery. Interfaces let us break direct dependencies and test classes in real isolation, an approach that scales better to many tests. For example, in the final version every test for a new formatting options (like 42.00 or 10,000.00 EUR) needs only to create a string instead of a Money object. Equivalently, any test for a new user of format() does not have to care about particular formatting rules.

Interface (computing) PHP Extract Testing

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Testing Repository Adapters With Hexagonal Architecture
  • Understanding and Solving the AWS Lambda Cold Start Problem
  • Building the Next-Generation Data Lakehouse: 10X Performance
  • Steel Threads Are a Technique That Will Make You a Better Engineer

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: