Консольные приложения
Консольные приложения главным образом используются для выполнения вторичных или фоновых задач, таких как генерация кода, компиляция поискового индекса, отправка сообщений электронной почты и т.д. Yii предоставляет инструмент для разработки консольных приложений, придерживаясь объектно-ориентированного подхода. Он позволяет консольному приложению получить доступ к ресурсам, которые использует основное веб-приложение (например, к базе данных).
1. Обзор ¶
Каждая консольная задача представлена в Yii как команда. Консольная команда описывается в классе, наследуемом от CConsoleCommand.
После использования yiic webapp для создания начального прототипа приложения, в protected будут два файла:
- yiic — скрипт для Linux/Unix;
- yiic.bat — скрипт для Windows.
В консоли можно ввести следующие команды:
cd protected yiic help
После ввода будет отображён список всех доступных на данный момент команд. По умолчанию это команды, предоставляемые Yii (так называемые системные команды) и команды, разработанные для конкретных приложений (так называемые пользовательские команды).
Для получения справки по команде можно запустить
yiic help
Для запуска команды используется следующий формат:
yiic [параметры…]
2. Создание команд ¶
Консольные команды находятся в файлах с классами в папке, указанной в CConsoleApplication::commandPath. По умолчанию это protected/commands .
Класс консольной команды должен быть унаследован от CConsoleCommand. Имя класса должно быть вида XyzCommand , где Xyz соответствует имени команды, первая буква которого приведена к верхнему регистру. К примеру, команда sitemap должна использовать класс SitemapCommand . Имена консольных команд регистрозависимы.
Подсказка: Конфигурируя CConsoleApplication::commandMap, можно при желании изменить порядок именования и расположения классов команд.
Для создания новой команды необходимо либо реализовать метод CConsoleCommand::run(), либо одно или несколько действий (будут описаны далее).
При вводе консольной команды приложение запускает метод CConsoleCommand::run(). Параметры метода передаются в соответствии с следующим заголовком:
public function run($args)
где $args — дополнительные параметры, переданные из командной строки.
Внутри консольной команды для получения доступа к экземпляру консольного приложения можно использовать Yii::app() . Через полученный экземпляр можно обращаться к различным компонентам, таким как соединение с базой данных ( Yii::app()->db ). Насколько можно судить, это очень похоже на обычное веб-приложение.
Информация: Начиная с версии 1.1.1 можно создавать глобальные команды, которые используются всеми приложениями. Для этого определяется переменная окружения YII_CONSOLE_COMMANDS и в её значение записывается путь к директории с классами глобальных консольных команд.
3. Действие консольной команды ¶
Примечание: Данная возможность доступна начиная с версии 1.1.5.
Часто в консольной команде требуется работать с различными параметрами. Часть из них могут быть обязательными, а часть нет. Также может потребоваться реализовать субкоманды для выполнения различных подзадач. Всё это упрощается при использовании действий.
Действие консольной команды — метод в её классе. Имя метода должно быть вида actionXyz , где Xyz соответствует имени действия и первой буквой, приведённой к верхнему регистру. К примеру, метод actionIndex задаёт действие с именем index .
Для того, чтобы запустить определённое действие, используется следующий формат команды:
yiic --параметр1=значение1 --параметр2=значение2 .
Дополнительные пары имя-значение передаются методу действия как именованные параметры. Значение опции xyz соответствует параметру $xyz метода действия. К примеру, если мы определим следующий класс команды:
class SitemapCommand extends CConsoleCommand < public function actionIndex($type, $limit=5) < . >public function actionInit() < . >>
То все следующие консольные команды вызовут actionIndex(‘News’, 5) :
yiic sitemap index --type=News --limit=5 // $limit принимает значение по умолчанию yiic sitemap index --type=News // $limit принимает значение по умолчанию. // Так как 'index' — действие по умолчанию, мы можем опустить имя действия. yiic sitemap --type=News // порядок опций не важен yiic sitemap index --limit=5 --type=News
Если значение опции не указано (то есть —type вместо —type=News ), соответствующему параметру действия будет присвоено значение true .
Примечание: Альтернативные форматы указания опций, такие как —type News или -t News не поддерживаются.
Если объявить параметр как массив, он сможет принять массив значений:
public function actionIndex(array $types)
Чтобы передать массив значений необходимо указать одну и ту же опцию несколько раз:
yiic sitemap index --types=News --types=Article
Команда, приведённая выше, запустит actionIndex(array(‘News’, ‘Article’)) .
Начиная с версии 1.1.6, Yii позволяет использовать анонимные параметры действий и глобальные опции.
Анонимные параметры — это параметры командной строки, не являющиеся опциями. К примеру, в команде yiic sitemap index —limit=5 News встречается анонимный параметр со значением News . Именованный параметр (опция) limit принимает значение, равное 5.
Для того, чтобы использовать анонимные параметры, действие должно описать параметр с именем $args :
public function actionIndex($limit=10, $args=array())
В массиве $args будут содержаться все доступные значения анонимных параметров.
Глобальные опции — это параметры командной строки, общие для всех действий команды. К примеру, нам может понадобиться для команды с несколькими действиями завести общую опцию verbose . Конечно, можно определить параметр $verbose для каждого действия, но лучше задать его public свойством класса команды, что автоматически сделает verbose глобальной опцией:
class SitemapCommand extends CConsoleCommand < public $verbose=false; public function actionIndex($type) >
Приведённый код позволяет использовать опцию verbose :
yiic sitemap index --verbose=1 --type=News
4. Код возврата ¶
Примечание: возможность указать код возврата в консольной команде появилась в версии 1.1.11.
При автоматическом запуске консольных команд, например, по cron или используя сервер непрерывной интеграции, важно знать, завершилось ли выполнение команды с ошибкой или успешно. Как раз для этого и предназначены коды возврата.
Данные коды являются целочисленными значениями от 0 до 254 (данный интервал задан в PHP), где 0 возвращается в случае успеха, а все остальные значения используются для различных ошибок.
Как в методе run() , так и в действиях команды вы можете вернуть целое число. Оно будет использовано в качестве кода возврата.
if (/* ошибка */) < return 1; // выходим с кодом 1 >// … всякая всячина … return 0; // всё хорошо, выходим с кодом 0
Если консольная команда ничего не возвращает, приложение завершается с кодом 0.
5. Настройка консольного приложения ¶
По умолчанию, если приложение создаётся с использованием yiic webapp , конфигурация консольного приложения находится в protected/config/console.php . Как и конфигурация веб-приложения, данный файл является PHP-скриптом, возвращающим массив с начальными значениями экземпляра консольного приложения. То есть в данном файле можно задать любое public свойство CConsoleApplication.
Так как консольные команды часто создаются для поддержки веб-приложения, требуется использовать те же ресурсы (такие, как соединения с БД), что используются в веб-приложении. Это можно сделать настроив соответствующие компоненты в конфигурации консольного приложения:
return array( . 'components'=>array( 'db'=>array( . ), ), );
Формат конфигурации очень похож на тот, что используется в веб-приложении, так как и CConsoleApplication и CWebApplication наследуют один и тот же базовый класс.
Компилируем быстрые консольные .exe приложения на PHP 8.1 в 2023 году, а почему бы и нет? (upd)
С каждым релизом PHP становится всё быстрее, а при включении JIT (Just-In-Time) компиляции, достигает почти отметок того же C.
У многих в своё время, наверное, было желание легкого написание консольных и оконных приложений. Ребята рунета иногда писали незаменимые приложения для решения мелких задач и делились на форумах, хотя некоторые из них и содержали костыли.
Однако времена меняются, и люди начали осознавать свои ошибки, переходя, скажем, на ООП.
Чтобы поностальгировать и продемонстрировать нового Франкенштейна, мы соберем полноценное консольное exe-приложение на PHP.
PHP — интерпретируемый язык, поэтому для работы нам просто необходимо иметь его бинарные файлы.
Для создания приложения нам понадобятся основной exe-файл и dll-библиотека движка. 32-битная версия PHP 8.1 занимает 7 Мб! Hello World с размером в 7 метров нам не подходит, согласитесь.
Следуя инструкции, мне удалось скомпилировать PHP без каких-либо дополнительных модулей, оставив только JIT и FFI (есть причина).
Размер получившихся файлов составил 5 Мб (x64), что уже не плохо, но недостаточно. Мы продолжим работать с x64, так как разница между ним и x86 составляет всего 100кб.

Теперь нам нужен PHP-код для нашего приложения.
Ради примера возьмём скрипт Zend Benchmark и добавим несколько аргументов командной строки для тестов.
Чтобы упаковать наше приложение в один файл, мы будем использовать бесплатную утилиту Enigma Virtual Box.
Но для этого Энигме нужен главный обработчик, который вызовет интерпретатор.
Скачиваем 32 битную версию gcc, устанавливаем, перезаходим в учётную запись, берем и делаем «gcc -o dabro.exe program.c», содержимое program.c (не забудьте поменять имя папки):
#include #include int main(int argc, char **argv) < char command[1024]; sprintf(command, "\"%s\\имя папки\\render.exe\" -f \"%s\\имя папки\\exe.c\" %s", getenv("LOCALAPPDATA"), getenv("LOCALAPPDATA"), GetCommandLineA() + strlen(argv[0]) + 1); STARTUPINFO si = < sizeof(si) >; PROCESS_INFORMATION pi; BOOL success = CreateProcess(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); if (!success) < DWORD error = GetLastError(); printf("Error: %d\n", error); return error; >WaitForSingleObject(pi.hProcess, INFINITE); DWORD exitCode; GetExitCodeProcess(pi.hProcess, &exitCode); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return exitCode; >
Версия с мультиязычной поддержкой в названиях папок и параметров (Нужен Visual Studio 2013+ для компиляции):
Скрытый текст
#define _CRT_SECURE_NO_WARNINGS #include "stdafx.h" #include #include #include #include #include #include int wmain(int argc, wchar_t **argv) < wchar_t command[32000]; if (argc >= 2 && wcslen(argv[1]) > 0 && argv[1][0] == L'-') < argv++; argc--; // Сбрасываем строку, если первая команда содержит тире >wchar_t localAppData[MAX_PATH]; if (GetEnvironmentVariableW(L"LOCALAPPDATA", localAppData, MAX_PATH) == 0) < wprintf(L"Error: Unable to retrieve LOCALAPPDATA environment variable!\n"); return -1; >swprintf(command, 32000, L"\"%s\\имя папки\\render.exe\" -f \"%s\\имя папки\\exe.c\" %s", localAppData, localAppData, GetCommandLineW() + wcslen(argv[0]) + 1); STARTUPINFOW si = < sizeof(si) >; PROCESS_INFORMATION pi; if (!CreateProcessW(NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) < DWORD error = GetLastError(); wprintf(L"Error: %d\n", error); return error; >WaitForSingleObject(pi.hProcess, INFINITE); DWORD exitCode; GetExitCodeProcess(pi.hProcess, &exitCode); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return exitCode; >
Иконку программы вы можете добавить через —icon=file.ico при компилировании в gcc, или в Resource Tuner, добавив ресурс Icon.
Выбираем обработчик (input file), забрасываем наши бинарники с исходниками в окно Энигмы, используем папку Local, %Application Data Folder%. В Files Option кликаем на галочку компрессии.
В свойствах файлов по Методу Виртуализации ставим на -Запись если не существует-, сам скрипт советую ставить на -Запись всегда-, чтобы легче было дебажить код в первое время. В настройках также убираем все галочки: «Временные файлы. разрешить запуск виртуальных exe и т.д.».
Бинарником PHP является файл render.exe, скрипт exe.c,

Скомпилировав приложение мы получаем портативный файл, который весит 2.7 Мб!
Если бы мы пошли дальше и убрали все ненужные встроенные зависимости, а также написали бы легкий обработчик, который самостоятельно распаковывает архив, то, возможно, размер приложения уменьшился бы до сотен килобайтов (отпишитесь, если знаете такой).
Сжатый .7z архив с нынешними бинарниками весит всего 1.5 Мб.
Для сравнения, скомпилированный Hello World бинарник Go приложения, весит 1.2 Мб.

Работает как часы, чтобы вы понимали про заявленную скорость ассемблер кода генерируемым JIT, моя машина проходит бенчмарк за 0.7с (с настройкой function JIT 0.3с), при выключенном же JIT, она просядает до 2.4с.
Реализовать параллельность для ресурсоёмких вычислительных операций не проблема (функции popen() / proc_open), можно также воспользоваться готовыми библиотеками, как amphp/parallel.
Запуск самих приложений быстрый и составляет менее секунды.
Вычислим ещё корневой Merkle хеш большого файла:
Вы не забыли про FFI?
Благодаря этому расширению мы можем писать C код прямо в PHP, подключать dll библиотеки, создавать WinAPI приложения, вот пример кода MessageBox:
MessageBoxA(null, "Hello!", "Habr", 0); ?>
А вот результат:

Вывод:
Писать простые исполняемые приложения на PHP можно (первое такое приложение по описанным выше методам). Они могут выступать бэкенд утилитами для графических обёрток, но не следует этого делать, поскольку PHP отлично проявил себя в web-разработке, а не в десктоп-решениях.
Эксперименты на сырой платформе могут привести к проблемам.

- Блог компании Timeweb Cloud
- PHP
- C++
- Разработка под Windows
H Делаем консольное php приложение в черновиках Recovery Mode Tutorial
Недавно на одном из форумов наткнулся на запись о том, что php — это язык для веб-разработки. Да, большинство сайтов (включая даже википедию) написаны именно на php, но этот язык можно использовать не только для веба.
Статья рассчитана на новичков php, на людей которые не знают, как писать консольные программы, поэтому, если вы гуру-программист, можете листать дальше. Под катом рассматривается пример простой консольной программы на php.
Что бы в unix системах в командной строке выполнить php скрипт, его нужно запустить следующей командой
php app.php [param] [param]
С помощью параметров можно передавать нашему скрипту какие либо данные. Они будут хранится в массиве
$argv
Нулевой элемент этого массива — название php файла.
В качестве примера консольной php программы мы рассмотрим хранилище контактов, которое будет выдавать нам следующую информацию:
============================================== Идентификатор: 1 ФИО: Вася Телефон: 8-920-000-00-00 Email: vasya@mail.ru Комментарий: Знакомый из садика ============================================== ============================================== Идентификатор: 2 ФИО: Коля Телефон: 234543 Email: kolya@ya.ru Комментарий: Заметка о контакте ==============================================
Данные будут хранится в MySQL базе данных. Создадим таблицу
CREATE TABLE `contacts` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` text, `email` text, `tel` text, `comment` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
Для работы с базой данных мы будем использовать класс SafeMySQL (GitHub)
Подключаем класс базы данных и описываем класс настроек
include "safemysql.class.php"; // Настройка программы class set < public $bdset = array( 'host' =>'localhost', // Хост сервера базы данных 'user' => 'root', // Логин для доступа к БД 'pass' => 'root',// Пароль для доступа к БД 'db' => 'cont', // Название базы данных ); public $q = "hello"; >
Не забываем поменять данные на свои.
В зависимости от параметра будем выполнять разные функции
system("clear"); //Очищаем экран перед выводом // Шапка программы (там написано contacts) echo " _ _\n"; echo " ___ ___ ___| |_ ___ ___| |_ ___\n"; echo "| _| . | | _| .'| _| _|_ -|\n"; echo "|___|___|_|_|_| |__,|___|_| |___|\n"; echo "Надёжное хранение ваших контактов\n"; // Выполнение действия, в зависимости от параметра switch ($argv[1]) < case 'view': $do = new action(); $do->view(); break; case 'edit': if (isset($argv[2])) < $do = new action(); $do ->edit($argv[2]); > echo "Ошибка параметра, воспользуйтесь справкой \n"; break; case 'add': $do = new action(); $do ->add(); break; case 'delete': if (isset($argv[2])) < $do = new action(); $do ->delete($argv[2]); > echo "Ошибка параметра, воспользуйтесь справкой \n"; break; case 'help': $do = new action(); $do ->help(); break; default: // Если параметр нам не подходит, или его нет, говорим об ошибке echo "Ошибка параметра, воспользуйтесь справкой \n"; break; > echo "\n"; // Делаем ещё один перевод строки, что бы отделить вывод программы
Осталось описать класс action и все его функции.
Функция просмотра
public function view()< // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); $data = $db->getAll("SELECT * FROM contacts"); foreach ($data as $key) < echo "==============================================\n"; echo "Идентификатор: ".$key["id"]."\n"; echo "ФИО: ".$key["name"]."\n"; echo "Телефон: ".$key["tel"]."\n"; echo "Email: ".$key["email"]."\n"; echo "Комментарий: ".$key["comment"]."\n"; echo "==============================================\n"; >>
Функция добавления
public function add() < // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); // Задаём вопросы и записываем данные в переменные echo "Введите имя контакта: \n"; $addname = readline(); echo "Введите телефон контакта: \n"; $addtel = readline(); echo "Введите email контакта: \n"; $addemail = readline(); echo "Введите комментарий к контакту: \n"; $addcomment = readline(); // Добавляем в базу $db->query("INSERT INTO contacts (name, tel, email, comment) VALUES (?s, ?s, ?s, ?s)", $addname, $addtel, $addemail, $addcomment); echo "Запись добавлена \n"; // Показываем список всех контактов $do = new action(); $do->view(); >
Функция редактирования
public function edit($id) < // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); // Задаём вопросы и записываем данные в переменные echo "Введите имя контакта: \n"; $editname = readline(); echo "Введите телефон контакта: \n"; $edittel = readline(); echo "Введите email контакта: \n"; $editemail = readline(); echo "Введите комментарий к контакту: \n"; $editcomment = readline(); // Обновляем запись $db->query("UPDATE contacts SET name = ?s, tel = ?s, email = ?s, comment = ?s WHERE $editname, $edittel, $editemail, $editcomment, $id); echo "Запись обновлена \n"; // Показываем список всех контактов $do = new action(); $do->view(); >
Функция удаления
public function delete($id)< echo "Вы действительно хотите удалить контакт #".$id."? (y/n) \n"; $del = readline(); if ($del == "y") < // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); // Удаляем контакт из базы $db->query("DELETE FROM contacts WHERE $id); echo "Запись удалена \n"; // Показываем список всех контактов $do = new action(); $do->view(); > else echo "Ну нет, так нет. \n"; >
Функция показа справки
public function help()
Я старался написать понятный даже новичку код, комментировал почти каждую строчку и использовал ООП.
Программа не претендует на звание правильной и эффективной, но если кому понадобится — вот
Код полного файла
'localhost', // Хост сервера базы данных 'user' => 'root', // Логин для доступа к БД 'pass' => 'root',// Пароль для доступа к БД 'db' => 'cont', // Название базы данных ); public $q = "hello"; > // Класс обработки действий class action < // Функция показа контакта (контактов) public function view()< // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); $data = $db->getAll("SELECT * FROM contacts"); foreach ($data as $key) < echo "==============================================\n"; echo "Идентификатор: ".$key["id"]."\n"; echo "ФИО: ".$key["name"]."\n"; echo "Телефон: ".$key["tel"]."\n"; echo "Email: ".$key["email"]."\n"; echo "Комментарий: ".$key["comment"]."\n"; echo "==============================================\n"; >> // Функция добавления контакта public function add() < // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); // Задаём вопросы и записываем данные в переменные echo "Введите имя контакта: \n"; $addname = readline(); echo "Введите телефон контакта: \n"; $addtel = readline(); echo "Введите email контакта: \n"; $addemail = readline(); echo "Введите комментарий к контакту: \n"; $addcomment = readline(); // Добавляем в базу $db->query("INSERT INTO contacts (name, tel, email, comment) VALUES (?s, ?s, ?s, ?s)", $addname, $addtel, $addemail, $addcomment); echo "Запись добавлена \n"; // Показываем список всех контактов $do = new action(); $do->view(); > // Функция удаления контакта public function delete($id)< echo "Вы действительно хотите удалить контакт #".$id."? (y/n) \n"; $del = readline(); if ($del == "y") < // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); // Удаляем контакт из базы $db->query("DELETE FROM contacts WHERE $id); echo "Запись удалена \n"; // Показываем список всех контактов $do = new action(); $do->view(); > else echo "Ну нет, так нет. \n"; > // Функция редактирования контакта public function edit($id) < // Создаём объект класса настроек $sets = new set(); // Создаём объект класса базы данных и указываем ему параметры $db = new SafeMySQL($sets->bdset); // Задаём вопросы и записываем данные в переменные echo "Введите имя контакта: \n"; $editname = readline(); echo "Введите телефон контакта: \n"; $edittel = readline(); echo "Введите email контакта: \n"; $editemail = readline(); echo "Введите комментарий к контакту: \n"; $editcomment = readline(); // Обновляем запись $db->query("UPDATE contacts SET name = ?s, tel = ?s, email = ?s, comment = ?s WHERE $editname, $edittel, $editemail, $editcomment, $id); echo "Запись обновлена \n"; // Показываем список всех контактов $do = new action(); $do->view(); > // Функция показа справки public function help() < echo "==============================================\n"; echo "Справка по программе\n"; echo "==============================================\n"; echo "php contacts.php view - вывод всех контактов\n"; echo "php contacts.php view 3 - вывод контакта с определённым идентификатором\n"; echo "php contacts.php add - добавление контакта\n"; echo "php contacts.php delete 3 - удаление контакта с определённым идентификатором\n"; echo "php contacts.php help - показать эту подсказку\n"; >> system("clear"); //Очищаем экран перед выводом // Шапка программы (там написано contacts) echo " _ _\n"; echo " ___ ___ ___| |_ ___ ___| |_ ___\n"; echo "| _| . | | _| .'| _| _|_ -|\n"; echo "|___|___|_|_|_| |__,|___|_| |___|\n"; echo "Надёжное хранение ваших контактов\n"; // Выполнение действия, в зависимости от параметра switch ($argv[1]) < case 'view': $do = new action(); $do->view(); break; case 'edit': if (isset($argv[2])) < $do = new action(); $do ->edit($argv[2]); > echo "Ошибка параметра, воспользуйтесь справкой \n"; break; case 'add': $do = new action(); $do ->add(); break; case 'delete': if (isset($argv[2])) < $do = new action(); $do ->delete($argv[2]); > echo "Ошибка параметра, воспользуйтесь справкой \n"; break; case 'help': $do = new action(); $do ->help(); break; default: // Если параметр нам не подходит, или его нет, говорим об ошибке echo "Ошибка параметра, воспользуйтесь справкой \n"; break; > echo "\n";
Разработка консольных приложений и автоматизация задач на PHP: старый добрый язык как знакомая замена Bash
«Bash? Беляш? Эммм… Что? Как? По-че-му?» — если именно эти слова произносил ваш внутренний голос, когда вы, начитавшись статей в интернете, решили с помощью Bash автоматизировать ту рутинную задачу, то эта статья для вас. И она ещё более для вас, если вы уже знаете PHP — сейчас вы узнаете также то, что PHP отлично подходит не только для разработки сайтов, но и для консольных приложений.
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
Предполагается, что вы используете Linux и знаете PHP не ниже начального уровня:
