Загрузка нескольких версий одного и того же класса

Допустим, я выпускаю библиотеку кода как отдельный класс PHP. Затем кто-то использует версию 1.0 этой библиотеки в своем приложении. Позже я выпускаю версию 2.0 библиотеки, и тот же кто-то по любой причине должен использовать в своих приложениях одновременно 1.0 и 2.0, потому что он или я нарушали обратную совместимость с новым выпуском.

Если имена классов отличаются, достаточно легко включить и создать оба экземпляра, потому что нет конфликта имен. Но если имена классов остаются неизменными, мы сталкиваемся с проблемами:

include /lib/api-1.0/library.php;
$oldlibary = new Library();

include /lib/api-2.0/library.php;
$newlibrary = new Library();

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

namespace old {
    include /lib/api-1.0/library.php;
}
namespace new {
    include /lib/api-2.0/library.php;
}

$oldlibary = new old\Library();
$newlibrary = new new\Library();

К сожалению, это не очень масштабируемо. Это будет работать с ситуацией с двумя экземплярами (которую, я надеюсь, мне не нужно было бы использовать в первую очередь), но чтобы масштабировать ее до 3, 4, 5 или более экземпляров, вам необходимо определить дополнительные пространства имен и настройте, если вы не используете эти пространства имен в первую очередь, это куча ненужного кода.

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


Позвольте мне добавить некоторые уточнения...

Я создаю набор библиотек для использования другими разработчиками, которые создают плагины / модули для пары платформ CMS. В идеале, все всегда будут использовать последнюю версию моей библиотеки, но я не могу этого гарантировать, и я не могу гарантировать, что конечный пользователь всегда будет обновлять свои модули, когда новые версии станут доступными.

Вариант использования, с которым я пытаюсь работать, - это тот случай, когда конечный пользователь устанавливает два разных модуля от двух разных разработчиков: назовите их Apple и Orange. Оба модуля используют версию 1.0 моей библиотеки, и это здорово. Мы можем создать его один раз, и оба набора кода смогут воспользоваться функциональностью.

Позже я выпускаю небольшой патч для этой библиотеки. Это версия 1.1, потому что она не нарушает обратную совместимость с веткой 1.x. Разработчик Apple немедленно обновляет свою локальную версию и выпускает новую версию своей системы. Разработчик Orange находится в отпуске и не беспокоится.

Когда конечный пользователь обновляет Apple, она получает последнюю версию технической поддержки моей библиотеки. Поскольку это технический релиз, предполагается, что он полностью заменяет версию 1.0. Таким образом, код создает только экземпляры 1.1, а Orange извлекает выгоду из исправления, даже если разработчик не удосужился обновить их выпуск.

Даже позже, я решил обновить свой API, чтобы по какой-то причине добавить некоторые хуки в Facebook. Новые функции и расширения API сильно меняются в библиотеке, поэтому я поднял версию до 2.0, чтобы пометить ее как потенциально не обратно совместимую во всех ситуациях. И снова Apple входит и обновляет свой код. Ничего не сломалось, он просто заменил мою библиотеку в своем /lib папка с последней версией. Orange решил вернуться в школу, чтобы стать клоуном, и, тем не менее, прекратил поддерживать свой модуль, поэтому он не получает никаких обновлений.

Когда конечный пользователь обновляет Apple новым выпуском, она автоматически получает версию 2.0 моей библиотеки. Но у Orange был код в его системе, который уже добавил хуки для Facebook, поэтому возникнет конфликт, если 2.0 по умолчанию будет добавлен в его библиотеку. Поэтому вместо того, чтобы полностью заменить его, я один раз создаю экземпляр 2.0 для Apple и параллельно создаю экземпляр версии 1.0, поставляемой с Orange, чтобы он мог использовать правильный код.

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

3 ответа

Решение

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

Вместо этого я остановился на конкретной схеме именования для классов и загрузчика / экземпляра версии.

Каждый класс будет иметь следующий формат:

<?php
if( ! class_exists( 'My_Library' ) ) { class My_Library { } }

if( ! class_exists( 'My_Library_1_0' ) ) :
class My_Library_1_0 extends My_Library {
    ... class stuff ...
}
endif;

Родитель My_Library класс на самом деле будет содержать несколько идентификаторов, специфичных для библиотеки - назначение, операторы совместимости и т. д. Таким образом, я могу выполнить другие логические проверки, чтобы убедиться в правильности My_Library существует, прежде чем двигаться вперед и утверждать, что My_Library_1_0 действительно версия 1.0 библиотеки, которую я хочу.

Далее у меня есть класс загрузчика, который я буду использовать в своем основном проекте:

<?php
class Loader {
    static function load( $file, $class, $version ) {
        include( $file );
        $versionparts = explode('.', $version);
        foreach($versionparts as $part) $class .= '_' . $part;
        return new $class();
    }
}

Как только это будет сделано, вы можете использовать Loader чтобы загрузить оба экземпляра класса или простые ссылки, если вы хотите использовать статические методы:

$reference = Loader::load( 'library.php', 'My_Library', '1.0' );

$loader = new Loader();
$instance = $loader->load( 'library.php', 'My_Library', '1.0' );

Не совсем то же самое, что версия пространства имен, для которой я снимал, но она работает и снимает мои опасения по поводу поломок для конечного пользователя. Я предполагаю, что две разные версии My_Library_1_0 было бы то же самое, хотя... так что все еще есть зависимость от сторонних разработчиков, которые знают, что они делают.

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

Да, такой метод существует. Вы можете делать все что угодно с обработчиками eval и stream. Но это плохая практика и неправильный подход - вы можете попробовать использовать фабричный метод (код не тестируется - он показывает только пример):

<?php

if (!class_exists('Library')) {

    class Library
    {
        public static function create($version)
        {
            if (class_exists($c = 'Library' . $version))
                return new $c();
            return null;
        }
    }

}

class Library1
{

}

class Library2
{

}

...

Позвольте пользователю выбрать версию, а затем в соответствии с этим загрузить ваш файл API

Имя файла должно быть динамически определяемым, например:

include('/lib/api-'.$versionId.'/library.php'); 

если версия -1.0 как мудрый

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

Другие вопросы по тегам