O documento discute criação de scripts CLI em PHP, abordando tópicos como:
1) Lidar com argumentos, streams e roteamento de comandos;
2) Gerenciar início e término de robôs usando pcntl ou pthreads;
3) Criar códigos reutilizáveis em diferentes ambientes.
1. Roteiro
Scripts CLI
Lidando com argumentos, streams e roteamento de comandos1.
Robôs
Gerenciando início e término de robôs, usando pcntl ou pthreads2.
Reutilização
Criando códigos que rodem em mais de um ambiente3.
2. É a melhor solução para o meu problema?
Primeiro, considere se você está usando as melhores ferramentas para cada tipo de trabalho
4. Argumentos
As variáveis $argc e $argv guardam
informações sobre os argumentos do script
$argv é um array com os argumentos passados, sendo
que o índice 0 contém o nome do script invocado
if ($argc == 1) {
echo "Uso: php {$argv[0]} <comando>" . PHP_EOL;
exit(2);
}
switch ($argv[1]) {
case 'run':
// ...
break;
default:
// ...
break;
}
5. getopt()
array getopt( string $options [, array $longopts [, int &$optind ]] )
As opções podem ser:
Caracteres individuais: não aceitam valores
Caracteres seguidos por um dois-pontos: valor obrigatório
Caracteres seguidos por dois dois-pontos: valor opcional
$options = getopt(
'ab:c::',
['verbose', 'user:', 'password::']
);
// php getopt.php -a -b valor -c1 --verbose
// --user root --password
array(6) {
["a"] => bool(false)
["b"] => string(5) "valor"
["c"] => string(1) "1"
["verbose"] => bool(false)
["user"] => string(4) "root"
["password"] => bool(false)
}
6. I/O Streams
Uso das funções fopen , fgets , fputs ,
stream_get_line e diversas outras
Disponibilização das constantes STDIN , STDOUT e
STDERR
// Leitura do STDIN
echo "Qual seu nome? ";
$line = trim(fgets(STDIN));
echo "Bem-vindo, {$line}." . PHP_EOL;
// Saída para STDERR
fputs(STDERR, 'Erro no sistema');
7. Streams
Uso de funções como stream_context_create , stream_copy_to_stream ,
stream_filter_append , entre outras
Referências
Manual do PHP: php.net/stream
Palestra do Alexandre Gaigalas na PHP Experience em 2016
// Exemplo simples do poder das streams
stream_filter_append(STDERR, 'string.toupper');
stream_copy_to_stream(STDIN, STDERR);
8. Roteamento de comandos
Organize seu script para que ele seja
modular
Crie estrutura de controllers para facilitar a manutenção
if ($argc != 3) {
echo "Uso: {$argv[0]} " . PHP_EOL;
exit(2);
}
include 'vendor/autoload.php';
array_shift($argv); // nome do script
$module = array_shift($argv); // ou $options['module
$class = "MyCliControllers{$module}";
if (!class_exists($class)) {
throw new DomainException("Módulo {$module} não
}
$command = array_shift($argv); // ou $options['comma
if (!method_exists($class, $command)) {
throw new DomainException("Comando {$command} nã
}
(new $class())->{$command}($argv); // ou $options
10. Bibliotecas
ZendConsole
use ZendMvcControllerAbstractActionController;
class IndexController extends AbstractActionController
{
public function resetpasswordAction()
{
$request = $this->getRequest();
if (! $request instanceof ZendConsoleRequest) {
throw new RuntimeException('Requisição inválida');
}
$email = $request->getParam('email');
/* ... */
if ($request->getParam('verbose') || $request->getParam('v')) {
/* ... */
}
return 'Senha enviada com sucesso';
}
}
Adaptado da documentação o cial
11. Bibliotecas
Symfony Console
// application.php
require __DIR__.'/vendor/autoload.php';
$application = new SymfonyComponentConsoleApplication();
$application->add(new AppCommandCreateUserCommand());
$application->run();
Adaptado da documentação o cial
12. Bibliotecas
Symfony Console
Adaptado da documentação o cial
// src/Command/CreateUserCommand.php
namespace AppCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
class CreateUserCommand extends Command
{
protected function configure()
{
/* ... */
}
protected function execute(InputInterface $input, OutputInterface $outpu
{
/* ... */
}
}
13. Bibliotecas
Symfony Console
protected function configure()
{
$this
// Nome do comando, executado pelo bin/console
->setName('app:create-user')
// Descrição do comando ao executar bin/console list
->setDescription('Cria um novo usuário.')
// Descrição completa ao invocar o --help
->setHelp('Esse comando permite você criar um novo usuário...')
// Argumento obrigatório
->addArgument(
'username',
SymfonyComponentConsoleInputInputArgument::REQUIRED,
'Nome de usuário'
);
}
Adaptado da documentação o cial
14. Bibliotecas
Symfony Console
Adaptado da documentação o cial
protected function execute(InputInterface $input, OutputInterface $output)
{
// Imprime várias linhas (automaticamente adicionando n)
$output->writeln([
'Criando usuário',
'===============',
'',
]);
// Imprime sem n
$output->write('Nome de usuário: ');
$output->write($input->getArgument('username'));
}
17. Gerenciador de robôs
Para iniciar, terminar e acompanhar a
execução
Ou você pode ter daemons "auto-executáveis" - por
exemplo, diretamente via cron
interface DaemonManagerInterface
{
// Inicia todos os daemons
public function start();
// Inicia um daemon específico
public function startDaemon($class);
// Para todos os daemons
public function stop();
// Para um daemon específico
public function stopDaemon($class);
// Lista os daemons que devem ser iniciados
protected function getActive();
// Monitora o status de cada daemon
protected function watchStatus();
}
18. pcntl
Extensão Process Control
Manual do PHP: php.net/pcntl
pcntl_fork(); // Faz um fork do processo atual
pcntl_signal_dispatch(); // Invoca os handlers para
pcntl_signal(); // Instala um handler
pcntl_sigprocmask(); // Bloqueia/desbloqueia sinais
pcntl_sigtimedwait(); // Espera por um sinal, com ti
pcntl_sigwaitinfo(); // Espera por um sinal
pcntl_wait(); // Aguarda/retorna o status de um filh
pcntl_waitpid(); // Aguarda/retorna o status de um f
19. Fluxo simples de execução
Utilizando pcntl_fork()
Fazendo fork do processo atual
public function startDaemon($class)
{
$pid = pcntl_fork();
if ($pid == -1) {
throw new RuntimeException("Houve um erro no
}
if ($pid) {
// Processo pai
return $pid;
}
// Processo filho (robô)
$daemon = new $class();
$daemon->run();
die();
}
20. Fluxo simples de execução
Utilizando pcntl_waitpid()
int pcntl_waitpid(
int $pid ,
int &$status
[, int $options = 0
[, array &$rusage ]]
)
protected function watchStatus() {
$count = count($this->pool);
while ($count > 0) {
foreach ($this->pool as $index => $pid) {
// Retorna o PID do filho se ele estiver
if (pcntl_waitpid($pid, $status, WNOHANG
unset($this->pool[$index]);
echo "Filho {$index} morreu..." . PH
--$count;
}
}
sleep(1);
}
}
21. Fluxo simples de execução
Utilizando sinais
Para lidar com eventos externos
pcntl_signal(SIGINT, [$this, 'signalHandler']);
// Para SIGTERM, SIGINT, SIGHUP, SIGUSR1, etc
public function signalHandler($signal) {
switch ($signal) {
case SIGTERM:
case SIGINT:
case SIGHUP:
echo 'Terminando graciosamente...';
die();
case SIGUSR1:
echo "Capturado sinal SIGUSR1 " . PHP_EO
break;
/* ... */
}
}
23. pthreads
Biblioteca que implementa o padrão POSIX
Threads
A V3 foi totalmente reescrita para uso no PHP 7.2
Necessita do PHP compilado com ZTS (Zend Thread
Safety)
Classes disponíveis
Threaded Thread
Worker Collectable
Modifiers Pool
Mutex Cond
Volatile
24. Classe Thread
Ela deve implementar o método run()
class Task extends Thread
{
private $threadId;
public function __construct($threadId)
{
$this->threadId = (int) $threadId;
}
public function run()
{
echo "Iniciando a thread {$this->threadId}"
sleep(rand(1, 5));
echo "Finalizando a thread {$this->threadId}
}
}
25. Classe Worker
Agrupa tarefas para serem executadas
sequencialmente
$worker = new Worker();
$worker->start();
// Empilha 9 tarefas no worker
for ($i = 0; $i < 8; ++$i) {
$worker->stack(new Task($i));
}
// Aguarda o término das tarefas
while ($worker->collect());
// Desliga o worker
$worker->shutdown();
26. Classe Pool
Agrupa Worker s para serem executados
concorrentemente
// Cria 3 workers que serão executados simultaneamen
$pool = new Pool(3);
// Submete 9 tarefas para o pool
for ($i = 0; $i < 8; ++$i) {
$pool->submit(new Task($i));
}
// Aguarda o término das tarefas
while ($pool->collect());
// Desliga todos os workers
$pool->shutdown();
27. Pontos de atenção
Tenha cuidado ao realizar operações atômicas (métodos
synchronized , notify e wait )
Nem toda tarefa ganha performance ao ser dividida em
threads
Não se esqueça de aguardar as threads terminarem
( join )
Referências
Tutorial para instalação
Slides sobre ZTS e threads no PHP (@jpauli)
Tutorial sobre pthreads (SitePoint)
Tutorial sobre pthreads v2 x v3 (SitePoint)
Manual do PHP: php.net/pthreads
30. Boas práticas
Deixe seu código limpo e organizado para facilitar o entendimento
PHP CodeSniffer
Detecta e corrige violações de acordo com um padrão de
regras (por exemplo, PSR-2)
PHP Mess Detector
Analisador diversos aspectos, como variáveis
desnecessárias, códigos muito complexos, etc
SOLID
Cinco princípios para deixar softwares mais entendíveis,
exíveis e de fácil manutenção
Object Calisthenics
Conjunto de 9 regras para auxiliar na manutenção,
legibilidade e facilidade de teste
31. Con gurações
Como armazenar parâmetros, credenciais e
outras opções?
12 Factor Apps: III. Con g - Store con g in the environment
Arquivos PHP (com simples arrays, por exemplo)
// index.php
$config = require 'config.php';
$dbh = new PDO(
$config['db']['dsn'],
$config['db']['user'],
$config['db']['pass']
);
// config.php
return [
'db' => [
'dsn' => 'mysql:dbname=mydb;host=localhost'
'user' => 'user',
'pass' => 'my@p4ssw0rd'
]
];
32. Injeção de dependências
Aumentando a reusabilidade de seus
códigos
Interfaces
Dependa de Interfaces ao invés de classes (mesmo que
abstratas)
Containers
PSR-11 - Containers: https://github.com/php- g/ g-
standards/blob/master/accepted/PSR-11-container.md
The PHP League Container:
https://github.com/thephpleague/container
class MyClass
{
protected $logger;
public function __construct(PsrLogLoggerInter
{
$this->logger = $logger;
}
public function run()
{
$this->logger->notice('...');
}
}
// PsrContainerContainerInterface
$container = require 'container.php';
$myClass = new MyClass(
$container->get(PsrLogLoggerInterface::class)
);