diff --git a/psalm.xml.dist b/psalm.xml.dist
index a7ac0ef..8b144b4 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -13,7 +13,15 @@
 >
     <issueHandlers>
         <!--
-            Psalm doesn't recognize some variables always being set because of prior validation.
+            This is a false-positive caused by Doctrine's EntityManagerDecorator.
+        -->
+        <MethodSignatureMismatch>
+            <errorLevel type="suppress">
+                <file name="src/EntityManager.php"/>
+            </errorLevel>
+        </MethodSignatureMismatch>
+        <!--
+            This is a false-positive caused by Doctrine's ArrayCollection.
         -->
         <PossiblyNullReference>
             <errorLevel type="suppress">
@@ -30,15 +38,6 @@
         -->
         <PropertyNotSetInConstructor errorLevel="suppress"/>
         <RedundantPropertyInitializationCheck errorLevel="suppress"/>
-        <!--
-            We deliberately want to evaluate empty strings as FALSE in those files.
-        -->
-        <RiskyTruthyFalsyComparison>
-            <errorLevel type="suppress">
-                <file name="src/Console/AddRecordCommand.php"/>
-                <file name="src/Console/AddSetCommand.php"/>
-            </errorLevel>
-        </RiskyTruthyFalsyComparison>
         <!--
             Those classes are dynamically used depending on the given OAI verb.
             @see src/Middleware/Dispatcher.php:95
diff --git a/src/Console.php b/src/Console.php
index 786a940..490e316 100644
--- a/src/Console.php
+++ b/src/Console.php
@@ -107,12 +107,12 @@ abstract class Console extends Command
     protected function getPhpMemoryLimit(): int
     {
         if (!isset($this->memoryLimit)) {
-            $ini = trim(ini_get('memory_limit'));
-            $limit = (int) $ini;
-            if ($limit < 0) {
+            $phpValue = (string) ini_get('memory_limit');
+            $limit = (int) $phpValue;
+            if ($limit <= 0) {
                 return -1;
             }
-            $unit = strtolower($ini[strlen($ini) - 1]);
+            $unit = strtolower($phpValue[strlen($phpValue) - 1]);
             switch ($unit) {
                 case 'g':
                     $limit *= 1024;
diff --git a/src/Console/AddRecordCommand.php b/src/Console/AddRecordCommand.php
index eee5280..b29a0db 100644
--- a/src/Console/AddRecordCommand.php
+++ b/src/Console/AddRecordCommand.php
@@ -42,13 +42,14 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'oai:records:add',
     description: 'Add or update a record in the database'
 )]
-class AddRecordCommand extends Console
+final class AddRecordCommand extends Console
 {
     /**
      * Configures the current command.
      *
      * @return void
      */
+    #[\Override]
     protected function configure(): void
     {
         $this->addArgument(
@@ -82,6 +83,7 @@ class AddRecordCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         if (!$this->validateInput($input, $output)) {
@@ -90,7 +92,7 @@ class AddRecordCommand extends Console
 
         /** @var Format */
         $format = $this->em->getMetadataFormat($this->arguments['format']);
-        $content = file_get_contents($this->arguments['file']) ?: '';
+        $content = (string) file_get_contents($this->arguments['file']);
 
         $record = new Record($this->arguments['identifier'], $format);
         if (trim($content) !== '') {
diff --git a/src/Console/AddSetCommand.php b/src/Console/AddSetCommand.php
index 27b0c53..ac64f62 100644
--- a/src/Console/AddSetCommand.php
+++ b/src/Console/AddSetCommand.php
@@ -40,13 +40,14 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'oai:sets:add',
     description: 'Add or update a set in the database'
 )]
-class AddSetCommand extends Console
+final class AddSetCommand extends Console
 {
     /**
      * Configures the current command.
      *
      * @return void
      */
+    #[\Override]
     protected function configure(): void
     {
         $this->addArgument(
@@ -79,6 +80,7 @@ class AddSetCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         if (!$this->validateInput($input, $output)) {
@@ -86,6 +88,7 @@ class AddSetCommand extends Console
         }
 
         if (array_key_exists('file', $this->arguments)) {
+            /** @psalm-suppress RiskyTruthyFalsyComparison */
             $description = file_get_contents($this->arguments['file']) ?: null;
         }
 
diff --git a/src/Console/CsvImportCommand.php b/src/Console/CsvImportCommand.php
index f66f7fb..2d6aecf 100644
--- a/src/Console/CsvImportCommand.php
+++ b/src/Console/CsvImportCommand.php
@@ -52,13 +52,14 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'oai:records:import:csv',
     description: 'Import records from a CSV file'
 )]
-class CsvImportCommand extends Console
+final class CsvImportCommand extends Console
 {
     /**
      * Configures the current command.
      *
      * @return void
      */
+    #[\Override]
     protected function configure(): void
     {
         $this->addArgument(
@@ -120,6 +121,7 @@ class CsvImportCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         if (!$this->validateInput($input, $output)) {
@@ -226,6 +228,7 @@ class CsvImportCommand extends Console
         $headers = array_flip($headers);
 
         $callback = function (string $column) use ($headers): ?int {
+            /** @psalm-suppress InvalidArgument */
             return array_key_exists($column, $headers) ? $headers[$column] : null;
         };
 
diff --git a/src/Console/DeleteRecordCommand.php b/src/Console/DeleteRecordCommand.php
index ab411a3..388089a 100644
--- a/src/Console/DeleteRecordCommand.php
+++ b/src/Console/DeleteRecordCommand.php
@@ -39,13 +39,14 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'oai:records:delete',
     description: 'Delete a record while obeying deleted record policy'
 )]
-class DeleteRecordCommand extends Console
+final class DeleteRecordCommand extends Console
 {
     /**
      * Configures the current command.
      *
      * @return void
      */
+    #[\Override]
     protected function configure(): void
     {
         $this->addArgument(
@@ -69,6 +70,7 @@ class DeleteRecordCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         if (!$this->validateInput($input, $output)) {
diff --git a/src/Console/PruneDeletedRecordsCommand.php b/src/Console/PruneDeletedRecordsCommand.php
index 5385936..b485e04 100644
--- a/src/Console/PruneDeletedRecordsCommand.php
+++ b/src/Console/PruneDeletedRecordsCommand.php
@@ -40,13 +40,14 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'oai:records:prune',
     description: 'Prune deleted records from database'
 )]
-class PruneDeletedRecordsCommand extends Console
+final class PruneDeletedRecordsCommand extends Console
 {
     /**
      * Configures the current command.
      *
      * @return void
      */
+    #[\Override]
     protected function configure(): void
     {
         $this->addOption(
@@ -66,13 +67,14 @@ class PruneDeletedRecordsCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         $policy = Configuration::getInstance()->deletedRecords;
-        $forced = (bool) $input->getOption('force');
+        $forced = $input->getOption('force');
         if (
             $policy === 'no'
-            or ($policy === 'transient' && $forced)
+            or ($policy === 'transient' && $forced === true)
         ) {
             $deleted = $this->em->pruneDeletedRecords();
             $this->clearResultCache();
diff --git a/src/Console/PruneResumptionTokensCommand.php b/src/Console/PruneResumptionTokensCommand.php
index 302ab03..74cec76 100644
--- a/src/Console/PruneResumptionTokensCommand.php
+++ b/src/Console/PruneResumptionTokensCommand.php
@@ -38,7 +38,7 @@ use Symfony\Component\Console\Output\OutputInterface;
     name: 'oai:tokens:prune',
     description: 'Prune expired resumption tokens from database'
 )]
-class PruneResumptionTokensCommand extends Console
+final class PruneResumptionTokensCommand extends Console
 {
     /**
      * Executes the current command.
@@ -48,6 +48,7 @@ class PruneResumptionTokensCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         $expired = $this->em->pruneExpiredTokens();
diff --git a/src/Console/UpdateFormatsCommand.php b/src/Console/UpdateFormatsCommand.php
index d617e65..1dc0952 100644
--- a/src/Console/UpdateFormatsCommand.php
+++ b/src/Console/UpdateFormatsCommand.php
@@ -41,7 +41,7 @@ use Symfony\Component\Validator\Exception\ValidationFailedException;
     name: 'oai:formats:update',
     description: 'Update metadata formats in database from configuration'
 )]
-class UpdateFormatsCommand extends Console
+final class UpdateFormatsCommand extends Console
 {
     /**
      * Executes the current command.
@@ -51,6 +51,7 @@ class UpdateFormatsCommand extends Console
      *
      * @return int 0 if everything went fine, or an error code
      */
+    #[\Override]
     protected function execute(InputInterface $input, OutputInterface $output): int
     {
         $formats = Configuration::getInstance()->metadataPrefix;
diff --git a/src/Entity/Format.php b/src/Entity/Format.php
index f749b7f..d58451b 100644
--- a/src/Entity/Format.php
+++ b/src/Entity/Format.php
@@ -37,7 +37,7 @@ use Symfony\Component\Validator\Exception\ValidationFailedException;
  */
 #[ORM\Entity(repositoryClass: FormatRepository::class)]
 #[ORM\Table(name: 'formats')]
-class Format extends Entity
+final class Format extends Entity
 {
     /**
      * The unique metadata prefix.
diff --git a/src/Entity/Record.php b/src/Entity/Record.php
index a8d20ed..640703e 100644
--- a/src/Entity/Record.php
+++ b/src/Entity/Record.php
@@ -42,7 +42,7 @@ use Symfony\Component\Validator\Exception\ValidationFailedException;
 #[ORM\Index(name: 'format_idx', columns: ['format'])]
 #[ORM\Index(name: 'last_changed_idx', columns: ['last_changed'])]
 #[ORM\Index(name: 'format_last_changed_idx', columns: ['format', 'last_changed'])]
-class Record extends Entity
+final class Record extends Entity
 {
     /**
      * The record identifier.
diff --git a/src/Entity/Set.php b/src/Entity/Set.php
index b051d3f..3d2d3b3 100644
--- a/src/Entity/Set.php
+++ b/src/Entity/Set.php
@@ -37,7 +37,7 @@ use Symfony\Component\Validator\Exception\ValidationFailedException;
  */
 #[ORM\Entity(repositoryClass: SetRepository::class)]
 #[ORM\Table(name: 'sets')]
-class Set extends Entity
+final class Set extends Entity
 {
     /**
      * The unique set spec.
diff --git a/src/Entity/Token.php b/src/Entity/Token.php
index 1a09ac9..ef21c00 100644
--- a/src/Entity/Token.php
+++ b/src/Entity/Token.php
@@ -40,7 +40,7 @@ use OCC\OaiPmh2\Repository\TokenRepository;
 #[ORM\Entity(repositoryClass: TokenRepository::class)]
 #[ORM\Table(name: 'tokens')]
 #[ORM\Index(name: 'valid_until_idx', columns: ['valid_until'])]
-class Token extends Entity
+final class Token extends Entity
 {
     /**
      * The resumption token.
diff --git a/src/EntityManager.php b/src/EntityManager.php
index 14e659f..56f992a 100644
--- a/src/EntityManager.php
+++ b/src/EntityManager.php
@@ -205,11 +205,11 @@ final class EntityManager extends EntityManagerDecorator
             ->setMaxResults($maxRecords);
         if (isset($from)) {
             $dql->andWhere($dql->expr()->gte('records.lastChanged', ':from'));
-            $dql->setParameter('from', new DateTime($from));
+            $dql->setParameter('from', new DateTime($from), 'datetime');
         }
         if (isset($until)) {
             $dql->andWhere($dql->expr()->lte('records.lastChanged', ':until'));
-            $dql->setParameter('until', new DateTime($until));
+            $dql->setParameter('until', new DateTime($until), 'datetime');
         }
         if (isset($set)) {
             $dql->innerJoin(
@@ -338,7 +338,7 @@ final class EntityManager extends EntityManagerDecorator
     public function isValidRecordIdentifier(string $identifier): bool
     {
         $records = $this->getRepository(Record::class)->findBy(['identifier' => $identifier]);
-        return (bool) count($records) > 0;
+        return count($records) > 0;
     }
 
     /**
@@ -369,7 +369,7 @@ final class EntityManager extends EntityManagerDecorator
         $dql = $this->createQueryBuilder();
         $dql->delete(Token::class, 'tokens')
             ->where($dql->expr()->lt('tokens.validUntil', ':now'))
-            ->setParameter('now', new DateTime());
+            ->setParameter('now', new DateTime(), 'datetime');
         /** @var int */
         return $dql->getQuery()->execute();
     }
diff --git a/src/Middleware.php b/src/Middleware.php
index bc8dbfc..c65ecec 100644
--- a/src/Middleware.php
+++ b/src/Middleware.php
@@ -142,6 +142,7 @@ abstract class Middleware extends AbstractMiddleware
      *
      * @return ServerRequestInterface The processed server request
      */
+    #[\Override]
     protected function processRequest(ServerRequestInterface $request): ServerRequestInterface
     {
         /** @var OaiRequestMetadata */
@@ -158,6 +159,7 @@ abstract class Middleware extends AbstractMiddleware
      *
      * @return ResponseInterface The processed response
      */
+    #[\Override]
     protected function processResponse(ResponseInterface $response): ResponseInterface
     {
         if (!ErrorHandler::getInstance()->hasErrors() && isset($this->preparedResponse)) {
diff --git a/src/Middleware/Dispatcher.php b/src/Middleware/Dispatcher.php
index 84858f3..0bfd431 100644
--- a/src/Middleware/Dispatcher.php
+++ b/src/Middleware/Dispatcher.php
@@ -34,7 +34,7 @@ use Psr\Http\Message\ServerRequestInterface;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class Dispatcher extends AbstractMiddleware
+final class Dispatcher extends AbstractMiddleware
 {
     /**
      * List of defined OAI-PMH parameters.
@@ -85,6 +85,7 @@ class Dispatcher extends AbstractMiddleware
      *
      * @return ServerRequestInterface The processed server request
      */
+    #[\Override]
     protected function processRequest(ServerRequestInterface $request): ServerRequestInterface
     {
         $request = $this->getRequestWithAttributes($request);
@@ -108,6 +109,7 @@ class Dispatcher extends AbstractMiddleware
      *
      * @return ResponseInterface The final response
      */
+    #[\Override]
     protected function processResponse(ResponseInterface $response): ResponseInterface
     {
         // TODO: Add support for content compression
diff --git a/src/Middleware/ErrorHandler.php b/src/Middleware/ErrorHandler.php
index c4e51f6..9fa9764 100644
--- a/src/Middleware/ErrorHandler.php
+++ b/src/Middleware/ErrorHandler.php
@@ -36,7 +36,7 @@ use Psr\Http\Message\StreamInterface;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class ErrorHandler extends AbstractMiddleware
+final class ErrorHandler extends AbstractMiddleware
 {
     use Singleton;
 
@@ -99,6 +99,7 @@ class ErrorHandler extends AbstractMiddleware
      *
      * @return ResponseInterface The error response
      */
+    #[\Override]
     protected function processResponse(ResponseInterface $response): ResponseInterface
     {
         if ($this->hasErrors()) {
diff --git a/src/Middleware/GetRecord.php b/src/Middleware/GetRecord.php
index 53ea423..e98a3e1 100644
--- a/src/Middleware/GetRecord.php
+++ b/src/Middleware/GetRecord.php
@@ -34,7 +34,7 @@ use Psr\Http\Message\ServerRequestInterface;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class GetRecord extends Middleware
+final class GetRecord extends Middleware
 {
     /**
      * Prepare the response body for verb "GetRecord".
@@ -43,6 +43,7 @@ class GetRecord extends Middleware
      *
      * @return void
      */
+    #[\Override]
     protected function prepareResponse(ServerRequestInterface $request): void
     {
         $oaiRecord = $this->em->getRecord(
diff --git a/src/Middleware/Identify.php b/src/Middleware/Identify.php
index f2e9fd5..a02e399 100644
--- a/src/Middleware/Identify.php
+++ b/src/Middleware/Identify.php
@@ -36,7 +36,7 @@ use Psr\Http\Message\ServerRequestInterface;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class Identify extends Middleware
+final class Identify extends Middleware
 {
     /**
      * Prepare the response body for verb "Identify".
@@ -45,6 +45,7 @@ class Identify extends Middleware
      *
      * @return void
      */
+    #[\Override]
     protected function prepareResponse(ServerRequestInterface $request): void
     {
         $response = new Response($request);
diff --git a/src/Middleware/ListIdentifiers.php b/src/Middleware/ListIdentifiers.php
index 4fa6b07..de3724d 100644
--- a/src/Middleware/ListIdentifiers.php
+++ b/src/Middleware/ListIdentifiers.php
@@ -43,6 +43,7 @@ class ListIdentifiers extends Middleware
      *
      * @return void
      */
+    #[\Override]
     protected function prepareResponse(ServerRequestInterface $request): void
     {
         $this->checkResumptionToken();
diff --git a/src/Middleware/ListMetadataFormats.php b/src/Middleware/ListMetadataFormats.php
index d32277f..3fca10f 100644
--- a/src/Middleware/ListMetadataFormats.php
+++ b/src/Middleware/ListMetadataFormats.php
@@ -34,7 +34,7 @@ use Psr\Http\Message\ServerRequestInterface;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class ListMetadataFormats extends Middleware
+final class ListMetadataFormats extends Middleware
 {
     /**
      * Prepare the response body for verb "ListMetadataFormats".
@@ -43,6 +43,7 @@ class ListMetadataFormats extends Middleware
      *
      * @return void
      */
+    #[\Override]
     protected function prepareResponse(ServerRequestInterface $request): void
     {
         $formats = $this->em->getMetadataFormats($this->arguments['identifier']);
diff --git a/src/Middleware/ListRecords.php b/src/Middleware/ListRecords.php
index 1e601f2..55691f0 100644
--- a/src/Middleware/ListRecords.php
+++ b/src/Middleware/ListRecords.php
@@ -30,7 +30,7 @@ namespace OCC\OaiPmh2\Middleware;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class ListRecords extends ListIdentifiers
+final class ListRecords extends ListIdentifiers
 {
     /**
      * "ListIdentifiers" and "ListRecords" are practically identical except the
diff --git a/src/Middleware/ListSets.php b/src/Middleware/ListSets.php
index 18f9b6b..4c66a98 100644
--- a/src/Middleware/ListSets.php
+++ b/src/Middleware/ListSets.php
@@ -34,7 +34,7 @@ use Psr\Http\Message\ServerRequestInterface;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class ListSets extends Middleware
+final class ListSets extends Middleware
 {
     /**
      * Prepare the response body for verb "ListSets".
@@ -43,6 +43,7 @@ class ListSets extends Middleware
      *
      * @return void
      */
+    #[\Override]
     protected function prepareResponse(ServerRequestInterface $request): void
     {
         $this->checkResumptionToken();
diff --git a/src/Validator/ConfigurationValidator.php b/src/Validator/ConfigurationValidator.php
index 39f4484..6925e5d 100644
--- a/src/Validator/ConfigurationValidator.php
+++ b/src/Validator/ConfigurationValidator.php
@@ -33,7 +33,7 @@ use Symfony\Component\Validator\Validation;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class ConfigurationValidator
+final class ConfigurationValidator
 {
     /**
      * Get constraints for configuration array.
diff --git a/src/Validator/RegExValidator.php b/src/Validator/RegExValidator.php
index 2516758..ad4e7b0 100644
--- a/src/Validator/RegExValidator.php
+++ b/src/Validator/RegExValidator.php
@@ -33,7 +33,7 @@ use Symfony\Component\Validator\Validation;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class RegExValidator
+final class RegExValidator
 {
     /**
      * Get constraints for regular expression.
diff --git a/src/Validator/UrlValidator.php b/src/Validator/UrlValidator.php
index a05437d..cc4897e 100644
--- a/src/Validator/UrlValidator.php
+++ b/src/Validator/UrlValidator.php
@@ -33,7 +33,7 @@ use Symfony\Component\Validator\Validation;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class UrlValidator
+final class UrlValidator
 {
     /**
      * Get constraints for URLs.
diff --git a/src/Validator/XmlValidator.php b/src/Validator/XmlValidator.php
index 42046c5..653adf9 100644
--- a/src/Validator/XmlValidator.php
+++ b/src/Validator/XmlValidator.php
@@ -34,7 +34,7 @@ use Symfony\Component\Validator\Validation;
  * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
  * @package OAIPMH2
  */
-class XmlValidator
+final class XmlValidator
 {
     /**
      * Get constraints for XML.