开发相同PHP代码的命名空间和非命名空间版本的策略

我正在维护为PHP 5.2编写的库,我想创buildPHP 5.3的命名空间版本。 不过,我也会保持非命名空间的版本,直到PHP 5.3变得如此之久,甚至Debian稳定版本才能运行;)

我有相当干净的代码,约80个类Project_Directory_Filename命名scheme(我会改变他们\Project\Directory\Filename当然),只有less数的函数和常量(也带有项目名称的前缀)。

问题是:并行开发名称空间和非名称空间版本的最佳方法是什么?

  • 我应该只是在仓库中创build分叉,并保持合并分支之间的变化? 是否有反斜杠代码变得难以合并的情况?

  • 我应该编写脚本,将5.2版本转换为5.3,反之亦然? 我应该使用PHP标记器吗? sed ? C预处理器?

  • 有没有更好的方法来使用名称空间,并保持与旧版本PHP的向后兼容性?


更新: 决定不要使用命名空间 。

我不认为预处理5.3代码是个好主意。 如果您的代码在function上与PHP 5.2和5.3相同,但使用名称空间(而不是下划线分隔的前缀)除外,为什么要使用名称空间呢? 在这种情况下,我觉得你想要使用命名空间,为了使用命名空间。

我认为你会发现当你迁移到命名空间时,你将会开始对组织你的代码进行“有点不同的想法”。

出于这个原因,我非常赞同你的第一个解决scheme。 创build一个分支,并做function和错误修正backports。

祝你好运!

这是我以前的答案的后续行动:

命名空间模拟代码非常稳定。 我已经可以得到symfony2的工作(一些问题,但基本上)。 尽pipe除了new $class之外,还有一些东西缺失,如variables名称空间parsing。

现在我写了一个脚本,它将通过一个目录recursion迭代并处理所有文件: http : //github.com/nikic/prephp/blob/master/prephp/namespacePortR.php


使用说明

您的代码工作的要求

你的类名不能包含_字符。 如果他们这样做,转换时,类名可能会变得模棱两可。

您的代码不得在命名空间中重新声明任何全局函数或常量。 因此确保所有的代码都可以在编译时被parsing。

基本上这些是你的代码的唯一限制。 虽然我应该注意到,在默认configuration下,命名空间$className = 'Some\\NS\\Class'; new $className不会parsing类似于$className = 'Some\\NS\\Class'; new $className $className = 'Some\\NS\\Class'; new $className ,因为它需要插入额外的代码。 最好稍后进行修补(手动或使用自动修补系统)。

组态

因为我们已经假设在命名空间中没有重新声明全局函数或常量,所以必须在命名空间侦听器中设置assumeGlobal类常量。 在同一个文件中将SEPARATOR常量设置为_

在命名空间PortR中更改configuration块以满足您的需求。


PS:脚本可以提供一个?skip=int选项。 这告诉它跳过第一个int文件。 如果您已将覆盖模式设置为智能,则不需要此设置。

这是我发现的:

这样做正则expression式是一个噩梦。 你可以用几个简单的expression式来完成大部分工作,但是边缘案例是一个杀手。 我已经结束了可怕的,脆弱的混乱,几乎没有一个代码库的作品。

内置的tokenizer和简单的recursion下降parsing器可以处理这个简单的语言子集。

我已经结束了相当丑陋的devise(在一个语法分析器和变换器 – 大多只是改变或重新发射令牌),因为它似乎太多的工作,build立有用的语法树,保持空白(我想结果代码是人类可读)。

我想尝试phc为此,但不能说服我的configure ,我已经build立了所需的Boost库的版本。

我还没有尝试ANTLR,但它可能是这类任务的最佳工具。

我正在开发一个在PHP 5.2上模拟PHP 5.3的项目: prephp 。 它包括名称空间支持(但尚未完成)。

现在,根据写这个的经验,在命名空间parsing中存在一个不明确的问题:非限定的函数调用和常量查找会回退到全局名称空间。 因此,只有在完全限定或限定了所有的函数调用/常量查找,或者如果您没有在名称空间中重新定义任何函数或常量(与函数中内置的PHP相同的名称),您才能自动转换您的代码。

如果你严格遵守这个惯例(无论你select哪一个),转换你的代码是相当容易的。 这将是在prephp中模拟名称空间的代码的一个子集。 如果你需要执行的帮助,请随意问我,我会感兴趣;)

PS: prephp的命名空间模拟代码还没有完成,可能是越野车。 但它可能会给你一些见解。

以下是我认为你将能够find的最佳答案:

第1步:为每个目录w / php5.3代码创build一个名为5.3的目录,并粘贴所有5.3特定的代码。

第2步:select一个你想放在命名空间的类,在5.3 / WebPage / Consolidator.inc.php中执行

 namespace WebPage; require_once 'WebPageConsolidator.inc.php'; class Consolidator extends \WebpageConsolidator { public function __constructor() { echo "PHP 5.3 constructor.\n"; parent::__constructor(); } } 

步骤3:使用策略函数来使用新的PHP 5.3代码。 放在非PHP5.3中findclass.inc.php:

 // Copyright 2010-08-10 Theodore R. Smith <phpexperts.pro> // License: BSD License function findProperClass($className) { $namespaces = array('WebPage'); $namespaceChar = ''; if (PHP_VERSION_ID >= 50300) { // Search with Namespaces foreach ($namespaces as $namespace) { $className = "$namespace\\$className"; if (class_exists($className)) { return $className; } } $namespaceChar = "\\"; } // It wasn't found in the namespaces (or we're using 5.2), let's search global namespace: foreach ($namespaces as $namespace) { $className = "$namespaceChar$namespace$className"; if (class_exists($className)) { return $className; } } throw new RuntimeException("Could not load find a suitable class named $className."); } 

第4步:重写你的代码看起来像这样:

 <?php require 'findclass.inc.php'; $includePrefix = ''; if (PHP_VERSION_ID >= 50300) { $includePrefix = '5.3/'; } require_once $includePrefix . 'WebPageConsolidator.inc.php'; $className = findProperClass('Consolidator'); $consolidator = new $className; // PHP 5.2 output: PHP 5.2 constructor. // PHP 5.3 output: PHP 5.3 constructor. PHP 5.2 constructor. 

将为你工作。 这是一个性能明智的,但只是一点点,当你决定停止支持5.3将被淘汰。

我所做的是,使用下划线命名约定(等等)的大型代码库,并且需要大量代替自动加载器,就是定义一个自动加载器,并将定义别名的文件中的class_alias行添加到类旧名称在改变名字之后和命名空间一起使用。

然后,我开始删除require_once语句,其中执行不依赖于包含顺序,因为自动加载器会挑选东西,命名空间的东西,因为我去修复错误等等。

迄今为止工作得很好。

那么,我不知道这是否是“最好”的方式,但理论上,你可以使用一个脚本来获取你的5.3迁移代码,并把它移回5.2(甚至可能使用PHP)。

在你的命名空间文件,你会想做一些转换:

 namespace \Project\Directory\Filename; class MyClass { public $attribute; public function typedFunction(MyClass $child) { if ($child instanceof MyClass) { print 'Is MyClass'; } } } 

像这样的东西:

 class Project_Directory_Filename_MyClass { public $attribute; public function typedFunction(Project_Directory_Filename_MyClass $child) { if ($child instanceof Project_Directory_Filename_MyClass) { print 'Is MyClass'; } } } 

而在你的命名空间代码,你需要从以下转换:

 $myobject = new Project\Directory\Filename\MyClass(); 

至:

 $myobject = new Project_Directory_Filename_MyClass(); 

虽然所有的includesrequires都保持不变,但是我认为,如果你使用它们,你几乎需要保留一些你所有的类和名字空间的Cache来对“instanceof”和types化的参数进行复杂的转换。 这是我能看到的最棘手的事情。

我没有自己testing过,但你可以看看这个PHP 5.2 – > PHP 5.3转换脚本 。

这与5.3 – > 5.2不一样,但是也许你会在那里find一些有用的东西。

我们的DMS Software Reengineering Toolkit可能会很好地实施您的解决scheme。 它旨在执行可靠的源代码转换,通过使用AST到AST转换在表面语法方面进行编码。

它有一个PHP前端 ,这是一个完整的,精确的PHPparsing器,AST生成器和AST到PHP代码再生器。 DMS提供AST漂白打印或保真度打印(“尽可能保留列号”)。

这个组合已经被用来实现PHP 4和5的各种值得信赖的PHP源代码操作工具。

编辑(回应有些不相信的评论):

对于OP的解决scheme,下面的DMS转换规则应该完成大部分的工作:

 rule replace_underscored_identifier_with_namespace_path(namespace_path:N) :namespace_path->namespace_path "\N" -> "\complex_namespace_path\(\N\)" if N=="NCLASS_OR_NAMESPACE_IDENTIFIER" && has_underscores(N); 

该规则查找允许使用名称空间path的所有“简单”标识符,并将相应的名称空间pathreplace为简单标识符,方法是将标识符的string拆分为由下划线分隔的构成元素。 必须在DMS的实现语言PARLANSE中编写一些程序帮助来检查标识符是否包含下划线(“has_underscores”),并通过构build相应的名称空间path子树(“complex_namespace_path”)来实现拆解逻辑。

该规则通过抽象地标识与语言非终结符对应的树(在本例中为“namespace_path”),并用代表全名空间path的更复杂的树来代替简单树,规则被写为文本,但规则本身被parsing由DMS构build它需要匹配PHP树的树。

DMS规则应用程序逻辑可以在整个由PHPparsing器生成的AST中随处应用这个规则。

面对构成PHP语言的所有复杂东西,这个答案可能看起来过于简单,但是所有其他的复杂性都隐藏在DMS使用的PHP语言定义中; 这个定义大约有10,000行的词汇和语法定义,但已经被testing和工作。 所有的DMS设备和这些10K线都是简单的正则expression式无法可靠地工作的迹象。 (令人惊讶的是,要达到这个目标需要多less机械设备;自1995年以来,我一直在从事DMS的工作)。

如果你想看看DMS如何定义/操作语言的所有机制,你可以看到一个很好的简单例子 。