PHP: Handling file-version dependencies in a project
I know about the dependency injection pattern which seems like the best solution available to handle classes that require instances of other classes to function properly. But when it comes to handling the scenario of a certain class existing, but in a wrong version, DI or any other OOP pattern obviously isn't going to help at all.
What is a good way to handle version-based dependencies of libraries in PHP?
Here's an illustrated way to show what I mean (psuedo-code):
class payment_module_base {
var $baseVersion;
function __construct() {
$this->baseversion = 12; //Version 12
}
开发者_开发知识库
// Other relevant methods...
}
class card_payments extend payment_module_base {
function construct() {
if ($this->baseversion <= 10)
Throw New Exception("Card payments isnt working with base module V. <10");
}
}
Note: This is for illustrative purposes - I know that this should be lifted out to specific tests to not clutter the production code with conditionals regarding versions unless really neccessary.
I simply don't handle this in PHP but handle it on the server, in the DI configuration or in my autoloader. When I need to use multiple different versions of the same library, I usually set up my directory structure like this:
/usr/share/php/SomeLibrary-1.0
/usr/Share/php/SomeLibrary-2.0
/usr/share/php/SomeLibrary --> SomeLibrary-2.0
Each version is put into a separate, versioned folder. There's an unversioned symlink pointed to the latest version.
In my DI container (I often use the one from symfony-components) I simply configure it to load the file from the correct path. Alternatively, you can set up your autoloaders so that the right version will be loaded.
In short, what library versions (and ultimately what library paths) to use is part of the application configuration, not the application code.
Update
When one library requires a specific version of another library and it can only find an incorrect version, I simply throw an exception and let the application handle it (usually that means displaying an error and exiting). I don't try to load a specific version of a library. I load whatever is configured in the configuration file for sfServiceContainer (my Dependency Injection solution of choice). If that is the wrong version then the administrator has to update the configuration settings.
Don't try to automagically search for different library versions, trying to load the correct one. Just have a human configure which library should be loaded from which path (and provide a configuration with sane defaults).
A lot of people swear by definitions in the top of library files. It's simple, and the performance is good ... perhaps too simple.
// -- At the top of the library, we define this ...
define('PAYMENT_LIBRARY_VER', 324);
// -- At some point in the code, we check it!
if( defined('PAYMENT_LIBRARY_VER') && PAYMENT_LIBRARY_VER <= 320 )
throw new HissyFit("This is just an alias for an exception.");
I think it's a bit unwieldy myself, and prefer factory version management along the lines of ...
// -- In a file far far away ...
class PaymentProcessor_r324 extends PaymentProcessor
{
...
}
// -- Somewhere else ...
class PaymentProcessor_r325 extends PaymentProcessor
{
...
}
// -- In the main library class "holder"
class PaymentProcessorManager
{
/**
* The latest version of the library
*/
const LATEST_VERSION = 324;
/**
* Initialise a new instance of the PaymentProcessor.
* @param $ver
* @return PaymentProcessor
*/
public static function Initialise( $revision = null )
{
if( $revision === null )
$revision = self::LATEST_VERSION;
var $c = "PaymentProcessor_r{$revision}";
return new $c();
}
}
// -- Pretty much anywhere you like, and autoloading friendly to boot.
if( PaymentProcessorManager::LATEST_VERSION < 300 )
throw new HissyFit("This is just an alias for an exception.");
This model is more extensible. You have to reference the class to get the latest version, but don't need an instance of it. The reference means that any autoloading code you have will get a chance to pick up the class, and you can run version checks at any point in the pipeline. At the same time, you can optionally support multiple versions of the class, and easily load development versions.
// -- Even though the 'latest version' is 324, there's nothing to stop me
// from loading my development version, #325.
var $paymentProcessor = PaymentProcessorManager::Initialise(325);
This model also supports building dependency headers ...
if( PaymentProcessorManager::LATEST_VERSION < 300
|| OtherModuleManager::LATEST_VERSION < 240 )
throw new HissyFit("The minimum requirements for this functionality are not met.");
I would say as a boot note though that PHP doesn't seem to lend itself well to this kind of arrangement ... it's pretty loose, and requires a fair amount of manual checking to ensure global compatibility. If reasonably possible, I'd avoid versioning in the wonderful world of PHP.
Update: Oh yes, and I almost forgot. You'd very possibly want a class alias along the lines of the following to not require horrible version specific chains.
class_alias('PaymentProcessor_r' . PaymentProcessorManager::LATEST_VERSION, 'PaymentProcessorLatest');
精彩评论