开发相同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();
虽然所有的includes
和requires
都保持不变,但是我认为,如果你使用它们,你几乎需要保留一些你所有的类和名字空间的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如何定义/操作语言的所有机制,你可以看到一个很好的简单例子 。