我正在维护为PHP 5.2编写的库,我想创建它的PHP 5.3命名空间版本。但是,我还会保留非命名空间版本,直到PHP 5.3变得如此陈旧,甚至Debian稳定版也可以发布它;)
我有相当干净的代码,大约有80个类遵循Project_Directory_Filename
命名方案(当然我将它们更改为\Project\Directory\Filename
)并且只有少数函数和常量(也以项目名称为前缀)
问题是:并行开发命名空间和非命名空间版本的最佳方法是什么?
我应该只在存储库中创建fork并继续在分支之间合并更改吗?是否存在反斜杠代码难以合并的情况?
我应该编写将5.2版本转换为5.3版本的脚本,反之亦然吗?我应该使用PHP tokenizer吗? sed
? C预处理器?
有没有更好的方法可以在可用的地方使用命名空间并保持与旧版PHP的向后兼容性?
答案 0 :(得分:9)
我不认为预处理5.3代码这是一个好主意。如果您的代码在PHP 5.2和5.3中功能相同,除了使用命名空间而不是下划线分隔的前缀,为什么要使用命名空间?在这种情况下,为了使用命名空间,它听起来像你想要使用名称空间..
我认为你会发现,当你迁移到名称空间时,你会开始对组织你的代码有所不同。
出于这个原因,我非常同意你的第一个解决方案。创建一个fork并执行功能和错误修正的后退。
祝你好运!答案 1 :(得分:7)
这是my previous answer的后续内容:
命名空间模拟代码非常稳定。我已经可以让symfony2工作了(一些问题仍然存在,但基本上)。虽然除new $class
之外的所有情况仍然存在一些缺失的变量名称空间分辨率。
现在我编写了一个脚本,它将递归遍历目录并处理所有文件:http://github.com/nikic/prephp/blob/master/prephp/namespacePortR.php
您的类名不得包含_
字符。如果他们这样做,那么转换时类名可能会变得模棱两可。
您的代码不得重新声明命名空间中的任何全局函数或常量。因此,确保您的所有代码都可以在编译时解析。
基本上这些是对代码的唯一限制。虽然我应该注意,在默认配置中,namespacePortR不会解析$className = 'Some\\NS\\Class'; new $className
之类的内容,因为它需要插入额外的代码。最好在以后修补(手动修补或使用自动修补系统。)
由于我们假设在命名空间中没有重新声明全局函数或常量,因此必须在namespace listener中将assumeGlobal
类设置为常量。在同一文件中,将SEPARATOR
常量设置为_
。
在namespacePortR中更改配置块以满足您的需求。
PS:脚本可能会提供?skip=int
选项。这告诉它跳过第一个int
文件。如果您已将覆盖模式设置为智能,则不应该需要它。
答案 2 :(得分:1)
这是我发现的:
使用正则表达式执行此操作是一场噩梦。只需几个简单的表达式就可以完成大部分工作,但边缘情况是一个杀手。我最终得到了一个可怕的,脆弱的混乱,几乎不能用于一个代码库。
内置的tokenizer和简单的递归下降解析器只能处理语言的简化子集。
我最终得到了相当丑陋的设计(解析器和变换器合二为一 - 大多数只是改变或重新发出令牌),因为构建有用的语法树并保持空白空间似乎太多了(我希望得到的代码是人类可读的)。
我想尝试phc
,但无法说服configure
我已经构建了所需版本的Boost库。
我还没有尝试过ANTLR,但它可能是完成这类任务的最佳工具。
答案 3 :(得分:1)
我正在开发一个在PHP 5.2上模拟PHP 5.3的项目:prephp。它包括名称空间支持(但尚未完成。)
现在,出于编写本文的经验,namespace resolution中存在一个模糊问题:非限定函数调用和常量查找具有全局命名空间的回退。因此,只有在完全限定或限定所有函数调用/常量查找或者未在名称空间中重新定义任何函数或常量且与PHP内置函数相同的名称时,才能自动转换代码。
如果您严格遵守这种做法(无论您选择哪种方式),转换代码都会相当容易。它将是在prephp中模拟名称空间的代码的子集。如果您需要实施方面的帮助,可以随意问我,我会感兴趣;)
PS:The namespace emulation code of prephp尚未完成,可能有问题。但它可能会给你一些见解。
答案 4 :(得分:1)
以下是我认为你能找到的最佳答案:
步骤1:为每个w / php5.3代码目录创建一个名为 5.3 的目录,并将所有特定于5.3的代码粘贴在其中。
第2步:获取要放入命名空间的类,并在 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时,它将被废除。
答案 5 :(得分:1)
我所做的是,使用下划线命名约定的大型代码库(以及其他)和require_once
代替自动加载器,是定义自动加载器,并添加class_alias文件中的行在将名称更改为名称空间后,定义类旧名称的别名。
然后我开始删除require_once
语句,其中执行不依赖于包含顺序,因为自动加载器会选择填充,以及命名空间的东西,因为我一直在修复错误等等。
到目前为止它运作良好。
答案 6 :(得分:0)
好吧,我不知道它是否是“最佳”方式,但理论上,您可以使用脚本来获取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
都保持不变,但我认为您几乎需要保留所有类和命名空间的某种缓存来围绕'instanceof'进行复杂转换如果使用它们,则输入参数。这是我能看到的最棘手的事情。
答案 7 :(得分:-1)
我没有亲自对此进行过测试,但您可以查看php 5.2 -> php 5.3 conversion script。
这与5.3 - &gt;不同5.2,但也许你会在那里找到一些有用的东西。
答案 8 :(得分:-1)
我们的DMS Software Reengineering Toolkit可能会很好地实施您的解决方案。它旨在通过使用表面语法术语编码的AST到AST变换来执行可靠的源代码转换。
它有一个PHP Front End,它是一个完整,精确的PHP解析器,AST构建器和AST到PHP代码的再生器。 DMS提供AST漂亮打印或保真打印(“尽可能保留列号”)。
这个组合已被用于为PHP 4和5实现各种值得信赖的PHP源代码操作工具。
编辑(回应一些有些不相信的评论):
对于OP的解决方案,以下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);
此规则查找允许使用命名空间路径的所有“简单”标识符, 并用构造的相应命名空间路径替换这些简单标识符 通过将标识符的字符串拆分为由下划线分隔的成员元素。一个人必须编写一些程序帮助 在DMS的实现语言PARLANSE中,检查标识符是否包含下划线(“has_underscores”),并通过构建相应的命名空间路径子树(“complex_namespace_path”)来实现撕裂逻辑。
规则的工作原理是抽象地识别对应于语言非终结符的树(在本例中为“namespace_path”,并用表示全名空间路径的更复杂的树替换简单的树。规则写为文本,但规则它本身由DMS解析,以构建匹配PHP树所需的树。
DMS规则应用程序逻辑可以在PHP解析器生成的整个AST中轻松应用此规则。
面对构成PHP语言的所有复杂内容,这个答案看似过于简单,但所有其他复杂性都隐藏在DMS使用的PHP语言定义中;该定义是大约10,000行的词法和语法定义,但已经过测试和工作。所有DMS机器和这些10K生产线都表明为什么简单的正则表达式无法可靠地完成工作。 (令人惊讶的是,要实现这一目标需要多少机器;自1995年以来我一直在研究DMS。)
如果您想查看 all 组成DMS定义/操作语言的机制,您可以see a nice simple example。