Loading multiple versions of the same class
Asked Answered
T

3

7

Let's say I release a code library as a standalone PHP class. Someone then uses version 1.0 of that library in their application. Later, I release version 2.0 of the library and that same someone, for any reason, needs to use both 1.0 and 2.0 side-by-side in their application because either he or I broke backwards compatibility with the new release.

If the class names are different, it's easy enough to include and instantiate both because there's no naming conflict. But if the class names are kept the same, we run into problems:

include /lib/api-1.0/library.php;
$oldlibary = new Library();

include /lib/api-2.0/library.php;
$newlibrary = new Library();

This just won't work because we can't load two classes both with the name Library. One alternative another developer suggested was to use namespaces. The following should work:

namespace old {
    include /lib/api-1.0/library.php;
}
namespace new {
    include /lib/api-2.0/library.php;
}

$oldlibary = new old\Library();
$newlibrary = new new\Library();

Unfortunately, this isn't very scalable. It would work with a 2-instance situation (which, hopefully, I wouldn't have to use in the first place), but to scale it to 3, 4, 5, or more instances you'd need to have additional namespaces defined and set up, If you're not using those namespaces in the first place, that's a bunch of unnecessary code.

So is there a way to dynamically create a namespace, include a file, and instantiate the class contained within that file in a uniquely-named variable?


Let me add some more clarification ...

I'm building a set of libraries to be used by other developers who build plugins/modules for a couple of CMS platforms. Ideally, everyone would always use the latest version of my library, but I can't guarantee that and I can't guarantee the end user will always upgrade their modules when new versions become available.

The use case I'm trying to work with is one where the end user installs two different modules by two different developers: call them Apple and Orange. Both modules are using version 1.0 of my library, which is great. We can instantiate it once and both sets of code can benefit from the functionality.

Later, I release a minor patch to this library. It's versioned 1.1 because it doesn't break backwards compatibility with the 1.x branch. The developer of Apple immediately updates his local version and pushes a new edition of his system. The developer of Orange is on vacation and doesn't bother.

When the end user updates Apple she gets the latest maintenance release of my library. Because it's a maintenance release, it's assumed to be safe to completely replace version 1.0. So the code only instantiates 1.1 and Orange benefits from a maintenance patch even though the developer never bothered to update their release.

Even later, I decide to update my API to add some hooks to Facebook for some reason. The new features and API extensions change a lot about the library, so I up the version to 2.0 to flag it as potentially not backwards-compatible in all situations. Once again, Apple goes in and updates his code. Nothing broke, he just replaced my library in his /lib folder with the latest version. Orange decided to go back to school to become a clown and has stopped maintaining his module, though, so it doesn't get any updates.

When the end user updates Apple with the new release, she automatically gets version 2.0 of my library. But Orange had code in his system that added Facebook hooks already, so there would be a conflict if 2.0 was rolled in to his library by default. So instead of replacing it entirely, I instantiate 2.0 once for Apple and, side-by-side, instantiate the 1.0 version that shipped with Orange so it can use the right code.

The entire point of this project is to allow third party developers to build systems based on my code without depending on them to be reliable and update their code when they're supposed to. Nothing should break for the end user, and updating my library when used inside someone else's system should be a simple file replacement, not going through and changing all of the class references.

Truncated answered 26/4, 2011 at 15:23 Comment(5)
Why are you worrying about this? I understand the use-case for it, but don't see the benefit.Pori
A presentation made by a google developper might interest you youtube.com/watch?v=aAb7hSCtvGwWillman
Benefit: More than one developer can use my library to build modules for a specific CMS platform. If one developer ties their system to 1.0 and one to 2.0, and an end user tries to install both modules the platform should be able to instantiate an object from both versions at the same time.Truncated
coders will most likely build an adapter for your third party code. this way they can switch a few lines in the adapter to make the app work with your new version of the library.Ensoul
A real-life example where something like this is useful is two different CMS modules, one providing facebook login and one providing registration using facebook. Both are using the facebook PHP SDK but cannot use different versions if they cannot be namespaced. Whatever version that gets loaded first will take precedence.Achlamydeous
T
3

I decided on a slightly alternate route. The namespace method works, but you need a different namespace for each version of the class. So it's not really scalable, because you have to pre-define the number of available namespaces.

Instead, I've settled on a specific naming schema for the classes and a version loader/instantiater.

Each class will take the following format:

<?php
if( ! class_exists( 'My_Library' ) ) { class My_Library { } }

if( ! class_exists( 'My_Library_1_0' ) ) :
class My_Library_1_0 extends My_Library {
    ... class stuff ...
}
endif;

The parent My_Library class will actually end up containing a few identifiers specific to the library - purpose, compatibility statements, etc. That way I can perform other logical checks to make sure the right My_Library exists before moving forward and claiming that My_Library_1_0 is really version 1.0 of the library I want.

Next, I have a loader class that I'll be using in my main project:

<?php
class Loader {
    static function load( $file, $class, $version ) {
        include( $file );
        $versionparts = explode('.', $version);
        foreach($versionparts as $part) $class .= '_' . $part;
        return new $class();
    }
}

Once this is done, you can use Loader to load both instances of the class or simple references if you want to use static methods:

$reference = Loader::load( 'library.php', 'My_Library', '1.0' );

$loader = new Loader();
$instance = $loader->load( 'library.php', 'My_Library', '1.0' );

Not quite the same as the namespace version I was shooting for, but it works and alleviates my concerns about breaking things for the end user. I am assuming that two different versions of My_Library_1_0 would be the same, though ... so there's still a dependence on third party developers knowing what they're doing.

Truncated answered 27/4, 2011 at 3:37 Comment(0)
R
1

So is there a way to dynamically create a namespace, include a file, and instantiate the class contained within that file in a uniquely-named variable?

Yes, such method exists. You can do anything you want with eval and stream handlers. But it is bad practice and wrong approach - you can try to use factory method (the code is not tested - it only shows example):

<?php

if (!class_exists('Library')) {

    class Library
    {
        public static function create($version)
        {
            if (class_exists($c = 'Library' . $version))
                return new $c();
            return null;
        }
    }

}

class Library1
{

}

class Library2
{

}

...
Rhizo answered 26/4, 2011 at 15:41 Comment(0)
D
-1

Let the user select a version, then according to that load your api file

The file name should be dynamically determinable, for example:

include('/lib/api-'.$versionId.'/library.php'); 

if version -1.0 as wise

Be careful to ensure that the user input is converted into a single decimal float and nothing nefarious.

Dexamyl answered 26/4, 2011 at 15:46 Comment(1)
The problem there is that the class is called Library in both the /api-1.0 folder and the /api-2.0 folder. You can't have two classes with the same name ... which is why I suggested namespaces. But I don't want to hard-code the namespaces, I want that part to be dynamic. There will be more than one "user" and the likely won't be communicating to select a version number ...Truncated

© 2022 - 2024 — McMap. All rights reserved.