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.
É a melhor solução para o meu problema?
Primeiro, considere se você está usando as melhores ferramentas para cada tipo de trabalho
Scripts CLI
Criando utilitários para a linha de comando
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;
}
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)
}
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');
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);
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
Bibliotecas
ZendConsole
Retirado da documentação o cial
// config/autoload/*.php
return [
'console' => [
'router' => [
'routes' => [
'user-reset-password' => [
'options' => [
'route' => 'user resetpassword [--verbose|-v] <em
'defaults' => [
'controller' => ApplicationControllerIndexCont
'action' => 'resetpassword'
]
]
]
]
]
]
];
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
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
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
{
/* ... */
}
}
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
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'));
}
Bibliotecas
Outras opções
Laravel Zero CLImate
Aura.Cli CLIFramework
Robôs
Usando o PHP para criar daemons
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();
}
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
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();
}
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);
}
}
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;
/* ... */
}
}
Demonstração
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
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}
}
}
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();
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();
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
Demonstração
Reutilização
Criando códigos que rodem em mais de um ambiente
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
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'
]
];
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)
);
Obrigado!
GitHub
@vcampitelli
Twitter
@vcampitelli
Slides
viniciuscampitelli.com/slides/php-fora-da-web
PHP fora da Web
Utilizando nossa linguagem preferida ♥ para criar scripts CLI e robôs
Quem sou eu?
Vinícius Campitelli
@vcampitelli
@MediaPost e MT4 Tecnologia
Curseduca

PHP fora da Web

  • 1.
    Roteiro Scripts CLI Lidando comargumentos, 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 melhorsolução para o meu problema? Primeiro, considere se você está usando as melhores ferramentas para cada tipo de trabalho
  • 3.
    Scripts CLI Criando utilitáriospara a linha de comando
  • 4.
    Argumentos As variáveis $argce $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 dasfunçõ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çõescomo 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 Organizeseu 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
  • 9.
    Bibliotecas ZendConsole Retirado da documentaçãoo cial // config/autoload/*.php return [ 'console' => [ 'router' => [ 'routes' => [ 'user-reset-password' => [ 'options' => [ 'route' => 'user resetpassword [--verbose|-v] <em 'defaults' => [ 'controller' => ApplicationControllerIndexCont 'action' => 'resetpassword' ] ] ] ] ] ] ];
  • 10.
    Bibliotecas ZendConsole use ZendMvcControllerAbstractActionController; class IndexControllerextends 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 dadocumentaçã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 functionconfigure() { $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 dadocumentaçã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')); }
  • 15.
    Bibliotecas Outras opções Laravel ZeroCLImate Aura.Cli CLIFramework
  • 16.
    Robôs Usando o PHPpara criar daemons
  • 17.
    Gerenciador de robôs Parainiciar, 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 Manualdo 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 deexecuçã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 deexecuçã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 deexecuçã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; /* ... */ } }
  • 22.
  • 23.
    pthreads Biblioteca que implementao 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 deveimplementar 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 tarefaspara 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 Workers 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 Tenhacuidado 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
  • 28.
  • 29.
    Reutilização Criando códigos querodem em mais de um ambiente
  • 30.
    Boas práticas Deixe seucó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 armazenarparâ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 Aumentandoa 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) );
  • 33.
  • 34.
    PHP fora daWeb Utilizando nossa linguagem preferida ♥ para criar scripts CLI e robôs
  • 35.
    Quem sou eu? ViníciusCampitelli @vcampitelli @MediaPost e MT4 Tecnologia Curseduca