GitHub Actions for PHP Developers (HCL)

• 11 min read
Update 01.10.2019

The demo application mentioned in this article has been updated to work with the new YAML syntax. Checkout this Pull Request to learn what has changed.

Update 15.08.2019

On Augusth 8th GitHub announced some major updates to Actions. Switching from HCL to YAML syntax, CI/CD support and much more. This blog post was written before this announcement, so stuff might be different, depending on when you're reading this article.

If you're a developer using GitHub, you probably have heard of GitHub Actions. GitHub's new automated workflow system (similar to the CI feature of GitLab). It has been announced in October 2018 and is currently still in beta (You can sign up here to get access).

I recently got access to the beta and began developing Actions suited for my projects. As I currently spend most of my time writing code in PHP, the Actions mentioned in this article are focused on that language. The logic can be easily ported to other languages though.

A Workflow is a collection of multiple Actions which can be triggered by multiple GitHub webhook events (for example when code is pushed to the repository, a Pull Request has been merged or when a new Issue has been created)

An Action can basically do everything: run your test suite, publish a package to npm, deploy your site to your server, send a Slack message. You name it.
The code for an Action can live in the project repository itself, in a separate public GitHub repository or in a Docker Hub image.

Your Workflow is defined in the main.workflow file in the .github folder in your repository. This means your Actions are written in code and are in version control. If you like to work with a GUI, Workflows and Actions can also be configured and edited in a visual editor on github.com.

Screenshot of the Visual Editor for GitHub Actions on github.com
Screenshot of the Visual Editor for GitHub Actions on github.com

In this post I'm going to cover 3 Actions which I think could be useful for PHP developers in their daily workflows:

  • Run your phpunit test suite
  • Run phpinsights
  • Run php-cs-fixer

I've published a sample Laravel application, with all three Actions configured, on GitHub. You can clone it, fork it and see for yourself how the Actions are set up.
The process of adding those Actions is documented in this Pull Request.

As mentioned earlier, your Workflow and Actions are defined in a main.workflow file. The final file for our sample application looks like this:

workflow "phpunit / phpinsights / php-cs-fixer" { on = "push" resolves = [ "phpunit", "phpinsights", "auto-commit-php-cs-fixer", ] } # Install composer dependencies action "composer install" { uses = "MilesChou/composer-action@master" args = "install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist" } # Run phpunit testsuite action "phpunit" { needs = ["composer install"] uses = "./actions/run-phpunit/" args = "tests/" } # Run phpinsights action "phpinsights" { needs = ["composer install"] uses = "stefanzweifel/laravel-phpinsights-action@v1.0.0" args = "-v --min-quality=80 --min-complexity=80 --min-architecture=80 --min-style=80" } # Run php-cs-fixer action "php-cs-fixer" { uses = "docker://oskarstark/php-cs-fixer-ga" } action "auto-commit-php-cs-fixer" { needs = ["php-cs-fixer"] uses = "stefanzweifel/git-auto-commit-action@v1.0.0" secrets = ["GITHUB_TOKEN"] env = { COMMIT_MESSAGE = "Apply php-cs-fixer changes" COMMIT_AUTHOR_EMAIL = "jon.doe@example.com" COMMIT_AUTHOR_NAME = "Jon Doe" } }

GitHub Actions are not written in YAML or JSON, but in HCL. If you've worked with Terraform in the past, the syntax might look familiar to you.

I won't go deep what each keyword in the Workflow syntax does (uses, needs, secrets, etc.).

The most important keywords for us right now are:

  • uses: Set which Action in a Workflow should be used
  • needs: Set which Action must successfully run, before this Action runs. (Similar to Dependencies)
  • env: Environment variables defined in the Workflow file itself. Allows you as a Action consumer to change things within an Action
  • secrets: Secret environment variables like API keys which should not be stored in the repository

Each Action is executed in a Docker container. Therefore, each Action needs a Dockerfile. If you now think: "Oh no! I don't know Docker!", then we have something in common. I've read and heard a lot about Docker over the years, but never really worked with it.

The good thing is that you don't have to be a Docker expert to create or work with Actions. All you need to do is set a base image and then you're good to go. The "core" Actions code can basically be written in any language.

One caveat you have to keep in mind when working with Actions, is that even though each Action is run in its own container, the underlying filesystem is shared with other Actions. Meaning: If one Action changes repository files, these changes are also available in other Actions.

So let's get started with our first PHP Action.

To do anything with a Laravel project, we first have to install its dependencies with composer.

This can be accomplished by using the general composer Action developed by MilesChou.

workflow "composer install" { on = "push" resolves = [ "composer install" ] } action "composer install" { uses = "MilesChou/composer-action@master" args = "install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist" }

As all following task depend on composer dependencies, all other task should have "composer install" defined in the needs keyword. The "composer install" Action is therefore executed before all other Actions. (GitHub is clever enough though, to only run the Action once).

One of the most common things to do in Continuous Integration is running your projects test suite on each code push.

As the test suite setup differs from project to project, I won't use a publicly available Action to run phpunit. I will use an Action defined in the project itself. In the root of my project I create an actions/run-phpunit-folder and within it the following files:

FROM php:7.3-cli-alpine

LABEL "com.github.actions.name"="phpunit-test"
LABEL "com.github.actions.description"="Run phpunit test suite"
LABEL "com.github.actions.icon"="check-circle"
LABEL "com.github.actions.color"="green"

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh -l
set -eu

#  Setup Laravel App
cp .env.example .env
php artisan key:generate

#  Run phpunit Tests
vendor/bin/phpunit $*

In the Dockerfile we tell Actions that we want to use PHP 7.3 and that we want to execute the entrypoint.sh-file next.

The entrypoint.sh-file is where the logic of the Action lives. To run the test suite, we first create a copy of the example Laravel environment file and generate a fresh application key.

Next, phpunit is executed. Any provided arguments (args) are passed down and will be placed where the $* variable is.

If your project needs more PHP extensions or a MySQL database, the entrypoint.sh would be the place where you set these things up. (That's why I didn't use an already existing Action. The setup differs from project to project.)

In our Workflow file, we can now add the Action:

workflow "phpunit" { on = "push" resolves = [ "phpunit" ] } action "composer install" { uses = "MilesChou/composer-action@master" args = "install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist" } action "phpunit" { needs = ["composer install"] uses = "./actions/run-phpunit/" args = "tests/" }

Even though this was quite easy to set up, I personally wouldn't use GitHub Actions to run the test suite for my bigger projects yet.

Other CI services like Travis or Circle CI are much better suited for this task. These services give you richer notifications and better performance features like parallelism and dependency caching out of the box. With GitHub Actions you would have to implement this on your own.

However, I think Actions is perfectly fine for smaller projects.

Our next Action is going to run phpinsights on each push. As we don't have to set up a database or PHP extensions to run insights we can use an existing Action. As no phpinsights Actions existed when I wrote this article, I wrote my own: phpinsights-action and laravel-phpinsights-action.

As we're working with a Laravel app, we're going to use the Laravel version of the Action.

workflow "phpunit / phpinsights / php-cs-fixer" { on = "push" resolves = [ "phpunit", "phpinsights" ] } # Other Actions action "phpinsights" { needs = ["composer install"] uses = "stefanzweifel/laravel-phpinsights-action@v1.0.0" args = "-v --min-quality=80 --min-complexity=80 --min-architecture=80 --min-style=80" }

As you can see, I'm passing the --min-xxx arguments to the Action. If the code quality of my app would drop in a future Pull Request, the Action would return a "failed" status code which in turn would mark the Pull Request as failed.

To see the reported issues of phpinsights you can open the log on github.com

Screenshot of PHP Insights output
Screenshot of PHP Insights output

Another common use case to use a CI service, is to check if your code follows the code convention and style guide you or your team has defined.

This can be accomplished by running an existing php-cs-fixer Action developed by Oskar Stark.

workflow "phpunit / phpinsights / php-cs-fixer" { on = "push" resolves = [ "phpunit", "phpinsights", "php-cs-fixer", ] } # Other Actions action "php-cs-fixer" { uses = "docker://oskarstark/php-cs-fixer-ga" }

Here we've used another way to define an Action: By using the docker:// protocol, you can directly point to an image on DockerHub.

The Action uses your existing .php_cs configuration and runs php-cs-fixer on your project. However, the Action always returns a "successful" status code. It doesn't matter if violations happen.

Screenshot of Output of php-cs-fixer Action
Screenshot of Output of php-cs-fixer Action

But if violations would happen, php-cs-fixer automatically fixes them. So wouldn't it be cool if the fixed files would automatically be committed and pushed back to your branch? 🤔

As I said in the beginning of this article, the underlying file system is shared between multiple Actions in a Workflow. So committing the fixed files is just "an Action away".

I've created a git-auto-commit-Action which commits all changed files and pushes the commit back to the repository.

Our updated main.workflow file now looks like this.

workflow "phpunit / phpinsights / php-cs-fixer" { on = "push" resolves = [ "phpunit", "phpinsights", "auto-commit-php-cs-fixer", ] } # Other Actions action "php-cs-fixer" { uses = "docker://oskarstark/php-cs-fixer-ga" } action "auto-commit-php-cs-fixer" { needs = ["php-cs-fixer"] uses = "stefanzweifel/git-auto-commit-action@v1.0.0" secrets = ["GITHUB_TOKEN"] env = { COMMIT_MESSAGE = "Apply php-cs-fixer changes" COMMIT_AUTHOR_EMAIL = "john.doe@example.com" COMMIT_AUTHOR_NAME = "John Doe" } }

Now on every push, possible style changes are automatically committed and pushed back to your repository. No need to run the command manually or for a Third-Party-service.

Screenshot of auto-commit. Two committers have been attributed with the commit.
Screenshot of auto-commit. Two committers have been attributed with the commit.

We now also added our first secret: GITHUB_TOKEN. This is a special secret which is available to all Actions in a repository. But it's not enabled by default. You have to add it manually in the visual editor on github.com

Add the GITHUB_TOKEN by checking the corresponding checkbox in the editor.
Add the GITHUB_TOKEN by checking the corresponding checkbox in the editor.

As this Action uses the GITHUB_TOKEN to authenticate the git push-command, GitHub won't trigger a second run of the Workflow. (Keep that in mind!)

I think this covers the basics of GitHub Actions for PHP developers. I'm very excited about Actions and what the future holds. I hope the feature leaves the beta soon, so that more people can use it in their projects.

GitHub already announced support for Scheduled Workflows which opens a big realm of possibilities. (Jason Etcovitch writes here how he uses Scheduled Workflows to automatically create weekly meeting notes). Or for example, you could also build an Uptime-Monitoring Action which is triggered every few minutes and would send Slack or Email notifications.

Personally, I would like to use the Scheduling feature to fully automate Laravel Download Statistics (a side project of mine). A workflow could trigger the update of download numbers, create a new HTML export and push everything to Netlify.
No need for humans any more 🤖🤯.

If I could excite you about Actions and you want to start developing your own Actions, here are a few resources I've found while working on this article:

And here's a list of Actions I found, which I actually would like to implement in my projects.

Thanks to Peter Brinck, Célien Boillat and Max Almonte for proof reading this article and giving feedback.

Webmentions What are webmentions?

    Likes and more

    Replies

    • Photo of AlcidesRC AlcidesRC mentioned

      GitHub Actions for PHP Developers (HCL) • stefanzweifel.io stefanzweifel.io/posts/github-a…

      February 1st, 2020

    • Photo of Alfred Nutile Alfred Nutile mentioned

      I like this post stefanzweifel.io/posts/github-a… via @\_stefanzweifel

      December 19th, 2019

    • Photo of David Bisset David Bisset mentioned

      Digging into Github Actions? 👉🏻Intro via @jeffrafter: jeffrafter.com/working-with-g… 👉🏻This auto commits changed files back to Github: github.com/stefanzweifel/… 👉🏻@\_stefanzweifel on GitHub Actions for #PHP devs: stefanzweifel.io/posts/github-a… 👉🏻Big list of stuff: github.com/sdras/awesome-…

      September 30th, 2019

    • Photo of Elvis Mørales Fdz Elvis Mørales Fdz mentioned

      stefanzweifel.io/posts/github-a…

      September 14th, 2019

    • Photo of Kazuki Higashiguchi Kazuki Higashiguchi mentioned

      GitHub Actions for PHP Developers (HCL) stefanzweifel.io/posts/github-a…

      September 3rd, 2019

    • Photo of Łukasz Pawłowski Łukasz Pawłowski mentioned

      GitHub Actions for PHP Developers by Stefan Zweifel stefanzweifel.io/posts/github-a…

      June 24th, 2019

    • Photo of Indorse Indorse mentioned

      🆕 "@github Actions for #PHP Developers" insightful post by web developer, @\_stefanzweifel! #Developers #Programming #GUI #Workflows #Docker #PHPunit #PHPinsights #Commit #301DaysOfCode #Coding #Devs #Programmers #GitHub stefanzweifel.io/posts/github-a…

      June 23rd, 2019

    • Photo of Manuel Canga Manuel Canga mentioned

      GitHub Actions for #PHP Developers: stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Lucasoft co.uk Lucasoft co.uk mentioned

      RT @LaravelLiveIN 📜@\_stefanzweifel wrote a blog post about #GitHub Actions and how #developers can use them in their #PHP projects to automate stuff. Like run phpunit or automatically fix all style problems in your project.📜 📖Read More stefanzweifel.io/posts/github-a… #webdev #dev

      June 22nd, 2019

    • Photo of F4HWN Λrmel F4HWN Λrmel mentioned

      GitHub Actions for #php Developers stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Insolita Insolita mentioned

      #php stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of La semana PHP La semana PHP mentioned

      GitHub Actions para desarrolladores PHP stefanzweifel.io/posts/github-a… vía @\_stefanzweifel

      June 22nd, 2019

    • Photo of LaravelLive India 🇮🇳 | Official 🗣️ LaravelLive India 🇮🇳 | Official 🗣️ mentioned

      📜@\_stefanzweifel wrote a blog post about #GitHub Actions and how #developers can use them in their #PHP projects to automate stuff. Like run phpunit or automatically fix all style problems in your project.📜 📖Read More stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Laravel News Links Laravel News Links mentioned

      GitHub Actions for PHP Developers stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of PHP South Wales PHP South Wales mentioned

      An awesome writup on how to use @github #GitHubActions in #PHP by @\_stefanzweifel stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Josh Pollock 🌋 Josh Pollock 🌋 mentioned

      This will be useful. stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Alfred Danda Alfred Danda mentioned

      A very good introduction to GitHub actions for PHP developers by @\_stefanzweifel #GitHub #PHP #GitHubActions stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Gregoire Pineau Gregoire Pineau mentioned

      @\_stefanzweifel Hello, On stefanzweifel.io/posts/github-a… you wrote HSL instead of HCL :) Nice post Anyway

      June 22nd, 2019

    • Photo of azphp azphp mentioned

      github actions for PHP projects stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Yohan J. Rodríguez Yohan J. Rodríguez mentioned

      #PHPNews #PHP #Automated | GitHub Actions for PHP Developers stefanzweifel.io/posts/github-a…

      June 22nd, 2019

    • Photo of Simon Brandhof Simon Brandhof replied

      Nice. You can also easily detect bugs, code smells and vulnerabilities by adding github.com/sonarsource/so… to your workflow! 😋

      June 24th, 2019

    • Photo of Stefan Zweifel Stefan Zweifel replied

      June 24th, 2019