加载同一类的多个版本
假设我将一个代码库作为独立的 PHP 类发布。然后有人在他们的应用程序中使用该库的 1.0 版本。后来,我发布了该库的 2.0 版本,而同一个人出于任何原因需要在其应用程序中同时使用 1.0 和 2.0,因为他或我破坏了与新版本的向后兼容性。
如果类名不同,则很容易包含并实例化两者,因为不存在命名冲突。但如果类名保持相同,我们就会遇到问题:
include /lib/api-1.0/library.php;
$oldlibary = new Library();
include /lib/api-2.0/library.php;
$newlibrary = new Library();
这行不通,因为我们无法加载两个都名为 Library
的类。另一位开发人员建议的另一种选择是使用命名空间。以下应该可以工作:
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();
不幸的是,这不太可扩展。它适用于 2 个实例的情况(希望我一开始就不必使用),但要将其扩展到 3、4、5 或更多实例,您需要定义额外的命名空间并设置,如果您一开始就不使用这些命名空间,那么这是一堆不必要的代码。
那么有没有一种方法可以动态创建命名空间、包含文件并在唯一命名的变量中实例化该文件中包含的类?
让我补充一些说明...
我正在构建一组库,供其他为几个 CMS 平台构建插件/模块的开发人员使用。理想情况下,每个人都会始终使用我的库的最新版本,但我不能保证这一点,也不能保证最终用户在新版本可用时始终会升级他们的模块。
我尝试使用的用例是最终用户安装两个不同开发人员的两个不同模块:将它们称为 Apple 和 Orange。这两个模块都使用我的库的 1.0 版本,这非常棒。我们可以实例化它一次,并且两组代码都可以从该功能中受益。
后来,我发布了这个库的一个小补丁。它的版本为 1.1,因为它不会破坏与 1.x 分支的向后兼容性。 Apple的开发者立即更新了他的本地版本并推送了他的系统的新版本。 Orange 的开发者正在度假,不打扰。
当最终用户更新Apple时,她会获得我的库的最新维护版本。因为它是一个维护版本,所以假设完全替换 1.0 版本是安全的。因此,代码仅实例化 1.1,并且 Orange 从维护补丁中受益,即使开发人员从未费心更新其版本。
甚至后来,出于某种原因,我决定更新我的 API 以向 Facebook 添加一些钩子。新功能和 API 扩展对库进行了很多更改,因此我将版本升级到 2.0,以将其标记为在所有情况下都可能不向后兼容。苹果再次介入并更新了他的代码。没有任何问题,他只是用最新版本替换了他的 /lib
文件夹中的我的库。 Orange 决定回到学校成为一名小丑,并已停止维护他的模块,因此它不会得到任何更新。
当最终用户使用新版本更新 Apple 时,她会自动获取我的库的 2.0 版本。但是 Orange 在他的系统中已经添加了 Facebook 挂钩的代码,因此如果默认将 2.0 引入到他的库中,就会发生冲突。因此,我没有完全替换它,而是为 Apple 实例化一次 2.0,并并行实例化 Orange 附带的 1.0 版本,以便它可以使用正确的代码。
该项目的全部目的是允许第三方开发人员基于我的代码构建系统,而不依赖于它们的可靠性并在应该更新其代码时更新其代码。对于最终用户来说,什么都不应该破坏,并且在其他人的系统中使用时更新我的库应该是简单的文件替换,而不是遍历和更改所有类引用。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我决定走一条稍微不同的路线。命名空间方法有效,但您需要为类的每个版本使用不同的命名空间。所以它并不是真正可扩展的,因为你必须预先定义可用命名空间的数量。
相反,我已经为类和版本加载器/实例化器确定了特定的命名模式。
每个类将采用以下格式:
父
My_Library
类实际上最终会包含一些特定于库的标识符 - 用途、兼容性声明等。这样我就可以执行其他逻辑检查以确保右My_Library
在继续前进并声称My_Library_1_0
确实是我想要的库的 1.0 版本之前就存在。接下来,我有一个将在我的主项目中使用的加载器类:
完成此操作后,您可以使用
Loader
加载该类的两个实例或简单引用(如果您想使用静态)方法:与我正在拍摄的名称空间版本不太一样,但它有效并减轻了我对破坏最终用户的事情的担忧。不过,我假设两个不同版本的
My_Library_1_0
是相同的......因此仍然依赖第三方开发人员知道他们在做什么。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:
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 rightMy_Library
exists before moving forward and claiming thatMy_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:
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: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.是的,这样的方法是存在的。您可以使用 eval 和流处理程序执行任何操作。但这是不好的做法和错误的方法 - 您可以尝试使用工厂方法(代码未经测试 - 它仅显示示例):
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):
让用户选择一个版本,然后根据该版本加载你的api文件
文件名应该是动态确定的,例如:
如果版本为-1.0,
请小心确保用户输入被转换为单个十进制浮点型 没有任何邪恶的事情。
Let the user select a version, then according to that load your api file
The file name should be dynamically determinable, for example:
if version -1.0 as wise
Be careful to ensure that the user input is converted into a single decimal
float
and nothing nefarious.