Предположим, я выпустил библиотеку кода как отдельный класс 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
последней версией. Оранжевый решил вернуться в школу, чтобы стать клоуном и, несмотря на это, прекратил поддерживать свой модуль, поэтому он не получает никаких обновлений.
Когда конечный пользователь обновляет Apple с новой версией, она автоматически получает версию 2.0 моей библиотеки. Но у Orange был код в его системе, который уже добавил Facebook-крючки, поэтому возникнет конфликт, если по умолчанию он будет загружен в свою библиотеку. Поэтому вместо того, чтобы полностью заменить его, я создаю экземпляр 2.0 для Apple и бок о бок создаю версию 1.0, поставляемую с Orange, чтобы он мог использовать правильный код.
Весь смысл этого проекта заключается в том, чтобы сторонние разработчики могли создавать системы на основе моего кода, не завися от того, чтобы быть надежными и обновлять их код, когда они должны. Ничто не должно ломаться для конечного пользователя, и обновление моей библиотеки при использовании внутри чужой системы должно быть простой заменой файла, не проходящей и не изменяющей все ссылки на классы.
Я решил немного поочередно. Метод пространства имен работает, но для каждой версии класса требуется другое пространство имен. Таким образом, он не является масштабируемым, потому что вы должны предварительно определить количество доступных пространств имен.
Вместо этого я решил использовать определенную схему именования для классов и загрузчика версии / instantiater.
Каждый класс будет иметь следующий формат:
<?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
запятой и ничего не гнусного.