Wish php had double class extension

So maybe someone can suggest an alternative set up to what I have:


class ftp {} // handles opening ftp connections, listing files, downloading files, etc
class mongo_conn {} //handles mongodb connections
class dur_files extends ftp {} //dur is a type of file I regularly deal with, allows me to list dur files, download them locally, extract them (gz) and then calls dur_import
class dur_import extends mongo_conn {} // handles the import of a file from dur_files, ideally I'd extend dur_files as well as mongo_conn, but I cant, so Im calling this class from within dur_files

10,000’ view is that I need to be able to list files that are in a directory, determine if they are loaded into mongodb, pull them down, extract and then load line by line (all this is programmed) but I’m running into organization / extension issues.

Certain problems of multiple inheritance can now be dealt with using PHP Traits as of PHP 5.4

This seems like an ugly hack for Zend to have implemented… I’m trying it out now

Especially since I can’t use the trait directly? Boo Zend, just boo.

PHP has ugly hacks? Say it ain’t so Joe.

1,000 internet points for Michael

You really don’t want to inherit ftp and mongo_conn together anyway, nor really do you want to do that with dur_files and dur_import? Why? Because they are not related. ftp is not a mongo_conn and the opposite isn’t true either, mongo_conn is not an ftp.

Really you should use dependency injection for this (probably even true for dur_files and dur_import, as I bet dur_files is not an FTP and dur_import is not a mongo_conn. However, I bet dur_files needs FTP and dur_import needs dur_files and mongo_conn.

I’d personally lay it out like so:

class dur_files
{
  private $ftp;
  public function __construct($ftp)
  {
    $this->ftp = $ftp;
  }
}

class dur_import
{
  private $files;
  private $db;
  public function __construct($db, $files)
  {
    $this->db = $db;
    $this->files = $files;
  }
}

This way if you need to utilize another database, you can pass that forward too and as long as they have the same method implementation, it just works.

I agree with cpradio. Is it correct to say that a file “is-a” transfer protocol? This is a case where composition would be better than inheritance.

Here’s how I might put it together.

First, your connection classes.

class FtpConnection
{
    // ...
}

class MongoConnection
{
    // ...
}

We want to make sure these two classes can be used interchangeably, so we’re going to make sure they both support the same interface.

interface ConnectionInterface
{
    public function connect();

    public function list();

    public function get();

    // or whatever you want the methods to be
}

class FtpConnection implements ConnectionInterface
{
    // implement all the methods from ConnectionInterface using FTP
}

class MongoConnection implements ConnectionInterface
{
    // implement all the methods from ConnectionInterface using Mongo
}

Now you can make a one-time decision to instantiate either FtpConnection or MongoConnection, and all your code from that point forward can be ignorant of which kind of connection you’re using.

If you have common ways that you handle remote files, then you could create some sort of remote file manager class.

class RemoteFileManager
{
    private $connection;

    // this class can and probably should be ignorant of the kind of connection we're using
    // so it expects an existing connection object to be passed to it
    // this is the dependency injection pattern cpradio was talking about
    public function __construct(ConnectionInterface $connection)
    {
        $this->connection = $connection;
    }

    // ...
}

I re-read your comments about what each class is supposed to do, and I realized that what I posted doesn’t really work the same way, but hopefully it helps to convey the gist of composition over inheritance.

I actually liked where you went with it, other than I’m not sure I’d give the same interface between a file transfer protocol and a database protocol. I would have given two different interfaces, so if you switched from FTP to Telnet, or Curl, or SCP all of those could be built the same.

I don’t necessarily think the same is true for MongoDB, MySQL, Postgres, Oracle, etc, however, all of those would have the same process as each other, just not the same as FTP, Telnet, etc.

I really wish people would utilize interfaces more often (this comes from my .NET background). Interfaces are a godsend for APIs and forcing classes to abide by a specific implementation. It is unfortunately so many get it wrong or refuse to use it.

@K_Wolfe ; one thing that really has helped me choose between composition and inheritance is using the “is a” rule, but on top of that, if I ever had the thought, “Man, I wish I could inherit both X and Y”, I know I screwed up somewhere and I need to re-evaluate what I’ve done. Inheritance complicates things, and more inheritance only leads to further complication.

Let me leave you with this thought. Where I previously worked a contractor built our original web service that allowed third parties to get quotes from our system. The VERY base class of the 13 level inheritance was named ErrorClass (I’m not kidding!). The only thing I can tell you now, is at least he knew where his code was heading. Everything sucked, it threw more errors than returning success messages, etc. After reworking it for a couple of years (it took us years to get an architecture that could easily be swamped out), our largest inheritance level was 3. We only needed that third level for a special implementation of a project and once everything was transferred over, it went down to 2, but I think this will bring home the point (inheritance isn’t bad, but it’s complicated, and you need to understand why you are doing it and you are doing it for the right reasons).

Your viewing my “connections” incorrectly. My process MUST use both.

So maybe I should rephrase how it works then…

dur_files utilizes ftp and dur import utliizes mongo, and in order to do use dur_import, it needs the files (fur_files).

Indeed. I realized this too late.

“use” is the key word, because dur_import can use dur_files without needing to extend dur_files. Inheritance is unnecessary. Likewise, dur_import can use mongo_conn without needing to extend mongo_conn. (Imagine if all your controllers were required to extend your database just to use the database.) I don’t actually think what you’re doing requires any inheritance at all.

/**
 * An FTP connection.
 */
class ftp
{
    // ...
}

/**
 * An FTP file manager. (Requires an FTP connection object be passed in.)
 */
class dur_files
{
    private $ftp;
    
    public function __construct($ftp)
    {
        $this->ftp = $ftp;
    }
    
    // ...
}

/**
 * A Mongo DB connection.
 */
class mongo_conn
{
    // ...
}

/**
 * Import dur files from an FTP source into a Mongo DB.
 * (Requires a dur_files object and a mongo_conn object be passed in.)
 */
class dur_import
{
    private $dur_files;
    private $mongo_conn;
    
    public function __construct($dur_files, $mongo_conn)
    {
        $this->dur_files = dur_files;
        $this->mongo_conn = mongo_conn;
    }
    
    // ...
}

// Tie it all together and start things running.
$ftp = new ftp('ftp://yadayada');
$dur_files = new dur_files($ftp);
$mongo_conn = new mongo_conn('mongodb://localhost:27017');
$dur_import = new dur_import($dur_files, $mongo_conn);
$dur_import->doImport();

At the risk of sounding a bit arrogant :wink: We circled back to what I posted on how I’d lay it out :slight_smile:

Granted, if I felt that a common interface should be used across FTP and MongoDB (still not sold that it should be, although it “could be”) there is a bit I’d tweak, but regardless, you would still be primarily looking at composition with dependency injection instead of inheritance.

We’ll overlook the arrogance since you are 100% correct. :slight_smile: