/commands/php/php-l-check-syntax
php -l file.php: check a PHP file for syntax errors (lint)
- tool
- PHP
- versions
- PHP 5.x – 8.x
- os
- linux, macos, windows
php -l file.php parses a PHP file without running it and reports syntax errors. A valid file prints No syntax errors detected in file.php and exits with status 0; a broken one prints the parse error with its line number, Errors parsing file.php, and exits with status 255. -l is short for --syntax-check.
Valid and invalid file
The example uses PHP 8.4 compiled to WebAssembly (php-wasm, from the WordPress Playground project) and calls its CLI with the same arguments as php in a terminal:
npm init -y >/dev/null
npm install --silent --no-audit --no-fund @php-wasm/node@3.1.56 @php-wasm/universal@3.1.56 >/dev/null 2>&1
cat > php.mjs <<'EOF'
// Minimal "php" command: copies *.php files into the virtual file system and runs the CLI
import fs from "node:fs";
import { PHP } from "@php-wasm/universal";
import { loadNodeRuntime } from "@php-wasm/node";
const php = new PHP(await loadNodeRuntime("8.4", { emscriptenOptions: { processId: 1 } }));
php.mkdir("/app");
for (const f of fs.readdirSync(".").filter((f) => f.endsWith(".php"))) php.writeFile(`/app/${f}`, fs.readFileSync(f));
php.chdir("/app");
const r = await php.cli(["php", ...process.argv.slice(2)]);
process.stdout.write(await r.stdoutText);
process.stdout.write(await r.stderrText);
process.exit(await r.exitCode);
EOF
printf '<?php\n$items = [1, 2, 3];\necho count($items), "\\n";\n' > good.php
printf '<?php\n$total = 0\nforeach ([1, 2] as $n) { $total += $n; }\n' > bad.php
node php.mjs -l good.php
node php.mjs -l bad.phpOutput · exit 255
No syntax errors detected in good.php Parse error: syntax error, unexpected token "foreach" in bad.php on line 3 Errors parsing bad.php PHP Parse error: syntax error, unexpected token "foreach" in bad.php on line 3
The missing semicolon on line 2 is reported on line 3, where the parser meets the unexpected foreach. The error appears twice: once on standard output (display_errors) and once on standard error with the PHP prefix (log_errors, which writes to stderr when no error_log file is set). With a different php.ini, you may see only one of them.
Lint a whole project
# every .php file below src/, stop on the first failure
find src -name '*.php' -print0 | xargs -0 -n1 php -l
# PHP 8.3+: several files in one call
php -l src/a.php src/b.php-n1 runs one php -l per file, which also works on PHP versions that accept only one file per call.
Options
| Command | What it does |
|---|---|
| php -l file.php | Syntax check only; nothing is executed |
| php --syntax-check file.php | Long form of -l |
| php -l -d display_errors=stderr file.php | Send the message to stderr only |
| php -n -l file.php | Ignore php.ini (no extensions or ini settings loaded) |
| php -v | Show which PHP version does the checking |
| php -r "code" | Run code instead of checking it, see php -r |
What it does not catch
php -l only finds what the parser rejects: missing semicolons or brackets, unclosed strings, invalid tokens, syntax that is too new for the interpreter. It does not run the file, so Call to undefined function, undefined variables, wrong argument types and runtime warnings such as headers already sent are not reported.
Confirm it worked
Check the exit status: php -l file.php; echo $? prints 0 for a valid file and 255 for a parse error, which is what CI jobs and Git pre-commit hooks test.
FAQ
Does php -l find undefined functions or wrong types?
No. It only parses the file. A call to a function that does not exist, a missing class or a type error appears only when the code runs, as Call to undefined function or a TypeError. Use a static analyser such as PHPStan or Psalm for those.
Can php -l check several files at once?
Since PHP 8.3 php -l accepts several file names. On older versions it checks only the first file, so loop with find ... -exec php -l {} \; or xargs -n1.
Which PHP version does php -l check against?
The version of the php binary you run. Code that uses newer syntax, such as enums or readonly properties, fails on an older interpreter; lint with the version you deploy.