2

Possible Duplicate:
Loading multiple versions of the same class

What is the best way to setup different versions of PHP classes and toggle them based on a configuration value?

Here's my scenario (using Zend Framework - shouldn't matter, but might):

I have 2 different versions of a web service that I'd like to be able to call. I need to switch between them using a value in my config file.

Right now, I have a class that acts as a factory and determines what version of the class needs to be returned based on the config value. Both versions of the class that the factory returns extend the same abstract class, so once the factory returns the object, I can treat both versions the same in the rest of the code. This seems fine to me in theory, but I end up with code that looks like this in my factory class, which doesn't seem to be good practice:

require_once APPLICATION_PATH . '/models/Search/SearchModelV' . $this->config->model->version . '.php';

$model_class = 'Search_Model_V' . $this->config->model->version;
return new $model_class();

I'm using PHP 5.3, so I've implemented namespaces. Just throwing that out there in case it can be used as a solution.

Community
  • 1
  • 1
Luke
  • 768
  • 2
  • 14
  • 33

3 Answers3

3

I think the solution is probably the "factory" pattern. This means that you have a static function that makes an appropriate object based on a parameter. This is possible even on an abstract class, because you can declare and call static methods on abstract classes.

abstract class Search_Model_Abstract {
    public static function create($version) {
        $class = 'Search_Model_V' . $version;

        return new $class;
    }
}

You can then create the appropriate object with Search_Model_Abstract::create($this->config->model->version)

Note that this method really should also handle any exceptions that might be thrown from the constructors, and it should also check $version against possible values.

lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • +1 You may want to pass the config itself and let the factory read the setting(s) it cares about, so every piece of client code doesn't have to know where in the config this particular setting lives. – grossvogel Oct 12 '11 at 18:56
2

I think your theory is fine, and what's making it feel hacky is your implementation. Some suggestions.

  1. Use a global autoload mechanism to avoid requiring files all over the place. If you don't do this, you can at least apply (2) before feeding the config value into require_once.
  2. Validate the config setting before using it. I don't know what's valid in your case, but favor whitelisting to be sure you're getting an accepted value.
  3. Make sure you're able to instantiate the new object, and handle errors appropriately, before returning it.
grossvogel
  • 6,694
  • 1
  • 25
  • 36
0

In theory your application should not care about versions. It just have to use current one. That's the case with versions. For example in php we don't have file_get_contents, file_get_contents_v51, etc. We just use current available versions. For such situations changing include_path to "current" version, or symlink or build script with "copy", etc will do the work for you. But that's not your case. You need to have available different instances in same time. We are not talking not about versions, but "strategies". In such situation there is no escape of dynamically building the path to file. You only have a choice where to do dynamic include. As far as we talk about "same" classes with same name (I guess so), personally I'll choose to use dependency injection container and give container different config depending on "version" in config value.

e.g.

$config = array(
'service_v1.0' => array('path' => 'path/to/my/service/v1.0/');
'service_v1.1' => array('path' => 'path/to/my/service/v1.1/');
);
$version = 'service_v1.1';
DIContainer::create($config[$version]);

With such solution I don't care about dependencies (if so) of "current" service (e.g. different service key, different storage, etc.) as far as I can describe all of them in config and inject them. For more details about dependency injection you can read here. My two cents about problem

Ivan
  • 2,262
  • 1
  • 18
  • 16