Integração contínua para aplicações PHP usando Github Actions

Github Actions é um serviço relativamente novo e, ainda que em estágio beta, possui boa estabilidade. Ele permite que criemos workflows com jobs a serem executados a partir dos eventos disparados no Github. Então, por exemplo, sempre que um novo pull request é criado ou que um push é feito no Github, são exemplos de eventos que podem disparar a execução de workflows. Esse serviço favorece muito a implementação de integração e entrega contínua sem que haja a necessidade de sairmos do Github para serviços externos como Travis, CircleCI etc.

No caso específico do PHP podemos criar um workflow para que a cada novo push em determinado branch dispare a execução de um job para rodar os testes unitários do projeto, executar o PHPStan (analisador estático) e até mesmo executar o PHP Insights para avaliar a qualidade do código. Enfim, é possível executar qualquer ferramenta que faça sentido pro contexto do projeto.

Mas, há de ressaltar, o Github Actions é muito mais que isso. Possui uma infinidade de eventos que podem ser trabalhados. Nesse artigo focaremos na integração contínua de uma aplicação PHP tradicional, no sentido de rodar testes unitários, executar o PHPStan e o PHP Insights a cada novo push ou pull request a fim de garantir que o código esteja dentro dos parâmetros exigidos pela equipe antes dele ser incorporado. Mas o Github Actions pode ser usado para qualquer projeto de qualquer linguagem.

Git e GitHub - Controle de versão
Curso de Git e GitHub - Controle de versão
CONHEÇA O CURSO

No que se baseia o Github Actions?

O Github Actions se baseia em Workflows que são fluxos de trabalho, por exemplo: Build, Teste e Publicação poderiam ser três tipos de workflows. Cada Workflow deve possuir um ou mais jobs. O Job refere-se à execução de uma tarefa, por exemplo, poderíamos em um workflow termos um job para dar build nos assets e outros job para executar os testes. É no workflow que configuramos quais eventos e ambientes (ubuntu, macOS etc) os nossos jobs serão executados. Em resumo, temos um workflow que possui N jobs e um job possui N steps (ações). Veremos isso na prática daqui a pouquinho, o que tornará mais fácil de visualizar a dinâmica da coisa toda.

O Github Actions é pago ou grátis?

É grátis para repositórios públicos e pago para repositórios privados com cobrança por minuto de execução. Na página do Github Actions você consegue visualizar melhor os detalhes de cobrança. Lembrando que enquanto estiver em fase beta o serviço estará grátis tanto para repositórios públicos quanto para privados.

O que eu preciso fazer para começar a usar?

Como o serviço ainda está em beta é preciso que você se inscreva para utilizá-lo. Basta que você esteja logado na sua conta do Github e então clicar no botão “Sign up for the beta“:

Depois é só confirmar:

O projeto de teste

Criei um projeto de uma classe e um método só, para que foquemos no assunto central do artigo, que se trata da criação de um Workflow de teste e validação do código do nosso projeto.

O nosso Workflow executará as seguintes ferramentas:

  • PHPUnit
  • PHPStan
  • PHP Insights

Recomendo que você leia o artigo Ferramentas essenciais para um projeto PHP para conhecer um pouco mais sobre elas.

O projeto completo com o Workflow criado e ativo você pode visualizar em:

https://github.com/KennedyTedesco/github-actions-php

Antes de entrarmos na parte que toca a configuração do Workflow, primeiro quero mostrar o funcionamento visual dele lá no Github.

No projeto tem uma aba chamada Actions, clique nela:

Ela lista os Workflows configurados para o projeto. No nosso caso o workflow se chama “App Workflow“. Do lado direito temos a relação dos eventos que dispararam a execução dele. Ele foi disparado três vezes no evento de “push”, ou seja, quando subimos pro Github alterações.

No primeiro evento (de baixo pra cima) o workflow foi executado com sucesso, sem nenhum erro. Os testes passaram, o PHPStan não reportou nenhum erro e as métricas do PHP Insights estavam todas boas.

Já no segundo teste houve uma falha. Ah, o resultado da execução dos workflows também aparece na listagem dos commits:

https://github.com/KennedyTedesco/github-actions-php/commits/master

Observe os ícones de sucesso e erro.

Voltando na tela anterior, clique em um evento em que a execução do workflow teve sucesso, por exemplo, esse aqui:

https://github.com/KennedyTedesco/github-actions-php/commit/4a5be2bbaf5a09247321efa6c2e799f4c621d699/checks?check_suite_id=264838943

É nessa tela que vemos os Jobs e suas Steps (passos onde ações são executadas). No caso, o Job “Tests” (à esquerda) executou todas as Steps (ações) da tela escura à direita. As ações que configuramos foram no sentido de definir o ambiente PHP em que a execução ocorrerá, instalação das dependências do Composer no projeto e execução das ferramentas de análise e teste.

O evento que causou falha na execução do workflow se deu por causa desse commit:

https://github.com/KennedyTedesco/github-actions-php/commit/9f6fb1142a5e2b98a2ecedd904eaa10301c1eb0e

Explicitamente fiz um teste falhar ao fazer ele comparar 4 === 8.

Quando subi essa alteração para o Github, um evento de push foi disparado fazendo executar o workflow, que então falhou:

https://github.com/KennedyTedesco/github-actions-php/commit/9f6fb1142a5e2b98a2ecedd904eaa10301c1eb0e/checks?check_suite_id=264846074

Definindo o Workflow no projeto

Agora que já vimos como funciona lá na interface do Github a parte visual da execução do workflow, veremos como de fato podemos criar-lo dentro do nosso projeto.

Primeiro de tudo, devemos criar uma pasta chamada .github dentro do projeto. E, dentro dessa pasta, devemos criar uma pasta chamada workflows.

- github-actions-php
- - .github
- - - workflows
- - - - main.yaml

É dentro da pasta workflows que criamos os arquivos de definição dos nossos workflows. São arquivos yaml. Criamos apenas um workflow, portanto teremos apenas um arquivo yaml nessa pasta e demos o nome dele de main.yaml:

name: App Workflow

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  build:
    name: Tests
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@master

      - name: Setting up PHP
        uses: ./.github/actions/php

      - name: Installing Composer
        run: ./.github/scripts/run-composer.sh

      - name: Running PHPStan
        run: ./.github/scripts/run-phpstan.sh

      - name: PHP Insights
        run: ./.github/scripts/run-phpinsights.sh

      - name: Running PHPUnit
        run: ./.github/scripts/run-phpunit.sh

Esse é o nosso workflow completo. Em name definimos o nome dele.

Em on especificamos em quais eventos de quais branches esse workflow será executado. No caso, estamos dizendo que ele será executado sempre que um push ou um pull request for feito para o branch master.

Em jobs é onde definimos os jobs. No caso, temos apenas um job chamado build, veja que na estrutura do yaml eu dei o nome de build mas ao definir o nome dele lá pro Github eu o chamei de “Tests”:

  build:
    name: Tests

Isso não importa muito, você vai definir o nome que achar mais conveniente e lógico para o que você estiver executando. Eu poderia tranquilamente tê-lo chamado de:

  tests:
    name: Tests

Sem problema algum.

A diretiva runs-on do job especifica em qual ambiente a execução acontecerá. O ideal é você escolher o mesmo ambiente que você roda em produção.

Em steps é onde executamos as ações do job. Um job pode executar N ações. A action Checkout é padrão, é ela quem clona o projeto no ambiente de execução. Veja que ela possui um endereço do Github:

    steps:
      - name: Checkout
        uses: actions/checkout@master

Ela é mantida pelo Github e pode ser visualizada aqui: https://github.com/actions/checkout

Ou seja, podemos executar actions que estejam em repositórios arbitrários. Existe todo um ecossistema de actions que podem ser reutilizadas. Existe até mesmo um Marketplace de Actions no próprio Github:

https://github.com/marketplace?type=actions

Também tem uma lista de centenas de actions mantidas pela comunidade:

https://github.com/sdras/awesome-actions

Voltando ao nosso workflow, a segunda action:

      - name: Setting up PHP
        uses: ./.github/actions/php

Ela especifica que se trata de um Dockerfile contido em ./.github/actions/php que instalará a versão 7.3 do PHP que será usada para rodar os testes e validações:

https://github.com/KennedyTedesco/github-actions-php/tree/master/.github/actions/php

FROM php:7.3-cli-alpine

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

Ou seja, podemos criar actions baseadas em Dockerfiles.

A terceira action:

https://github.com/KennedyTedesco/github-actions-php/blob/master/.github/scripts/run-composer.sh

      - name: Installing Composer
        run: ./.github/scripts/run-composer.sh

Veja que ao invés de uses ela especifica run, pois estamos definindo qual comando ou arquivo ela deverá executar ao invés de qual action ela deverá usar.

Essa action é responsável pela instalação das dependências do composer no projeto. Nesse ponto já teremos o PHP configurado. Eu gosto de trabalhar com arquivos shell script para definir as tarefas que serão executadas. O conteúdo do arquivo run-composer.sh é:

#!/bin/sh
cp .env.ci .env
composer install --no-ansi --no-interaction --no-suggest --prefer-dist

Ela copia o arquivo .env.ci para .env na raiz do projeto e depois instala as dependências. O arquivo env.ci não tem nenhuma utilidade para o nosso projeto, eu deixei ele criado apenas para mostrar o que você pode fazer quando estiver criando um workflow para uma aplicação Symfony ou Laravel que use variáveis de ambiente. Nesse sentido, você pode ter variáveis de ambiente específicas para a execução do workflow.

Ao invés de executar o arquivo run-composer.sh poderíamos ter executado esses comandos diretamente no yaml do workflow, por exemplo:

      - name: Installing Composer
        run: |
          cp .env.ci .env
          composer install --no-ansi --no-interaction --no-suggest --prefer-dist

Mas eu prefiro definir arquivos shell script.

As últimas três ações rodam o PHPStan, PHP Insights e PHPUnit, respectivamente. Também são simples arquivos shell script. E com isso fechamos a configuração do nosso workflow.

Testando localmente as suas actions

Você pode usar a ferramenta act para localmente testar as suas actions. Incrível, não?

Concluindo

Eu encorajo que você leia a documentação e veja todos os recursos e opções disponíveis. Por exemplo, na página Workflow syntax for GitHub Actions tem muita informação valiosa sobre as outras sintaxes e opções.

E, não menos importante, quando você for criar um workflow para sua aplicação de maior porte, você certamente precisará lidar com a integração de variáveis de ambiente entre os jobs e actions, para isso, sugiro que você leia Virtual environments for GitHub Actions.

Como não dá pra masterizar o assunto em um artigo, tentei focar no essencial, no principal para uma aplicação PHP tradicional. Mas essas mesmas ideias podem ser agregadas para que você crie sua integração contínua para Laravel, Symfony ou outro framework qualquer. Sem contar que além do workflow de teste, você poderia ter um workflow para uma tarefa de corrigir o estilo do seu código e criar um pull request disso de forma automatizada, por exemplo. Como também poderia ter workflows para entrega/deploy contínuos. As possibilidades são muitas.

Até a próxima!

Formação:
CONHEÇA A FORMAÇÃO
Deixe seu comentário

Head de desenvolvimento. Vasta experiência em desenvolvimento Web com foco em PHP. Graduado em Sistemas de Informação. Pós-graduando em Arquitetura de Software Distribuído pela PUC Minas. Zend Certified Engineer (ZCE) e Coffee Addicted Person (CAP). @KennedyTedesco