加载同一类的多个版本

时间:2011-04-26 15:23:31

标签: php namespaces dynamic-code

假设我将代码库作为独立的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以将其标记为在所有情况下可能不向后兼容。再次, Apple 进入并更新他的代码。没有什么破坏,他只是用我最新版本的/lib文件夹替换了我的库。 Orange 决定回到学校成为一名小丑并且已经停止维护他的模块,所以它没有得到任何更新。

当最终用户使用新版本更新 Apple 时,她会自动获取我的库的2.0版本。但是 Orange 在他的系统中已经添加了Facebook钩子的代码,因此如果默认情况下2.0被引入他的库,就会发生冲突。因此,我没有完全替换它,而是为 Apple 实例化2.0一次,然后并排实例化 Orange 附带的1.0版本,以便它可以使用正确的代码

这个项目的重点是允许第三方开发人员根据我的代码构建系统,而不依赖于它们是可靠的,并在他们应该的时候更新他们的代码。对于最终用户来说,没有什么可以破坏,并且在其他人的系统中使用时更新我的​​库应该是一个简单的文件替换,而不是通过并更改所有类引用。

3 个答案:

答案 0 :(得分:3)

我决定采用稍微不同的路线。命名空间方法有效,但每个版本的类需要一个不同的命名空间。所以它不是真正可扩展的,因为你必须预先定义可用命名空间的数量。

相反,我已经确定了类的特定命名模式和版本加载器/实例化器。

每个班级将采用以下格式:

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

My_Library类实际上最终会包含一些特定于库的标识符 - 目的,兼容性语句等。这样我可以执行其他逻辑检查以确保正确 My_Library之前存在,并且声称My_Library_1_0确实是我想要的库的1.0版。

接下来,我将在我的主项目中使用一个加载器类:

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

完成此操作后,如果要使用静态方法,可以使用Loader加载类的实例或简单引用:

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

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

与我拍摄的命名空间版本不完全相同,但它可以解决我对最终用户破坏事物的担忧。我假设My_Library_1_0的两个不同版本是相同的,但是......所以仍然依赖第三方开发人员知道他们在做什么。

答案 1 :(得分:0)

  

那么有没有办法动态创建命名空间,包含一个文件,并在一个唯一命名的变量中实例化该文件中包含的类?

是的,这种方法存在。您可以使用eval和流处理程序执行任何操作。但这是不好的做法和错误的方法 - 您可以尝试使用工厂方法(代码未经过测试 - 它只显示示例):

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

}

...

答案 2 :(得分:-1)

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

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

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

如果版本-1.0明智

小心确保将用户输入转换为单个小数float并且没有任何恶意。