Slide 1

Slide 1 text

Roteiro Scripts CLI Lidando com argumentos, streams e roteamento de comandos 1. Robôs Gerenciando início e término de robôs, usando pcntl ou pthreads 2. Reutilização Criando códigos que rodem em mais de um ambiente 3.

Slide 2

Slide 2 text

É a melhor solução para o meu problema? Primeiro, considere se você está usando as melhores ferramentas para cada tipo de trabalho

Slide 3

Slide 3 text

Scripts CLI Criando utilitários para a linha de comando

Slide 4

Slide 4 text

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]} " . PHP_EOL; exit(2); } switch ($argv[1]) { case 'run': // ... break; default: // ... break; }

Slide 5

Slide 5 text

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) }

Slide 6

Slide 6 text

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');

Slide 7

Slide 7 text

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);

Slide 8

Slide 8 text

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 = "MyCli\Controllers\\{$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

Slide 9

Slide 9 text

Bibliotecas Zend\Console Retirado da documentação o cial // config/autoload/*.php return [ 'console' => [ 'router' => [ 'routes' => [ 'user-reset-password' => [ 'options' => [ 'route' => 'user resetpassword [--verbose|-v] [ 'controller' => Application\Controller\IndexCont 'action' => 'resetpassword' ] ] ] ] ] ] ];

Slide 10

Slide 10 text

Bibliotecas Zend\Console use Zend\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function resetpasswordAction() { $request = $this->getRequest(); if (! $request instanceof \Zend\Console\Request) { 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

Slide 11

Slide 11 text

Bibliotecas Symfony Console // application.php require __DIR__.'/vendor/autoload.php'; $application = new Symfony\Component\Console\Application(); $application->add(new App\Command\CreateUserCommand()); $application->run(); Adaptado da documentação o cial

Slide 12

Slide 12 text

Bibliotecas Symfony Console Adaptado da documentação o cial // src/Command/CreateUserCommand.php namespace App\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class CreateUserCommand extends Command { protected function configure() { /* ... */ } protected function execute(InputInterface $input, OutputInterface $outpu { /* ... */ } }

Slide 13

Slide 13 text

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', \Symfony\Component\Console\Input\InputArgument::REQUIRED, 'Nome de usuário' ); } Adaptado da documentação o cial

Slide 14

Slide 14 text

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')); }

Slide 15

Slide 15 text

Bibliotecas Outras opções Laravel Zero CLImate Aura.Cli CLIFramework

Slide 16

Slide 16 text

Robôs Usando o PHP para criar daemons

Slide 17

Slide 17 text

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(); }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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(); }

Slide 20

Slide 20 text

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); } }

Slide 21

Slide 21 text

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; /* ... */ } }

Slide 22

Slide 22 text

Demonstração

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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} } }

Slide 25

Slide 25 text

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();

Slide 26

Slide 26 text

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();

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Demonstração

Slide 29

Slide 29 text

Reutilização Criando códigos que rodem em mais de um ambiente

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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' ] ];

Slide 32

Slide 32 text

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(\Psr\Log\LoggerInter { $this->logger = $logger; } public function run() { $this->logger->notice('...'); } } // Psr\Container\ContainerInterface $container = require 'container.php'; $myClass = new MyClass( $container->get(\Psr\Log\LoggerInterface::class) );

Slide 33

Slide 33 text

Obrigado! GitHub @vcampitelli Twitter @vcampitelli Slides viniciuscampitelli.com/slides/php-fora-da-web

Slide 34

Slide 34 text

PHP fora da Web Utilizando nossa linguagem preferida ♥ para criar scripts CLI e robôs

Slide 35

Slide 35 text

Quem sou eu? Vinícius Campitelli @vcampitelli @MediaPost e MT4 Tecnologia Curseduca