Как предотвратить конфликты зависимости Composer между WP плагинами?

В нашей разработке плагинов мы используем Composer для установки, например, Symfony\Process который мы позже используем в коде. Большой вопрос: как мы убеждаемся, что эта зависимость не конфликтует с каким-либо другим плагином, также используя Symfony\Process в другой версии.

Теперь я знаю, что это в основном неотъемлемая проблема PHP, но мы хотели бы предложить нашим пользователям решение, которое, по крайней мере, предупреждает их, когда происходит такой конфликт (вместо того, чтобы терпеть неудачу). Например, одним из способов является сканирование каталога плагинов для папок vendor и попытка выяснить, используются ли несовместимые версии в каком-либо другом плагине. Я знаю это далеко (как действительно, очень далеко) от совершенства, но я просто пытаюсь привести пример.

Как вы с этим справитесь? Я знаю, что Composer, вероятно, не является основным среди разработчиков плагинов, но у нас у всех будет такая проблема рано или поздно, поэтому мне интересно, понял ли кто-нибудь какую-то стратегию для этого.

Ответ – вы не можете, потому что WordPress не может. В WP нет концепции управления зависимостями, и это был тот же случай, когда Composer даже существовал.

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

Единственный сценарий, в котором Composer действительно помогает вам с ним, – использовать его для управления всей установкой WP с единой папкой общего поставщика.

Работая над объявлениями CMS, сильно вдохновленными WordPress (osclass.org, чтобы не называть его), я наткнулся на вашу нить. Мы находимся в той же ситуации, имея дело с несколькими плагинами, использующими ту же зависимость.

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

  • P1 использует зависимость D1 в версии 1.20
  • P2 использует зависимость D1 в версии 1.30

Мы должны убедиться, что версия 1.30 загружена для правильной работы обоих плагинов.

1. Порядок загрузки плагинов

WordPress (и Osclass) имеют параметр active_plugins (в таблице wp_options ), который определяет активные плагины и, что более важно, в нашем случае: порядок загрузки плагинов .

Это простой массив.

2. Проверка версии

В начале наших двух плагинов мы проверяем версию D1, чтобы убедиться, что версия – это то, что нам нужно (или лучше).

 // Custom autoloader, we'll check it in a moment. require __DIR__ . "/vendor/composer_components/madhouse/autoloader/autoload.php"; if(!function_exists("mdh_utils") || (function_exists("mdh_utils") && strnatcmp(mdh_utils(), "1.30") === -1)) { // Dependency version is lower than 1.30 (which we need), let's bump. mdh_bump_me(); } else { // Rest of the plugin code. } 

Простой случай: что происходит, когда P2 загружается до P1?

  • P2 загружается и D1 с ним. Примечание: загруженная версия D1 равна 1,30.
  • Затем загружается P1, проверяет, загружен ли D1, а версия – 1,20 или выше, и затем найдите версию 1.30.

Все хорошо.

Интересный случай: что происходит, когда P1 загружается до P2?

  • P1 загружается до P2, наша зависимость и наш плагин P1 загружаются нормально. Примечание: загруженная версия D1 равна 1,20.
  • Затем загружается P2, он проверяет версию D1 и выясняет, что D1 был загружен, но версия недостаточно высока (1,20 вместо требуемого 1,30).

Здесь идет версия bump.

3. Удар!

Помните, что плагины загружаются в порядке, определенном в active_plugins .

Функция bump – в этом случае mdh_bump_me () – поднимет текущий плагин до вершины active_plugins (первая позиция массива).

Похоже на это. Это код, относящийся к Osclass, но его можно легко изменить для работы с WordPress:

 function mdh_bump_me() { // Sanitize & get the {PLUGIN_NAME}/index.php. $path = str_replace(osc_plugins_path(), '', osc_plugin_path(__FILE__)); if(osc_plugin_is_installed($path)) { // Get the active plugins. $plugins_list = unserialize(osc_active_plugins()); // This is the active_plugins fields unserialized. if(!is_array($plugins_list)) { return false; } // Remove $path from the active plugins list foreach($plugins_list as $k => $v) { if($v == $path) { unset($plugins_list[$k]); } } // Re-add the $path at the beginning of the active plugins. array_unshift($plugins_list, $path); // Serialize the new active_plugins list. osc_set_preference('active_plugins', serialize($plugins_list)); if(Params::getParam("page") === "plugins" && Params::getParam("action") === "enable" && Params::getParam("plugin") === $path) { //osc_redirect_to(osc_admin_base_url(true) . "?page=plugins"); } else { osc_redirect_to(osc_admin_base_url(true) . "?" . http_build_query(Params::getParamsAsArray("get"))); } } } с function mdh_bump_me() { // Sanitize & get the {PLUGIN_NAME}/index.php. $path = str_replace(osc_plugins_path(), '', osc_plugin_path(__FILE__)); if(osc_plugin_is_installed($path)) { // Get the active plugins. $plugins_list = unserialize(osc_active_plugins()); // This is the active_plugins fields unserialized. if(!is_array($plugins_list)) { return false; } // Remove $path from the active plugins list foreach($plugins_list as $k => $v) { if($v == $path) { unset($plugins_list[$k]); } } // Re-add the $path at the beginning of the active plugins. array_unshift($plugins_list, $path); // Serialize the new active_plugins list. osc_set_preference('active_plugins', serialize($plugins_list)); if(Params::getParam("page") === "plugins" && Params::getParam("action") === "enable" && Params::getParam("plugin") === $path) { //osc_redirect_to(osc_admin_base_url(true) . "?page=plugins"); } else { osc_redirect_to(osc_admin_base_url(true) . "?" . http_build_query(Params::getParamsAsArray("get"))); } } } 

В двух словах,

  • Получить текущий список active_plugins.
  • Удалите из него текущий плагин.
  • Добавьте текущий плагин в начало массива.
  • Сохраните параметр в базе данных.
  • Перенаправление на ту же страницу.

5. Пользовательский автозагрузчик

Слово на нашем автозагрузчике. Автозагрузчик Composer загружает каждый класс (даже если он уже определен), новый заменяет старый.

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


Надеюсь, это достаточно ясно.

Далеко-далеко от идеального решения, но работает для нас, и это похоже на шаг вперед.