Wern Ancheta

PHP Design Patterns: Adapter Pattern

· Wern Ancheta

This is the third post in a series of articles that will walk you through how to implement design patterns in PHP.

In this part, we’ll look at how you can implement the adapter pattern. Just like the name suggests, the Adapter pattern is simply a way to translate a third-party code to a common interface that you can use it within your existing codebase.

For example, we have a Client class which uses a filesystem library to open a file:

<?php 

// adapterpattern/app/Client.php

namespace AdapterPattern\App;

class Client
{
    public function readFile(FilesystemInterface $filesystem)
    {
        return $filesystem->open();
    }
}

Here’s what the FilesystemInterface looks like:

<?php 

// adapterpattern/app/FilesystemInterface.php

namespace AdapterPattern\App; 

interface FilesystemInterface 
{
    public function open();
    
}

This is implemented by a class which allows the client to open a file in the local filesystem:

<?php 
// adapterpattern/app/LocalFilesystem.php

namespace AdapterPattern\App;

class LocalFilesystem implements FilesystemInterface
{

    public function open()
    {
        return 'opening local file';
    }

}

Here’s how we go about using it:

<?php 
// adapterpattern/AdapterTest.php

require_once __DIR__ . '/../vendor/autoload.php';

use PHPUnit\Framework\TestCase;

class AdapterTest extends TestCase
{
    public function test_filesystem_adapter() : void
    {   
        $client = new AdapterPattern\App\Client();

        $local_filesystem = new AdapterPattern\App\LocalFilesystem();
        $this->assertEquals($client->readFile($local_filesystem), 'opening local file');
       
    }
} 

Of course this will work as expected since we own the LocalFilesystem class and we can easily make modifications to it as needed. But what if we decided to use a third-party library later on?

In the below example, we want to use a third-party library which talks to the Dropbox API to open a file in a user’s Dropbox:

<?php
public function test_filesystem_adapter() : void
{   
    $client = new AdapterPattern\App\Client();

    $dropbox_filesystem = new AdapterPattern\App\Dropbox();
    $this->assertEquals($client->readFile($dropbox_filesystem), 'opening dropbox file');
}

Here’s the contents of the Dropbox class:

<?php 
// adapterpattern/app/Dropbox.php

namespace AdapterPattern\App;

class Dropbox implements CloudFilesystemInterface
{
    public function openFile()
    {
        return 'opening dropbox file';
    }
}

And the interface it adheres to:

<?php

// adapterpattern/app/CloudFilesystemInterface.php

namespace AdapterPattern\App;

interface CloudFilesystemInterface 
{
    public function openFile();
}

Obviously, this wouldn’t work and you’ll get the following error:

TypeError: AdapterPattern\App\Client::readFile(): Argument #1 ($filesystem) must be of type AdapterPattern\App\FilesystemInterface, AdapterPattern\App\Dropbox given, called in /design-patterns/adapterpattern/AdapterTest.php on line 18

This means that the Client class was expecting something that implements the FilesystemInterface but instead it got the Dropbox class. Moreover, the open method is used by the FilesystemInterface while the Dropbox class has the openFile method. So even if you remove the constraint in the Client class like so:

<?php
// adapterpattern/app/Client.php

class Client
{
    public function readFile($filesystem) // removed FilesystemInterface type
    {
        return $filesystem->open();
    }
}

You would still get an error since the method you’re trying to call is undefined:

Error: Call to undefined method AdapterPattern\App\Dropbox::open()

/design-patterns/adapterpattern/app/Client.php:9

This is where the Adapter pattern comes in. You can implement it by creating a class which would use the third-party library you’re trying to use. The key thing here is that this class has to implement the interface which you are already using. In this case it’s the FilesystemInterface. From there, all you have to do is pass an instance of the third-party library you’re trying to use in the constructor then implement all the methods that the interface has specified. In this case, we simply translate the openFile method from the Dropbox library to the open method of the FilesystemInterface:

<?php 
// adapterpattern/app/FilesystemAdapter.php

namespace AdapterPattern\App;

class FilesystemAdapter implements FilesystemInterface 
{

    public function __construct(private CloudFilesystemInterface $filesystem)
    {

    }

    public function open()
    {
        return $this->filesystem->openFile();
    }

}

At this point, all you need to do is wrap the instance of the Dropbox class with the FilesystemAdapter:

public function test_filesystem_adapter() : void
{   
    $client = new AdapterPattern\App\Client();

    $local_filesystem = new AdapterPattern\App\LocalFilesystem();
    $this->assertEquals($client->readFile($local_filesystem), 'opening local file');

    // this wont work since dropbox doesnt adhere to the same interface
    /*
    $dropbox_filesystem = new AdapterPattern\App\Dropbox();
    $this->assertEquals($client->readFile($dropbox_filesystem), 'opening dropbox file');
    */
    
    // so we use an adapter which implements the interface we want to adhere to
    $dropbox_filesystem = new AdapterPattern\App\Dropbox();
    $this->assertEquals($client->readFile(new AdapterPattern\App\FilesystemAdapter($dropbox_filesystem)), 'opening dropbox file');
}

That’s all there is to the Adapter pattern. It’s simply a way to create an adapter for third-party code so that you can plug it in to your existing code.

Be sure to add the following on your composer.json file then run composer dump-autoload:

"autoload": {
    "psr-4": {
        "AdapterPattern\\App\\": "adapterpattern/app/"
    }
},

Also install phpunit (composer require phpunit/phpunit) if you haven’t done so already.

You can then run it by executing the following:

vendor/bin/phpunit adapterpattern/app/AdapterTest.php

That’s really all there is to the Facade pattern. It’s a way to simplify the usage of an otherwise complex.

You can find the source code used in this tutorial on this GitHub repo.