Компонент Процесс

Компонент Процесс выполняет команды в подпроцессах.

Установка

Вы можете установить компонент 2 разными способами:

Then, require the vendor/autoload.php file to enable the autoloading mechanism provided by Composer. Otherwise, your application won't be able to find the classes of this Symfony component.

Использование

Класс Process позволяет вам выполнять команду в подпроцессе:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

$process = new Process('ls -lsa');
$process->run();

// выполняется после завершения команды
if (!$process->isSuccessful()) {
    throw new ProcessFailedException($process);
}

echo $process->getOutput();

Этот компонент заботится о тонкой разнице между различными платформами при выполнении этой команды.

Метод getOutput() всегда возвращает все содержимое стандартного вывода команды и содержимое getErrorOutput() вывода ошибки. Как вариант, методы getIncrementalOutput() и getIncrementalErrorOutput() возваращают новый вывод после последнего вызова.

Метод clearOutput() очищает содержание вывода, а clearErrorOutput() - содержание вывода ошибки.

Вы можете также использовать класс Process с концепцией foreach, чтобы получить вывод во время его генерации. По умолчанию, цикл ждёт нового вывода до перехода к следующей итерации:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$process = new Process('ls -lsa');
$process->start();

foreach ($process as $type => $data) {
    if ($process::OUT === $type) {
        echo "\nRead from stdout: ".$data;
    } else { // $process::ERR === $type
        echo "\nRead from stderr: ".$data;
    }
}

Tip

Компонент Процесс внутренне использует PHP итератор, чтобы получить вывод во время его генерации. Итератор демонстрируется через метод getIterator(), чтобы позволить настройку его поведения:

1
2
3
4
5
6
$process = new Process('ls -lsa');
$process->start();
$iterator = $process->getIterator($process::ITER_SKIP_ERR | $process::ITER_KEEP_OUTPUT);
foreach ($iterator as $data) {
    echo $data."\n";
}

New in version 3.2: Метод getIterator() был представлен Symfony 3.2.

Метод mustRun() идентичен методу run(), кроме того, что он будет вызывать ProcessFailedException, если процесс не мог быть выполнен успешно (т.е. процесс завершился ненулевым кодом):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');

try {
    $process->mustRun();

    echo $process->getOutput();
} catch (ProcessFailedException $e) {
    echo $e->getMessage();
}

Получение вывода процесса в настоящем времени

При выполнении долгосрочной команды (вроде синхронизации файлов с удалённым сервером), вы можете предоставить обратную связь с конечным пользователем в реальном времени, передав анонимную функцию методу run():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');
$process->run(function ($type, $buffer) {
    if (Process::ERR === $type) {
        echo 'ERR > '.$buffer;
    } else {
        echo 'OUT > '.$buffer;
    }
});

Асинхронный запуск процессов

Вы можете также начать подпроцесс,а потом запустить его асихнронно, получая вывод и статус вашего основного процесса, когда они вам нужны. Используйте метод start(), чтобы начать асинхронный процсс, метод isRunning(), чтобы проверить, завершён ли процесс, и метод getOutput(), чтобы получить вывод:

1
2
3
4
5
6
7
8
$process = new Process('ls -lsa');
$process->start();

while ($process->isRunning()) {
    // ожидание окончания процесса
}

echo $process->getOutput();

Вы можете также подождать, пока процесс завершится, если вы начали его асинхронно и закончили делать другие дела:

1
2
3
4
5
6
7
8
$process = new Process('ls -lsa');
$process->start();

// ... делать другие вещи

$process->wait();

// ... делать вещи после завершения процесса

Note

Метод wait() - блокирующий, что означает, что ваш код будет остановлен на этой строке до тех пор, пока не будет завершён внешний процесс.

Note

Если Response отправлен до завершения дочернего процесса, то процесс сервера будет убит (в зависимости от вашей ОС). Это означает, что ваша задача будет моментально остановлена. Запуск асинхронного процсса не равняется запуску процесса, переживающего родительский процесс.

Если вы хотите, чтобы ваш процесс пережил цикл запрос / ответ, то вы можете воспользоваться преимуществами события kernel.terminate, и запустить вашу команду асинхронно внутри этого события. Имейте в виду, что kernel.terminate вызывает только, если выиспользуете PHP-FPM.

Caution

Также имейте в виду, что если вы это сделаете, то вышеназванный процесс PHP-МФП не будет доступен для обслуживания любого нового запроса до завершения подпроцесса. Это означает, что вы можете быстро заблокировать ваш МФП-пул, если вы не будете осторожны. Это то, почему обычно намного лучше не делать ничего мудрёного даже после отправки запроса, а вместо этого использвать очередь задач.

wait() берёт один необязательноый аргумент: обратный вызов, который постоянно вызывается во время работы процесса, передавая вывод и его тип:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$process = new Process('ls -lsa');
$process->start();

$process->wait(function ($type, $buffer) {
    if (Process::ERR === $type) {
        echo 'ERR > '.$buffer;
    } else {
        echo 'OUT > '.$buffer;
    }
});

Потоковая передача в стандартный ввод процесса

До начала процесса вы можете указать его стандартный ввод, используя либо метод setInput(), либо 4й аргумент контруктора. Предоставленный ввод может быть строкой, источником потока или траверсабельным объектом:

1
2
3
$process = new Process('cat');
$process->setInput('foobar');
$process->run();

Когда этот ввод будет полностью написан в стандартном вводе подпроцесса, соответствущая труба будет закрыта.

Чтобы написать в стандартный ввод подроцесса во время его работы, компонент предоставляет класс InputStream:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$input = new InputStream();
$input->write('foo');

$process = new Process('cat');
$process->setInput($input);
$process->start();

// ... прочитать вывод процесса или сделать что-то ещё

$input->write('bar');
$input->close();

$process->wait();

// отразит: foobar
echo $process->getOutput();

Метод write() принимает скалярные значения, источники потока или траверсабельные объекты в качестве аргумента. Как показано в примере выше, вам нужно ясно вызвать метод close(), когда вы закончите писать в стандартный ввод подпроцесса.

Остановка процесса

Любой асинхронный процесс можно остановить в любое время методом stop(). Этот метод берёт два аргумента: превышение лимита времени и сигнал. Когда лимит времени достигнут, сигнал отправляется текущему процессе. Сигнал по умолчанию, который отправляется процессу - SIGKILL. Пожалуйста, прочтите документацию сигнала ниже, чтобы узнать больше об обработке сигнала в компоненте Процесс:

1
2
3
4
5
6
$process = new Process('ls -lsa');
$process->start();

// ... сделать что-то другое

$process->stop(3, SIGINT);

Выполнение PHP кода в изоляции

Если вы хотите выполнить некоторый PHP код в изооляции, используйте вместо этого PhpProcess:

1
2
3
4
5
6
7
use Symfony\Component\Process\PhpProcess;

$process = new PhpProcess(<<<EOF
    <?php echo 'Hello World'; ?>
EOF
);
$process->run();

Чтобы ваш код лучше работал на всех платформах, вам может захотеться использовать вместо этого класс ProcessBuilder:

1
2
3
4
use Symfony\Component\Process\ProcessBuilder;

$builder = new ProcessBuilder(array('ls', '-lsa'));
$builder->getProcess()->run();

В случае, если вы строите бинарный драйвер, то вы можете использовать метод setPrefix(), чтобы добавить префикс ко всем сгенерированным командам процесса.

Следующий пример сгенерирует две команды процесса для бинарного адаптера tar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Symfony\Component\Process\ProcessBuilder;

$builder = new ProcessBuilder();
$builder->setPrefix('/usr/bin/tar');

// '/usr/bin/tar' '--list' '--file=archive.tar.gz'
echo $builder
    ->setArguments(array('--list', '--file=archive.tar.gz'))
    ->getProcess()
    ->getCommandLine();

// '/usr/bin/tar' '-xzf' 'archive.tar.gz'
echo $builder
    ->setArguments(array('-xzf', 'archive.tar.gz'))
    ->getProcess()
    ->getCommandLine();

Превышение лимита времени процесса

Вы можете ограничивать количество времени, которое занимает выполнение процесса, установив истечение срока ожидания (в секундах):

1
2
3
4
5
use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');
$process->setTimeout(3600);
$process->run();

Если лимит времени достигнут, то вызывается RuntimeException.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$process->setTimeout(3600);
$process->start();

while ($condition) {
    // ...

    // проверить, достигнут ли лимит времени
    $process->checkTimeout();

    usleep(200000);
}

Превышение лимита времени бездействия процесса

В отличие от превышения лимита времени в предыдущем параграфе, лимит бездействия процесса рассматривает только то время, которое прошло с момента последнего вывода, произведённого процессом:

1
2
3
4
5
6
use Symfony\Component\Process\Process;

$process = new Process('something-with-variable-runtime');
$process->setTimeout(3600);
$process->setIdleTimeout(60);
$process->run();

Вышеописанном случае, процесс считается законченным, когда либо общее количество времени работы превышает 3600 секунд, либо процесс не производит никакого вывода в течение 60 секунд.

Сигналы процесса

При асинхронным запуске программы, вы можете отправлять сигналы с помощью метода signal():

1
2
3
4
5
6
7
use Symfony\Component\Process\Process;

$process = new Process('find / -name "rabbit"');
$process->start();

// отправит SIGKILL процессу
$process->signal(SIGKILL);

Caution

В связи с некоторыми ограничениями в PHP, если вы используете сигналы с компонентом Процесс, то вам может понадобиться добавлять к вашим командам префикс exec. Пожалуйста, прочтите Symfony Issue#5759 и PHP Bug#39992, чтобы понять, почему так происходит.

POSIX сигналы недоступны на платформах Windows, пожалуйста, обратитесь к документации PHP для списка доступных сигналов.

Pid процесса

Вы можете получить доступ к pid текущего процесса с помощью метода getPid().

1
2
3
4
5
6
use Symfony\Component\Process\Process;

$process = new Process('/usr/bin/php worker.php');
$process->start();

$pid = $process->getPid();

Caution

В связи с некоторыми ограничениями в PHP, если вы используете сигналы с компонентом Процесс, то вам может понадобиться добавлять к вашим командам префикс exec. Пожалуйста, прочтите Symfony Issue#5759, чтобы понять, почему так происходит.

Отключение вывода

Так как стандартный вывод и вывод ошибок всегда извлекаются из основоположного процесса, может быть удобным отключить вывод в некоторых случаях для сохранения памяти. Используйте disableOutput() и enableOutput(), чтобы переключить эту функцию:

1
2
3
4
5
use Symfony\Component\Process\Process;

$process = new Process('/usr/bin/php worker.php');
$process->disableOutput();
$process->run();

Caution

Вы не можете включать или отключать вывод во время выполнения процесса.

Если вы отключите вывод, вы не сможете получить доступ к getOutput(), getIncrementalOutput(), getErrorOutput(), getIncrementalErrorOutput() или setIdleTimeout().

Однако, возможно передать обратный вызов методам start, run или mustRun, чтобы обработать процесс вывода в потоке.

New in version 3.1: Возможность передавать обратный вызов этим методам при отключённом выводе была добавлена в Symfony 3.1.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.