Git workflow for PHP developers: how not to break production

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Git workflow for PHP developers: from chaos to order (and back)

\n\n

Have you ever pushed code to master on a Friday evening, praying everything would work? Or stepped on the rake with composer.lock, when everything broke after a merge? Welcome to the club. Git is not just a version control system, it's your daily driver. But without the right workflow, even the coolest PHP code turns into a pumpkin.

\n\n

In this article, we'll break down a git workflow for PHP developers that actually works: from choosing a branching strategy to deployment. No fluff, only code, examples, and life hacks. Let's go!

\n\n

1. Choosing a strategy: Git Flow vs. Trunk-Based

\n\n

The debate between git flow and trunk-based development is longer than the history of PHP frameworks. But for most PHP projects (especially legacy ones on Symfony/Laravel), Git Flow remains the gold standard. Why?

\n\n
    \n
  • Clear isolation of features, releases, and hotfixes.
  • \n
  • Control over what goes to production.
  • \n
  • Working with composer — no need to merge the dev branch every time.
  • \n
\n\n

But if you have microservices and CI/CD on steroids, trunk-based is also an option. We'll focus on the classic approach.

\n\n

2. Feature Branch: your sandbox

\n\n

Each new feature is a separate branch. The name should be descriptive: feature/PROJ-123-add-payment-gateway. No fix-bug or new-stuff.

\n\n

Example of getting started:

\n\n
git checkout -b feature/PROJ-42-new-cache-system develop\ngit push origin feature/PROJ-42-new-cache-system
\n\n

While you're writing code, don't forget about composer. If you're adding a new dependency, commit composer.json and composer.lock together. This will save your colleagues from "why doesn't it work for me?"

\n\n
composer require predis/predis\ngit add composer.json composer.lock\ngit commit -m "PROJ-42: Add predis for caching"
\n\n

Important: don't commit vendor/! Add it to .gitignore right away.

\n\n

3. Pull Request: code review without pain

\n\n

When the feature is ready, create a PR (or MR in GitLab) into develop. Here's a checklist that will save your reviewer's nerves:

\n\n
    \n
  • ✅ Code passes phpcs and phpstan (minimum level 6).
  • \n
  • ✅ Tests are written (PHPUnit, Pest).
  • \n
  • ✅ composer.lock is updated.
  • \n
  • ✅ No conflicts with develop.
  • \n
\n\n

Example CI script (GitHub Actions):

\n\n
name: PHP CI\n\non:\n  pull_request:\n    branches: [ develop ]\n\njobs:\n  php-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Install dependencies\n        run: composer install --no-interaction --prefer-dist\n      - name: Run PHPStan\n        run: vendor/bin/phpstan analyse src --level=6\n      - name: Run tests\n        run: vendor/bin/phpunit
\n\n

This workflow for PHP developers automates code checking even before someone looks at the PR. Magic, right?

\n\n

4. Release branch: preparing for production

\n\n

When enough features have accumulated for a release, create release/x.y.z from develop. Here you fix bugs, update the changelog, bump the version in composer.json.

\n\n
git checkout -b release/1.2.0 develop\n# fix bugs, update version\nvim composer.json  # change "version": "1.2.0"\ngit commit -am "Bump version to 1.2.0"\n# merge into master and develop\ngit checkout master\ngit merge --no-ff release/1.2.0\ngit tag -a v1.2.0 -m "Release version 1.2.0"\ngit push origin master --tags
\n\n

Note the --no-ff. This preserves the branching history so you always know where the code came from.

Blogs

Book Recommendations