diff --git a/.phpmd.xml b/.phpmd.xml
new file mode 100644
index 0000000000000000000000000000000000000000..79c17910ce24ce4b6e7f6c931ecc59d53b0d9fb6
--- /dev/null
+++ b/.phpmd.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PMD Ruleset for Drupal" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
+<description>
+  A PMD Ruleset for Drupal coding standards.
+</description>
+
+<!--
+  Include each rule explicitly so we know what we have.
+  @see https://github.com/phpmd/phpmd/blob/master/src/main/resources/rulesets/
+-->
+
+<!-- Clean Code -->
+<!--
+These don't align with Drupal standards, so they are excluded.
+@todo Static calls are hard to test and extend, is there a way to whitelist the ones that are OK?
+<rule ref="rulesets/cleancode.xml/BooleanArgumentFlag"/>
+<rule ref="rulesets/cleancode.xml/ElseExpression"/>
+<rule ref="rulesets/cleancode.xml/StaticAccess"/>
+-->
+
+<!-- Code Size -->
+<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
+<rule ref="rulesets/codesize.xml/NPathComplexity"/>
+<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
+<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
+<rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>
+<rule ref="rulesets/codesize.xml/ExcessivePublicCount"/>
+<rule ref="rulesets/codesize.xml/TooManyFields"/>
+
+<!-- Controversial -->
+<rule ref="rulesets/controversial.xml/Superglobals"/>
+<!--
+These checks do not need to be included since PHPCS will check for style.
+<rule ref="rulesets/controversial.xml/CamelCaseClassName"/>
+<rule ref="rulesets/controversial.xml/CamelCasePropertyName"/>
+<rule ref="rulesets/controversial.xml/CamelCaseMethodName"/>
+<rule ref="rulesets/controversial.xml/CamelCaseParameterName"/>
+<rule ref="rulesets/controversial.xml/CamelCaseVariableName"/>
+-->
+
+<!-- Design -->
+<rule ref="rulesets/design.xml/ExitExpression"/>
+<rule ref="rulesets/design.xml/EvalExpression"/>
+<rule ref="rulesets/design.xml/GotoStatement"/>
+<rule ref="rulesets/design.xml/NumberOfChildren"/>
+<rule ref="rulesets/design.xml/DepthOfInheritance"/>
+<rule ref="rulesets/design.xml/CouplingBetweenObjects"/>
+<rule ref="rulesets/design.xml/DevelopmentCodeFragment"/>
+
+<!-- Naming -->
+<rule ref="rulesets/naming.xml/ShortVariable">
+  <properties>
+    <!-- Allow $id and $op as a variable name. -->
+    <property name="exceptions" description="Comma-separated list of exceptions" value="id,op"/>
+  </properties>
+</rule>
+<rule ref="rulesets/naming.xml/LongVariable">
+  <properties>
+    <!-- Bump variable length to a more reasonable number. -->
+    <property name="maximum" description="The variable length reporting threshold" value="35"/>
+  </properties>
+</rule>
+<rule ref="rulesets/naming.xml/ShortMethodName"/>
+<rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass"/>
+<rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
+<rule ref="rulesets/naming.xml/BooleanGetMethodName"/>
+
+<!-- Unused Code -->
+<rule ref="rulesets/unusedcode.xml/UnusedPrivateField"/>
+<!-- <rule ref="rulesets/unusedcode.xml/UnusedLocalVariable"/> -->
+<rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod"/>
+<!--
+Hooks often have unused parameters, so ignore this warning.
+@todo is there a way to allow unused parameters in hooks but not elsewhere?
+<rule ref="rulesets/unusedcode.xml/UnusedFormalParameter"/>
+-->
+
+</ruleset>
\ No newline at end of file
diff --git a/.phpqa.yml b/.phpqa.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2229e00984c3719fd986adcf0db462d8db46fdb6
--- /dev/null
+++ b/.phpqa.yml
@@ -0,0 +1,66 @@
+# ==============================================================================
+# Default common config for Phpqa.
+#
+# For more options see:
+#   https://github.com/EdgedesignCZ/phpqa/blob/master/.phpqa.yml
+# ==============================================================================
+
+phpqa:
+  analyzedDirs: ./
+  buildDir: ./tests/code-quality
+  ignoredDirs: ".gitlab-ci,.git,.idea,bin,docroot,node_modules,private,.tests/assets,vendor,web"
+  ignoredFiles: ""
+  report: true
+  # verbose: true
+  # execution: no-parallel
+  tools:
+    - phpmetrics
+    - phploc
+    - phpcs
+    - phpmd
+    - pdepend
+    - phpcpd
+    - phpstan
+    - security-checker
+  extensions:
+    - php
+    - inc
+    - module
+    - install
+    - test
+    - profile
+    - theme
+    - info
+    - txt
+    - md
+
+phpcs:
+  standard: Drupal
+  ignoreWarnings: true
+  # https://github.com/squizlabs/PHP_CodeSniffer/wiki/Reporting
+  reports:
+    cli:
+      - full
+    file:
+      checkstyle: checkstyle.xml
+
+pdepend:
+# coverageReport: build/coverage-clover.xml
+
+phpmd:
+  standard: ./.phpmd.xml
+
+phpstan:
+  level: 3
+  # https://github.com/phpstan/phpstan#configuration
+  standard: phpstan.neon
+
+phpcpd:
+  minLines: 5
+  minTokens: 70
+
+phpmetrics:
+  config: null
+  git: false
+
+
diff --git a/build.xml b/build.xml
index b497e3d03ca503090e5d5a1b58ba9fb8c1d446d1..d8e740a510f6168f579f9905aacfb07537a7504c 100644
--- a/build.xml
+++ b/build.xml
@@ -4,6 +4,7 @@
   <property name="drush" value="${project.basedir}/bin/drush" />
   <property name="composer" value="/usr/local/bin/composer" />
   <property name="npm" value="/usr/local/bin/npm" />
+  <property name="phpqa" value="${project.basedir}/bin/phpqa"/>
   <property name="rsync" value="/usr/bin/rsync" />
   <property name="db.type" value="mysql" />
   <property name="db.host" value="localhost" />
@@ -32,6 +33,7 @@
 
     <echo message="Found Drush: ${drush}" />
     <echo message="Found Composer: ${composer}" />
+    <echo message="Found phpqa: ${phpqa}" />
     <echo message="Found NPM: ${npm}" />
     <echo message="Found rsync: ${rsync}" />
   </target>
@@ -60,6 +62,14 @@
     <filesync destinationDir="." rsyncPath="${rsync}" sourceDir="${profile}/" verbose="false" exclude="libraries,modules/contrib,behat.local.yml" />
   </target>
 
+  <!-- Prepares the behat tests environment. -->
+  <target name="code-quality-check" depends="env">
+    <exec executable="${phpqa}" passthru="true">
+      <arg value="--analyzedDirs=./"/>
+      <arg value="--buildDir=./tests/code-quality"/>
+    </exec>
+  </target>
+
   <!-- Prepares the docroot for installation via the UI. -->
   <target name="preinstall" depends="uninstall">
     <if>