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

时间:2009-12-02 22:41:35

标签: php namespaces migration compatibility

我正在维护为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的向后兼容性?


更新:Decided against use of namespaces after all

9 个答案:

答案 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();

虽然您的所有includesrequires都保持不变,但我认为您几乎需要保留所有类和命名空间的某种缓存来围绕'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