Back-End

24 set, 2012

PHP para todas as coisas

Publicidade

O PHP foi inicialmente projetado para sites e ainda é amplamente considerado apenas como uma linguagem de programação para a web. Mas, com as abordagens abaixo e uma variedade de bibliotecas de console úteis, ele também funciona muito bem para scripts de linha de comando.

Na Etsy, temos uma variedade de utilitários de linha de comando e todos estão escritos em PHP. Ter o nosso site e utilitários de linha de comando na mesma linguagem significa trocas de contexto mentais mais fáceis entre os dois ambientes. Isso também nos permite reutilizar o código web a partir da linha de comando, particularmente para acesso a dados ou processamento assíncrono.

Argumentos

Assim como nas páginas da web, scripts de linha de comando geralmente tornam as coisas mais interessantes quando recebem argumentos. Os argumentos de linha de comando podem ser acessados com as globais $argc e $argv. $argc contém o número de argumentos, e $argv inclui um array que contém cada argumento; $argv[0] é sempre o nome do script PHP sendo executado.

Para opções mais simples, utilizar $argv diretamente é bem fácil. Considere o uso de getopt() ou esta classe Args se você quiser permitir opções mais complexas e flags como esta: ./script -n -g --near 4 --orange rectangle.

Os scripts de linha de comando frequentemente têm necessidades diferentes de memória que solicitações da web. Se você encontrar seu script sendo executado sem memória, não hesite em, de forma explícita, aumentar o limite de memória: ini_set('memory_limit', '256M');

Backticks

A ferramenta de primeira mão para scripts é o execution operator conhecido como backticks (“). Tal como acontece com shell script, backticks irá executar um determinado comando e retornar o que foi escrito para a saída padrão.

Um lugar onde fazemos uso extensivo de backticks é no nosso script builda, que usamos para compilar CSS e JavaScript como parte do nosso processo de implementação. Em vez de usarmos funções do PHP para navegar em arquivos e diretórios, pegamos tudo com um comando shell, como no exemplo a seguir.

$files = explode("\n", trim(`find $js_dir -type f`));

Observe que os backticks ampliam variáveis como $js_dir apenas com strings de aspas duplas. Lembre-se de que qualquer entrada estranha deve ser precedida com escapeshellarg() ou escapeshellcmd() antes de enviá-la para o shell.

O Builda foi originalmente escrito em “não-PHP”, mas estagnou, porque nem todo mundo no Etsy estava confortável o suficiente com essa linguagem para entrar de cabeça nela e manter o projeto. Desde que foi reescrito seis meses atrás em PHP, com o qual todos os engenheiros conseguem trabalhar, sete engenheiros diferentes têm contribuído para tornar a ferramenta melhor.

Standard error

print e eco são maneiras fáceis de escrever para a saída stdout. Mas, às vezes, os programas de linha de comando querem escrever para o standard error. Para uma rápida escrita em stderr, tente o seguinte:

file_put_contents('php://stderr', "error message\n");

Como alternativa, você pode abrir stderr como um arquivo e gravar nele as funções de arquivos padrão.

$err = fopen('php://stderr', 'w');
fwrite($err, "another error message\n");

Pipes

Nos casos em que é exigida mais interatividade com os processos filhos, proc_open() muitas vezes é a ferramenta certa. Ela inicia um processo e abre pipes, de modo que os dois processos podem se comunicar um com o outro.

Como um exemplo de como você pode usar proc_open, considere o caso de carregamento de dados em bancos de dados em shard. Originalmente, tínhamos apenas três fragmentos, e era fácil executar manualmente um script para cada um. Como o número de fragmentos aumentou, isso se tornou mais demorado, então escrevemos outro script para o carregamento de dados para todos os pedaços de uma só vez.

O novo script é, na verdade, dois scripts. Primeiro, o processo filho, shard_load, é muito simples. Ele abre uma conexão a um fragmento único, lê a entrada a partir de stdin e se insere linhas no banco de dados. Ao quebrar o trabalho em dois scripts, cada tarefa é simples e pode ser testada de forma independente.

 

O script principal, load_all_shards, é igualmente simples. Ele só lê um fluxo de entrada de stdin, abre um processo filho para cada fragmento e fileiras de pipes de entrada para o processo filho apropriado. Os pipes abertos por proc_open podem ser lidos e gravados com funções como fgets() e fwrite(), assim como qualquer outro ponteiro de arquivo.

do {
$line = $fgets($stdin);
list($shard_id,$row) = split("\t", $line, 2);
fwrite($shards[$shard_id]->stdin, $row);
$line = fgets($handle);
} while ($line);

Tudo o que precisa ser feito é conectá-los com proc_open:

function openShard($shard, $tbl, $fields) {
$desc = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
);
$pipes = array();
$cmd = "./shard_load $shard $tbl $fields";
$proc = proc_open($cmd, $desc, $pipes);
return new ShardProc($shard, $proc, $pipes);
}

A onipresença do PHP

Com fácil acesso ao shell e comunicação interprocessos com pipes, não há razão para não aproveitar a experiência de engenharia existente (e código web) e utilizar PHP para scripts utilitários e offline também.

***

Texto original disponível em http://phpadvent.org/2011/php-for-all-the-things-by-matt-graham