Skip to content

Instantly share code, notes, and snippets.

@hollodotme
Last active December 17, 2020 08:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hollodotme/9c1b805e9a2f946433512563edc4b702 to your computer and use it in GitHub Desktop.
Save hollodotme/9c1b805e9a2f946433512563edc4b702 to your computer and use it in GitHub Desktop.
PHP linting shell script - suitable for usage in PHP alpine docker containers
#!/usr/bin/env sh
OPTIND=1
EXITCODE=0
# Default parallelization
PARALLELIZE=2
# Default file name pattern
FILENAME_PATTERN='*.php'
echo "PHP linting - looking for syntax errors in PHP files."
echo "by Holger Woltersdorf - https://github.com/hollodotme"
echo ""
helpFunction() {
echo ""
printf "Usage: %s [-p NUMBER] directory [directory2] [directory(n)] [...]\n\n" "$0"
printf "Example: %s -p4 -f'*.phtml' ./src ./tests ./public ./config\n\n" "$0"
printf "Every directory will be searched for PHP files.\n"
printf "Every PHP file will be checked for syntax errors using \"php -ln\"\n"
printf "If syntax errors were found the exit code is != 0\n\n"
printf "Options:\n"
printf " -f REGEX\tGlob pattern that matches the desired filenames (default: '${FILENAME_PATTERN}')\n"
printf " -h\t\tShow this help\n"
printf " -p NUMBER\tNumber of parallel processed checks (default: ${PARALLELIZE}, Number of CPUs recommended)\n"
echo ""
exit 1
}
if [ $# -eq 0 ]; then
helpFunction
fi
setParallelization() {
local value=${1}
local re='[0-9]+$'
if [[ $(expr "${value}" : "${re}") != 0 ]]; then
printf "ERROR: The value for parallelisation must be a NUMBER.\n\n" >&2
helpFunction
fi
PARALLELIZE=${value}
}
setFileNamePattern() {
local value=${1}
if [[ -z ${value// /} ]]; then
printf "ERROR: The value for the filename pattern is empty.\n\n" >&2
helpFunction
fi
FILENAME_PATTERN=${value}
}
checkIfDirExists() {
local dir=$1
if [ ! -d "${dir}" ]; then
printf "ERROR: Directory ${dir} does not exist.\n\n"
helpFunction
fi
}
runSyntaxCheckInDir() {
local dir="${1}"
local pattern="${2}"
local parallel="${3}"
checkIfDirExists ${dir}
printf "Checking %s ..." "${dir}"
find "${dir}" -type f -iname "${pattern}" -print0 \
| xargs -0 -n1 -P"${parallel}" php -derror_reporting=-1 -ln \
| (! grep -v "No syntax errors detected") >&2
}
while getopts "hp:f:" OPTION; do
case ${OPTION} in
p) setParallelization "${OPTARG}" ;;
f) setFileNamePattern "${OPTARG}" ;;
h) helpFunction ;;
esac
done
shift $((OPTIND - 1))
[ "${1:-}" = "--" ] && shift
echo ""
printf "Filename pattern:\t ${FILENAME_PATTERN}\n"
printf "Parallelization:\t ${PARALLELIZE}\n"
echo ""
for dir in "$@"; do
runSyntaxCheckInDir "${dir}" "${FILENAME_PATTERN}" "${PARALLELIZE}"
[[ $? -eq 0 ]] && printf "\e[32m ✔ OK\e[0m\n" || EXITCODE=1
done
exit ${EXITCODE}
@hollodotme
Copy link
Author

hollodotme commented Nov 9, 2020

Usage

Local run

sh phplint.sh -p4 -f'*.php' ./config ./public ./src ./tests

This means:
Check the directories ./config, ./public, ./src and ./tests for PHP files with the file extension .php and check each found file for PHP syntax errors with a parallelization of 4.

One-off run in a PHP alpine container

docker run \
  --interactive \
  --tty \
  --rm \
  --volume=$(shell pwd):/repo \
  --workdir=/repo \
  php:8.0-rc-cli-alpine \
  sh phplint.sh -p4 -f'*.php' ./config ./public ./src ./tests

# As one-liner
docker run -it --rm -v $(shell pwd):/repo -w /repo  php:8.0-rc-cli-alpine sh phplint.sh -p4 -f'*.php' ./config ./public ./src ./tests

Output / Result

If the code does not contain any syntax errors, the output will look like the following and the exit code is 0 (zero):

PHP linting - looking for syntax errors in PHP files.
by Holger Woltersdorf - https://github.com/hollodotme


Filename pattern:	 *.php
Parallelization:	 4

Checking ./config ... ✔ OK
Checking ./public ... ✔ OK
Checking ./src ... ✔ OK
Checking ./tests ... ✔ OK

If syntax errors were found (e.g. in ./src), it looks like the following and the exit code is 1 (one):

PHP linting - looking for syntax errors in PHP files.
by Holger Woltersdorf - https://github.com/hollodotme


Filename pattern:	 *.php
Parallelization:	 4

Checking ./config ... ✔ OK
Checking ./public ... ✔ OK
Checking ./src ...
Parse error: Unclosed '{' on line 8 in ./src/Application/Exceptions/FileDoesNotExist.php on line 11
Errors parsing ./src/Application/Exceptions/FileDoesNotExist.php
xargs: php: exited with status 255; aborting
Checking ./tests ... ✔ OK

@hollodotme
Copy link
Author

As suggested @BackEndTea, I added the PHP runtime option -d error_reporting=-1 for maximum error reporting, especially for deprications.

@holtkamp
Copy link

Noice!

On a Mac the parallelisation argument of the "local run" seems not detected properly...

sh phplint.sh -p4 -f'*.php' ./library                        
PHP linting - looking for syntax errors in PHP files.
by Holger Woltersdorf - https://github.com/hollodotme

expr: syntax error
ERROR: The value for parallelisation must be a NUMBER.

When using the default it workst:

sh phplint.sh -f'*.php' ./library      
PHP linting - looking for syntax errors in PHP files.
by Holger Woltersdorf - https://github.com/hollodotme


Filename pattern:	 *.php
Parallelization:	 2

Checking ./library ...

@hollodotme
Copy link
Author

@holtkamp Thanks for the pointer, I fixed the expr statement. Works on OSX and Alpine now.

--- if [[ $(expr match "${value}" $re) != 0 ]]; then
+++ if [[ $(expr "${value}" : "${re}") != 0 ]]; then

@holtkamp
Copy link

holtkamp commented Nov 11, 2020

@hollodotme, wow, quick reply. Thanks, both approaches now work from the command line:

sh phplint.sh -p6 -f'*.php' ./src
docker run -it --rm -v $(pwd):/repo -w /repo php:8.0-rc-cli-alpine sh phplint.sh -p6 -f'*.php' ./src

Usage in a Makefile
Note that to make the dockerized approach work when using a Makefile like this:

phplint:
	sh phplint.sh -p6 -f'*.php' ./src

phplint-docker:
	docker run -it --rm -v $(pwd):/repo -w /repo php:8.0-rc-cli-alpine sh phplint.sh -p6 -f'*.php' ./src

We should use replace $(pwd) with $(shell pwd) to prevent an error like: "sh: can't open 'phplint.sh': No such file or directory" 😉

phplint-docker:
	docker run -it --rm -v $(shell pwd):/repo -w /repo php:8.0-rc-cli-alpine sh phplint.sh -p6 -f'*.php' ./src

@hollodotme
Copy link
Author

@holtkamp Thanks! I changed the commands above to use $(shell pwd).

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