Home

Awesome

PHPQA

Analyze PHP code with one command.

License Latest Stable Version Total Downloads Build Status Windows status

Requirements

Why?

Every analyzer has different arguments and options in different formats (no surprise in PHP world :). If you ever tried to get ignoring directories to work then you know what I mean. On the other hand CLI tools are cool because you can analyze any directory or file. Unfortunately Jenkins, Travis, Scrutiziner needs special configuration file. What if you want to analyze every bundle in your Symfony app? Will you create e.g. Jenkins project/task for each bundle?

Available tools

ToolDescription
phplocMeasure the size of a PHP project
phpcpdCopy/Paste Detector (CPD) for PHP code
phpcsDetect violations of a coding standard
pdependPHP adaptation of JDepend
phpmdScan PHP project for messy code
phpmetricsStatic analysis tool for PHP
Suggested tools

Newly added tools aren't preinstalled. You have to install relevant composer packages if you want to use them.

ToolPHPSupported sinceDescription
security-checker>= 5.61.24Check composer.lock for known security issues
php-cs-fixer>= 5.31.12Automatically detect and fix PHP coding standards issues
phpunit>= 5.31.13The PHP Unit Testing framework
phpstan>= 7.01.9Discover bugs in your code without running it
psalm>= 5.61.14A static analysis tool for finding errors in PHP applications
parallel-lint>= 5.41.9Check syntax of PHP files
deptrac>= 7.21.25Enforce rules for dependencies between software layers
MacFJA/phpqa-extensions--PHP Assumptions, Magic Number Detector, ...

Tip: use bin/suggested-tools.sh install for installing the tools.

Install

Clone + composer

# install phpqa
git clone https://github.com/EdgedesignCZ/phpqa.git && cd phpqa && composer install --no-dev

# make phpqa globally accessible
## you can symlink binary
sudo ln -s /path-to-phpqa-repository/phpqa /usr/bin/phpqa
## or add this directory to your PATH in your ~/.bash_profile (or ~/.bashrc)
export PATH=~/path-to-phpqa-repository-from-pwd:$PATH

Composer

# global installation
composer global require edgedesign/phpqa --update-no-dev
# Make sure you have ~/.composer/vendor/bin/ in your PATH.

# local installation
composer require edgedesign/phpqa --dev

Of course you can add dependency to require-dev section in your composer.json. But I wouldn't recommend it. In my experience one QA tool which analyzes N projects is better than N projects with N analyzers. It's up to you how many repositories you want to update when new version is released.

Symfony3 components

Symfony3 is supported since version 1.7. Install at least version ~3.0 of sebastian/phpcpd, otherwise you'll get error The helper "progress" is not defined.

{
    "require-dev": {
        "edgedesign/phpqa": ">=1.7",
        "sebastian/phpcpd": "~3.0"
    }
}
Fake global installation in local project

Do you have problems with dependencies and you can't install phpqa globally? Install phpqa in subdirectory.

#!/bin/sh

if [ ! -f qa/phpqa ];
then
    echo "installing phpqa"
    (git clone https://github.com/EdgedesignCZ/phpqa.git ./qa  && cd qa && composer install --no-dev)
fi

qa/phpqa

Docker

Official docker image repository is https://github.com/EdgedesignCZ/phpqa/pkgs/container/phpqa. Images can be used at Gitlab CI.

docker run --rm -it ghcr.io/edgedesigncz/phpqa:v1.26.2-php7.2 phpqa tools
# using a tool without phpqa
docker run --rm -it ghcr.io/edgedesigncz/phpqa:v1.26.2-php7.2 phploc -v
ImagePHP versionComposer versionTools versions
ghcr.io/edgedesigncz/phpqa:v1.26.2-php7.27.21.8.0Versions that supports symfony2 components from default composer.lock. Not latest versions.
ghcr.io/edgedesigncz/phpqa:v1.26.2-php8.18.12.2.12Generally, latest versions available at the moment. If you need different versions, then build custom docker image

Beware that images as lean as possible. That can be a problem for running PHPUnit tests. In that case, you might need different PHP version, miss PHP extensions for database etc. You can install phpqa in another php image Or build custom docker image.

docker run --rm -it ghcr.io/edgedesigncz/phpqa:v1.26.2-php7.2 sh -c "php --version && composer --version && composer outdated --direct --all && phpqa tools"
docker run --rm -it ghcr.io/edgedesigncz/phpqa:v1.26.2-php8.1 sh -c "php --version && composer --version && composer outdated --direct --all && phpqa tools"

There are also available images eko3alpha/docker-phpqa and sparkfabrik/docker-phpqa. phpqa is used as an entrypoint (I haven't been able to use these images at Gitlab CI + Windows probably needs different environment variable).

docker run --rm -u $UID -v $PWD:/app eko3alpha/docker-phpqa --report --ignoredDirs vendor,build,migrations,test

Analyze

CommandDescription
phpqa --helpShow help - available options, tools, default values, ...
phpqa --analyzedDirs ./ --buildDir ./buildAnalyze current directory and save output to build directory
phpqa --analyzedDirs src,testsAnalyze source and test directory (phpmetrics analyzes only src)
phpqa --analyzedDir ./Deprecated in v1.8 in favor of --analyzedDirs
phpqa --ignoredDirs build,vendorIgnore directories
phpqa --ignoredFiles RoboFile.phpIgnore files
phpqa --tools phploc,phpcsRun only selected tools
phpqa --tools phpmd:1,phpcs:0,phpcpd:0Check number of errors and exit code. New in v1.6
phpqa --verboseShow output from executed tools
phpqa --quietShow no output at all
phpqa --output cliCLI output instead of creating files in --buildDir
phpqa --execution no-parallelDon't use parallelism if --execution != parallel
phpqa --config ./my-configUse custom configuration
phpqa --reportBuild html reports
phpqa --report offlineBuild html reports with bundled assets. New in v1.16
phpqa toolsShow versions of available tools

Tip: CLI options can be defined in .phpqa.yml

Output modes

Tool--output file (default) - generated files--output cli
phplocphploc.xml
phpcpdphpcpd.xml
phpcscheckstyle.xmlfull report
pdependpdepend-jdepend.xml, pdepend-summary.xml, pdepend-dependencies.xml, pdepend-jdepend.svg, pdepend-pyramid.svg
phpmdphpmd.xml
phpmetricsphpmetrics.html (v1), phpmetrics/index.html (v2), phpmetrics.xml
php-cs-fixerphp-cs-fixer.html
parallel-lintparallel-lint.html
phpstanphpstan.html, phpstan-phpqa.neon, phpstan-phpqa.neon
psalmpsalm.html, psalm.xml, psalm-phpqa.xml, psalm-phpqa.xml
deptracdeptrac.html

Exit code

phpqa can return non-zero exit code since version 1.6. It's optional feature that is by default turned off. You have to define number of allowed errors for phpcpd, phpcs, phpmd in --tools.

modeSupported versionWhat is analyzed?
--output file>= 1.6Number of errors in XML files, or exit code for tools without XML
--output cli>= 1.9Exit code

Let's say your Travis CI or Circle CI build should fail when new error is introduced. Define number of allowed errors for each tools and watch the build:

phpqa --report --tools phpcs:0,phpmd:0,phpcpd:0,parallel-lint:0,phpstan:0,phpmetrics,phploc,pdepend

Number of allowed errors can be also defined in .phpqa.yml.

phpqa:
    # can be overriden by CLI: phpqa --tools phpcs:1
    tools:
        - phpcs:0

File mode

screenshot from 2016-07-23 13 53 34

Tip: override phpcs.ignoreWarnings if you want to count just errors without phpcs warnings.

CLI mode

screenshot from 2016-12-21 14 31 27

Tip: use echo $? for displaying exit code.

Advanced configuration - .phpqa.yml

Provide CLI options from .phpqa.yml:

CLI option.phpqa.yml
phpqa --analyzedDirs ./phpqa.analyzedDirs: ./
phpqa --buildDir ./build phpqa.buildDir: ./build
phpqa --ignoredDirs build,vendorphpqa.ignoredDirs: build,vendor
phpqa --ignoredFiles RoboFile.phpphpqa.ignoredFiles: RoboFile.php
phpqa --tools phploc,phpcs:0phpqa.tools: phploc,phpcs:0
phpqa --reportphpqa.report: true
phpqa --execution no-parallelphpqa.execution: no-parallel
phpqa --output cli phpqa.output: cli
phpqa --verbosephpqa.verbose: true

Files

.phpqa.yml is automatically detected in current working directory, but you can specify directory via option:

# use .phpqa.yml from defined directory
phpqa --config path-to-directory-with-config

You don't have to specify full configuration. Missing or empty values are replaced with default values from our .phpqa.yml. Example of minimal config that defines only standard for CodeSniffer:

phpcs:
    standard: Zend

Tip: use PHP Coding Standard Generator for generating phpcs/phpmd standards.

You can specify multiple configurations directory (separated by ,). They are loaded in the order they are defined. This can be useful if you have a common configuration file that you want to use across multiple project but you still want to have per project configuration. Also, path inside configuration file are relative to where the configuration file is, so if you have a package that bundle a custom tool, the .phpqa.yml in the package can refers files within it.

phpqa --config ~/phpqa/,my-config/,$(pwd)

Custom binary

Every tool can define custom binary. Use phar or global tool, if you have troubles with dependencies, e.g.:

Generally, composer installation is preferred because of detecting version. Phar works too, but it might be tricky. If a tool has composer package with phar (e.g. vimeo/phar), use it instead of custom binary:

psalm:
    binary: /usr/local/bin/psalm.phar

Possibilities are infinite. You can define new tool and run it. For example I like exploring codebase in phpmetrics v1 and composer info in v2. Install phpmetrics v2 in composer and use phar for v1 to avoid phpstan conflicts:

$ cat tests/.ci/.phpqa.yml
phpmetricsV1:
    binary: /usr/local/bin/phpmetrics.phar
tool:
    phpmetricsV1: Edge\QA\Tools\Analyzer\PhpMetrics

$ phpqa --config tests/.ci/ --tools phpmetricsV1,phpmetrics

Override tools' settings

ToolSettingsDefault ValueYour value
phpqa.extensionsPHP File extensionsphpName of php file to parse, you can specify it like a string php,inc,modules or like a yaml array.
phpcs.standardCoding standardPSR2Name of existing standard (PEAR, PHPCS, PSR1, PSR2, Squiz, Zend), or path to your coding standard. To specify multiple standards, you can use an array
phpcs.ignoreWarningsIf number of allowed errors is compared with warnings+errors, or just errors from checkstyle.xmlfalseBoolean value
phpcs.reportsReport typesfull report in cli mode, checkstyle in file modePredefined report types or custom reports
php-cs-fixer.rulesCoding standard rules@PSR2String value
php-cs-fixer.allowRiskyRulesWhether risky rules may runfalseBoolean value
php-cs-fixer.configLoad configuration from filenullPath to .phpcs file
php-cs-fixer.isDryRunIf code is just analyzed or fixers are appliedtrueBoolean value
phpmetrics.configConfiguration for phpmetrics v1nullPath to .phpmetrics.yml file
phpmetrics.gitphpmetrics v2 analyses based on Git HistorynullBoolean value or path to git binary
phpmetrics.junitphpmetrics v2 evaluates metrics according to JUnit logsnullPath to JUnit xml
phpmetrics.composerphpmetrics v2 analyzes composer dependenciesnullPath to composer.json when the file is not included in analyzedDirs
pdepend.coverageReportLoad Clover style CodeCoverage reportnullPath to report produced by PHPUnit's --coverage-clover option
phpmd.standardRulesetEdgedesign's standardPath to ruleset. To specify multiple rule sets, you can use an array
phpmd.ignoreParsingErrorsIf parsing errors affect exit code, or just violationstrueBoolean value
phpcpdMinimum number of lines/tokens for copy-paste detection5 lines, 70 tokens
phpstanLevel, config file, memory limitLevel 0, %currentWorkingDirectory%/phpstan.neon, memoryLimit: nullTake a look at phpqa config in tests/.ci
phpunit.binaryPhpunit binaryphpqa's phpunitPath to phpunit executable in your project, typically vendor/bin/phpunit
phpunit.configPHPUnit configuration, analyzedDirs and ignoredDirs are not used, you have to specify test suites in XML filenullPath to phpunit.xml file
phpunit.reportsReport typesno reportList of reports and formats, corresponds with CLI option, e.g. --log-junit is log: [junit] in .phpqa.yml
psalm.configPsalm configuration, analyzedDirs and ignoredDirs are appended to projectFilesPredefined configPath to psalm.xml file
psalm.deadCodeEnable or not --find-dead-code option of psalmfalseBoolean value
psalm.threadsSet the number of process to use in parallel (option --threads of psalm) (Only if --execution == parallel for phpqa)1Number (>= 1)
psalm.showInfoDisplay or not information (non-error) messages (option --show-info= of psalm)trueBoolean value
psalm.memoryLimitCustom memory limit, ignore unless you are getting Fatal error: Allowed memory size of ... bytes exhaustednullString value, e.g. '1024M', '1G'
deptrac.depfileComplete deptract config (phpqa won't update source and excluded files)nullPath to depfile.yml file
security-checker.composerLockUse it if composer.lock is not in current working directory or analyzed directorynullPath to composer.lock file

HTML reports

If you don't have Jenkins or other CI server, then you can use HTML reports. HTML files are built when you add option --report. Take a look at report from phpqa.

# build html reports
phpqa --report

Custom templates

Define custom templates if you don't like default templates. You have to define path to xsl files in your .phpqa.yml:

# use different template for PHPMD, use default for other tools
report:
    phpmd: my-templates/phpmd.xsl

Be aware that all paths are relative to .phpqa.yml. Don't copy-paste section report if you don't have custom templates!

Requirements

xsl extension must be installed and enabled for exporting HTML reports. Otherwise you'll get error PHP Fatal error: Class 'XSLTProcessor' not found.

# install xsl extension in Ubuntu
sudo apt-get update
sudo apt-get install php5-xsl
sudo service apache2 restart

Continuous integration

We use Jenkins-CI in Edgedesign. Below you can find examples of Phing, Robo and bash tasks.

Project with one directory

Typically in Symfony project you have project with src directory with all the code and tests. So you don't need ignore vendors, web directory etc.

Phing - build.xml

<target name="ci-phpqa">
    <exec executable="phpqa" passthru="true">
        <arg value="--analyzedDirs=./src" />
        <arg value="--buildDir=./build/logs" />
        <arg value="--report" />
    </exec>
</target>

Robo - RoboFile.php

public function ciPhpqa()
{
    $this->taskExec('phpqa')
        ->option('analyzedDirs', './src')
        ->option('buildDir', './build/logs')
        ->option('report')
        ->run();
}

Project with multiple directories (src, tests, ...)

When you analyze root directory of your project don't forget to ignore vendors and other non-code directories. Otherwise the analysis could take a very long time.

Since version 1.8 phpqa supports analyzing multiple directories. Except phpmetrics that analyzes only first directory. Analyze root directory and ignore other directories if you rely on phpmetrics report.

Phing - build.xml

<target name="ci-phpqa">
    <exec executable="phpqa" passthru="true">
        <arg value="--analyzedDirs=./" />
        <arg value="--buildDir=./build/logs" />
        <arg value="--ignoredDirs=app,bin,build,vendor,web" />
        <arg value="--ignoredFiles= " />
        <arg value="--verbose" />
        <arg value="--report" />
    </exec>
</target>

Robo - RoboFile.php

public function ciPhpqa()
{
    $this->taskExec('phpqa')
        ->option('verbose')
        ->option('report')
        ->option('analyzedDirs', './')
        ->option('buildDir', './build')
        ->option('ignoredDirs', 'build,bin,vendor')
        ->option('ignoredFiles', 'RoboFile.php,error-handling.php')
        ->run();
}

Bash

phpqa --verbose --report --analyzedDirs ./ --buildDir ./var/CI --ignoredDirs=bin,log,temp,var,vendor,www

Circle.ci - artifacts + global installation

machine:
    php:
        version: 7.0.4

dependencies:
    cache_directories:
        - ~/.composer/cache
    post:
        - 'git clone https://github.com/EdgedesignCZ/phpqa.git ./qa && cd qa && composer install --no-dev'

test:
    override:
        - vendor/bin/phpunit --testdox-html ./var/tests/testdox.html --testdox-text ./var/tests/testdox.txt --log-junit $CIRCLE_TEST_REPORTS/phpunit/junit.xml
        - qa/phpqa --report --verbose --buildDir var/QA --ignoredDirs vendor --tools=phpcs:0,phpmd:0,phpcpd:0,phploc,pdepend,phpmetrics
    post:
        - cp -r ./var/QA $CIRCLE_ARTIFACTS
        - cp -r ./var/tests $CIRCLE_ARTIFACTS

Gitlab.ci - docker installation + composer cache + artifacts

stages:
  - test

test:
  stage: test
  image: ghcr.io/edgedesigncz/phpqa:v1.26.2-php7.2
  variables:
    BACKEND_QA: "*/backend/var/QA"
    BACKEND_CACHE: $CI_PROJECT_DIR/.composercache
  cache:
    paths:
    - $BACKEND_CACHE
  script:
    - 'export COMPOSER_CACHE_DIR=$BACKEND_CACHE'
    - 'composer install --ignore-platform-reqs --no-progress --no-suggest'
    - 'phpqa --report --tools phpcs:0,phpunit:0 --buildDir var/QA --analyzedDirs ./ --ignoredDirs var,vendor'
  artifacts:
    when: always
    paths:
    - $BACKEND_QA

Github actions - docker installation + composer cache + artifacts

name: QA

on: [push]

jobs:
  qa:
    container: ghcr.io/edgedesigncz/phpqa:v1.26.2-php8.1
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      # composer is not necessary, if you are not running phpunit/psalm/phpstan
      - name: Cache composer
        uses: actions/cache@v2
        with:
          path: |
            ~/.composer/cache
            vendor
          key: php-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: "php-composer-74"
      - name: Install dependencies
        run: |
          composer install --no-interaction --no-progress --ignore-platform-reqs;

      - name: phpqa
        run: phpqa --report --tools phpunit:0,phpcs:0,phpmd:0,psalm:0,phpstan:0 --buildDir build --analyzedDirs ./ --ignoredDirs build,vendor

      - name: Upload QA files
        uses: actions/upload-artifact@v2
        with:
          name: phpqa
          path: build

Contributing

Contributions from others would be very much appreciated! Send pull request/issue. Thanks!

License

Copyright (c) 2015 - present Edgedesign.cz. MIT Licensed, see LICENSE for details.