Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have a situation where I think I might need dynamic class inheritance in PHP 5.3, but the idea doesn't sit well and I'm looking for a different design pattern to solve my problem if it's possible.

Use Case

I have a set of DB abstraction layer classes that dynamically compiles SQL queries, with one DAL class for each DB type (MySQL, MsSQL, Oracle, etc.). Each table in the database has its own class that extends the appropriate DAL class. The idea is that you interact with the table classes, but never directly use the DAL class. If you want to support a different DB type for your app, you don't need to rewrite any queries or even any code, you simply change a setting that swaps one DAL class out for another...and that's it.

To give you a better idea of how this is used, you can take a look at the DAL class, the table classes, and how they are used on this StackExchange Code Review page. To really understand what I'm trying to do, please take a look at my implementation first before suggesting a solution.

Issues

The strategy that I had used previously was to have all of the DAL classes share the same class name. This eliminated autoloading, so I had to manually load the appropriate DAL class in a switch statement. However, this approach presents some problems for testing and documentation purposes, so I'd like to find a different way to solve the problem of loading the correct DAL class more elegantly.

Update to clarify the issue

The problem basically boils down to inconsistencies in the class name (pre-PHP 5.3) or class namespace (PHP 5.3) and its location in the directory structure. At this point, all of my DAL classes have the same name, DBObject, but reside in different folders, MySQL, Oracle, etc. My table classes all extend DBObject, but which DBObject they extend varies depending on which one has been loaded.

Basically, I'm trying to have my cake and eat it too. The table classes act as a stable API and extend a dynamic backend, the DAL (DBObject) classes. It works great, but I outsmarted myself and because of the inconsistencies with the class names and their locations, I can't autoload the DBObject, which makes running unit tests and generating API docs impossible for the DBObject classes because the tests and docs rely on auto-loading.

Just loading the appropriate DBObject into memory using a factory method won't work because there will be times when I need to load multiple DBObjects for testing. Because the classes currently share a name, this causes a class is already defined error. I can make exceptions for the DBObjects in my test code, obviously, but I'm looking for something a little less hacky as there may future instances where something similar would need to be done.

Solutions?

Worst case scenario, I can continue my current strategy, but I don't like it very much, especially as I'll soon be converting my code to PHP 5.3.

I suspect that I can use some sort of dynamic inheritance via either namespaces (preferred) or a dynamic class extension, but I haven't been able to find good examples of this implemented in the wild.

In your answers, please suggest either an alternate pattern that would work for this use case or an example of dynamic inheritance done right. Please assume PHP 5.3 with namespaced code. Any code examples are greatly encouraged and I'm definitely open to different approaches.

The preferred constraints for the solution are:

  • DAL class can be autoloaded.
  • DAL classes don't share the same exact same namespace, but share the same class name. As an example, I would prefer to use classes named DbObject that use namespaces like Vm\Db\MySql and Vm\Db\Oracle.
  • Table classes don't have to be rewritten with a change in DB type.
  • The appropriate DB type is determined via a single setting only. That setting is the only thing that should need to change to interchange DB types. Ideally, the setting check should occur only once per page load, but I'm flexible on that.

Final Update

Thanks to NikiC's suggestion, I created an abstract factory class that loads the proper DBObject and then wraps its public methods on a 1:1 basis. The table classes now extend the factory instead of the DBObject class.

This setup will now allow me to test and use each DBObject class without worrying about conflicts and it lets me use multiple DBMS's at a time. It also allows me to retain the code hinting in an IDE for each table class because they each extend the factory wrapper. Using wrapping methods may not be the perfect solution, but it doesn't rely on dynamic class inheritance, it solves my design problem, it meets my requirements, and it works.

share|improve this question
    
Is there a reason you're not just using Doctrine?... –  Demian Brecht Nov 15 '11 at 5:12
    
@DemianBrecht - Short answer is that Doctrine doesn't fit my needs or the way that I want to code. I'm not really open to using a different library at this point. –  VirtuosiMedia Nov 15 '11 at 5:36
    
some problems for testing and documentation purposes What kind problems? That's the whole point of your question, please expand... –  Yannis Rizos Nov 15 '11 at 7:24
    
@YannisRizos - See my update. –  VirtuosiMedia Nov 15 '11 at 9:50
    
I see. Your problem is in the DAL classes don't share the same exact same namespace, but share the same class name constrain. IMHO, it's the wrong way to do it, it offers nothing of value and it goes against the common hierarchy for such situations. Your DBObject class should be an abstract class from where database specific classes inherit from. I think you should rethink your approach... In any case, if you really wanna do it this way, I can't think of anything that would help with testing. –  Yannis Rizos Nov 15 '11 at 10:03

1 Answer 1

up vote 4 down vote accepted
+100

It looks like you are having a design issue here: Tables should not extend the Database Abstraction Layer. Instead the DAL should be injected into the table as a dependency.

abstract class Table {
    protected $dal;

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

    // whetever else all tables have in common
}

class Table_User extends Table {
    public function someMethod() {
        $this->dal->someOtherMethod();
    }
}

$table = new MyTable($dal);
$table->someMethod();

That way you will create a DAL at some upper scope and pass it down. This will also allow you to use multiple different database engines at the same time.

Additionally you obviously should not create your tables directly in your controller but let a specialized class do that. For example you could use a factory:

class TableFactory {
    protected $dal;

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

    public function createTable($name) {
        $className = 'Table_' . $name;
        return new $className($this->dal);
    }
}

That way you can create a table factory at some point with an injected DAL and pass that table factory around.

$table = $factory->createTable('User');
share|improve this answer
    
+1 - This may work with a few changes. I'll test it out over the weekend. –  VirtuosiMedia Nov 17 '11 at 12:41
    
"createTable" might not be the best terminology here. Otherwise, a solid answer. +1 –  Yam Marcovic Nov 18 '11 at 20:08
    
I'm marking this as the accepted answer. I created an abstract factory class that loads the proper DBObject and then wraps its methods on a 1:1 basis. The table classes now extend the factory instead of the DBObject class. This setup will now allow me to test and use each DBObject class without worrying about conflicts and it lets me use multiple DBMS's at a time. It also allows me to retain the code hinting in an IDE for each table class because they each extend the factory wrapper. The wrapper may not be the perfect solution, but it's infinitely better than what I had and it works. Thanks! –  VirtuosiMedia Nov 19 '11 at 0:23

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.