Стратегия разработки версий одного и того же PHP-кода с пространством имен и без него - PullRequest
49 голосов
/ 03 декабря 2009

Я поддерживаю библиотеку, написанную для PHP 5.2, и я хотел бы создать ее версию с пространством имен PHP 5.3. Однако я бы также поддерживал актуальность версии без пространства имен до тех пор, пока PHP 5.3 не станет настолько старым, что даже стабильная версия Debian поставит его;)

У меня довольно чистый код, около 80 классов следуют Project_Directory_Filename схеме именования (я бы, конечно, поменял бы их на \Project\Directory\Filename) и только несколько функций и констант (также с префиксом имени проекта).

Вопрос в том, как лучше всего разрабатывать версии с пространством имен и без пространства имен параллельно?

  • Должен ли я просто создать форк в репозитории и продолжать объединять изменения между ветками? Существуют ли случаи, когда код с обратной косой чертой становится трудным для слияния?

  • Должен ли я написать скрипт, который преобразует версию 5.2 в 5.3 или наоборот? Должен ли я использовать токенайзер PHP? sed? С препроцессором?

  • Есть ли лучший способ использовать пространства имен, где это возможно, и поддерживать обратную совместимость со старым PHP?


Обновление: Принято решение не использовать пространства имен после всех .

Ответы [ 9 ]

9 голосов
/ 28 декабря 2009

Я не думаю, что предварительная обработка кода 5.3 - это отличная идея. Если ваш код функционально идентичен как в PHP 5.2, так и в 5.3, за исключением использования пространств имен вместо префиксов, разделенных подчеркиванием, зачем вообще использовать пространства имен? В таком случае для меня это звучит так, как будто вы хотите использовать пространства имен для использования пространств имен.

Я думаю, вы обнаружите, что по мере перехода к пространствам имен вы начнете «думать немного иначе» об организации своего кода.

По этой причине я полностью согласен с вашим первым решением. Создайте форк и сделайте бэкпорт функций и исправлений.

Удачи!

7 голосов
/ 07 сентября 2010

Это продолжение моего предыдущего ответа :

Код моделирования пространства имен стал довольно стабильным. Я уже могу заставить Symfony2 работать (некоторые проблемы все еще, но в основном). Хотя по-прежнему не хватает некоторых вещей, таких как разрешение пространства имен переменных для всех случаев, кроме new $class.

Теперь я написал скрипт, который рекурсивно просматривает каталог и обрабатывает все файлы: http://github.com/nikic/prephp/blob/master/prephp/namespacePortR.php


Инструкция по применению

Требования к вашему коду для работы

Ваши имена классов не должны содержать символ _. Если они это сделают, имена классов могут стать неоднозначными при конвертации.

Ваш код не должен повторно объявлять какие-либо глобальные функции или константы в пространстве имен. Таким образом, гарантируется, что весь ваш код может быть разрешен во время компиляции.

По сути, это единственные ограничения для вашего кода. Хотя я должен отметить, что в конфигурации по умолчанию namespacePortR не разрешает такие вещи, как $className = 'Some\\NS\\Class'; new $className, потому что это потребует вставки дополнительного кода. Лучше, если это будет исправлено позже (либо вручную, либо с помощью автоматической системы исправлений).

Конфигурация

Поскольку мы сделали предположение, что глобальная функция или константа не объявлена ​​повторно в пространстве имен, вы должны установить константу класса assumeGlobal в слушателе пространства имен . В этом же файле установите для константы SEPARATOR значение _.

В namespacePortR измените блок конфигурации, чтобы удовлетворить ваши потребности.


PS: сценарию может быть предоставлена ​​опция ?skip=int. Это говорит о том, что нужно пропустить первые int файлы. Вам это не нужно, если вы установили интеллектуальный режим переопределения.

1 голос
/ 05 января 2012

То, что я сделал, с большой кодовой базой, которая использовала соглашение об именах подчеркивания (среди прочих), и require_once вместо автозагрузчика, было определить автозагрузчик и добавить class_alias строки в файлах, определяющие псевдонимы к старому имени класса после изменения их имен, чтобы они соответствовали пространствам имен.

Затем я начал удалять операторы require_once, где выполнение не зависело от порядка включения, поскольку автозагрузчик собирал вещи и вещи из пространства имен по мере исправления ошибок и т. Д.

Пока все работает довольно хорошо.

1 голос
/ 04 сентября 2010

Вот лучший ответ, который, я думаю, вы сможете найти:

Шаг 1: Создайте каталог с именем 5.3 для каждого каталога с кодом php5.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.

1 голос
/ 03 сентября 2010

Я работаю над проектом, который эмулирует PHP 5.3 на PHP 5.2: prephp . Включает поддержку пространства имен (пока не завершена.)

Теперь, из опыта написания этого, есть одна проблема неоднозначности в разрешении пространства имен : неквалифицированные вызовы функций и постоянный поиск имеют откат к глобальному пространству имен. Таким образом, вы можете преобразовывать свой код автоматически, только если вы либо полностью квалифицировали, либо квалифицировали все вызовы функций / поиск констант или если вы не переопределили какую-либо функцию или константу в пространстве имен с тем же именем, что и встроенная функция PHP.

Если бы вы строго придерживались этой практики (какую бы из них вы ни выбрали), было бы довольно легко преобразовать ваш код. Это было бы подмножество кода для эмуляции пространств имен в prephp. Если вам нужна помощь с реализацией, не стесняйтесь спрашивать меня, мне было бы интересно;)

PS: Код эмуляции пространства имен prephp еще не завершен и может содержать ошибки. Но это может дать вам некоторое представление.

1 голос
/ 22 февраля 2010

Вот что я нашел:

Делать это с регулярными выражениями - это кошмар. Вы можете сделать большую часть всего за несколько простых выражений, но тогда крайние случаи - убийца. Я закончил с ужасным, хрупким беспорядком, который едва работает с одной кодовой базой.

Это возможно благодаря встроенному токенизатору и простому парсеру рекурсивного спуска, который обрабатывает только упрощенное подмножество языка.

У меня получился довольно уродливый дизайн (парсер и преобразователь в одном - в основном просто смена или переиздание токенов), потому что создание полезного синтаксического дерева с сохранением пробела казалось слишком трудоемким (я хотел, чтобы полученный код был читаемый человек).

Я хотел попробовать phc для этого, но не смог убедить configure, что я собрал требуемую версию библиотеки Boost.

Я еще не пробовал ANTLR для этого, но, вероятно, это лучший инструмент для подобных задач.

0 голосов
/ 07 сентября 2010

Наш инструментарий реинжиниринга программного обеспечения DMS , вероятно, может реализовать ваше решение довольно хорошо. Он предназначен для выполнения надежных преобразований исходного кода с использованием преобразований AST в AST, закодированных в терминах поверхностного синтаксиса.

Имеет PHP Front End , который является полным, точным парсером PHP, сборщиком AST и AST to PHP-регенератором кода. DMS обеспечивает печать AST prettyprinting или fidelity («сохраняйте номера столбцов, где это возможно»).

Эта комбинация использовалась для реализации множества надежных инструментов манипулирования исходным кодом PHP для PHP 4 и 5.

РЕДАКТИРОВАТЬ (в ответ на несколько недоверчивый комментарий):

Для решения 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 может тривиально применять это правило повсюду в AST, создаваемом синтаксическим анализатором PHP.

Этот ответ может показаться слишком простым перед лицом всех сложных вещей, составляющих язык PHP, но вся эта другая сложность скрыта в определении языка PHP, используемом DMS; это определение составляет около 10 000 строк лексических и грамматических определений, но уже протестировано и работает. Все механизмы DMS и эти 10K-строки являются признаками того, почему простые регулярные выражения не могут выполнять работу надежно. (Удивительно, сколько оборудования требуется, чтобы получить это право; я работаю над DMS с 1995 года.)

Если вы хотите увидеть все механизм, который определяет, как DMS определяет / манипулирует языком, вы можете увидеть хороший простой пример .

0 голосов
/ 06 июня 2010

Я не проверял это самостоятельно, но вы можете взглянуть на этот php 5.2 -> скрипт php 5.3 .

Это не то же самое, что 5.3 -> 5.2, но, возможно, вы найдете там что-нибудь полезное.

0 голосов
/ 28 декабря 2009

Ну, я не знаю, является ли это "лучшим" способом, но теоретически вы могли бы использовать скрипт, чтобы взять код переноса 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 и typeed Вы используете их. Это самая хитрая вещь, которую я вижу.

...