Symfony Console: команды PHP — мастерская и 8 практических сценариев
Создаём удобные PHP-команды для импорта, отчётов и автоматизации. Восемь сценариев с настоящими результатами CLI, тесты, исходники и запуск на Windows/Linux.

Symfony Console без полного фреймворка: импорт CSV, отчёты JSON, прогресс, подтверждения и команды для автоматизации. Восемь сценариев мастерской, реальные результаты CLI, исходники и запуск на Windows, Linux и VPS.
Symfony Console: удобная командная строка для PHP-проекта
У сайта появляются задачи, которые неудобно выполнять открытием страницы: импорт справочника, подготовка отчёта, проверка конфигурации, обработка большого набора записей. Сначала разработчик пишет отдельный PHP-скрипт и читает аргументы вручную. Затем нужны справка, флаги, коды ошибок, подтверждение и нормальный вывод. Symfony Console предоставляет готовую основу для такого инструмента.
Компонент можно использовать отдельно от полного Symfony Framework. Вы создаёте приложение, регистрируете команды и запускаете их через PHP CLI. Одна команда получает понятное имя, описание, аргументы и параметры. Пользователь видит справку, а планировщик получает предсказуемый код завершения. Бизнес-логика остаётся вашей: Console помогает организовать интерфейс командной строки.
В этой статье собран самостоятельный проект: импорт CSV, JSON-отчёт, прогресс, подтверждение, окружение и ошибки. Восемь сценариев мастерской позволяют сравнить параметры и настоящие записанные результаты. Исходники содержат CLI, тесты, lockfile и шаблоны запуска для Windows и Linux.
Где применять: импорт, обслуживание, отчёты и автоматизация
CLI особенно полезен там, где действие повторяется и должно одинаково работать у разработчика, администратора и в расписании. Например, команда проверяет каталог поставщика перед импортом, пересчитывает статистику или формирует выгрузку для другого приложения. Параметры запуска фиксируют намерение: какой файл обработать, какой период выбрать, требуется ли только предварительная проверка.
Другой сценарий — диагностика развёртывания. Команда проверяет необходимые настройки и возвращает ненулевой код, если приложение не готово. CI может остановить следующий шаг, а администратор получает понятную причину. Для долгой обработки CLI удобнее обычного HTTP-запроса, однако ограничения памяти, время выполнения и обработку сигналов всё равно нужно продумывать.
Не стоит переносить любую кнопку сайта в терминал. Пользовательские действия с авторизацией остаются частью веб-приложения, а команды подходят для регламентных и служебных операций. Если работу нужно распределять между воркерами, повторять после сбоев и отслеживать в очереди, CLI становится точкой входа, но сама система очередей проектируется отдельно.
Почему выбран Symfony Console 7.4 LTS и PHP 8.2+
В проекте закреплена версия symfony/console 7.4.23. Ветка 7.4 относится к длительно поддерживаемым и допускает PHP начиная с 8.2. Это удобно для самостоятельного инструмента, который нужно запустить на существующем хостинге или VPS с PHP 8.2/8.3. Выбор сделан по совместимости, а не потому, что более новые ветки не существуют.
Ветка Symfony 8 требует PHP 8.4 и выше. Если инфраструктура уже обновлена, можно оценить её отдельно, прочитать изменения и повторить проверки. Не меняйте системный PHP только ради примера: на сервере от него могут зависеть другие сайты. Сначала выясните, какой бинарный файл использует CLI и какой интерпретатор обслуживает веб-приложение.
В Composer задана платформа PHP 8.2.0 и сохранён lockfile. Это помогает не получить зависимость, требующую более новую версию PHP, только потому, что сборка выполнялась на свежем компьютере. Проверка фактических требований при установке всё равно необходима. Сведения о поддержке смотрите на официальной странице ветки 7.4.
{
"require": {
"php": ">=8.2",
"symfony/console": "7.4.23"
},
"config": { "platform": { "php": "8.2.0" } }
}
Мини-мастерская: параметры, stdout, stderr и код выхода
Выберите сценарий и вариант запуска. Мастерская покажет команду, полезный вывод, диагностический поток и код завершения. Можно сравнить импорт с предварительной проверкой и реальной записью, посмотреть ошибочный CSV, переключить формат отчёта или вызвать учебный сбой на третьем шаге. Для окружения есть команды Bash и PowerShell.
На странице не исполняются PHP и команды сервера. Результаты заранее получены настоящими дочерними процессами PHP CLI из опубликованных исходников. Браузер выбирает соответствующий вариант и показывает его без подмены вычислений. Это позволяет безопасно изучать серверный инструмент на обычной странице; собственные данные и произвольные параметры проверяются уже в скачанном проекте.
Кнопка скачивает выбранный результат JSON вместе с командой для повторения. Отдельно доступны исходный ZIP, корректный и ошибочный CSV, шаблоны systemd и Планировщика Windows. Загрузка шаблона не создаёт расписание. Если встроенная область кажется тесной, откройте мастерскую на весь экран и сравните выводы рядом.
Установка через Composer и структура самостоятельного приложения
В новом проекте компонент устанавливается Composer. В скачанном архиве файл зависимостей уже подготовлен, поэтому используйте composer install: он читает lockfile и восстанавливает проверенный набор. Команда update решает другую задачу — пересматривает версии. Не подменяйте ею воспроизводимую установку перед каждым развёртыванием.
Точка входа находится в bin/console. Она подключает автозагрузку Composer, создаёт приложение и возвращает его код завершения. Регистрация команд вынесена в src/Factory.php, работа с CSV и JSON — в отдельный Repository. Так обработчики интерфейса не смешиваются с файловой логикой, а тесты могут проверять их независимо.
В каталоге public лежит статическая мастерская, в templates — примеры расписаний. Каталог runtime создаётся только для локальных результатов и исключён из архива поставки. Если Composer пока незнаком, начните с подробного руководства по Composer на сайте: там разобраны установка, зависимости и основные команды.
composer require symfony/console:7.4.23
# Для готового архива:
composer install --no-interaction --prefer-dist
php bin/console list
Точка входа, регистрация команды и её ответственность
Приложение Console хранит набор команд и отвечает за маршрутизацию аргументов. У команды должно быть конкретное действие и понятный результат. Названия вроде contacts:import и contacts:report сразу показывают предметную область. Один огромный обработчик с десятками несвязанных режимов быстро становится труднее сопровождать, чем несколько небольших команд.
В проекте используется регистрация через register и обработчик с интерфейсами ввода и вывода. Это компактный способ показать устройство самостоятельного CLI. В большом приложении команды можно разнести по классам и внедрять зависимости. Полный контейнер Symfony для первых примеров не требуется.
Обработчик должен проверять вход, вызывать прикладной сервис и преобразовывать результат в вывод и код завершения. Чтение CSV, блокировку и замену файла лучше держать в отдельном классе. Такой подход позволяет повторно использовать логику в другом интерфейсе и тестировать операции без симуляции целого терминала.
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
$app = new Application('My tools', '1.0.0');
$app->register('app:check')->setCode(
function (InputInterface $input, OutputInterface $output): int {
$output->writeln('Проверка завершена');
return Command::SUCCESS;
}
);
exit($app->run());
Пример 1. Аргументы, флаги и встроенная справка
Аргумент обозначает позиционное значение: в команде приветствия это имя. Флаг включает режим, например верхний регистр. Опция со значением передаёт дополнительный параметр вроде формата отчёта. Разделяйте их по смыслу: обязательный объект действия удобно сделать аргументом, а настройки поведения — именованными опциями.
В мастерской сравните обычное приветствие и вариант --uppercase. Затем откройте справку: описание аргумента и опции появляется автоматически из определения команды. Значение с пробелами передаётся в кавычках. В исходниках длина имени проверяется отдельно, потому что наличие обязательного аргумента ещё не гарантирует подходящее прикладное значение.
Используйте понятные значения по умолчанию и избегайте неявных опасных режимов. Справка должна объяснять, что изменяет команда и куда пишет результат. В проекте пользовательский текст выводится как обычный текст, без интерпретации форматирующих тегов Console; это сохраняет предсказуемое отображение даже для необычного имени.
$app->register('app:greet')
->addArgument('name', InputArgument::REQUIRED, 'Имя пользователя')
->addOption('uppercase', null, InputOption::VALUE_NONE, 'Верхний регистр');
// Запуск:
// php bin/console app:greet "Анна Демина" --uppercase
// php bin/console app:greet --help
Пример 2. CSV: проверить весь файл перед изменением данных
Импорт начинается с чтения файла, а не с немедленной записи первой строки. Учебный формат имеет заголовок name,email,city. Проверяются размер файла, количество полей, имя, email, город и повторные адреса внутри пакета. Если ошибка находится в третьей строке, команда сообщает её номер и не меняет прежний результат.
Это особенно полезно для файлов, подготовленных вручную. Пользователь получает конкретное место проблемы и может исправить документ целиком. Проект ограничивает один CSV одним мегабайтом и двумястами контактами. Ограничения принадлежат примеру, а не Symfony Console; для производственного импорта выбирайте их исходя из памяти, времени и формата данных.
Метод fgetcsv разбирает кавычки и разделители корректнее ручного деления строки по запятой. Вызов явно задаёт разделитель, ограничитель и escape-параметр. Для своего формата дополнительно определите кодировку, допустимость пустых строк и политику неизвестных колонок. Эти правила должны быть описаны рядом с образцом файла.
$stream = fopen($path, 'rb');
try {
$header = fgetcsv($stream, 0, ',', '"', '');
if ($header !== ['name', 'email', 'city']) {
throw new RuntimeException('Неверный заголовок CSV');
}
while (($row = fgetcsv($stream, 0, ',', '"', '')) !== false) {
// Проверить всю строку и добавить в подготовленный пакет.
}
} finally {
fclose($stream);
}
Dry-run, реальная запись и повторный импорт без дублей
Флаг --dry-run показывает, сколько контактов будет добавлено, пропущено и останется всего. Он не создаёт runtime и не сохраняет JSON. Это даёт возможность проверить входной файл и выбранную политику дублей до изменения данных. В мастерской предварительная проверка исходного набора показывает три новых контакта.
Реальный запуск пишет только runtime/contacts.json текущего проекта. Перед записью берётся локальная блокировка и план пересчитывается по актуальному содержимому. Данные сначала попадают во временный файл, затем заменяют целевой. Повторный импорт тех же email пропускает записи; существующие контакты не обновляются. Такой конкретный контракт делает поведение воспроизводимым.
Предварительный результат не является резервированием состояния. Между dry-run и настоящим запуском другой процесс мог изменить данные. Поэтому проверку нужно повторять внутри реальной операции. Для серверной базы вместо файловой замены применяются её транзакции и ограничения уникальности, а для распределённого запуска понадобится блокировка, общая для всех исполнителей.
php bin/console contacts:import fixtures/contacts.csv --dry-run
php bin/console contacts:import fixtures/contacts.csv
php bin/console contacts:import fixtures/contacts.csv
# Повтор: added=0, skipped=3, total=3
Пример 3. Таблица для человека и JSON для программы
Одни и те же данные могут понадобиться в двух формах. Таблица удобна администратору, который проверяет содержимое в терминале. JSON подходит следующему скрипту, интеграции или сохранению в файл. Команда отчёта принимает формат и необязательный город, затем читает результат локального импорта. До первого импорта набор пустой — это нормальное состояние.
Для машинного формата полезный поток должен содержать только документ. Если перед JSON напечатать приветствие, рамку или сообщение об успехе, следующий инструмент не сможет разобрать результат. В проекте JSON формируется с обработкой ошибок кодирования и выводится без декоративной разметки.
Фильтр города применяется к прочитанным контактам и сохраняет массив даже при одном совпадении. Это важная стабильность контракта: клиенту не приходится угадывать, вернулся объект или список. В рабочем отчёте добавьте версию формата, период данных и необходимые агрегаты, сохраняя одинаковую структуру для пустого и непустого результата.
php bin/console contacts:report
php bin/console contacts:report --format=json
php bin/console contacts:report --format=json --city=Москва
# Linux: отдельно сохранить полезный вывод
php bin/console contacts:report --format=json > report.json
Stdout и stderr: как не испортить конвейер сообщений
Стандартный поток вывода предназначен для результата, а поток ошибок — для диагностики. Это не означает, что любой текст в stderr обязательно сообщает о сбое: там удобно показывать прогресс, оставляя stdout чистым. Окончательный статус операции определяется кодом завершения вместе с контрактом команды.
В проекте вывод ошибок использует отдельный поток, если объект вывода поддерживает ConsoleOutputInterface. Благодаря этому отчёт можно направить в JSON-файл, а сообщения — в журнал. В тестах потоки также захватываются раздельно. В мастерской они показаны соседними блоками, поэтому различие видно сразу.
Не объединяйте каналы через 2>&1, если следующая программа ожидает JSON. Для диагностики объединение иногда удобно, но это уже другой формат. В старом Windows PowerShell перенаправление текста может менять кодировку; используйте современный PowerShell или явное сохранение UTF-8 после проверки кода завершения. Команда не должна объявлять успех только потому, что удалось открыть выходной файл.
php bin/console batch:process --count=8 > result.json 2> progress.log
php bin/console demo:fail --kind=runtime > result.txt 2> error.log
echo "$?"
Пример 4. ProgressBar и завершение на ошибочном шаге
Если команда обрабатывает десятки записей, молчание выглядит как зависание. ProgressBar показывает текущий шаг и общий объём. В учебном цикле можно выбрать восемь или двадцать операций, а также ошибку на третьем шаге. Это настоящий вывод Console, записанный во время сборки мастерской; страница не изображает текущую серверную обработку.
Прогресс направлен в stderr, итоговый JSON — в stdout. Когда возникает учебная ошибка, команда возвращает ненулевой код и не печатает успешный итог. Принудительно доводить шкалу до ста процентов после сбоя нельзя: визуально это обещало бы выполненную работу. В реальной обработке дополнительно указывайте число успешных, пропущенных и ошибочных объектов.
Шкала не даёт транзакционности и не организует повтор. Если первые две операции уже изменили внешнюю систему, ошибка третьей не отменяет их автоматически. Для таких задач нужны идемпотентные действия, контрольные точки и политика восстановления. Console предоставляет отображение и входной интерфейс, а надёжность обработки определяется прикладной логикой.
$progress = new ProgressBar($stderr, $total);
$progress->start();
foreach ($items as $item) {
processItem($item);
$progress->advance();
}
$progress->finish();
$stderr->writeln('');
Пример 5. Подтверждение и неинтерактивный запуск
Команда, которая меняет данные, иногда требует подтверждения. Для интерактивного режима используйте явный вопрос и безопасный ответ по умолчанию. В примере подтверждается учебный план, при этом файлы вообще не удаляются. Пользователь может ответить отрицательно, и команда завершится без выполнения действия.
В расписании никто не ответит на вопрос. Флаг --no-interaction отключает ожидание ввода, но не должен автоматически означать согласие на опасное действие. В нашем контракте автоматический запуск требует отдельного --yes. Без него команда возвращает ошибку параметров и объясняет, чего не хватает.
Такой подход помогает отделить осознанное подтверждение от технического режима исполнения. Для production добавьте предварительный план, точное имя объекта и ограничение области действия. Не заставляйте оператора подтверждать каждое безопасное чтение, иначе он перестанет внимательно читать важные вопросы. Подтверждение полезно там, где последствия операции действительно требуют внимания.
if (!$input->getOption('yes')) {
if (!$input->isInteractive()) {
return Command::INVALID;
}
if (!$io->confirm('Подтвердить учебный план?', false)) {
return Command::FAILURE;
}
}
// Выполнить только явно подтверждённое действие.
Пример 6. Переменные окружения и различия с полным Symfony
Самостоятельный компонент Console не загружает файл .env автоматически и не создаёт окружения приложения за вас. В проекте команда конфигурации читает APP_ENV и APP_DEBUG через getenv. Допустимые значения проверяются, а в вывод попадают только эти несекретные настройки. В ответе отдельно указано, что автоматической загрузки dotenv нет.
В полном Symfony Framework привычное поведение окружения обеспечивается другими частями приложения. При переносе примера в отдельный PHP-инструмент нельзя предполагать, что эти механизмы появились вместе с Console. Если нужен dotenv, подключите соответствующую библиотеку и явную загрузку в точке входа.
Секреты не следует печатать в справке, диагностическом JSON или передавать как обычные аргументы, видимые в истории терминала. В планировщике окружение также может отличаться от интерактивной сессии. Укажите необходимые значения для процесса и проверьте запуск именно под той учётной записью, которая будет выполнять задачу.
# Linux / Bash
APP_ENV=prod APP_DEBUG=0 php bin/console app:config
# Windows / PowerShell
$env:APP_ENV = 'prod'
$env:APP_DEBUG = '0'
php bin/console app:config
Пример 7. Коды завершения и проверяемые ошибки
Нулевой код означает успешное завершение. В наших обработчиках единица используется для ошибки выполнения, двойка — для неверных входных параметров. Это позволяет вызывающему скрипту остановиться или выбрать следующий шаг. Сообщение человеку при этом отправляется в stderr и объясняет причину.
Не считайте, что любая ошибка разбора Console обязательно вернёт двойку. В опубликованном проекте отсутствие обязательного имени обрабатывает сама библиотека и возвращает единицу. Мастерская показывает этот вариант рядом с собственными кодами. Надёжный вызывающий инструмент проверяет общий признак ненулевого результата, а более тонкую классификацию использует только по документированному контракту.
В Bash код находится в $?, в PowerShell для внешней программы — в $LASTEXITCODE. Считывайте его сразу после нужной команды: следующий запуск способен изменить значение. При построении конвейера отдельно изучите правила своей оболочки, иначе успех последнего этапа может скрыть ошибку предыдущего.
php bin/console demo:fail --kind=invalid
# Bash:
echo "$?"
# PowerShell:
$LASTEXITCODE
Пример 8. Команда как часть расписания
Планировщик должен запускать уже проверенную команду, а не скрывать внутри себя её бизнес-логику. Сначала выполните действие вручную, получите ожидаемый код и проверьте файлы результата. Затем зафиксируйте путь PHP, рабочий каталог, пользователя, окружение и место журналов. Это избавляет от частой проблемы «в терминале работает, по расписанию нет».
В архиве есть шаблоны systemd service и timer, а также PowerShell-заготовка для Windows. Они используют read-only отчёт по контактам. На странице можно скачать файлы и посмотреть соответствующий вывод, но никакие задания автоматически не устанавливаются. Настройка требует ваших путей и выбранной учётной записи.
Если операция длится дольше интервала, решите вопрос повторного запуска. Для одного systemd unit активное выполнение не запускается заново параллельно тем же механизмом. Независимые ручные процессы могут существовать одновременно, поэтому импорт имеет собственную файловую блокировку. Для распределённой обработки этот локальный механизм уже недостаточен.
Запуск и проверка проекта в Windows
Распакуйте ZIP в отдельный каталог, проверьте PHP CLI и Composer, затем выполните установку из lockfile. В PowerShell команды composer.bat вызывают установленную Windows-обёртку Composer. После тестов запустите список команд и справку нужного действия. Первую запись лучше предварить dry-run, чтобы увидеть объём изменений.
Локальная мастерская открывается через встроенный PHP-сервер на адресе http://127.0.0.1:8165/. Он нужен только для просмотра HTML, CSS и записанных результатов. Сам CLI запускается отдельной командой в терминале. Открытие страницы не импортирует CSV в runtime и не выполняет PHP через браузер.
Если PHP в PATH отличается от нужной версии, используйте полный путь к исполняемому файлу. Аналогично проверьте интерпретатор, который вызывает Composer. Кодировка CSV — UTF-8, имена с пробелами заключайте в кавычки. В README есть команды для проверки окружения, потоков и результата; их удобно повторить перед первым автоматическим запуском.
php --version
composer.bat install --no-interaction --prefer-dist
composer.bat test
php bin/console list
php bin/console contacts:import fixtures/contacts.csv --dry-run
php bin/console contacts:import fixtures/contacts.csv
php -S 127.0.0.1:8165 -t public
Linux: PHP CLI, Composer и права на каталог результатов
На Linux нужен PHP CLI версии 8.2 или выше и Composer. Конкретные пакеты зависят от дистрибутива и его версии. Сначала проверьте установленный бинарный файл: PHP командной строки и PHP-FPM сайта могут иметь разные версии и конфигурации. Для этой мастерской веб-серверный обработчик PHP не требуется.
Установите зависимости из каталога проекта, выполните тесты и запустите команды вручную. Runtime должен быть доступен для записи тому пользователю, который исполняет импорт. Остальные файлы приложения обычно достаточно оставить доступными для чтения. Не решайте проблему прав рекурсивным разрешением записи всем пользователям.
Для размещения инструмента на сервере удобно выбрать отдельный каталог, например /opt/aen-console. Статика public может обслуживаться отдельно, а исходники CLI, vendor и результаты остаются вне публичного document root. Такой порядок предотвращает случайную раздачу служебных файлов через сайт и позволяет независимо обновлять интерфейс статьи и серверный инструмент.
php --version
composer install --no-interaction --prefer-dist
composer test
php bin/console contacts:import fixtures/contacts.csv --dry-run
php bin/console contacts:report --format=json
# Просмотр статики локально:
php -S 127.0.0.1:8165 -t public
VPS: сервис systemd с отдельным пользователем
Для регламентной задачи создайте отдельную учётную запись с минимально необходимыми правами. В unit укажите абсолютный путь PHP, рабочий каталог и конкретную команду. Это устраняет зависимость от PATH и текущего каталога интерактивного пользователя. Runtime подготовьте заранее и разрешите запись только нужной служебной учётной записи.
В шаблоне используется Type=oneshot: сервис выполняет команду и завершается. Вывод направляется в журнал systemd, где его можно посмотреть через journalctl. Ограничения NoNewPrivileges, PrivateTmp и ProtectSystem уменьшают доступ процесса к окружению; ReadWritePaths разрешает изменение только рабочего runtime. Перед применением проверьте, что выбранные пути существуют.
Конфигурация в статье является основой для адаптации, а не универсальным установщиком. Ваши файлы, сетевые API и права могут требовать других разрешений. Сначала запустите команду от имени нужного пользователя, затем проверьте unit средствами systemd. Не запускайте импорт от root только потому, что так исчезла ошибка доступа.
[Service]
Type=oneshot
User=aen-console
WorkingDirectory=/opt/aen-console
Environment=APP_ENV=prod
Environment=APP_DEBUG=0
ExecStart=/usr/bin/php /opt/aen-console/bin/console contacts:report --format=json --no-interaction --no-ansi
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/aen-console/runtime
StandardOutput=journal
StandardError=journal
Таймер systemd: расписание, пропущенные запуски и журналы
Timer отделяет расписание от описания выполняемой команды. В примере отчёт запускается каждый час. Параметр Persistent помогает выполнить пропущенное календарное срабатывание после возвращения системы к работе. Это не означает, что накопится отдельная задача за каждый пропущенный час: если нужна полноценная очередь событий, её проектируют отдельно.
Перед установкой проверьте часовой пояс сервера и ожидаемое время выполнения. Сначала проверьте синтаксис файлов, затем обновите конфигурацию systemd и включите таймер. После этого посмотрите список таймеров и журнал сервиса. Сам факт наличия unit-файла ещё не доказывает, что задача запускалась успешно.
Для production полезно отслеживать ненулевые коды и возраст последнего успешного результата. Команда может завершиться без ошибок, но обработать пустой вход, поэтому бизнес-проверки тоже нужны. Отключение таймера должно быть описано рядом с установкой, чтобы регламентная операция оставалась управляемой.
[Timer]
OnCalendar=hourly
Persistent=true
Unit=aen-console-report.service
[Install]
WantedBy=timers.target
# Проверка и управление в терминале:
# sudo systemd-analyze verify /etc/systemd/system/aen-console-report.service /etc/systemd/system/aen-console-report.timer
# sudo systemctl daemon-reload
# sudo systemctl enable --now aen-console-report.timer
# systemctl list-timers aen-console-report.timer
# journalctl -u aen-console-report.service -n 50 --no-pager
Планировщик Windows: действие, рабочий каталог и окружение
В Windows у задания есть исполняемый файл, аргументы, рабочий каталог, триггер и учётная запись. Укажите полный путь к PHP и проекту. Рабочий каталог особенно важен для относительных путей CSV: запуск из другой папки может привести к тому, что корректный файл не будет найден.
В архиве подготовлена PowerShell-заготовка действия и ежедневного триггера. Строка регистрации закомментирована, чтобы скачивание или просмотр примера не создавали задание случайно. Перед использованием выберите учётную запись с доступом к runtime и проверьте запуск без открытого пользовательского терминала. Максимальные привилегии не нужны для обычного чтения отчёта.
Планировщик не обязан наследовать переменные вашей текущей сессии. Для окружения и перенаправления потоков часто удобна отдельная обёртка, которая задаёт значения, запускает PHP, сохраняет журналы и возвращает его код. Само задание не превращает stdout в файл автоматически; место сохранения результата нужно указать явно.
$action = New-ScheduledTaskAction `
-Execute 'C:\PHP\php.exe' `
-Argument '"C:\apps\aen-console\bin\console" contacts:report --format=json --no-interaction' `
-WorkingDirectory 'C:\apps\aen-console'
$trigger = New-ScheduledTaskTrigger -Daily -At '09:00'
# После проверки путей и пользователя:
# Register-ScheduledTask -TaskName 'AEN Console report' -Action $action -Trigger $trigger
Блокировки, повторяемость и границы атомарности
Повторный запуск часто возникает естественно: оператор не уверен, завершился ли прошлый импорт, или планировщик повторяет задачу после сбоя. Если действие создаёт дубли, восстановление становится опасным. В нашем примере email служит ключом повторяемости: уже существующая запись пропускается, а итог сообщает количество пропусков.
Для файлового импорта используется flock на отдельном локальном lock-файле. Пока один процесс пересчитывает план и пишет результат, второй получает понятную ошибку занятости. Подготовленный JSON записывается во временный файл того же каталога и затем заменяет целевой. Это ограниченный учебный механизм для одного локального набора, не универсальная гарантия распределённых транзакций.
При работе с несколькими серверами, сетевыми файловыми системами или внешними API нужно выбирать соответствующий механизм блокировок и идемпотентности. Частично отправленные письма нельзя «откатить» заменой JSON. Для каждого побочного действия определите, как распознать повтор, что считать завершением и что делать после прерывания процесса.
Тесты CommandTester: проверяем поведение команды
CommandTester позволяет запустить зарегистрированную команду с аргументами и получить вывод без ручного терминала. В проекте проверяются не только успешные строки, но и последствия: dry-run не создаёт runtime, неправильный CSV не меняет прежний файл, повторный импорт не создаёт дублей. JSON разбирается как документ, а диагностический поток захватывается отдельно.
Есть проверки интерактивного отказа и согласия, отсутствующего подтверждения в автоматическом режиме, неверного окружения и ошибки на заданном шаге. Всего включены восемнадцать проверок. Для них создаётся отдельный каталог внутри runtime; очистка касается только известных файлов текущего теста.
После таких проверок полезен запуск настоящего дочернего процесса PHP. Именно так сборка записывает результаты мастерской: команда получает реальные аргументы и окружение, а приложение возвращает stdout, stderr и exit code. Это дополняет CommandTester и обнаруживает различия, связанные с точкой входа и процессом, которые тест одной команды мог бы не показать.
$tester = new CommandTester($app->find('contacts:import'));
$tester->execute([
'file' => 'fixtures/contacts.csv',
'--dry-run' => true,
], [
'interactive' => false,
'capture_stderr_separately' => true,
]);
assert($tester->getStatusCode() === 0);
$result = json_decode($tester->getDisplay(), true, 512, JSON_THROW_ON_ERROR);
Безопасность запуска и граница между CLI и веб-страницей
Доступ к CLI обычно имеют разработчик, администратор или служебный процесс. Это не повод исполнять любую строку, пришедшую от посетителя сайта. Веб-обработчик, который передаёт пользовательский текст в shell, создаёт совершенно другую поверхность риска. Наша мастерская состоит из статики и не предоставляет такого интерфейса.
Сборщик результатов запускает PHP через массив аргументов proc_open, без построения shell-команды из произвольного ввода. Файлы CSV берутся из подготовленного проекта, результаты записываются в отдельный runtime. При адаптации такого подхода сохраняйте фиксированную команду, проверяйте каждый параметр и ограничивайте ресурсы процесса.
Не включайте секреты и реальные персональные данные в учебные CSV, записанные выводы или архив. Логи могут храниться долго и быть доступны более широкому кругу людей, чем сама рабочая база. Команда диагностики должна показывать готовность настройки, а не обязательно её секретное значение. Публикуйте только public, оставляя vendor, runtime и конфигурацию вне document root.
Производительность, память и длительные операции
Учебный импорт сначала собирает проверенный пакет в памяти. Для нескольких сотен записей это удобно и позволяет отклонить файл до изменений. Для миллионов строк понадобится потоковая обработка, порции и другая стратегия фиксации. При этом атомарность меняется: завершённые порции могут остаться после ошибки следующей.
Оцените, что дороже: чтение файла, проверка, обращение к базе или вывод в терминал. Избыточная перерисовка прогресса и печать каждой строки тоже имеют стоимость. В автоматизации часто достаточно периодических показателей и итогового отчёта, а подробности оставляют для режима диагностики.
Долгую команду следует уметь остановить предсказуемо. Продумайте обработку сигналов на Linux, состояние незавершённой порции и возможность продолжения. Для постоянного воркера понадобятся управление памятью и процессный супервизор. Symfony Console обеспечивает командный интерфейс, но не заменяет мониторинг, очередь задач и правила восстановления после сбоя.
Удобство инструмента: справка, ошибки и мобильная мастерская
Командная строка тоже является пользовательским интерфейсом. Хорошее имя команды, пример в справке и сообщение с номером ошибочной строки экономят время не меньше, чем аккуратная форма на сайте. Опасные изменения должны быть видны из названия и параметров, а успешный результат — объяснять, что именно произошло.
В мастерской команды можно копировать и скачивать вместе с результатами. На узком экране блоки stdout и stderr располагаются вертикально, длинная команда переносится, а код прокручивается внутри своего блока. Подписи объясняют, что это запись реального запуска, поэтому пользователь не путает учебный просмотр с исполнением на сервере.
Перед использованием собственного CLI дайте его человеку, который не читал исходники. Попросите найти справку, выполнить проверку без записи, распознать ошибку и сохранить JSON. Если для каждого шага требуется объяснение автора, улучшайте интерфейс команды и README. Такой подход делает инструмент пригодным для команды, а не только для разработчика, который его создал.
Что скачать и как продолжить работу
Архив включает самостоятельное приложение PHP, Composer lock, CSV, тесты, готовую статическую мастерскую и шаблоны расписания. Начните с приветствия и help, затем выполните dry-run импорта. После реальной записи проверьте отчёт и повторный запуск. Только после этого адаптируйте модель под собственную задачу и подключайте расписание.
Для нового инструмента сохраните разделение ответственности: Console принимает параметры и показывает результат, прикладной сервис выполняет работу, хранилище обеспечивает нужную согласованность. Зафиксируйте коды завершения, формат JSON, политику повторов и ограничения входа. Это даст основу для ручного запуска, CI и регламентной автоматизации.
Собственный код распространяется по MIT. Лицензии Symfony Console и Bootstrap включены в поставку, версии зависимостей записаны в lockfile. Документация ниже помогает углубить отдельные темы: аргументы, оформление, прогресс и вопросы пользователю. При обновлении компонента повторите тесты и реальные CLI-запуски, особенно если результаты разбирают другие программы.


