PHP fora da Web

PHP fora da Web

Link dos slides: https://viniciuscampitelli.com/slides/php-fora-da-web

O PHP foi inicialmente feito para a Web, mas também podemos utilizá-lo para criar CLI scripts e até mesmo daemons. É lógico que existem linguagens focadas para isso, mas em alguns cenários podemos aproveitar os códigos PHP já existentes no backend e também a experiência dos programadores da equipe. Iremos ver como lidar com argumentos para scripts e roteá-los para comandos, como criar mecanismos de controle e execução de processos externos e em background e ter códigos que sejam reaproveitáveis entre ambientes.

375596b28a94ecfaec5d63ff64c7f948?s=128

Vinícius Campitelli

March 06, 2018
Tweet

Transcript

  1. 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.
  2. É a melhor solução para o meu problema? Primeiro, considere

    se você está usando as melhores ferramentas para cada tipo de trabalho
  3. Scripts CLI Criando utilitários para a linha de comando

  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 = "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
  9. 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] <em 'defaults' => [ 'controller' => Application\Controller\IndexCont 'action' => 'resetpassword' ] ] ] ] ] ] ];
  10. 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
  11. 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
  12. 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 { /* ... */ } }
  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', \Symfony\Component\Console\Input\InputArgument::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')); }
  15. Bibliotecas Outras opções Laravel Zero CLImate Aura.Cli CLIFramework

  16. Robôs Usando o PHP para criar daemons

  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; /* ... */ } }
  22. Demonstração

  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
  28. Demonstração

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

  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(\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) );
  33. Obrigado! GitHub @vcampitelli Twitter @vcampitelli Slides viniciuscampitelli.com/slides/php-fora-da-web

  34. PHP fora da Web Utilizando nossa linguagem preferida ♥ para

    criar scripts CLI e robôs
  35. Quem sou eu? Vinícius Campitelli @vcampitelli @MediaPost e MT4 Tecnologia

    Curseduca