加载同一类的多个版本

时间:2021-09-27 17:02:47

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.

假设我将一个代码库作为一个独立的PHP类发布。然后有人在他们的应用程序中使用该库的1.0版本。后来,我发布了这个库的2.0版本,由于某种原因,同一个人需要在他们的应用程序中同时使用1.0和2.0,因为他或我都破坏了与新版本的向后兼容性。

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.

不幸的是,这不是很可伸缩。将使用两个实例的情况(希望,我就不会首先使用),但规模3,4,5,或多个实例需要有其他名称空间定义和设置,如果你不使用这些名称空间首先,这是一堆不必要的代码。

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.

我正在构建一组库,供为几个CMS平台构建插件/模块的其他开发人员使用。理想情况下,每个人都将始终使用我的库的最新版本,但我不能保证这一点,我也不能保证当新版本可用时,最终用户总是会升级他们的模块。

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.

我正在尝试使用的用例是,最终用户由两个不同的开发人员安装两个不同的模块:称为Apple和Orange。两个模块都使用了我的库的1.0版本,这很棒。我们可以实例化它一次,两组代码都可以从功能中获益。

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.

后来,我向这个库发布了一个小补丁。版本号为1.1,因为它不会破坏与1的向后兼容性。x分支。苹果的开发人员立即更新了本地版本,并推出了新版本的系统。Orange公司的开发人员正在度假,他对此并不在意。

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.

当最终用户更新苹果时,她得到了我的库的最新维护版本。因为这是一个维护版本,所以完全替换1.0版本是安全的。因此,即使开发人员从不费心更新他们的版本,代码也只实例化了维护补丁带来的1.1和橙色的好处。

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.

甚至后来,出于某种原因,我决定更新我的API,向Facebook添加一些钩子。新特性和API扩展对库有很大的改变,因此我将版本调到2.0,以标记它在所有情况下都可能不向后兼容。再一次,苹果进入并更新了他的代码。没有任何问题,他只是用最新的版本替换了他/lib文件夹中的库。奥兰治决定回到学校当小丑,但他已经停止维护他的模块,所以它没有任何更新。

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.

当最终用户用新版本更新苹果时,她会自动获得我的库的2.0版本。但是Orange已经在他的系统中添加了一些添加了Facebook钩子的代码,所以如果在默认情况下将2.0卷到他的库中,就会产生冲突。因此,我没有完全替换它,而是为苹果实例化了2.0,并同时实例化了1.0版本,该版本使用了橙色,这样它就可以使用正确的代码。

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.

这个项目的全部目的是允许第三方开发人员基于我的代码构建系统,而不依赖于他们是否可靠,并在他们应该的时候更新他们的代码。对于最终用户来说,什么都不应该中断,在别人的系统中使用时更新我的库应该是简单的文件替换,而不是遍历和更改所有的类引用。

3 个解决方案

#1


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.

父My_Library类将会包含一些特定于图书馆的标识符——目的,兼容性声明等等。这样我可以执行其他逻辑检查,以确保正确的My_Library存在前进和声称My_Library_1_0之前真的是我想要的1.0版本库。

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:

完成后,如果您想使用静态方法,您可以使用Loader来加载类的两个实例或简单的引用:

$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.

与我所追求的命名空间版本并不完全相同,但是它可以工作并减轻我对为最终用户破坏东西的担忧。我假设两个不同版本的My_Library_1_ 0将是相同的,尽管…所以仍然依赖第三方开发者知道他们在做什么。

#2


0  

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):

是的,这样的方法存在。您可以使用eval和stream处理程序来做任何事情。但这是不好的做法和错误的方法——您可以尝试使用工厂方法(代码没有经过测试——它只显示示例):

<?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
{

}

...

#3


-1  

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

让用户选择一个版本,然后根据加载您的api文件。

The file name should be dynamically determinable, for example:

文件名应该是动态确定的,例如:

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

if version -1.0 as wise

如果版本-1.0是明智的

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

请注意,要确保用户输入被转换为一个十进制浮点数,并且没有任何恶意。

#1


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.

父My_Library类将会包含一些特定于图书馆的标识符——目的,兼容性声明等等。这样我可以执行其他逻辑检查,以确保正确的My_Library存在前进和声称My_Library_1_0之前真的是我想要的1.0版本库。

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:

完成后,如果您想使用静态方法,您可以使用Loader来加载类的两个实例或简单的引用:

$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.

与我所追求的命名空间版本并不完全相同,但是它可以工作并减轻我对为最终用户破坏东西的担忧。我假设两个不同版本的My_Library_1_ 0将是相同的,尽管…所以仍然依赖第三方开发者知道他们在做什么。

#2


0  

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):

是的,这样的方法存在。您可以使用eval和stream处理程序来做任何事情。但这是不好的做法和错误的方法——您可以尝试使用工厂方法(代码没有经过测试——它只显示示例):

<?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
{

}

...

#3


-1  

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

让用户选择一个版本,然后根据加载您的api文件。

The file name should be dynamically determinable, for example:

文件名应该是动态确定的,例如:

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

if version -1.0 as wise

如果版本-1.0是明智的

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

请注意,要确保用户输入被转换为一个十进制浮点数,并且没有任何恶意。