diff --git a/.cspell-project-words.txt b/.cspell-project-words.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f8c6cbdbd8de28434f0dab75c01df87a35a7e63e
--- /dev/null
+++ b/.cspell-project-words.txt
@@ -0,0 +1,7 @@
+analysing
+analyse
+larowlan
+analysed
+covidsafe
+ffaa
+mqnay
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c47e1223287b2d62b0de07a21abf984526153973
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,22 @@
+include:
+  ################
+  # DrupalCI includes:
+  # As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
+  # View these include files at https://git.drupalcode.org/project/gitlab_templates/
+  ################
+  - project: $_GITLAB_TEMPLATES_REPO
+    ref: $_GITLAB_TEMPLATES_REF
+    file:
+      - '/includes/include.drupalci.main.yml'
+      - '/includes/include.drupalci.variables.yml'
+      - '/includes/include.drupalci.workflows.yml'
+
+
+variables:
+  _TARGET_DB_VERSION: "$CORE_MYSQL_MAX"
+  OPT_IN_TEST_PREVIOUS_MINOR: 1
+  OPT_IN_TEST_PREVIOUS_MAJOR: 1
+  OPT_IN_TEST_NEXT_MINOR: 1
+  OPT_IN_TEST_NEXT_MAJOR: 1
+
+
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..76d65dafd121f43374669e2defb3ebe8d1be0f35
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ruleset name="drupal-project">
+  <description>PHP CodeSniffer configuration for a larowlan project.</description>
+  <rule ref="Drupal"/>
+  <arg name="extensions" value="php,inc,module,install,info,test,profile,theme"/>
+  <rule ref="Drupal.Commenting.DocComment.MissingShort">
+    <severity>0</severity>
+  </rule>
+
+  <!-- We can leave the following to PHPstan. -->
+  <rule ref="Drupal.Commenting.VariableComment.MissingVar">
+    <severity>0</severity>
+  </rule>
+</ruleset>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000000000000000000000000000000000000..83a78fc8fe52d2977a7f0a8c3ce32a19a02f5147
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,16 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Parameter \\$context of method Drupal\\\\filter_format_audit\\\\Form\\\\RunAnalysisForm\\:\\:analyse\\(\\) has invalid type DrushBatchContext\\.$#"
+			count: 1
+			path: src/Form/RunAnalysisForm.php
+
+		-
+			message: "#^Parameter \\$context of method Drupal\\\\filter_format_audit\\\\Form\\\\RunAnalysisForm\\:\\:clear\\(\\) has invalid type DrushBatchContext\\.$#"
+			count: 1
+			path: src/Form/RunAnalysisForm.php
+
+		-
+			message: "#^Call to static method decode\\(\\) on an unknown class Drupal\\\\Core\\\\Serialization\\\\Yaml\\.$#"
+			count: 1
+			path: tests/src/Kernel/FilterFormatAnalysisTest.php
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000000000000000000000000000000000000..f5a772d72c63839dee5300555c056880c377a265
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,10 @@
+includes:
+  - phpstan-baseline.neon
+parameters:
+  level: 1
+  paths:
+    - src
+    - tests
+  ignoreErrors:
+    - '#Unsafe usage of new static#'
+  reportUnmatchedIgnoredErrors: true
diff --git a/src/AnalysisBatchBuilder.php b/src/AnalysisBatchBuilder.php
index 37b6ead717f81b40f725eec07ecba133b68578fc..2743465630079b993d70043da0969ca08f955512 100644
--- a/src/AnalysisBatchBuilder.php
+++ b/src/AnalysisBatchBuilder.php
@@ -65,7 +65,8 @@ final class AnalysisBatchBuilder {
             ->accessCheck(FALSE)
             ->condition("$field_name.format", array_keys($formats), 'IN');
           $results = $query->execute();
-        } catch (QueryException $e) {
+        }
+        catch (QueryException $e) {
           // Corrupt field map.
           continue;
         }
diff --git a/src/ContentAnalysis.php b/src/ContentAnalysis.php
index b238ebbae81509d747f93e032b357974b46ae314..9c4aaf03711217ef893946f674838e5482f32a0f 100644
--- a/src/ContentAnalysis.php
+++ b/src/ContentAnalysis.php
@@ -2,11 +2,11 @@
 
 namespace Drupal\filter_format_audit;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
-use Drupal\filter_format_audit\Entity\AnalysisResult;
-use Drupal\Component\Utility\Html;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\filter_format_audit\Entity\AnalysisResult;
 
 /**
  * Defines a class for analysing imported content.
@@ -37,7 +37,7 @@ class ContentAnalysis {
    */
   public function __construct(
     EntityTypeManagerInterface $entity_type_manager,
-    ConfigFactoryInterface $config_factory
+    ConfigFactoryInterface $config_factory,
   ) {
     $this->entityTypeManager = $entity_type_manager;
     $this->configFactory = $config_factory;
diff --git a/src/EntityHandlers/FilterFormatAuditHandlerDefault.php b/src/EntityHandlers/FilterFormatAuditHandlerDefault.php
index 827e6e986d3df1e6a9395a8f15e83fb2b7397ad2..a42e2baf44634f2f9dff2a3410b835e49ef2f0ab 100644
--- a/src/EntityHandlers/FilterFormatAuditHandlerDefault.php
+++ b/src/EntityHandlers/FilterFormatAuditHandlerDefault.php
@@ -6,8 +6,8 @@ use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityHandlerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Url;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -26,6 +26,7 @@ class FilterFormatAuditHandlerDefault implements FilterFormatAuditHandlerInterfa
    * Constructs a new FilterFormatAuditHandlerDefault.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager service.
    */
   public function __construct(EntityTypeManagerInterface $entityTypeManager) {
     $this->entityTypeManager = $entityTypeManager;