Command Line Interface в PHP
До этого момента мы с Вами знали, что PHP работает на сервере. Клиент обращается к серверу по протоколу HTTP с каким-либо запросом, запрос на сервере обрабатывается и формируется ответ. После этого клиенту снова по протоколу HTTP в ответе отдаётся сформированный ответ. Однако, если взять какой-нибудь более-менее продвинутый сайт, то мы увидим, что есть задачи, которые не решаются стандартным клиент-серверным путем. Например: поздравлять пользователей с днём рождения и дарить им скидку на какой-нибудь продукт. Для того, чтобы это сделать, нам придется обновлять раз в день php-скрипт в браузере, чтобы он выбирал пользователей, у которых сегодня ДР, затем создавал для них скидки, и отправлял им сообщения по почте. Согласитесь, неудобно это делать вручную и в браузере. Для таких случаев в PHP предусмотрен Command Line Interface (CLI) – интерфейс командной строки.
CLI позволяет запускать программы на PHP не через привычную нам клиент-серверную архитектуру, а как простые программы в командной строке. Давайте создадим простейший скрипт, чтобы показать, как это работает. Создаём новую папку bin в корне проекта, а в ней файл – cli.php.
Пишем простейший код:
bin/cli.php
<?php
echo 2 + 2;
А теперь запускаем консоль из OpenServer:
Переходим в папку с нашим проектом, выполнив:
cd domains\myproject.loc
И пишем следующую команду:
php bin/cli.php
Написали простейшее консольное приложение! Уже неплохо. Но что если мы захотим сложить 2 числа, которые нужно передать скрипту? Как Вы понимаете, сделать это с помощью GET- или POST- запросов уже не получится. Так как же быть?
Аргументы консольного приложения
На помощь нам приходят аргументы, которые мы можем передать в скрипт, указав их после имени скрипта в командной строке. Вот так:
А для того, чтобы получить к ним доступ из php-скрипта используется магическая переменная $argv. Она представляет собой массив, в котором нулевой элемент – это путь до скрипта, а все последующие – это его аргументы в консоли.
bin/cli.php
<?php
var_dump($argv);
Давайте теперь запустим наш скрипт с параметрами:
Как видим, наши аргументы попали в этот массив. Давайте напишем простейший скрипт, который будет складывать все переданные ему аргументы.
<?php
unset($argv[0]);
$sum = 0;
foreach ($argv as $item) {
$sum += $item;
}
echo $sum;
Запустим его, и убедимся, что все работает:
И он действительно работает: 3 + 4 + 5 = 12.
А что если мы хотим передавать аргументы с именами? Вроде такого:
И затем в коде получать их в коде по их именам? Для этого нам следует написать простейший парсер, который будет находить вот такие именованные параметры и их значения. Пишем.
<?php
unset($argv[0]);
$params = [];
foreach ($argv as $argument) {
preg_match('/^-(.+)=(.+)$/', $argument, $matches);
if (!empty($matches)) {
$paramName = $matches[1];
$paramValue = $matches[2];
$params[$paramName] = $paramValue;
}
}
var_dump($params);
Отлично, теперь мы можем обращаться к элементам массива params, чтобы выяснить, были ли нам переданы какие-то аргументы или нет.
CLI и ООП
Мы с вами изучили некоторые основы работы с CLI. Давайте теперь перенесем эти знания на объектно-ориентированный подход и научимся работать через интерфейс командной строки с объектами.
Для этого нам понадобится создать отдельную директорию под «команды». Команды – так мы будем называть наши специальные классы, которые будут выполнять какой-то код через запуск из командной строки. Создаем новую директорию: src/MyProject/Cli.
И теперь создадим наш первый класс, который будет заниматься тем, что считает сумму переданных в него аргументов: -a и -b.
src/MyProject/Cli/Summator.php
<?php
namespace MyProject\Cli;
use MyProject\Exceptions\CliException;
class Summator
{
/** @var array */
private $params;
public function __construct(array $params)
{
$this->params = $params;
$this->checkParams();
}
public function execute()
{
echo $this->getParam('a') + $this->getParam('b');
}
private function checkParams()
{
$this->ensureParamExists('a');
$this->ensureParamExists('b');
}
private function getParam(string $paramName)
{
return $this->params[$paramName] ?? null;
}
private function ensureParamExists(string $paramName)
{
if (!isset($this->params[$paramName])) {
throw new CliException('Param with name "' . $paramName . '" is not set!');
}
}
}
В конструкторе класса мы принимаем список параметров, сохраняем их, а затем вызываем метод checkParams(), который проверяет наличие обязательных параметров для этого скрипта. В нём просто поочередно вызывается метод для проверки в массиве нужных ключей. Если их нет – метод кинет исключение. И, наконец, есть метод execute(), который содержит бизнес-логику. В нем используется метод getParam(), который вернет параметр (при его наличии), либо вернет null (при его отсутствии).
И также создаём исключение, специально для ошибок, возникающих при работе с CLI.
src/MyProject/Exceptions/CliException.php
<?php
namespace MyProject\Exceptions;
class CliException extends \Exception
{
}
Теперь давайте снова вернемся в нашу точку входа для консольных приложений cli.php. Этот файл можно назвать фронт-контроллером для консольных команд, он как index.php в случае с клиент-серверным подходом будет создавать другие объекты и запускать весь процесс.
Дополним этот код так, чтобы он создавал экземпляр нужного класса и передавал ему аргументы.
bin/cli.php
<?php
try {
unset($argv[0]);
// Регистрируем функцию автозагрузки
spl_autoload_register(function (string $className) {
require_once __DIR__ . '/../src/' . $className . '.php';
});
// Составляем полное имя класса, добавив нэймспейс
$className = '\\MyProject\\Cli\\' . array_shift($argv);
if (!class_exists($className)) {
throw new \MyProject\Exceptions\CliException('Class "' . $className . '" not found');
}
// Подготавливаем список аргументов
$params = [];
foreach ($argv as $argument) {
preg_match('/^-(.+)=(.+)$/', $argument, $matches);
if (!empty($matches)) {
$paramName = $matches[1];
$paramValue = $matches[2];
$params[$paramName] = $paramValue;
}
}
// Создаём экземпляр класса, передав параметры и вызываем метод execute()
$class = new $className($params);
$class->execute();
} catch (\MyProject\Exceptions\CliException $e) {
echo 'Error: ' . $e->getMessage();
}
Теперь мы можем запустить наш скрипт с помощью вот такой команды:
Если мы захотим создать еще один класс, в котором мы будем вычитать из аргумента a аргумент b, то нам нужно будет продублировать довольно большой объем кода. Но ведь если присмотреться – большую часть кода из класса Summator можно вынести в отдельный класс и использовать его повторно.
Давайте создадим абстрактный класс, который будет заниматься тем, что будет сохранять переданные в него параметры и запускать метод для их проверки.
src/MyProject/Cli/AbstractCommand.php
<?php
namespace MyProject\Cli;
use MyProject\Exceptions\CliException;
abstract class AbstractCommand
{
/** @var array */
private $params;
public function __construct(array $params)
{
$this->params = $params;
$this->checkParams();
}
abstract public function execute();
abstract protected function checkParams();
protected function getParam(string $paramName)
{
return $this->params[$paramName] ?? null;
}
protected function ensureParamExists(string $paramName)
{
if (!isset($this->params[$paramName])) {
throw new CliException('Param with name "' . $paramName . '" is not set!');
}
}
}
Теперь нам в классе Summator достаточно отнаследоваться от этого класса и он значительно упростится:
src/MyProject/Cli/Summator.php
<?php
namespace MyProject\Cli;
class Summator extends AbstractCommand
{
protected function checkParams()
{
$this->ensureParamExists('a');
$this->ensureParamExists('b');
}
public function execute()
{
echo $this->getParam('a') + $this->getParam('b');
}
}
Запустим скрипт снова и убедимся, что все успешно отработало:
Давайте создадим по аналогии скрипт, который будет вычитать из аргумента x аргумент y.
src/MyProject/Cli/Minusator.php
<?php
namespace MyProject\Cli;
class Minusator extends AbstractCommand
{
protected function checkParams()
{
$this->ensureParamExists('x');
$this->ensureParamExists('y');
}
public function execute()
{
echo $this->getParam('x') - $this->getParam('y');
}
}
А теперь давайте попробуем не указать один из аргументов – получим ошибку.
Вот таким вот нехитрым образом мы с вами научились создавать простейшие программы для запуска в консоли на PHP. А в следующем уроке мы с вами научимся запускать эти команды по расписанию.
Комментарии