How to Build a Dynamic GitHub Profile with GitHub Actions and PHP
Tags: php, githubactions, github, workflow
Published: 2022-02-06 17:59:32

In the summer of 2020, GitHub quietly released a feature that was quickly noticed by the community – profile READMEs. A [profile README](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme) is a global `README` file for your GitHub profile, which you can set up by creating a public repository whose name is identical to your GitHub username. For instance, as my username is `osteel`, I created the `osteel/osteel` [repository](https://github.com/osteel/osteel).

A little box like this one should appear while you add your own:

![Special repository](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptotj3km06fe5xng35iw.png)

Once the repository is created, add a `README` file with a short description explaining how great you are, and your GitHub profile page will display its content by default:

![GitHub profile README](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5z5jn5n9txt0btx5rab.png)

Neat and simple.

As I was [browsing examples](https://www.aboutmonica.com/blog/how-to-create-a-github-profile-readme "How To Create A GitHub Profile README") for some inspiration, I stumbled upon Simon Willison's [version](https://github.com/simonw), which features some dynamic content like recent work and blog publications. He explained how he used a combination of [GitHub Actions](https://github.com/features/actions) and Python to achieve this in a [blog post](https://simonwillison.net/2020/Jul/10/self-updating-profile-readme/), and I decided to do something similar with PHP.

## The placeholder

The first thing to do is to create a placeholder in the `README` file where the dynamic content will go. Since I wanted to automatically insert the latest publications of my blog, I used the following tags:

```
<!-- posts --><!-- /posts -->
```

You might recognise this format; since Markdown files also support HTML, I used some HTML comment tags to make sure they wouldn't show up on my profile page.

## The PHP script

I can't remember the last time I wrote some PHP without a framework; as a result, I had to do a [quick search](https://www.frobiovox.com/posts/2016/08/16/basic-hello-world-with-composer-and-php.html) just to get started with a basic PHP script and some Composer dependencies.

Turn out it's quite simple! The first step is to initialise the project with the following command:

```
$ composer init
```

From there, I installed [a lightweight library](https://github.com/dg/rss-php) to parse my blog's [RSS feed](https://tech.osteel.me/feeds/rss.xml):

```
$ composer require dg/rss-php
```

I then added a `posts.php` file at the root of the project, with the following content:

```
<?php

// Load Composer's autoload
require_once __DIR__ . '/vendor/autoload.php';

// Load the RSS feed
$feed = Feed::loadRss('https://tech.osteel.me/feeds/rss.xml')->toArray();

// Generate the list of blog posts
$posts = '';
foreach (array_slice($feed['item'], 0, 5) as $post) {
    $date   = date('d/m/Y', strtotime($post['pubDate']));
    $posts .= sprintf("\n* **[%s]** [%s](%s \"%s\")", $date, $post['title'], $post['link'], $post['title']);
}

// Generate the new content
$content = preg_replace(
    '#<!-- posts -->.*<!-- /posts -->#s',
    sprintf('<!-- posts -->%s<!-- /posts -->', $posts),
    file_get_contents('README.md')
);

// Overwrite the file
file_put_contents('README.md', $content);
```

Nothing too complicated here – Composer's _autoload_ is required at the top, allowing me to load the RSS parser to generate a list of blog posts as a string, in Markdown format.

The existing content of the `README` file is then loaded into the `$content` variable, and the Markdown string is inserted between its `<!-- posts -->` and `<!-- /posts -->` tags with `preg_replace`.

Finally, the file's entire content is replaced with the new one, using the `file_put_contents` function.

## The workflow

[GitHub Actions](https://github.com/features/actions) allow developers to write workflows to automate various tasks like running test suites or deploying web services.

They must be defined using YAML format in a `.github/workflows` folder at the root of the project, and contain a list of steps that they are to execute.

Here's mine, which I named `posts.yml`:

```
name: Update blog posts

on:
  push:
  workflow_dispatch:
  schedule:
    - cron:  '0 0 * * *'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Clone repository
      uses: actions/checkout@v2
    - name: Install PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.1'
    - name: Install Composer dependencies
      run: composer install
    - name: Insert blog posts
      run: php posts.php
    - name: Push changes
      uses: stefanzweifel/git-auto-commit-action@v4
      with:
        commit_message: Updated latest blog posts
```

Again, nothing too complicated. We first give the action a name, and then define a list of events that should trigger it – pushing some code to the repository, a manual trigger from the interface (`workflow_dispatch`), or periodically like a cron job (here, every day at midnight).

We then indicate that the action should run on an Ubuntu image, where it will:

* [clone the repository](https://github.com/actions/checkout);
* [instal PHP 8.1](https://github.com/shivammathur/setup-php);
* instal the Composer dependencies;
* run the PHP script;
* [commit and push the changes](https://github.com/stefanzweifel/git-auto-commit-action), if any.

That's it! My GitHub profile is now automatically updated every time I publish a new article.

## Conclusion

This was a quick experiment aiming at exploring GitHub Actions, which I expect to use more and more in the future. It was also fun to use PHP as a simple scripting language again, in a procedural way.

I've voluntarily left out a few things in order to keep this article short and simple – please refer to [the repository](https://github.com/osteel/osteel) for implementation details.

## Resources

* [Managing your profile README](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme)
* [GitHub Actions](https://github.com/features/actions)
* [This article’s repository](https://github.com/osteel/osteel)
* [GitHub Action: Checkout V2](https://github.com/actions/checkout)
* [GitHub Action: Setup PHP](https://github.com/shivammathur/setup-php)
* [GitHub Action: Git Auto Commit](https://github.com/stefanzweifel/git-auto-commit-action)