proc_open: Comunicando-se com o mundo lá fora

Atenção! Essa postagem foi escrita há mais de 2 anos. Na informática tudo evolui muito rápido e algumas informações podem estar desatualizadas. Embora o conteúdo possa continuar relevante, lembre-se de levar em conta a data de publicação enquanto estiver lendo. Caso tenha sugestões para atualizá-la, não deixe de comentar!

Comunicando-se com o mundo lá fora

Existem diversas maneiras de interagir com outras aplicações a partir do PHP para compartilhar dados: web services, serviço de enfileiramento de mensagens, sockets, arquivos temporários, exec(), etc. Bem, hoje eu gostaria de apresentar uma abordagem específica: proc_open(). A função executa um novo comando, mas deixa os ponteiros de arquivos abertos para enviar e receber dados, buscando comunicação interprocessos (IPC – Interprocess Communication).

O que é um Redirecionador de E/S?

Para entender como a função proc_open() envia e recebe dados, você precisa saber o que é um Redirecionador de Entrada/Saída.

A Filosofia Unix fez com que desenvolvedores escrevessem diversos pequenos programas com funcionalidades muito específicas. Estes programas compartilhavam o texto plano como formato de entrada/saíde e os usuários poderiam encadeá-los para obter funcionalidades maiores. As ligações virtuais que permitiam com que os dados corressem através de diferentes comandos ficaram conhecidas como redirecionadores de entrada/saída.

Se você já trabalhou em um shell do Unix então existe uma grande possibilidade de você já ter utilizado (talvez sem nem perceber). Por exemplo:

$ mysql -u usuario -p teste < dados.sql

Aqui chamamos a ferramenta mysql é logo após o sinal de redirecionamento indicamos um arquivo. Essa chamada fará com que todos o conteúdo de dados.sql seja enviado para a entrada padrão de mysql, como se você estivesse digitando direto do seu teclado.

Quando falamos de redirecionamento, também falamos de pipes. Pipes são encadeamentos de entradas e saídas. Existem dois tipos de pipes: anônimos and nomeados. O pipe anônimo é ad hoc (designado para uma função específica), que persiste apenas enquanto o processo estiver rodando e é destruído assim que não é mais necessário. Já o pipe nomeado recebe um nome e pode durar por tempo indeterminado. É criado utilizando comandos específicos e volta e meia aparecem com tipos de arquivos especiais no sistema de arquivos.

Independentemente do tipo, uma propriedade importante de pipes e redirecionamentos é que são estruturas FIFO (first in, first out – primeiro a entrar, primeiro a sair). Isso significa que o primeiro dado que é escrito em um pipe ou redirecionamento é o primeiro a ser lido pelo processo que está do outro lado.

Introdução à função proc_open()

A função PHP proc_open() executa um comando, bem parecido com o modo que a função exec() faz, mas com a habilidade de “canalizar” os streams de entrada e saída. Aceita alguns argumentos opcionais, mas os obrigatórios são:

  • um comando para ser executado;
  • um array que descreva os pipes que serão utilizados;
  • um array passado por referência que será preenchido posteriormente com referências aos pipes, assim você poderá enviar e receber dados;

Você pode encontrar mais informações sobre esse comando e os outros parâmetros no manual do PHP.

Além do comando que você deseja executar, eu diria que o array descritor que define os pipes é o argumento mais importante. A documenta explica como “Uma matriz indexada onde a chave representa o número do descritor e o valor representa como o PHP passará este descritor para o processo filho”, mas o que isso que dizer?

Os três principais fluxos (streams) de dados de um processo unix são: STDIN (standard input – entrada padrão), STDOUT (standard output – saída padrão) e STDERR (standard error – erro padrão). Ou seja, há um fluxo para entrada de dados, um para saída e um segundo para saída para mensagens de informação. Tradicionalmente STDIN é representada pelo número 0, STDOUT por 1 e STDERR por 2. Logo, a definição com a chave 0 será utilizada para configurar o stream de entrada, 1 para o stream de saída e 2 para stream de erros.

Essas definições podem tomar uma de duas formas: um recurso apontando para um arquivo aberto ou um array que descreve a natureza do pipe. Para um pipe anônimo, o primeiro elemento do array descritivo é a string “pipe” e o secundo é “r”, “w” ou “a”, dependendo se o pipe está no formato read (leitura), write (escrita) ou append (acrescentar). Para pipes nomeados, o array descritivo possui a string “file”, o nome do arquivo e então “r”, “w” ou “a”.

Uma vez chamada, a função proc_open() preenche o vetor que foi designado nos parâmetros com com referências que apontarão para o o processo. Os elementos na referência podem ser tratados como descritores de arquivos normais e trabalham tranquilamente com funções de stream como fwrite()fread()stream_get_contents(), etc.

Quando tiver terminado de interagir com o comando externo, é importante dar uma limpada em tudo. Você pode fechar os redirecionamentos criados (com fclose()) e o resource do processo (com proc_close()).

Dependendo da maneira como o comando/processo se comporta, pode ser necessário que você encerre a entrada de dados (STDIN) antes que ele comece a trabalhar (assim saberá que não deve esperar nenhuma outra entrada). Também é interessante que você feche as conexões STDOUT e STDERR antes de encerrar o processo, ou então seu processo pode ficar pendurado até que tudo esteja limpo para ser encerrado.

Um exemplo prático: convertendo marcação Wiki

Até agora eu só falei sobre como as coisas funciona, mas não apresentei nenhum exemplo utilizando proc_open() para realizar comunicação com um processo externo. Vamos ver o quão fácil é!

Vamos supor que precisamos converter uma porção de texto com marcação wiki para HTML e mostrar no navegador do usuário. Usaremos a ferramenta Nyctergatis Markup Engine (NME) para realizar a conversa, porém ela é compilada em C. Precisamos de uma maneira de executar o comando nme quando necessário e passar dados de entrada e receber dados de saída.

[php]<?php
// array descritor
$desc = array(
0 => array(‘pipe’, ‘r’), // 0 – STDIN
1 => array(‘pipe’, ‘w’), // 1 – STDOUT
2 => array(‘file’, ‘/tmp/saida-erros.txt’, ‘a’) // 2 – STDERR
);

// comando que chama a ferramenta nme
$cmd = "nme –strictcreole –autourllink –body –xref";

// inicia o processo
$p = proc_open($cmd, $desc, $pipes);

// envia o conteúdo da wiki como entrada para a ferramenta e então
// fecha o pipe de entrada, assim a ferramenta saberá que não deve
// mais esperar entradas e poderá iniciar o processo
fwrite($pipes[0], $conteudo_wiki);
fclose($pipes[0]);

// lê a saída da ferramenta
$html = stream_get_contents($pipes[1]);

// tudo pronto! hora de limpar o que ficou
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($p);[/php]

Primeiro o array descritor definido com 0 (STDIN) como pipe anônimo que será legível pela ferramenta de markup, 1 (STDOUT) como pipe anônimo com possibilidade de escrita pela ferramenta e 2 (STDERR) redirecionando qualquer mensagem de erro para um arquivo de log.

O “r” e o “w” na definição do pipe podem parecer contra-intuitivos à primeira vista, mas tenha em mente que são canais que a ferramenta usará e, portanto, a configuração é feita sob esse ponto de vista. Nós escrevemos no pipe de leitura porque será de lá que a ferramenta lerá dados. Nós lemos do pipe de escrita porque será lá que a ferramenta estará escrevendo.

Considerações finais

proc_open(), dependendo do que você precisa trabalhar

Existem diversos meios de interagir com processos externos. Algumas podem ser melhores do que utilizar proc_open() dependendo do que você precisa trabalhar, ou então proc_open() pode ser justamente o que o seu médico te receitou para esse caso. É óbvio que você vai implementar algo que faça sentido, mas agora você já sabe qual é a ferramenta que vai precisar para isso!

Espero que tenham gostado e que possa ser útil para as aplicações que estão desenvolvendo.

Um forte abraço a todos e fiquem com Deus!

Texto adaptado do original: http://phpmaster.com/proc-open-communicate-with-the-outside-world/