PHP Storm вместо консоли для MODX

Заметка о том, как настроить PHP Storm, чтобы не выходя из IDE, писать и запускать скрипты, которые работают с MODX прямо на удаленном сервере.

Понадобилось мне тут недавно написать один скрипт для импорта данных из API в MODX. Сам скрипт можно написать и локально, это обычно трудностей не вызывает, но сайт уже рабочий и мне нужно было иметь доступ к объектам MODX, чтобы их обновлять или создавать, в зависимости от ситуации.

В целом, решений для запуска кода в MODX хватает. Можно написать снипет и вызывать его где-то на странице. Можно установить дополнение Console или modalConsole, и запускать свой скрипт там. Так будет даже правильнее, чем через снипет. Но это всё работает через web-server, т.е. подходит для небольших скриптов, которые выполняются за отведённые стандартные 30 секунд. Для полноценного CLI-приложения не годится. Особенно, если мы говорим об импорте, который может работать и десятки минут, если задачи тяжелые. Для тяжелых задач правильнее использовать очереди, конечно же, но у нас MODX и несмотря на то, что очереди в MODX есть, работать с ними умеет не каждый.

Другая проблема заключается в том, что несмотря на существование Ace, в браузерных редакторах никогда не будет той мощи, которую дает полноценная IDE. Это проверки качества кода и умные инспекции, автоматическое дополнение и другие удобства. Я обычно пишу код в PHP Storm, поэтому решил, что потратить немного времени и настроить один раз IDE (и рассказать об этом в заметке) будет эффективнее, чем страдать с другими инструментами.

Рассказывать я буду на примере сайта, размещенного на хостинге modhost.pro, но подход можно применять на любом хостинге, где есть доступ по SSH. Файлы скопировать хватит и FTP, но чтобы запускать CLI-приложение, без SSH не обойтись. Так же я работаю на macOS, поэтому на других операционных системах некоторые комбинации клавиш или настройки путей могут быть иными, но я думаю, разберётесь.

В целом, схема получается следующая. Мы пишем код в IDE, он должен каким-то образом попасть на сервер, а после этого нам нужно этот код запустить из той же IDE. Одного инструмента для реализации такого порядка нет, но связка из нескольких позволяет так сделать. Итого, у нас имеется две проблемы, которые необходимо решить: автоматически доставить код на сервер после изменений и выполнить его на удаленном сервере. Начнем с автоматической доставки.

Настройка SSH в PHP Storm

Перед тем, как продолжить, нужно настроить PHP Storm, чтобы она была в курсе, как подключаться к удаленному серверу и взаимодействовать с ним. Я вынес этот пункт сюда, так как он понадобится на следующих двух этапах. Хостинг modhost.pro предоставляет все данные для подключения в наглядном виде, достаточно просто скопировать их в соответствующие поля в PHP Storm. Я рекомендую настроить вход на сервер по SSH-ключу, но для простоты этот шаг мы пока опустим, и настроим вход по паролю.

Настройки доступа к серверу на хостинге modhost.pro
Настройки доступа к серверу на хостинге modhost.pro

Чтобы настроить доступ в PHP Storm, следует открыть настройки и выбрать пункт Tools | SSH Configurations. Далее заполняем поля Host, Username, выбираем тип авторизации Password, не забываем отметить галочку “Save password” и нажимаем кнопку “Test Connection”. Если все настроено правильно, получим соответствующее сообщение. На этом настройка закончена. Можно переходить к следующему пункту.

Настройка SSH доступа в PHP Storm
Настройка SSH доступа в PHP Storm

Автоматическая доставка кода

Нужно сделать небольшую ремарку относительно MODX. В современной разработке код пишется локально, затем с помощью систем контроля версий попадает в репозиторий, откуда автоматически или в ручном режиме попадает на сервер, где уже может выполняться. В MODX так исторически сложилось, что многое из элементов сайта, в том числе и код, хранится в базе данных и классического разделения на данные и код нет. Поэтому распространен подход, когда есть живой сайт на сервере и код приходится писать прямо там. Физически можно писать его локально, конечно же, и в git изменения добавлять, но нужно синхронизировать локальную копию с копией на сервере. Делать это по цепочке “компьютер разработчика – удаленный репозиторий – сервер” будет слишком накладно, да и история будет засорена однотипными комитами, но к счастью есть другие, более удобные инструменты.

В PHP Storm есть инструмент под названием Deployment. Инструмент позволяет настроить синхронизацию локальной копии файлов с удаленным сервером по FTP/SFTP. Т. е. когда вы сохраняете файл локально, он сразу же автоматически загружается на удаленный сервер. Весьма удобно.

Чтобы быть уверенным, что наш код всегда есть на удаленном сервере перед запуском, имеет смысл настроить автоматическую синхронизацию этого кода через описанный абзацем выше инструмент. Сделать это достаточно легко. Идем в настройки и выбираем Build, Execution, Deployment | Deployment, нажимаем “+“ и добавляем новую конфигурацию. Тип следует выбрать “SFTP”, в SSH configuration выбираем нашу конфигурацию, добавленную на предыдущем этапе. Далее переходим к настройке путей к файлам. В случае с modhost все просто, там сайт расположен обычно по пути /home/{username}/www. Если у вас другой хостинг, проверьте через файловый менеджер хостинга (обычно есть в любой панели) или через консоль. Web Server URL – это по сути адрес, по которому открывается сайт, тоже можно скопировать из панели хостинга.

Настройка подключения автоматической доставки файлов
Настройка подключения автоматической доставки файлов

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

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

Теперь самое важное – настройки автоматической синхронизации. В настройках это все тот же раздел, но на один уровень глубже и называется Options. Здесь много параметров, которые вы можете настроить на свой вкус, но я хочу отметить настройку Upload changed files automatically to the default server. На выбор есть три варианта: Always, On explicit save action, Never. Первый будет загружать файлы на сервер всегда, как только было какое-то изменение. По сути, это автосохранение, которое в PHP Storm включено по умолчанию, но файл сохраняется автоматически еще и на сервере. Можно включить загрузку только после явного сохранения, например комбинации Cmd + S (Ctrl + S). Или выключить и загружать файлы руками через пункт в меню. Я предпочитаю выбирать Always. Другие мои настройки на экране ниже.

Настройка автоматической загрузки файлов на сервер
Настройка автоматической загрузки файлов на сервер

На этом настройка завершена. Чтобы проверить, что все работает верно, создайте файл, например code.php, где выводится какая-нибудь информация, вроде версии PHP. Он автоматически загрузится на хостинг и можно будет его выполнить, открыв ссылку http://yousite.com/code.php.

Удаленный исполнитель кода

Когда автоматическая синхронизация файлов настроена, нужно рассказать PHP Storm, что у нас есть интерпретатор PHP на удаленном сервере и вызывать код необходимо через него. PHP Storm позволяет настроить сколько угодно исполнителей кода, поэтому идем в настройки в раздел PHP и там выбираем версию PHP, которую использует наш код (он будет показывать правильные инспекции с учетом версии), и ниже настройка CLI Interpreter.

На выбор там будет один или несколько вариантов интерпретатора PHP, в зависимости от того, что смог обнаружить PHP Storm вашей системе. Но нам нужно добавить новый, который на другом сервере. Нажмите на три точки справа от поля, после этого откроется окно настройки интерпретаторов. Нажмите плюс и в выпадающем списке выберите первый пункт From Docker, Vagrant, VM, WSL, Remote…. Да-да, вы так же можете настроить выполнение кода внутри виртуальной машины, или Docker-контейнера, на что у вас фантазии хватит.

В новом окне выбираете пункт SSH и в списке конфигураций выбираете ту, которую мы добавили на самом первом шаге и ждем, пока IDE подключится к серверу и синхронизирует настройки PHP. Останется нажать Apply или OK.

Настройка удаленного исполнителя кода PHP через SSH
Настройка удаленного исполнителя кода PHP через SSH

В случае modhost обратите внимание на PHP interpreter path, потому что если оставить как есть /usr/bin/php, то версия будет 7.0. Чтобы запускать код на PHP 7.4, укажите в пути /usr/bin/php7.4. В итоге, настройки в разделе PHP должны выглядеть примерно вот так:

Вид настроек PHP в IDE
Вид настроек PHP в IDE

Для быстрого доступа к серверу по SSH прямо из PHP Storm можете еще добавить настройку для Tools | SSH Terminal. Там просто выберите добавленную ранее конфигурацию SSH и после, чтобы зайти на сервер, достаточно будет выбрать в меню “Tools | Start SSH Session…”.

Генерация превью в галерее

IDE настроена, теперь самое время написать наш первый скрипт. Будем решать вполне себе практическую задачу – обновление всех превью для всех товаров miniShop2. Решение через Console легко гуглится и тоже работает, но запускает скрипт пачкой по 10 штук, требует открытого браузера и входа в административную панель. Наш скрипт будет немного проще и будет работать как есть, с прямым запуском из PHP Storm.

Создадим папку scripts и файл regenerate.php внутри. Почти половину файла занимает код подключения MODX, но без этого никак. Далее логика проста, находим все файлы, у которых parent равно 0, т.е. это главные файлы, а не миниатюры. После, для каждого файла запускаем процессор, который генерирует новые картинки.

<?php

declare(strict_types = 1);

# подключаем конфигурацию и ядро MODX

require __DIR__ . '/../config.core.php';
require MODX_CORE_PATH . 'model/modx/modx.class.php';

# инициализируем MODX

$modx = new modX();
$modx->initialize('mgr');
$modx->getService('error','error.modError', '', '');

# настраиваем правила ведения журнала сообщений

$modx->setLogTarget();
$modx->setLogLevel(xPDO::LOG_LEVEL_WARN);

Обратите внимание на последние две строчки. Здесь мы настраиваем правильный уровень логирования и место, куда лог должен записываться. Лог в MODX пишется в файл, но нам это не нужно, мы хотим видеть лог прямо на экране во время выполнения скрипта. Поэтому вызываем $modx->setLogTarget();. Правильно передать еще значение ECHO для параметра target, которое означает, что лог будет выводиться в стандартный поток вывода, но это значение у параметра задано по умолчанию, поэтому можно не указывать.

Вторая строка задает уровень вывода ошибок не больше Warning. Правильнее было бы указать здесь уровень ошибок Info, но в таком случае в лог будут попадать все сообщения уровня Info, которые генерируются кодом MODX, который мы вызываем в виде процессора. На время отладки скрипта я бы так и сделал, но если скрипт работает стабильно, то переключение на Warning уменьшает количество сообщений в логе, и его просто удобнее читать.

Note! Если будете менять уровни логирования, не забывайте менять их так же в коде скрипта, в местах вызова метода $modx->log(xPDO::LOG_LEVEL_WARN, 'message').

К слову, подробнее про особенности настройки журналов учета ошибок можно прочитать в моей прошлой заметке Собственный лог-файл для скриптов в MODX. А так же очень хорошая заметка есть у Сергея Шлокова: Отдельный лог для своих сообщений.

Продолжим работать с кодом. Теперь нам нужно подключить сервис miniShop, выбрать нужные файлы и пройтись по ним в цикле и запустить процессор.

<?php

# ...

$modx->getService('minishop2');
$processors = $modx->getOption('core_path') . 'components/minishop2/processors/';

$query = $modx->newQuery(msProductFile::class, ['parent' => 0]);
$query->sortby('product_id');
$query->sortby('rank', 'DESC');

$found = $modx->getCount(msProductFile::class, $query);

$modx->log(xPDO::LOG_LEVEL_WARN, sprintf('Found %d files to process.', $found));

$processed = 0;
foreach ($modx->getIterator(msProductFile::class, $query) as $resource) {
    $modx->runProcessor(
        'mgr/gallery/generate',
        ['id' => $resource->id],
        ['processors_path' => $processors]
    );

    $processed++;

	$modx->log(
		xPDO::LOG_LEVEL_WARN, 
		sprintf('[i] Processed %d/%d images', $processed, $found)
	);
}

Скрипт готов, теперь нужно разобраться, как его запустить из IDE и получить результаты выполнения в консоль, чтобы видеть ход выполнения. PHP Storm позволяет настраивать различные скрипты, включая те, которые можно выполнять на удаленном сервере, а потом запускать их одной кнопкой. Если вы еще не пользовались этой функцией в IDE, то у вас панель запуска скриптов будет выглядеть примерно вот так. Нужно нажать Add configuration и добавить наш первый скрипт.

Добавление конфигурации для запуска скрипта
Добавление конфигурации для запуска скрипта

После этого нажимаем на “+”, в списке находим тип “PHP Script” и настраиваем параметры для запуска. В первую очередь нужно указать путь к файлу, который мы хотим запустить. Путь нужно указывать локальный. А дальше выбираем интерпретатор, который должен быть тем, что указывает на удаленный сервер. PHP Storm остальное сделает сам, включая определение правильного пути на сервере, потому что мы ранее уже всё это настроили. С остальными настройками можете поэкспериментировать самостоятельно, а минимальный набор выглядит вот так.

Настройка скрипта для запуска в PHP Storm
Настройка скрипта для запуска в PHP Storm

Теперь осталось запустить скрипт и посмотреть, что всё успешно выполняется. Сверху нажимаем на зеленую стрелку, или если у вас MacBook Pro с Touch Bar, нажимаем кнопку прямо там (очень удобно) и смотрим, что выводится в консоли.

Результат выполнения скрипта генерации превью в miniShop2
Результат выполнения скрипта генерации превью в miniShop2

Бонус: подключение библиотек через Composer

В примере мы рассмотрели достаточно простой вариант, который не требует никаких зависимостей, кроме собственно MODX. Но бывает, что скрипты обрабатывают какие-то данные. Например, в случае импорта CSV, удобно было бы воспользоваться готовой библиотекой для работы с CSV от The PHP League. Для подключения библиотек в мире PHP принято использовать Composer. Кроме того, давайте еще подключим код MODX и miniShop2, чтобы PHP Storm не ругался на несуществующие классы, а у нас была возможность в любой момент кликнуть на имя класса и перейти к его исходному коду, чтобы посмотреть реализацию.

Создадим composer.json внутри папки scripts и заполним примерно таким содержимым. Все поля в целом очевидны, вроде названия и описания, но отмечу момент с подключением кода miniShop2. Дело в том, что в исходном коде miniShop2 нет файла composer.json, поэтому в момент установки Composer ничего не знает про пакет, и нужно эту информацию указать вручную в секции repositories. Указываем тип package, далее имя и версию, а в секции dist указываем ссылку на zip-архив с исходным кодом. В момент установки этот файл будет скачан и распакован. Вот и вся магия. Да, код miniShop2 находится в репозитории у Вани Бочкарева – Ibochkarev/miniShop2, но я специально указал имя пакета как modx/minishop, чтобы код MODX и miniShop2 находились внутри одной папки modx внутри папки vendor. Просто для удобства.

{
  "name": "yourusername/scripts",
  "description": "Various scripts",
  "type": "project",
  "license": "MIT",
  "repositories": [
    {
      "type": "package",
      "package": {
        "name": "modx/minishop",
        "version": "2.9.3",
        "dist": {
          "url": "https://github.com/Ibochkarev/miniShop2/archive/refs/tags/v2.9.3-pl.zip",
          "type": "zip"
        }
      }
    }
  ],
  "minimum-stability": "dev",
  "require": {
    "league/csv": "9.x-dev"
  },
  "require-dev": {
    "roave/security-advisories": "dev-latest",
    "modx/revolution": "~2.0",
    "modx/minishop": "2.9.*"
  }
}

Далее просто запускаем composer install или composer update и все наши зависимости есть в папке vendor, остается только подключить их в коде скрипта, а затем импортировать используемые в скрипте классы. И всё, можно использовать код библиотек внутри скриптов.

<?php
# ...

namespace yourusername\scripts;

use modX;
use msProductFile;
use xPDO;

require __DIR__ . "/vendor/autoload.php";

# ...

Неплохой лонгрид получился, но по другому объяснить сложно. Не обещаю, но хочу сделать такую же инструкцию в формате видео-урока. Лучше мотивацией продолжать для меня будет пожертвование любой суммы через кнопки ниже или под заголовком здесь, или же на modx.pro.

На чытанне спатрэбілася 11 хвілін Нататка змяшчае 2310 слоў

Сябры, акрамя блога, я амаль што рэгулярна пішу адмысловую рассылку пра праграмванне і тэхналогіі, дзе раз на двы тыдні збіраю самыя цікавыя навіны тэхналогій і раблю агляд цікавых інструментаў, якія мне трапіліся. Таму хутчэй падпісвайцеся, каб не прапусціць штосці цікавае наступным разам!