No artigo anterior apresentei o OpenTBS para criar documentos OpenXML ou OpenDocument em PHP, o que permite que os utilizadores da aplicação consigam editar modelos de relatórios. Mas e se for necessário disponibilizar os documentos, para o utilizador final, em PDF?

Neste artigo mostro como converter documentos OpenXML ou (preferencialmente) OpenDocument para PDF através do LibreOffice ou OpenOffice. Tudo isto em PHP.

  1. Ferramentas necessárias
  2. Como funciona
  3. Porque é que é necessário o unoconv
  4. Configurações e notas importantes
  5. Não tenho controlo sobre o servidor. O que posso fazer?

Ferramentas necessárias

A solução que aqui apresento funciona tanto em servidores Windows como Linux, basta que tenham instalado o LibreOffice ou o OpenOffice. Caso não tenhas uma destas ferramentas instaladas e não o possas fazer porque não tens controlo no servidor (por exemplo em alojamentos partilhados), vê a minha recomendação no final do artigo.

Além de teres uma destas ferramentas instaladas vais precisar também do script Python chamado unoconv (descarrega a última versão através do GitHub.

Como funciona

A conversão é feita através do executável soffice fornecido com a instalação do LibreOffice ou OpenOffice. A execução do soffice é encapsulada através do unoconv e basta indicares qual o ficheiro que pretendes converter.

$documentPath = 'documentos' . DIRECTORY_SEPARATOR . 'relatorio.odt';

// Caminho para o executável do Python em instalações Linux
$pythonPath = '/usr/bin/python';
// Se estiveres a usar Windows, o caminho deverá ser semelhante a
// $pythonPath = 'C:\Program Files (x86)\LibreOffice 5\program\python';

// Caminho para o script unoconv.
$unoconvPath = 'lib' . DIRECTORY_SEPARATOR . 'unoconv';

// Criação do comando usado para converter o documento gerado para PDF. Este comando executa o
// script unoconv, através do executável do python e converte para PDF o ficheiro indicado.
$command = sprintf(
    '"%s" "%s" -f pdf "%s" 2>&1',
    $pythonPath,
    $unoconvPath,
    $documentPath
);

// Executa o comando e armazena o resultado da execução
exec($command, $output, $return);

// Nota: Se o $output tiver algum conteúdo, ocorreu um erro na conversão.
if (is_array($output) && !empty($output[0])) {
    throw new Exception(json_encode(implode('; ', $output), true));
}

A conversão é relativamente simples em termos de código, como podes ver. Há que ter atenção à variável $output que, caso tenha conteúdo, indica que ocorreu um erro ao converter o documento.

Porque é que é necessário o unoconv

Como já referi, o comando soffice é que faz a conversão do documento para PDF. No entanto, neste artigo estou a usar o script unoconv que, por sua vez, chama o soffice. O motivo pelo qual tenho um “intermediário” é que o soffice é executado mas o processo termina de imediato e o documento pode ainda não ter sido convertido.

Ou seja, caso uses apenas o soffice, depois da chamada ao exec() não há garantias que o ficheiro já esteja convertido ou que tenha sido convertido com sucesso. Com este objetivo, ao utilizares o unoconv garantes que o processo aguarda a conversão do ficheiro ou, em casos de erro, que tens acesso ao erro ocorrido. Por este motivo, caso queiras utilizar o ficheiro convertido de imediato (logo a seguir ao exec()) vais ter problemas se tentares usar o soffice.

Configurações e notas importantes

Estamos a executar um comando através do exec com um intermediário (unoconv) e, como calculas, há 1001 coisas que podem correr mal. Para te ajudar e para não cometeres os mesmos erros que eu, deixo aqui uma lista com os pontos mais importantes a que tens de estar atento.

  • Para garantires que não há incompatibilidades nas versões do Python (caso tenhas o Python instalado no servidor), o melhor é usares o executável do Python disponibilizado pelo próprio LibreOffice.
  • Se não tiveres instalado o LibreOffice, as packages que tens de instalar são (exemplo para CentOS)

    yum install libreoffice-headless
    yum install libreoffice
    yum install libreoffice-pyuno
    
  • Para que esta solução funcione, no momento em que executas o script unoconv não podes ter nenhum processo soffice.exe ou soffice.bin em execução, caso contrário terás um erro. Assim, tens de garantir que estes processos nunca estão em execução. Em alternativa podes solucionar esta question caso executes o soffice num serviço (que está sempre em execução).

  • Deves atribuir permissões de execução ao script unoconv e ao soffice para evitar problemas na execução do exec()

  • Estou a apresentar esta solução tanto para documentos OpenXML (docx, xlsx) como para OpenDocument (odt, ods). No entanto, recomendo vivamente que a uses com documentos OpenDocument. O motivo é simples, embora o soffice permita converter documentos OpenXML, é garantido que a compatibilidade com documentos OpenDocument é muito melhor. Quer isto dizer que, enquanto a solução pode funcionar com documentos OpenXML, é possível que o PDF fique desformatado.

  • Neste artigo refiro-me mais ao LibreOffice que ao OpenOffice porque não consegui que a solução funcionasse com o OpenOffice (confesso que não percebi porquê)

Não tenho controlo sobre o servidor. O que posso fazer?

Não tens permissões suficientes no servidor para instalar o LibreOffice ou para atribuir as permissões aos scripts? A melhor recomendação que posso fazer é adquirires um VPS, que se encontram a relativamente baixo custo (por exemplo na DigitalOcean) e criares uma API cujo único objetivo é a receção dos documentos OpenDocument e que devolva os documentos PDF convertidos.

Tudo depende do projeto em que estás inserido e quais são as variáveis (nomeadamente em termos financeiros), mas é bastante simples criares uma REST API e implementares um certificado SSL (vê o nosso artigo sobre a criação de certificados gratuítos) no servidor, de forma a que todo o processo seja seguro.

Se estivesse nesta situação era esta a solução pela qual optava mas, como disse, depende de todas as condicionantes do projeto. Se tal não for possível, lamento não ter outra solução para resolver este obstáculo.

Espero que estes dois artigos te tenham ajudado a criar um gestor de relatórios poderoso e flexível.