I was writing a package that required a storage medium, or "driver," to be user-configurable.

Say a user wanted to toggle between reading data in an XML file or CSV format. The first thought is to write two copies of your library or class. A fooXML and a fooCSV for example. That is a bit of a pain though, having to create two seperate copies of your core logic.

The solution is to build in the ability to change the storage medium while having a single copy of your core logic. How do we do that though?

The answer is drivers, or "interfaces." The two are pretty interchangabe in this context.

So how would we go about writing this theoretical application? Well first things first, you need to define your core application logic as an interface. For this example lets say we want to import an array of items from various storage mediums.

interface fooInterface
{
    public function import(): array;
}

This will be considered concrete law. Any custom drivers for this package MUST implement this interface.

Now lets write the array importer. Since it is abstracting all the logic away to the driver, we just need to forward all of the function calls to the driver. We can pass the driver to the constructor.

class myArrayManager implements fooInterface
{
    private $driver;

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

    public function import(): array
    {
        return $this->driver->import();
    }
}

Now lets write our two drivers, for CSV and XML.

class csvDriver implements fooInterface
{
    private $filePath;

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

    public function import(): array
    {
        // stolen from http://php.net/manual/en/function.str-getcsv.php#117692
        $csv = array_map('str_getcsv', file($this->filePath));
        array_walk($csv, function(&$a) use ($csv) {
          $a = array_combine($csv[0], $a);
        });
        array_shift($csv); # remove column header
        return $csv;
    }
}

class xmlDriver implements fooInterface
{
    private $filePath;

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

    public function import(): array
    {
        $xml_object = simplexml_load_file($this->filePath);
        return @json_decode(@json_encode($xml_object),true); // casts object to array
    }
}

We can now use those two drivers at will depending on the situation.

if (/* file is CSV */) {
    $driver = new csvDriver(/* file path */);
} else if (/* file is XML */) {
    $driver = new xmlDriver(/* file path */);
}
$myArrayManager = new myArrayManager($driver);
$myArrayManager->import();

The best part of all of this is that should you want to implement any other drivers, you just need to do the same steps above. Your core logic does not change, just how your data is read!