Replies: 5 comments 4 replies
-
|
The most reliable way to validate PHP syntax in GitHub Actions is to use To lint a single file: - name: Validate PHP syntax
run: php -l path/to/your/script.phpTo lint all PHP files recursively: - name: Validate PHP syntax
run: find . -name "*.php" -not -path "./vendor/*" | xargs -I{} php -l {}
Full example workflow: name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Validate PHP syntax
run: find . -name "*.php" -not -path "./vendor/*" | xargs -I{} php -l {}
- name: Deploy via SSH
run: ssh user@yourserver "cd /var/www && git pull"Why not the built-in server? If you also want to catch runtime/logic issues beyond syntax, consider adding PHPStan or Psalm as a next step. |
Beta Was this translation helpful? Give feedback.
-
|
You're right that Here's a layered CI that actually fails when the code is broken: name: Validate PHP
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
- with:
- php-version: '8.2'
- coverage: none
- tools: composer, phpstan
- name: Syntax check
- run: find . -name "*.php" -not -path "./vendor/*" -print0 | xargs -0 -n1 -P4 php -l
- name: Install deps
- run: composer install --no-interaction --prefer-dist
- name: Static analysis (PHPStan)
- run: vendor/bin/phpstan analyse --no-progress --level=5 src/
- name: Smoke-test endpoints
- run: |
- php -S 127.0.0.1:8000 -t public/ > server.log 2>&1 &
- SERVER_PID=$!
- sleep 2
- for route in / /health /api/users; do
- BODY=$(curl -sS "http://127.0.0.1:8000$route")
- if echo "$BODY" | grep -qiE "parse error|fatal error|uncaught|warning:"; then
- echo "::error::Broken response on $route"
- echo "$BODY"
- kill $SERVER_PID
- exit 1
- fi
- done
- kill $SERVER_PID
- ```
The trick for the built-in server is grepping the response body, not the HTTP code. The 200 you're seeing is the server saying "I replied", not "the script worked". Fatal errors get printed into the response body with words like "Parse error" or "Fatal error", so you match on those (with `display_errors=On`, which is the default in the built-in server).
PHPStan at level 5 or 6 catches the class of bugs you're after: typos in method names, calling things on nulls, wrong argument counts. Psalm does the same. Either needs `composer install` to have run first so it can see your classes.
If you have a test suite, add `vendor/bin/phpunit` before the smoke test. That's where you actually catch business-logic bugs. Static analysis and syntax checks only catch a subset of them.
One more thing: the warnings you quoted ("PHP error detection is completely broken on this runner") read like AI output rather than real PHP output. The runner is fine, `php -l` and `phpstan` behave the same on GitHub runners as they do locally. If you share the exact step that produced those, I can help narrow it down further. |
Beta Was this translation helpful? Give feedback.
-
|
"Works locally but not in Actions" on PHP is almost always one of three things:
For logic-level validation (undefined vars, wrong method calls, type mismatches, unreachable code) you want static analysis, not the built-in server. PHPStan is the go-to: - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- run: composer install --no-progress --prefer-dist
- run: vendor/bin/phpstan analyse --no-progress --error-format=githubMinimal It exits non-zero on any reported issue and surfaces them inline on the PR thanks to For runtime smoke tests (to catch 500s hidden behind 200s from the built-in server), assert on the response body, not the status code: - name: Smoke test
run: |
php -S localhost:8000 -t public &
sleep 2
response=$(curl -s http://localhost:8000/index.php)
echo "$response"
echo "$response" | grep -qiE "parse error|fatal error|warning:" && exit 1 || exit 0That grep catches the hidden errors the built-in server swallows. Pair that with PHPStan and you get both "logic is sane" and "runtime doesn't explode" checks. If you can share the workflow yaml and the output of the failing step, happy to tell you what's different between CI and your local run. |
Beta Was this translation helpful? Give feedback.
-
|
"I created a syntax error to see if the job would fail. I was not able to trigger the error." That's the key thing to debug. If
- name: Debug - list PHP files found
run: find . -name "*.php" -not -path "./vendor/*"If your broken file doesn't appear in the output, the
- name: Validate PHP syntax
run: |
find . -name "*.php" -not -path "./vendor/*" -print0 | xargs -0 -n1 php -l
echo "Exit: $?"Or even simpler to test: just hardcode the broken file directly: - name: Test lint
run: php -l path/to/your/broken_file.phpIf that doesn't fail either, post the exact error message you put in the file and the step output. At that point it's something specific to your runner setup. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
-
🏷️ Discussion Type
Question
💬 Feature/Topic Area
ARC (Actions Runner Controller)
Discussion Details
I have a simple GitHub workflow that runs git pull on the server via SSH. This is a simple PHP script.
I want to add validation of the code before I update the server so I don't upload broken PHP code.
But in return, the PHP building server returns 200 even when there is an error.
And give errors when I try to enforce the checks.
What can I do to validate that the PHP code actually works in GitHub Actions?
Beta Was this translation helpful? Give feedback.
All reactions