6

假设我将代码库作为独立的 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();

这是行不通的,因为我们不能同时加载两个名称为 name 的类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 平台构建插件/模块的其他开发人员使用。理想情况下,每个人都将始终使用我的库的最新版本,但我不能保证,我也不能保证最终用户在新版本可用时总是升级他们的模块。

我正在尝试使用的用例是最终用户安装两个不同开发人员的两个不同模块:称它们为AppleOrange。这两个模块都使用我的库的 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 版本,以便它可以使用正确的代码。

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

4

3 回答 3

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是相同的,但是......所以仍然依赖于第三方开发人员知道他们在做什么。

于 2011-04-27T03:37:20.597 回答
1

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

是的,这种方法是存在的。您可以使用 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
{

}

...
于 2011-04-26T15:41:29.747 回答
-1

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

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

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

如果版本 -1.0 一样明智

小心确保用户输入被转换成一个小数float并且没有恶意。

于 2011-04-26T15:46:42.613 回答