GitHub Actions
Table of contents
Loading...Introduction
GitHub Actions is very practical for Continuous Integration and Deployment. It automates tasks such as building the application, running tests and deploying the code.
Automating these tasks not only makes life easier, but also reduces the risk for potentially fatal mistakes when forgetting to run a task.
GitHub Actions workflow
A GitHub workflow is a YAML file that defines a set of jobs and steps that GitHub
Actions will automatically run on specified events.
This file is typically stored in the .github/workflows
directory of the repository.
The workflow file contains the following main sections:
name
: The name of the workflow.on
: This section specifies the events that trigger the workflow (e.g. push, pull request)jobs
: Defines the jobs that the workflow will run. Each job runs on an operating system (e.g. ubuntu) and contains a sequence of tasks called steps. Steps can run commands, run setup tasks, or run an action in your repository.
GitHub env configuration
The GitHub Actions environment has its own configuration file config/env/env.github.php
.
For settings.php
to load the values of this file, the APP_ENV
environment
variable has to be set in the workflow file.
# ...
jobs:
run:
steps:
- name: Run test suite
run: composer test
env:
APP_ENV: github
PHP_CS_FIXER_IGNORE_ENV: 1
In addition to the GitHub Actions configuration values, the test values also need to be loaded since tests are run on the GitHub server.
File: config/env/env.github.php
<?php
// `require` has to be used instead of `require_once`.
// The values have to be loaded for each test case not only once for the whole test suite.
require __DIR__ . '/env.test.php';
// Database
$settings['db']['host'] = '127.0.0.1';
$settings['db']['database'] = 'slim_example_project_test';
$settings['db']['username'] = 'root';
// The password in the workflow file is 'root' and not empty
$settings['db']['password'] = 'root';
Secret configuration
Sensitive values that should be hidden in the workflow file, such as API tokens, are stored as GitHub secrets accessible in the repository Settings > Security > Secrets and variables > Actions.
They can be accessed in the workflow file with the syntax ${{ secrets.SECRET_NAME }}
.
Build testing
With the following workflow file build.yml
,
the application is built, and the tests are run on every push to the master
or develop
branch
as well as on every pull request to these branches.
The matrix
section defines the different variables that are used in the workflow.
They can be accessed with ${{ matrix.variable-name }}
.
File: .github/workflows/build.yml
name: ๐งช Build test
on:
push:
branches:
- master
- develop
pull_request:
types: [ opened, synchronize, reopened ]
env:
# Set APP_ENV to 'github' so that settings.php loads the correct configuration for database migrations and testing
APP_ENV: github
jobs:
run:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ ubuntu-latest ]
php-versions: [ '8.2' ]
test-database: [ 'slim_example_project_test' ]
name: PHP ${{ matrix.php-versions }} Test
services:
mysql:
image: mysql:8.0.23
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: test
ports:
- 33306:3306
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0 #sonarcloud shallow clone warning https://stackoverflow.com/a/62500400/9013718
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, pdo, pdo_mysql, intl, zip
coverage: xdebug
- name: Check PHP version
run: php -v
- name: Check Composer version
run: composer -V
- name: Check PHP extensions
run: php -m
- name: Check MySQL version
run: mysql -V
- name: Start MySQL
run: sudo systemctl start mysql
- name: Check MySQL variables
run: mysql -uroot -proot -e "SHOW VARIABLES LIKE 'version%';"
- name: Set MySQL timezone to swiss time # Change to your timezone
run: mysql -uroot -proot -e "SET GLOBAL time_zone = '+01:00';"
- name: Create database
run: mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS ${{ matrix.test-database }} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install dependencies
run: composer update --no-ansi --no-interaction --no-progress
- name: Execute database migrations
run: composer migrate:prod
- name: Show test db tables
run: mysql -uroot -proot -D ${{ matrix.test-database }} -e "SHOW TABLES;"
- name: Run test suite
run: composer test:coverage
env:
PHP_CS_FIXER_IGNORE_ENV: 1
Continuous integration with Scrutinizer
For a more extended quality and security analysis, I recommend using a cloud-based code review tool that detects bugs, vulnerabilities, code smells and makes coverage reports. There are many great services, and most are free for open source projects (e.g. Code Climate, Codacy, SonarCloud etc.).
This guide shows how to set up Scrutinizer for open source projects.
Deploying to server
The deployment process involves multiple steps that can be automated with GitHub Actions.
The code should only be deployed to the server if the tests are passing and the code was
pushed to the master
branch.
With FTP-Deploy-Action
the files are uploaded to the server via FTP.
The paths .git*/**
, tests/**
and docs/**
are excluded.
The database migrations are executed on the server by making an ssh connection to the terminal via ssh-action and then running the migration command.
File: .github/workflows/deployment.yml
name: ๐ Deployment
# Only trigger, when the build workflow is done
on:
workflow_run:
workflows: [ "๐งช Build test" ] # Replace with the name of the build workflow
types:
- completed
jobs:
run:
# Run job only on success of the build test workflow, and if the event was a push to the master branch
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'master' }}
runs-on: ubuntu-latest
name: Deploy PHP application
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
# extensions: mbstring, pdo, pdo_mysql, intl, zip
coverage: none
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: ๐ Sync files
uses: SamKirkland/FTP-Deploy-Action@4.3.3
with:
server: ${{ secrets.FTP_HOST }}
username: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
server-dir: /
exclude: |
.git*/**
docs/**
tests/**
- name: Executing database migrations
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_KEY_PASSPHRASE }}
port: ${{ secrets.SSH_PORT }}
script: |
cd ${{ secrets.DEMO_PROJECT_ROOT }}
chmod +x vendor/bin/phinx
composer migrate:prod