Управление своими модулями для CMS c использованием composer +12


Здравствуйте, друзья! Появление популярного менеджера зависимостей в PHP — является одним из важнейших событий в жизни PHP-сообщества. Composer разделил время на "до" — когда наши самописные библиотеки лежали в zip-архивах или просто разбросаны по папкам и копировались "ручками", лишь в единичных случаях, использовался git или PEAR, и "после" — когда мы начали использовать composer. Но что делать если вы работаете с определенной CMS, а она не "знает" что такое composer?


Предисловие


Я работаю тимлидом в некрупной web-студии и около года назад мне пришлось пересмотреть подход к использованию наработок в моей команде. Для разработки проектов, более 70% функционала которых можно назвать стандартным (сайт, интернет-магазин), мы используем 1C-Битрикс. Как и все популярные CMS 1C-Битрикс имеет модульную структуру и поддерживает установку модулей сторонних разработчиков. Звучит хорошо, но на деле всё немного иначе. Вы можете разрабатывать свои модули и хранить их в архиве копируя исходные коды в нужные папки и устанавливая модули по-одному в панеле управления, а можете публиковать модули на предусмотренной для этого площадке “1С-Битрикс: Маркетплейс”. Для публикации модулей вам нужно всего-лишь заключить партнерский договор и подписать соглашение, в котором будет описана ваша ответственность. И ни о каких зависимостях модулей и установки цепочки зависимостей речи не идет.


Данная проблема в той или иной степени присуща всем CMS разработанным “до”. Она демотивирует разрабатывать мелкие модули (нам кажется что не стоит “этот простой функционал” оборачивать в модуль), и требует отдельного ведения документации.


В этой статье я расскажу как организовать хранение и использование наработок в команде/компании. Это будет интересно тем, кто сталкивался с подобной проблемой, тем, кто разрабатывает сайты с использованием CMS, тем кто разрабатывает свою CMS и тем кто просто разрабатывает проекты на PHP. Поехали!


Часть первая. Публичное размещение модулей


Первая задача, которую нужно решить, это где хранить модули. Исходные коды модулей можно хранить в любом git, mercurial или svn репозитории. Для публичных модулей я рекомендую GitHub. На GitHub у вас будет возможность легко просматривать исходные коды и поддерживать документацию в формате Markdown. Для использования composer Вам необходимо создать и наполнить информацией файл composer.json. Это можно сделать в редакторе, в вашей IDE или вызовом команды composer init. Здесь и далее я не буду углубляться в базовые возможности и команды composer, если Вы плохо знакомы с composer, прочитайте эту статью.


После того как Вы создали свой модуль (для примера пока пустой) и разместили его код на GitHub, Вам необходимо зарегистрировать модуль на сайте packagist.org. Packagist предложит Вам настроить хуки GitHub так, чтобы при попадании изменений в репозиторий, на packagist.org обновлялись сведения о модуле.


Часть вторая. Установка. Самое интересное тут


Вы, точно, очень хорошо знакомы с той CMS с которой работаете, а значит знаете все тонкости установки модулей в ней. В 1С-Битрикс установка модулей проходит в 2 этапа:


  • размещение исходного кода модуля в определенной директории <project_dir>/local/modules/<company_name>.<mod_mame>
  • Вызов функции RegisterModule(<company_name>.<mod_mame>). Как правило все действия установки модуля описываются в методе DoInstall класса, отвечающего за установку и удаления модуля. <project_dir>/local/modules/<company_name>.<mod_mame>/install/index.php

Часть два один. Прячем пакеты в надежное место


По-умолчанию composer устанавливает все пакеты в каталог <project_dir>/vendor если композер размещен в корне проекта, и не выполняет никаких хуков в Ваших пакетах. Но это легко изменить.


Нам необходимо в корне проекта разместить файл composer.json:


{
    "name": "sites/<sitename>",
    "description": "<SiteName>",
    "authors": [
        {
            "name": "<developerName>",
            "email": "<developerEmail>"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "config": {
        "vendor-dir": "local/vendor"
    }
}

В 1C-Битрикс код, написанный разработчиками, обычно, размещают в директории <project_dir>/local. Поэтому мы перенесли туда папку vendor записью в секции config. Теперь все пакеты сторонних разработчиков будут размещаться там. Но наши модули необходимо размещать в директории <project_dir>/local/modules/<company_name>.<mod_mame>, что-же делать?


Часть два два. Плагин установки модулей


У composer есть несколько типов пакетов, один из которых composer-plugin — это расширения для самого композера. Чтобы наши модули устанавливались так, как этого требует CMS нам нужно написать свой плагин. Для этого создаем отдельный проект и в его корне размещаем composer.json:


{
  "name": "<my_name>/installer",
  "description": "Plugin for custom installing",
  "type": "composer-plugin",
  "license": "MIT",
  "homepage": "<link to homepage github>",
  "version": "0.0.1",
  "authors": [
    {
      "name": "<name>",
      "email": "<email>"
    }
  ],
  "require": {
    "composer-plugin-api": "^1.0"
  },
  "require-dev": {
    "composer/composer": "^1.0"
  },
  "autoload": {
    "psr-4": { "<my_name>\\installer\\": "" }
  },
  "extra": {
    "class": "<my_name>\\installer\\Plugin"
  }
}

В этом файле есть 3 ключевых момента:


  • "type": "composer-plugin" — говорит композеру, что это плагин
  • autoload — описывает правила автозагрузки классов
  • extra — указывает какой класс является плагином

Плагин будет состоять из двух классов:


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

Плагин выполняет просто добавление инсталлятора (файл: Plugin.php)


namespace company_name\installer;

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;

class Plugin implements PluginInterface
{
    public function activate(Composer $composer, IOInterface $io)
    {
        $composer->getInstallationManager()->addInstaller(new Bitrix($io, $composer));
    }
}

Далее сам инсталятор (класс company_name\installer\Bitrix). Класс должен наследоваться от Composer\Installer\LibraryInstaller и содержать следующие методы:


  • supports — возвращает true если инсталлятор поддерживает данный тип пакетов
  • getInstallPath — возвращает путь, по которому необходимо разместить исходный код пакета
  • install/uninstall/update — хуки установки/удаления/обновления пакета

Все наши модули будут иметь тип bitrix-module и именно с ними должен работать инсталятор.


public function supports($packageType)
{
    return $packageType === 'bitrix-module';
}

Я решил сохранить целостность имени модуля (оно состоит из company_name и mod_name разделенных точкой) и именую пакеты <my_name>/<company_name>.<mod_mame> или <company_name>/<company_name>.<mod_mame>. Если мы возьмем имя пакета и разобьем по слешу, то вторая часть и будет именем модуля


public function getInstallPath(PackageInterface $package)
{
    $name = explode("/", $package->getName());
    return "local/modules/{$name[1]}/";
}

Методы initBitrix и getModule реализуют работу с API 1C-Битрикс для установки модуля. Метод update реализуется исходя из того, какая у Вас CMS и как вы выпускаете обновления модулей и как планируете их выполнять (файл: Bitrix.php).


namespace company_name\installer;

use Composer\Installer\LibraryInstaller;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;

class Bitrix extends LibraryInstaller
{

    public function supports($packageType)
    {
        return $packageType === 'bitrix-module';
    }

    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        parent::install($repo, $package);
        $name = explode("/", $package->getName());
        $this->initBitrix();
        $module = $this->getModule($name[1]);
        $module->DoInstall();
    }

    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $name = explode("/", $package->getName());
        $this->initBitrix();
        $module = $this->getModule($name[1]);
        $module->DoUninstall();
        parent::uninstall($repo, $package);
    }

    public function getInstallPath(PackageInterface $package)
    {
        $name = explode("/", $package->getName());
        return "local/modules/{$name[1]}/";
    }

    protected function initBitrix()
    {
        $_SERVER['DOCUMENT_ROOT'] = __DIR__ . "/../../../../";
        define('STOP_STATISTICS', true);
        define("NO_KEEP_STATISTIC", "Y");
        define("NO_AGENT_STATISTIC","Y");
        define("NOT_CHECK_PERMISSIONS", true);
        require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
        $GLOBALS['APPLICATION']->RestartBuffer();
    }

    protected function getModule($module)
    {
        include_once $_SERVER['DOCUMENT_ROOT'] . "/local/modules/" . $module . "/install/index.php";
        $class = str_replace(".", "_", $module);
        $module = new $class();
        return $module;
    }

}

После того, как вы проверили работу плагина, код можно заливать на GitHub и регистрировать в Packagist.


Часть два три. Модуль


Вернемся к самому модулю, который мы упомянули в первой части. а точнее к его composer.json.


{
    "name": "<my_name>/<company_name>.<mod_mame>",
    "type": "bitrix-module",
    "description": "Описание самого модуля",
    "version": "1.0.0",
    "time": "11.09.2018",
    "minimum-stability": "dev",
    "license": "MIT",
    "homepage": "<link to homepage github>",
    "authors": [
        {
            "name": "<name>",
            "email": "<email>"
        }
    ],
    "require": {
        "<my_name>/installer": "*"
    }
}

Имя модуля должно соответствовать требованиям CMS, тип должен быть указан тот, с которым работает инсталлятор (в нашем случае bitrix-module) и в зависимостях у модуля должен быть плагин (секция require). После создания самого модуля и проверки его работы заливаем его код на GitHub и регистрируем в Packagist.


Часть два четыре. использование


Напомню, что сам проект (сайт) имеет примерно следующий composer.json


{
    "name": "sites/<sitename>",
    "description": "<SiteName>",
    "authors": [
        {
            "name": "<developerName>",
            "email": "<developerEmail>"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "config": {
        "vendor-dir": "local/vendor"
    }
}

теперь мы можем перечислить в секции require все необходимые нам модули или вызвать команду


composer require "<my_name>/<company_name>.<mod_mame>" "*"

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


image


Сам модуль содержит код, который отвечает за логику авторизации, включать в него код отправки SMS не стоит, ведь отправку SMS выполняют и другие модули, например модуль уведомлений, значит SMS лучше сделать отдельным модулем, чтобы его код не дублировать. Так же и REST сервис. Его тоже могут использовать другие модули. И при всей этой непростой схеме, когда ваш модуль тянет за собой еще четыре, его установка остаётся всё такой же простой. Просто выполните одну команду:


composer require "<my_name>/<company_name>.<mod_mame>" "*"

А что и в какой последовательности скачивать и устанавливать позвольте решить композеру.


Часть третья. Приватные модули


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


  • GitLab — это аналог GitHub который Вы можете скачать и установить на свои сервера.
  • Satis — это генератор репозитория, с которым сможет работать композер.

Для начала установите GitLab и перенесите в него исходные коды Ваших модулей. после установите Satis и опишите все ваши модули в satis.json


{
    "name": "ropoName",
    "homepage": "https://composer.<company_name>.ru/",
    "repositories": [
        { 
            "type": "vcs",  
            "url": "https://gitlab.<company_name>.ru/<my_name>/installer"
        },
        { 
            "type": "vcs",  
            "url": "https://gitlab.<company_name>.ru/<my_name>/<company_name>.<mod_name>"
        }
    ],
    "config": {
        "gitlab-domains": [
            "gitlab.<company_name>.ru"
        ],
        "gitlab-token": {
            "gitlab.<company_name>.ru": "GitLab Token"
        }
    },
    "require-all": true
}

В GitLab нужно создать токен, которому будет доступно api и указать его в satis.json. После всех этих манипуляций выполните команду:


php bin/satis build satis.json ./web

И в папке web получите статический репозиторий, который можно опубликовать по адресу https://composer.<company_name>.ru/.


composer.json сайта будет отличатся лишь тем, что в нем будет присутствовать секция repositories


{
    "name": "sites/<sitename>",
    "description": "<SiteName>",
    "authors": [
        {
            "name": "<developerName>",
            "email": "<developerEmail>"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "config": {
        "vendor-dir": "local/vendor"
    },
    "repositories": [
        {
            "type": "composer",
            "url": "https://composer.<company_name>.ru/"
        }
    ]
}

Послесловие


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




К сожалению, не доступен сервер mySQL