Skip to content

Instantly share code, notes, and snippets.

Last active September 20, 2023 20:54
Show Gist options
  • Save oliworx/d579f3b422289aa28790bddd83ed3206 to your computer and use it in GitHub Desktop.
Save oliworx/d579f3b422289aa28790bddd83ed3206 to your computer and use it in GitHub Desktop.
Let's automate composer updates (my talk at the PHP user group Munich, September 2023)

Let's automate composer updates

automation comic
Image by Randall Munroe,

What are we talking about

  • Upgrading PHP packages only using composer
  • Packages following semantic versioning only
  • compatible updates only (minor and patch versions)
  • Using GitLab with runner that uses the Docker executor

Reasons to update PHP packages

  • more Security
  • less Bugs
  • new Features to make use of

Reasons NOT to update PHP packages

  • no time given from management
  • fear of braking something ("newer change a running system")
  • not using composer at all
  • missing awareness for security
  • wrong priorities
  • ...

Manually update PHP dependencies

  • create a ticket with a due date
  • pull the ticket and assign it to me
  • create a merge request / pull request and a new branch
  • checkout the master branch locally
  • run composer update
  • run composer bump
  • run phpunit
  • wait until test finish
  • checkout the new update branch locally
  • copy updated packages from composer output to clipboard
  • run git add composer.json composer.lock
  • run git commit
  • fill in commit message using "Update PHP dependencies week xx" + composer output from clipboard
  • run git push
  • open the browser tab with the merge request
  • wait for the CI pipeline
  • mark the MR as ready
  • assign a reviewer
  • move the ticket to the next column for code review
  • wait for the reviewer to review and approve the change
  • merge the branch and close the MR
  • wait for the CI/CD pipeline
  • create a new ticket with due date next week

How to automate most of the work

First do our homework

  • allow compatible update of packages in composer.json using ^
    "laravel/vapor-core": "^2.32.0",
    "laravel/vapor-ui": "^1.7.4",
    "league/flysystem-aws-s3-v3": "^1.0.30",
    "maatwebsite/excel": "^3.1.48",
    "mews/purifier": "^3.4.1",
    "sentry/sentry-laravel": "^3.8.0",
    "spatie/laravel-activitylog": "^3.17",
  • pin versions for packages which must not be upgraded
  • check for 0.x versions, as composer will NOT upgrade these
  • Specify the proper target platform in composer.json
    "config": {
         "platform": {
             "php": "8.0.21"
  • automated tests with decent code coverage
  • error and performance monitoring / alerting like Sentry or NewRelic
  • ability to rollback a deployment quickly

Implement the CI pipeline

  • Create a GitLab Acess-Token with API permission for a GitLab user
  • Create a GitHub token
  • In the .gitlab-ci.yml add a new stage e.g. update-dependencies
      - update-dependencies
      - code-check
      - build
      - tests
      - deploy

In the .gitlab-ci.yml add a new job for the new stage with the property only: - schedules:

  stage: update-dependencies
    - schedules
    - "composer update --no-ansi --no-interaction --no-scripts --no-install --no-audit
                       --no-progress --ignore-platform-req=ext-* 2>&1 | tee composer-out.txt"
    - composer bump
    - git config --global ""
    - git config --global "Some Name"
    - git config --global --add $CI_PROJECT_DIR
    - BRANCH_NAME="update-php-dpendencies-$(date '+%Y-%m-%d_%H-%M')"
    - git checkout -b $BRANCH_NAME
    - git add composer.json composer.lock
    - (echo "Automated PHP dependency upgrades $(date '+%Y-%m-%d')" ; echo "";) | tee commit-message.txt
    - | # extract list of updated packages and save to a file and a variable
      UPGRADED_PACKAGES=$(cat composer-out.txt | \
          sed -n '/^Writing lock file/ q; p' | \
          sed -n '/Updating dependencies/,$ p' | \
          sed '1 d' | tee -a commit-message.txt | \
          sed 's/$/\\n/'| tr -d '\n'); # convert to a single line for json
    - git commit -F commit-message.txt
    - |
          \"id\": ${CI_PROJECT_ID},
          \"description\": \"${UPGRADED_PACKAGES}\",
          \"source_branch\": \"${BRANCH_NAME}\",
          \"target_branch\": \"master\",
          \"remove_source_branch\": true,
          \"title\": \"Automated PHP dependency upgrades $(date '+%Y-%m-%d')\"
    - |
      curl --fail --no-progress-meter -X POST "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests" \
         --header "PRIVATE-TOKEN:${GITLAB_ACCESS_TOKEN}" \
         --header "Content-Type: application/json" \
         --data "${BODY}"

Important: all other jobs must get the property except: - schedules

  stage: code-style
  image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/php:8.0.21-cli-alpine
    - schedules
    - find . -type f -name '*.php' -print0 | xargs -0 -n1 -P4 php -l -n | (! grep -v "No syntax errors detected" )

Create a Scheduled Task


To test the job click the play button:


Then open CI/CD pipelines and open the latest job

As a result, a new Merge Request is created, ready to be reviewed, merged and deployed:


Further Ideas

  • abort if there are no upgrades (Nothing to modify in lock file)
  • automate removal of outdated MRs and branches
  • use variables for git user name and email
  • make a GitHub action
  • upgrade node_modules too
  • merge automatically
  • extract to a central yml file to be used by all repositories



Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment