Git workflow for PHP developers: from chaos to order (and back)
\n\nHave 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.
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\n1. Choosing a strategy: Git Flow vs. Trunk-Based
\n\nThe 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
But if you have microservices and CI/CD on steroids, trunk-based is also an option. We'll focus on the classic approach.
\n\n2. Feature Branch: your sandbox
\n\nEach new feature is a separate branch. The name should be descriptive: feature/PROJ-123-add-payment-gateway. No fix-bug or new-stuff.
Example of getting started:
\n\ngit checkout -b feature/PROJ-42-new-cache-system develop\ngit push origin feature/PROJ-42-new-cache-system\n\nWhile 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?"
composer require predis/predis\ngit add composer.json composer.lock\ngit commit -m "PROJ-42: Add predis for caching"\n\nImportant: don't commit vendor/! Add it to .gitignore right away.
3. Pull Request: code review without pain
\n\nWhen 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
- ✅ Code passes
phpcsandphpstan(minimum level 6). \n - ✅ Tests are written (PHPUnit, Pest). \n
- ✅
composer.lockis updated. \n - ✅ No conflicts with
develop. \n
Example CI script (GitHub Actions):
\n\nname: 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\nThis workflow for PHP developers automates code checking even before someone looks at the PR. Magic, right?
\n\n4. Release branch: preparing for production
\n\nWhen 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.
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\nNote the --no-ff. This preserves the branching history so you always know where the code came from.