From 242593daf679ba7bd50a8342fb94efeed66d7956 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dani=C3=ABl=20Klabbers?= <luceos@users.noreply.github.com>
Date: Fri, 5 Mar 2021 15:43:35 +0100
Subject: [PATCH] Laravel components v8 (#2576)

- update actions ci
- include json for 4 spaces tab
- provide output int for process code exit
- adhere to parent type hint of builder
- mailer instance now needs a name, multiple can be instantiated
- getOriginal now uses mutators in the model
- Temporarily loosen MailableInterface requirements. This avoids an immediate BC break for classes in extensions that implement this interface.
- Temporarily provide (and autoload) old symfony translator interface
- make queue exception handler compatible with the contract of L8
- Update phpunit schema for newer version
- Update phpunit assert calls for newer version
---
 framework/core/.editorconfig                  |  2 +-
 framework/core/.github/workflows/test.yml     | 10 ++--
 framework/core/.gitignore                     |  1 +
 framework/core/composer.json                  | 54 ++++++++++---------
 .../SendConfirmationEmailController.php       |  2 +-
 .../Api/Controller/SendTestMailController.php |  2 +-
 .../src/Api/Serializer/GroupSerializer.php    |  2 +-
 .../core/src/Console/AbstractCommand.php      |  2 +-
 framework/core/src/Discussion/UserState.php   |  2 +-
 framework/core/src/Forum/Content/Index.php    |  2 +-
 .../core/src/Forum/ForumServiceProvider.php   |  2 +-
 .../core/src/Foundation/AbstractValidator.php |  2 +-
 .../ErrorHandling/ViewFormatter.php           |  2 +-
 framework/core/src/Group/Permission.php       |  2 +-
 .../core/src/Locale/LocaleServiceProvider.php |  5 +-
 framework/core/src/Locale/Translator.php      |  8 ++-
 .../core/src/Mail/MailServiceProvider.php     |  1 +
 .../src/Notification/MailableInterface.php    |  8 ++-
 .../src/Notification/NotificationMailer.php   |  2 +-
 framework/core/src/Queue/ExceptionHandler.php | 17 +++---
 framework/core/src/TranslatorInterface.php    | 20 +++++++
 .../core/src/User/AccountActivationMailer.php |  2 +-
 framework/core/src/User/AvatarUploader.php    |  2 +-
 .../Command/RequestPasswordResetHandler.php   |  2 +-
 .../core/src/User/EmailConfirmationMailer.php |  2 +-
 .../api/authentication/WithApiKeyTest.php     |  8 +--
 .../integration/api/discussions/ListTest.php  | 40 +++++++-------
 .../ListTestWithFulltextSearch.php            |  4 +-
 .../extenders/ApiControllerTest.php           | 44 +++++++--------
 .../extenders/ApiSerializerTest.php           | 20 +++----
 .../tests/integration/extenders/EventTest.php |  2 +-
 .../integration/extenders/FilterTest.php      |  8 +--
 .../tests/integration/extenders/MailTest.php  |  6 +--
 .../integration/extenders/SettingsTest.php    | 12 ++---
 .../extenders/SimpleFlarumSearchTest.php      | 12 ++---
 framework/core/tests/phpunit.integration.xml  | 32 +++++------
 framework/core/tests/phpunit.unit.xml         | 34 ++++++------
 .../Prerequisite/WritablePathsTest.php        |  4 +-
 .../tests/unit/User/AvatarUploaderTest.php    |  6 +--
 39 files changed, 214 insertions(+), 174 deletions(-)
 create mode 100644 framework/core/src/TranslatorInterface.php

diff --git a/framework/core/.editorconfig b/framework/core/.editorconfig
index 658a43499..a61a3ab36 100644
--- a/framework/core/.editorconfig
+++ b/framework/core/.editorconfig
@@ -15,5 +15,5 @@ indent_size = 2
 [*.{diff,md}]
 trim_trailing_whitespace = false
 
-[*.{php,xml}]
+[*.{php,xml,json}]
 indent_size = 4
diff --git a/framework/core/.github/workflows/test.yml b/framework/core/.github/workflows/test.yml
index 288b2d5c3..d3cfc5a82 100644
--- a/framework/core/.github/workflows/test.yml
+++ b/framework/core/.github/workflows/test.yml
@@ -8,7 +8,7 @@ jobs:
 
     strategy:
       matrix:
-        php: ['7.2', '7.3', '7.4', '8.0']
+        php: [7.3, 7.4, '8.0']
         service: ['mysql:5.7', mariadb]
         prefix: ['', flarum_]
 
@@ -21,12 +21,6 @@ jobs:
             prefixStr: (prefix)
 
         exclude:
-          - php: 7.2
-            service: 'mysql:5.7'
-            prefix: flarum_
-          - php: 7.2
-            service: mariadb
-            prefix: flarum_
           - php: 7.3
             service: 'mysql:5.7'
             prefix: flarum_
@@ -80,3 +74,5 @@ jobs:
 
       - name: Run Composer tests
         run: composer test
+        env:
+          COMPOSER_PROCESS_TIMEOUT: 600
diff --git a/framework/core/.gitignore b/framework/core/.gitignore
index e79d4cf73..0c30db214 100644
--- a/framework/core/.gitignore
+++ b/framework/core/.gitignore
@@ -4,6 +4,7 @@ composer.phar
 node_modules
 .DS_Store
 Thumbs.db
+tests/.phpunit.result.cache
 /tests/integration/tmp
 .vagrant
 .idea/*
diff --git a/framework/core/composer.json b/framework/core/composer.json
index 23fc87f7a..4504345be 100644
--- a/framework/core/composer.json
+++ b/framework/core/composer.json
@@ -1,7 +1,10 @@
 {
     "name": "flarum/core",
     "description": "Delightfully simple forum software.",
-    "keywords": ["forum", "discussion"],
+    "keywords": [
+        "forum",
+        "discussion"
+    ],
     "homepage": "https://flarum.org/",
     "license": "MIT",
     "authors": [
@@ -17,27 +20,27 @@
         "docs": "https://flarum.org/docs/"
     },
     "require": {
-        "php": ">=7.2",
+        "php": ">=7.3",
         "axy/sourcemap": "^0.1.4",
         "components/font-awesome": "^5.14.0",
         "dflydev/fig-cookies": "^3.0.0",
         "doctrine/dbal": "^2.7",
         "franzl/whoops-middleware": "^2.0.0",
-        "illuminate/bus": "^6.0",
-        "illuminate/cache": "^6.0",
-        "illuminate/config": "^6.0",
-        "illuminate/container": "^6.0",
-        "illuminate/contracts": "^6.0",
-        "illuminate/database": "^6.0",
-        "illuminate/events": "^6.0",
-        "illuminate/filesystem": "^6.0",
-        "illuminate/hashing": "^6.0",
-        "illuminate/mail": "^6.0",
-        "illuminate/queue": "^6.0",
-        "illuminate/session": "^6.0",
-        "illuminate/support": "^6.0",
-        "illuminate/validation": "^6.0",
-        "illuminate/view": "^6.0",
+        "illuminate/bus": "^8.0",
+        "illuminate/cache": "^8.0",
+        "illuminate/config": "^8.0",
+        "illuminate/container": "^8.0",
+        "illuminate/contracts": "^8.0",
+        "illuminate/database": "^8.0",
+        "illuminate/events": "^8.0",
+        "illuminate/filesystem": "^8.0",
+        "illuminate/hashing": "^8.0",
+        "illuminate/mail": "^8.0",
+        "illuminate/queue": "^8.0",
+        "illuminate/session": "^8.0",
+        "illuminate/support": "^8.0",
+        "illuminate/validation": "^8.0",
+        "illuminate/view": "^8.0",
         "intervention/image": "^2.5.0",
         "laminas/laminas-diactoros": "^2.4.1",
         "laminas/laminas-httphandlerrunner": "^1.2.0",
@@ -54,25 +57,26 @@
         "psr/http-server-handler": "^1.0",
         "psr/http-server-middleware": "^1.0",
         "s9e/text-formatter": "^2.3.6",
-        "symfony/config": "^4.3.4",
-        "symfony/console": "^4.3.4",
-        "symfony/event-dispatcher": "^4.3.4",
+        "symfony/config": "^5.2.2",
+        "symfony/console": "^5.2.2",
+        "symfony/event-dispatcher": "^5.2.2",
         "symfony/mime": "^5.2.0",
-        "symfony/translation": "^4.3.4",
-        "symfony/yaml": "^4.3.4",
+        "symfony/translation": "^5.1.5",
+        "symfony/yaml": "^5.2.2",
         "tobscure/json-api": "^0.3.0",
         "wikimedia/less.php": "^3.0"
     },
     "require-dev": {
-        "mockery/mockery": "^1.3.3",
-        "phpunit/phpunit": "^8.0"
+        "mockery/mockery": "^1.4",
+        "phpunit/phpunit": "^9.0"
     },
     "autoload": {
         "psr-4": {
             "Flarum\\": "src/"
         },
         "files": [
-            "src/helpers.php"
+            "src/helpers.php",
+            "src/TranslatorInterface.php"
         ]
     },
     "autoload-dev": {
diff --git a/framework/core/src/Api/Controller/SendConfirmationEmailController.php b/framework/core/src/Api/Controller/SendConfirmationEmailController.php
index 361c2c77c..ce228e680 100644
--- a/framework/core/src/Api/Controller/SendConfirmationEmailController.php
+++ b/framework/core/src/Api/Controller/SendConfirmationEmailController.php
@@ -20,7 +20,7 @@ use Laminas\Diactoros\Response\EmptyResponse;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\RequestHandlerInterface;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class SendConfirmationEmailController implements RequestHandlerInterface
 {
diff --git a/framework/core/src/Api/Controller/SendTestMailController.php b/framework/core/src/Api/Controller/SendTestMailController.php
index 4474444dc..25f0e5953 100644
--- a/framework/core/src/Api/Controller/SendTestMailController.php
+++ b/framework/core/src/Api/Controller/SendTestMailController.php
@@ -16,7 +16,7 @@ use Laminas\Diactoros\Response\EmptyResponse;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\RequestHandlerInterface;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class SendTestMailController implements RequestHandlerInterface
 {
diff --git a/framework/core/src/Api/Serializer/GroupSerializer.php b/framework/core/src/Api/Serializer/GroupSerializer.php
index 7675adc46..cac9cb578 100644
--- a/framework/core/src/Api/Serializer/GroupSerializer.php
+++ b/framework/core/src/Api/Serializer/GroupSerializer.php
@@ -11,7 +11,7 @@ namespace Flarum\Api\Serializer;
 
 use Flarum\Group\Group;
 use InvalidArgumentException;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class GroupSerializer extends AbstractSerializer
 {
diff --git a/framework/core/src/Console/AbstractCommand.php b/framework/core/src/Console/AbstractCommand.php
index 10b7dcc9a..902ccc3ea 100644
--- a/framework/core/src/Console/AbstractCommand.php
+++ b/framework/core/src/Console/AbstractCommand.php
@@ -34,7 +34,7 @@ abstract class AbstractCommand extends Command
         $this->input = $input;
         $this->output = $output;
 
-        $this->fire();
+        return $this->fire() ?: 0;
     }
 
     /**
diff --git a/framework/core/src/Discussion/UserState.php b/framework/core/src/Discussion/UserState.php
index 0d74ab81d..3859e9e96 100644
--- a/framework/core/src/Discussion/UserState.php
+++ b/framework/core/src/Discussion/UserState.php
@@ -91,7 +91,7 @@ class UserState extends AbstractModel
      * @param Builder $query
      * @return Builder
      */
-    protected function setKeysForSaveQuery(Builder $query)
+    protected function setKeysForSaveQuery($query)
     {
         $query->where('discussion_id', $this->discussion_id)
               ->where('user_id', $this->user_id);
diff --git a/framework/core/src/Forum/Content/Index.php b/framework/core/src/Forum/Content/Index.php
index 2f0d506e0..79ccc28cc 100644
--- a/framework/core/src/Forum/Content/Index.php
+++ b/framework/core/src/Forum/Content/Index.php
@@ -18,7 +18,7 @@ use Flarum\User\User;
 use Illuminate\Contracts\View\Factory;
 use Illuminate\Support\Arr;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class Index
 {
diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php
index f91849e89..383107133 100644
--- a/framework/core/src/Forum/ForumServiceProvider.php
+++ b/framework/core/src/Forum/ForumServiceProvider.php
@@ -32,7 +32,7 @@ use Flarum\Settings\Event\Saved;
 use Flarum\Settings\Event\Saving;
 use Flarum\Settings\SettingsRepositoryInterface;
 use Laminas\Stratigility\MiddlewarePipe;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class ForumServiceProvider extends AbstractServiceProvider
 {
diff --git a/framework/core/src/Foundation/AbstractValidator.php b/framework/core/src/Foundation/AbstractValidator.php
index c5b6819f7..5629213aa 100644
--- a/framework/core/src/Foundation/AbstractValidator.php
+++ b/framework/core/src/Foundation/AbstractValidator.php
@@ -12,7 +12,7 @@ namespace Flarum\Foundation;
 use Illuminate\Support\Arr;
 use Illuminate\Validation\Factory;
 use Illuminate\Validation\ValidationException;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 abstract class AbstractValidator
 {
diff --git a/framework/core/src/Foundation/ErrorHandling/ViewFormatter.php b/framework/core/src/Foundation/ErrorHandling/ViewFormatter.php
index 6189ae7ab..a9935367a 100644
--- a/framework/core/src/Foundation/ErrorHandling/ViewFormatter.php
+++ b/framework/core/src/Foundation/ErrorHandling/ViewFormatter.php
@@ -14,7 +14,7 @@ use Illuminate\Contracts\View\Factory as ViewFactory;
 use Laminas\Diactoros\Response\HtmlResponse;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 /**
  * A formatter for turning caught exceptions into "pretty" HTML error pages.
diff --git a/framework/core/src/Group/Permission.php b/framework/core/src/Group/Permission.php
index 185c7e347..7e5135a47 100644
--- a/framework/core/src/Group/Permission.php
+++ b/framework/core/src/Group/Permission.php
@@ -39,7 +39,7 @@ class Permission extends AbstractModel
      * @param Builder $query
      * @return Builder
      */
-    protected function setKeysForSaveQuery(Builder $query)
+    protected function setKeysForSaveQuery($query)
     {
         $query->where('group_id', $this->group_id)
               ->where('permission', $this->permission);
diff --git a/framework/core/src/Locale/LocaleServiceProvider.php b/framework/core/src/Locale/LocaleServiceProvider.php
index 42d5bb3af..8a4ffbb3e 100644
--- a/framework/core/src/Locale/LocaleServiceProvider.php
+++ b/framework/core/src/Locale/LocaleServiceProvider.php
@@ -15,7 +15,8 @@ use Flarum\Foundation\Paths;
 use Flarum\Settings\SettingsRepositoryInterface;
 use Illuminate\Contracts\Events\Dispatcher;
 use Illuminate\Contracts\Translation\Translator as TranslatorContract;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Translation\TranslatorInterface as DeprecatedTranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class LocaleServiceProvider extends AbstractServiceProvider
 {
@@ -62,9 +63,11 @@ class LocaleServiceProvider extends AbstractServiceProvider
 
             return $translator;
         });
+
         $this->container->alias('translator', Translator::class);
         $this->container->alias('translator', TranslatorContract::class);
         $this->container->alias('translator', TranslatorInterface::class);
+        $this->container->alias('translator', DeprecatedTranslatorInterface::class);
     }
 
     private function getDefaultLocale(): string
diff --git a/framework/core/src/Locale/Translator.php b/framework/core/src/Locale/Translator.php
index 369924b8a..99023cfa7 100644
--- a/framework/core/src/Locale/Translator.php
+++ b/framework/core/src/Locale/Translator.php
@@ -12,8 +12,9 @@ namespace Flarum\Locale;
 use Illuminate\Contracts\Translation\Translator as TranslatorContract;
 use Symfony\Component\Translation\MessageCatalogueInterface;
 use Symfony\Component\Translation\Translator as BaseTranslator;
+use Symfony\Component\Translation\TranslatorInterface;  // Defined locally as BC layer
 
-class Translator extends BaseTranslator implements TranslatorContract
+class Translator extends BaseTranslator implements TranslatorContract, TranslatorInterface
 {
     const REFERENCE_REGEX = '/^=>\s*([a-z0-9_\-\.]+)$/i';
 
@@ -84,4 +85,9 @@ class Translator extends BaseTranslator implements TranslatorContract
 
         return $translation;
     }
+
+    public function setLocale($locale)
+    {
+        parent::setLocale($locale);
+    }
 }
diff --git a/framework/core/src/Mail/MailServiceProvider.php b/framework/core/src/Mail/MailServiceProvider.php
index 512c5806c..f690bfe85 100644
--- a/framework/core/src/Mail/MailServiceProvider.php
+++ b/framework/core/src/Mail/MailServiceProvider.php
@@ -63,6 +63,7 @@ class MailServiceProvider extends AbstractServiceProvider
 
         $this->container->singleton('mailer', function ($container) {
             $mailer = new Mailer(
+                'flarum',
                 $container['view'],
                 $container['swift.mailer'],
                 $container['events']
diff --git a/framework/core/src/Notification/MailableInterface.php b/framework/core/src/Notification/MailableInterface.php
index d52664668..ac2706ace 100644
--- a/framework/core/src/Notification/MailableInterface.php
+++ b/framework/core/src/Notification/MailableInterface.php
@@ -9,7 +9,7 @@
 
 namespace Flarum\Notification;
 
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 interface MailableInterface
 {
@@ -23,7 +23,11 @@ interface MailableInterface
     /**
      * Get the subject line for a notification email.
      *
+     * @param TranslatorInterface $translator
+     *
      * @return string
      */
-    public function getEmailSubject(TranslatorInterface $translator);
+    // Uncomment beta 17. Commented as temporary BC layer since Symfony changed
+    // the namespace of their translator interface
+    // public function getEmailSubject(TranslatorInterface $translator);
 }
diff --git a/framework/core/src/Notification/NotificationMailer.php b/framework/core/src/Notification/NotificationMailer.php
index 20464b712..e5308243c 100644
--- a/framework/core/src/Notification/NotificationMailer.php
+++ b/framework/core/src/Notification/NotificationMailer.php
@@ -12,7 +12,7 @@ namespace Flarum\Notification;
 use Flarum\User\User;
 use Illuminate\Contracts\Mail\Mailer;
 use Illuminate\Mail\Message;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class NotificationMailer
 {
diff --git a/framework/core/src/Queue/ExceptionHandler.php b/framework/core/src/Queue/ExceptionHandler.php
index d7cc9afd7..d311370b1 100644
--- a/framework/core/src/Queue/ExceptionHandler.php
+++ b/framework/core/src/Queue/ExceptionHandler.php
@@ -12,6 +12,7 @@ namespace Flarum\Queue;
 use Exception;
 use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling;
 use Psr\Log\LoggerInterface;
+use Throwable;
 
 class ExceptionHandler implements ExceptionHandling
 {
@@ -28,10 +29,10 @@ class ExceptionHandler implements ExceptionHandling
     /**
      * Report or log an exception.
      *
-     * @param  \Exception $e
+     * @param  Throwable $e
      * @return void
      */
-    public function report(Exception $e)
+    public function report(Throwable $e)
     {
         $this->logger->error((string) $e);
     }
@@ -40,10 +41,10 @@ class ExceptionHandler implements ExceptionHandling
      * Render an exception into an HTTP response.
      *
      * @param  \Illuminate\Http\Request $request
-     * @param  \Exception               $e
+     * @param  Throwable               $e
      * @return \Symfony\Component\HttpFoundation\Response
      */
-    public function render($request, Exception $e)
+    public function render($request, Throwable $e)
     {
         // TODO: Implement render() method.
     }
@@ -52,10 +53,10 @@ class ExceptionHandler implements ExceptionHandling
      * Render an exception to the console.
      *
      * @param  \Symfony\Component\Console\Output\OutputInterface $output
-     * @param  \Exception                                        $e
+     * @param  Throwable                                        $e
      * @return void
      */
-    public function renderForConsole($output, Exception $e)
+    public function renderForConsole($output, Throwable $e)
     {
         // TODO: Implement renderForConsole() method.
     }
@@ -63,10 +64,10 @@ class ExceptionHandler implements ExceptionHandling
     /**
      * Determine if the exception should be reported.
      *
-     * @param  \Exception $e
+     * @param  Throwable $e
      * @return bool
      */
-    public function shouldReport(Exception $e)
+    public function shouldReport(Throwable $e)
     {
         return true;
     }
diff --git a/framework/core/src/TranslatorInterface.php b/framework/core/src/TranslatorInterface.php
new file mode 100644
index 000000000..91acaf53c
--- /dev/null
+++ b/framework/core/src/TranslatorInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of Flarum.
+ *
+ * For detailed copyright and license information, please view the
+ * LICENSE file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+/**
+ * @deprecated beta 16, remove beta 17.
+ * This is here to provide a graceful transition for classes typehinting the old interface.
+ * Temporarily, `Flarum\Locale\Translator` will implement this to avoid breaking that typehint.
+ * Before beta 17, this should be removed from autoload.
+ */
+interface TranslatorInterface
+{
+}
diff --git a/framework/core/src/User/AccountActivationMailer.php b/framework/core/src/User/AccountActivationMailer.php
index 883b170d6..3d6d79b9c 100644
--- a/framework/core/src/User/AccountActivationMailer.php
+++ b/framework/core/src/User/AccountActivationMailer.php
@@ -14,7 +14,7 @@ use Flarum\Mail\Job\SendRawEmailJob;
 use Flarum\Settings\SettingsRepositoryInterface;
 use Flarum\User\Event\Registered;
 use Illuminate\Contracts\Queue\Queue;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class AccountActivationMailer
 {
diff --git a/framework/core/src/User/AvatarUploader.php b/framework/core/src/User/AvatarUploader.php
index 5bfb3d8a4..2071664b1 100644
--- a/framework/core/src/User/AvatarUploader.php
+++ b/framework/core/src/User/AvatarUploader.php
@@ -49,7 +49,7 @@ class AvatarUploader
      */
     protected function removeFileAfterSave(User $user)
     {
-        $avatarPath = $user->getOriginal('avatar_url');
+        $avatarPath = $user->getRawOriginal('avatar_url');
 
         $user->afterSave(function () use ($avatarPath) {
             if ($this->uploadDir->has($avatarPath)) {
diff --git a/framework/core/src/User/Command/RequestPasswordResetHandler.php b/framework/core/src/User/Command/RequestPasswordResetHandler.php
index 9dc7d726a..b1cce0c59 100644
--- a/framework/core/src/User/Command/RequestPasswordResetHandler.php
+++ b/framework/core/src/User/Command/RequestPasswordResetHandler.php
@@ -18,7 +18,7 @@ use Illuminate\Contracts\Queue\Queue;
 use Illuminate\Contracts\Validation\Factory;
 use Illuminate\Database\Eloquent\ModelNotFoundException;
 use Illuminate\Validation\ValidationException;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class RequestPasswordResetHandler
 {
diff --git a/framework/core/src/User/EmailConfirmationMailer.php b/framework/core/src/User/EmailConfirmationMailer.php
index 1a052f974..bb6990659 100644
--- a/framework/core/src/User/EmailConfirmationMailer.php
+++ b/framework/core/src/User/EmailConfirmationMailer.php
@@ -14,7 +14,7 @@ use Flarum\Mail\Job\SendRawEmailJob;
 use Flarum\Settings\SettingsRepositoryInterface;
 use Flarum\User\Event\EmailChangeRequested;
 use Illuminate\Contracts\Queue\Queue;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class EmailConfirmationMailer
 {
diff --git a/framework/core/tests/integration/api/authentication/WithApiKeyTest.php b/framework/core/tests/integration/api/authentication/WithApiKeyTest.php
index fdbf14128..80fcc4e61 100644
--- a/framework/core/tests/integration/api/authentication/WithApiKeyTest.php
+++ b/framework/core/tests/integration/api/authentication/WithApiKeyTest.php
@@ -45,7 +45,7 @@ class WithApiKeyTest extends TestCase
             $this->request('GET', '/api')
         );
 
-        $data = json_decode($response->getBody(), true);
+        $data = json_decode($response->getBody()->getContents(), true);
         $this->assertFalse($data['data']['attributes']['canViewUserList']);
     }
 
@@ -59,7 +59,7 @@ class WithApiKeyTest extends TestCase
                 ->withAddedHeader('Authorization', 'Token mastertoken; userId=1')
         );
 
-        $data = json_decode($response->getBody(), true);
+        $data = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue($data['data']['attributes']['canViewUserList']);
         $this->assertArrayHasKey('adminUrl', $data['data']['attributes']);
 
@@ -78,7 +78,7 @@ class WithApiKeyTest extends TestCase
                 ->withAddedHeader('Authorization', 'Token personaltoken; userId=1')
         );
 
-        $data = json_decode($response->getBody(), true);
+        $data = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue($data['data']['attributes']['canViewUserList']);
         $this->assertArrayNotHasKey('adminUrl', $data['data']['attributes']);
 
@@ -97,7 +97,7 @@ class WithApiKeyTest extends TestCase
                 ->withAddedHeader('Authorization', 'Token personaltoken')
         );
 
-        $data = json_decode($response->getBody(), true);
+        $data = json_decode($response->getBody()->getContents(), true);
         $this->assertTrue($data['data']['attributes']['canViewUserList']);
         $this->assertArrayNotHasKey('adminUrl', $data['data']['attributes']);
 
diff --git a/framework/core/tests/integration/api/discussions/ListTest.php b/framework/core/tests/integration/api/discussions/ListTest.php
index 9a16f411f..db3bd2c1a 100644
--- a/framework/core/tests/integration/api/discussions/ListTest.php
+++ b/framework/core/tests/integration/api/discussions/ListTest.php
@@ -86,7 +86,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -105,7 +105,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -124,7 +124,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -143,7 +143,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -162,7 +162,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -181,7 +181,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -200,7 +200,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -219,7 +219,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -241,7 +241,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -263,7 +263,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -282,7 +282,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -301,7 +301,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -320,7 +320,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -339,7 +339,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -358,7 +358,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -377,7 +377,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['1'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -396,7 +396,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['4'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -415,7 +415,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['1', '2', '3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -437,7 +437,7 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['3'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 
     /**
@@ -459,6 +459,6 @@ class ListTest extends TestCase
         $data = json_decode($response->getBody()->getContents(), true)['data'];
 
         // Order-independent comparison
-        $this->assertEquals(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing(['1', '2'], Arr::pluck($data, 'id'), 'IDs do not match');
     }
 }
diff --git a/framework/core/tests/integration/api/discussions/ListTestWithFulltextSearch.php b/framework/core/tests/integration/api/discussions/ListTestWithFulltextSearch.php
index ce9edf8f9..1b591fe35 100644
--- a/framework/core/tests/integration/api/discussions/ListTestWithFulltextSearch.php
+++ b/framework/core/tests/integration/api/discussions/ListTestWithFulltextSearch.php
@@ -75,7 +75,7 @@ class ListTest extends TestCase
         }, $data['data']);
 
         // Order-independent comparison
-        $this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['3'], $ids, 'IDs do not match');
     }
 
     /**
@@ -97,7 +97,7 @@ class ListTest extends TestCase
         }, $data['data']);
 
         // Order-independent comparison
-        $this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
+        $this->assertEquals(['3'], $ids, 'IDs do not match');
     }
 
     /**
diff --git a/framework/core/tests/integration/extenders/ApiControllerTest.php b/framework/core/tests/integration/extenders/ApiControllerTest.php
index 2c6be92ff..7247738da 100644
--- a/framework/core/tests/integration/extenders/ApiControllerTest.php
+++ b/framework/core/tests/integration/extenders/ApiControllerTest.php
@@ -68,7 +68,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertEquals('dataSerializationPrepCustomTitle', $payload['data']['attributes']['title']);
     }
@@ -89,7 +89,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertEquals(CustomPrepareDataSerializationInvokableClass::class, $payload['data']['attributes']['title']);
     }
@@ -115,7 +115,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('referenceTest', $payload['data']['relationships']);
     }
@@ -139,7 +139,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('referenceTest2', $payload['data']['relationships']);
     }
@@ -164,7 +164,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertEquals('dataSerializationPrepCustomTitle2', $payload['data']['attributes']['title']);
     }
@@ -193,7 +193,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertEquals('dataSerializationPrepCustomTitle4', $payload['data']['attributes']['title']);
     }
@@ -218,7 +218,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSerializer2', $payload['data']['attributes']);
     }
@@ -247,7 +247,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSerializer', $payload['data']['attributes']);
     }
@@ -263,7 +263,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']);
     }
@@ -284,7 +284,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSerializer', $payload['data']['attributes']);
     }
@@ -310,7 +310,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSerializer', $payload['data']['attributes']);
     }
@@ -333,7 +333,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']);
     }
@@ -349,7 +349,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayNotHasKey('customApiControllerRelation', $payload['data']['relationships']);
         $this->assertArrayNotHasKey('customApiControllerRelation2', $payload['data']['relationships']);
@@ -375,7 +375,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customApiControllerRelation', $payload['data']['relationships']);
     }
@@ -402,7 +402,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customApiControllerRelation2', $payload['data']['relationships']);
     }
@@ -418,7 +418,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('groups', $payload['data']['relationships']);
     }
@@ -439,7 +439,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayNotHasKey('groups', Arr::get($payload, 'data.relationships', []));
     }
@@ -481,7 +481,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertCount(3, $payload['data']);
     }
@@ -502,7 +502,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertCount(1, $payload['data']);
     }
@@ -525,7 +525,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertCount(1, $payload['data']);
     }
@@ -587,7 +587,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertEquals(200, $response->getStatusCode());
         $this->assertEquals([3, 1, 2], Arr::pluck($payload['data'], 'id'));
@@ -647,7 +647,7 @@ class ApiControllerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertEquals(200, $response->getStatusCode());
         $this->assertEquals([2, 1, 3], Arr::pluck($payload['data'], 'id'));
diff --git a/framework/core/tests/integration/extenders/ApiSerializerTest.php b/framework/core/tests/integration/extenders/ApiSerializerTest.php
index 283e069f4..d1f11b67a 100644
--- a/framework/core/tests/integration/extenders/ApiSerializerTest.php
+++ b/framework/core/tests/integration/extenders/ApiSerializerTest.php
@@ -62,7 +62,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayNotHasKey('customAttribute', $payload['data']['attributes']);
     }
@@ -89,7 +89,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']);
     }
@@ -112,7 +112,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customAttributeFromInvokable', $payload['data']['attributes']);
     }
@@ -139,7 +139,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']);
     }
@@ -172,7 +172,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customAttribute', $payload['data']['attributes']);
         $this->assertEquals('newValue', $payload['data']['attributes']['customAttribute']);
@@ -200,7 +200,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSingleAttribute', $payload['data']['attributes']);
         $this->assertArrayHasKey('customSingleAttribute_0', $payload['data']['attributes']);
@@ -225,7 +225,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSingleAttribute_1', $payload['data']['attributes']);
     }
@@ -250,7 +250,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSingleAttribute_2', $payload['data']['attributes']);
     }
@@ -279,7 +279,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customSingleAttribute_3', $payload['data']['attributes']);
         $this->assertEquals('newValue', $payload['data']['attributes']['customSingleAttribute_3']);
@@ -312,7 +312,7 @@ class ApiSerializerTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('someCustomAttribute', $payload['data']['attributes']);
         $this->assertEquals('newValue', $payload['data']['attributes']['someCustomAttribute']);
diff --git a/framework/core/tests/integration/extenders/EventTest.php b/framework/core/tests/integration/extenders/EventTest.php
index 080a716bb..011f03e87 100644
--- a/framework/core/tests/integration/extenders/EventTest.php
+++ b/framework/core/tests/integration/extenders/EventTest.php
@@ -18,7 +18,7 @@ use Flarum\Tests\integration\TestCase;
 use Flarum\User\User;
 use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
 use Illuminate\Contracts\Events\Dispatcher;
-use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
 
 class EventTest extends TestCase
 {
diff --git a/framework/core/tests/integration/extenders/FilterTest.php b/framework/core/tests/integration/extenders/FilterTest.php
index 68b81bd7e..aa1fed9a4 100644
--- a/framework/core/tests/integration/extenders/FilterTest.php
+++ b/framework/core/tests/integration/extenders/FilterTest.php
@@ -60,8 +60,8 @@ class FilterTest extends TestCase
         $this->prepDb();
 
         $searchForAll = json_encode($this->filterDiscussions([], 5));
-        $this->assertContains('DISCUSSION 1', $searchForAll);
-        $this->assertContains('DISCUSSION 2', $searchForAll);
+        $this->assertStringContainsString('DISCUSSION 1', $searchForAll);
+        $this->assertStringContainsString('DISCUSSION 2', $searchForAll);
     }
 
     /**
@@ -74,8 +74,8 @@ class FilterTest extends TestCase
         $this->prepDb();
 
         $withResultSearch = json_encode($this->filterDiscussions(['noResult' => 0], 5));
-        $this->assertContains('DISCUSSION 1', $withResultSearch);
-        $this->assertContains('DISCUSSION 2', $withResultSearch);
+        $this->assertStringContainsString('DISCUSSION 1', $withResultSearch);
+        $this->assertStringContainsString('DISCUSSION 2', $withResultSearch);
         $this->assertEquals([], $this->filterDiscussions(['noResult' => 1], 5));
     }
 
diff --git a/framework/core/tests/integration/extenders/MailTest.php b/framework/core/tests/integration/extenders/MailTest.php
index a62773b3f..742c89aa7 100644
--- a/framework/core/tests/integration/extenders/MailTest.php
+++ b/framework/core/tests/integration/extenders/MailTest.php
@@ -34,7 +34,7 @@ class MailTest extends TestCase
             ])
         );
 
-        $fields = json_decode($response->getBody(), true)['data']['attributes']['fields'];
+        $fields = json_decode($response->getBody()->getContents(), true)['data']['attributes']['fields'];
 
         // The custom driver does not exist
         $this->assertArrayNotHasKey('custom', $fields);
@@ -65,7 +65,7 @@ class MailTest extends TestCase
             ])
         );
 
-        $fields = json_decode($response->getBody(), true)['data']['attributes']['fields'];
+        $fields = json_decode($response->getBody()->getContents(), true)['data']['attributes']['fields'];
 
         $this->assertArrayHasKey('custom', $fields);
         $this->assertEquals(['customSetting1' => ''], $fields['custom']);
@@ -87,7 +87,7 @@ class MailTest extends TestCase
             ])
         );
 
-        $requiredFields = json_decode($response->getBody(), true)['data']['attributes']['fields']['smtp'];
+        $requiredFields = json_decode($response->getBody()->getContents(), true)['data']['attributes']['fields']['smtp'];
 
         $this->assertEquals(['customSetting1' => ''], $requiredFields);
     }
diff --git a/framework/core/tests/integration/extenders/SettingsTest.php b/framework/core/tests/integration/extenders/SettingsTest.php
index 84433de25..22b14de3b 100644
--- a/framework/core/tests/integration/extenders/SettingsTest.php
+++ b/framework/core/tests/integration/extenders/SettingsTest.php
@@ -50,7 +50,7 @@ class SettingsTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayNotHasKey('customPrefix.customSetting', $payload['data']['attributes']);
     }
@@ -73,7 +73,7 @@ class SettingsTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customPrefix.customSetting', $payload['data']['attributes']);
         $this->assertEquals('customValue', $payload['data']['attributes']['customPrefix.customSetting']);
@@ -99,7 +99,7 @@ class SettingsTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customPrefix.customSetting', $payload['data']['attributes']);
         $this->assertEquals('customValueModified', $payload['data']['attributes']['customPrefix.customSetting']);
@@ -123,7 +123,7 @@ class SettingsTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customPrefix.customSetting2', $payload['data']['attributes']);
         $this->assertEquals('customValueModifiedByInvokable', $payload['data']['attributes']['customPrefix.customSetting2']);
@@ -147,7 +147,7 @@ class SettingsTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customPrefix.noCustomSetting', $payload['data']['attributes']);
         $this->assertEquals('customDefault', $payload['data']['attributes']['customPrefix.noCustomSetting']);
@@ -173,7 +173,7 @@ class SettingsTest extends TestCase
             ])
         );
 
-        $payload = json_decode($response->getBody(), true);
+        $payload = json_decode($response->getBody()->getContents(), true);
 
         $this->assertArrayHasKey('customPrefix.noCustomSetting', $payload['data']['attributes']);
         $this->assertEquals('customDefaultModified2', $payload['data']['attributes']['customPrefix.noCustomSetting']);
diff --git a/framework/core/tests/integration/extenders/SimpleFlarumSearchTest.php b/framework/core/tests/integration/extenders/SimpleFlarumSearchTest.php
index 70548bed7..587de38a5 100644
--- a/framework/core/tests/integration/extenders/SimpleFlarumSearchTest.php
+++ b/framework/core/tests/integration/extenders/SimpleFlarumSearchTest.php
@@ -77,12 +77,12 @@ class SimpleFlarumSearchTest extends TestCase
         $this->prepDb();
 
         $searchForAll = json_encode($this->searchDiscussions('in text', 5));
-        $this->assertContains('DISCUSSION 1', $searchForAll);
-        $this->assertContains('DISCUSSION 2', $searchForAll);
+        $this->assertStringContainsString('DISCUSSION 1', $searchForAll);
+        $this->assertStringContainsString('DISCUSSION 2', $searchForAll);
 
         $searchForSecond = json_encode($this->searchDiscussions('lightsail', 5));
-        $this->assertNotContains('DISCUSSION 1', $searchForSecond);
-        $this->assertContains('DISCUSSION 2', $searchForSecond);
+        $this->assertStringNotContainsString('DISCUSSION 1', $searchForSecond);
+        $this->assertStringContainsString('DISCUSSION 2', $searchForSecond);
     }
 
     /**
@@ -105,8 +105,8 @@ class SimpleFlarumSearchTest extends TestCase
         $this->prepDb();
 
         $withResultSearch = json_encode($this->searchDiscussions('noResult:0', 5));
-        $this->assertContains('DISCUSSION 1', $withResultSearch);
-        $this->assertContains('DISCUSSION 2', $withResultSearch);
+        $this->assertStringContainsString('DISCUSSION 1', $withResultSearch);
+        $this->assertStringContainsString('DISCUSSION 2', $withResultSearch);
         $this->assertEquals('[]', json_encode($this->searchDiscussions('noResult:1', 5)));
     }
 
diff --git a/framework/core/tests/phpunit.integration.xml b/framework/core/tests/phpunit.integration.xml
index 0799fafe5..23afc237d 100644
--- a/framework/core/tests/phpunit.integration.xml
+++ b/framework/core/tests/phpunit.integration.xml
@@ -1,22 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
-
-<phpunit backupGlobals="false"
-         backupStaticAttributes="false"
-         colors="true"
-         convertErrorsToExceptions="true"
-         convertNoticesToExceptions="true"
-         convertWarningsToExceptions="true"
-         processIsolation="true"
-         stopOnFailure="false">
-
+<phpunit
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
+    backupGlobals="false"
+    backupStaticAttributes="false"
+    colors="true"
+    convertErrorsToExceptions="true"
+    convertNoticesToExceptions="true"
+    convertWarningsToExceptions="true"
+    processIsolation="true"
+    stopOnFailure="false"
+>
+    <coverage processUncoveredFiles="true">
+        <include>
+            <directory suffix=".php">../src/</directory>
+        </include>
+    </coverage>
     <testsuites>
         <testsuite name="Flarum Integration Tests">
             <directory suffix="Test.php">./integration</directory>
         </testsuite>
     </testsuites>
-    <filter>
-        <whitelist processUncoveredFilesFromWhitelist="true">
-            <directory suffix=".php">./src/</directory>
-        </whitelist>
-    </filter>
 </phpunit>
diff --git a/framework/core/tests/phpunit.unit.xml b/framework/core/tests/phpunit.unit.xml
index eb4e32c11..d3a4a3e3d 100644
--- a/framework/core/tests/phpunit.unit.xml
+++ b/framework/core/tests/phpunit.unit.xml
@@ -1,25 +1,27 @@
 <?xml version="1.0" encoding="UTF-8"?>
-
-<phpunit backupGlobals="false"
-         backupStaticAttributes="false"
-         colors="true"
-         convertErrorsToExceptions="true"
-         convertNoticesToExceptions="true"
-         convertWarningsToExceptions="true"
-         processIsolation="false"
-         stopOnFailure="false">
-
+<phpunit
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
+    backupGlobals="false"
+    backupStaticAttributes="false"
+    colors="true"
+    convertErrorsToExceptions="true"
+    convertNoticesToExceptions="true"
+    convertWarningsToExceptions="true"
+    processIsolation="false"
+    stopOnFailure="false"
+>
+    <coverage processUncoveredFiles="true">
+        <include>
+            <directory suffix=".php">../src/</directory>
+        </include>
+    </coverage>
     <testsuites>
         <testsuite name="Flarum Unit Tests">
             <directory suffix="Test.php">./unit</directory>
         </testsuite>
     </testsuites>
-    <filter>
-        <whitelist processUncoveredFilesFromWhitelist="true">
-            <directory suffix=".php">./src/</directory>
-        </whitelist>
-    </filter>
     <listeners>
-        <listener class="\Mockery\Adapter\Phpunit\TestListener"></listener>
+        <listener class="\Mockery\Adapter\Phpunit\TestListener" />
     </listeners>
 </phpunit>
diff --git a/framework/core/tests/unit/Install/Prerequisite/WritablePathsTest.php b/framework/core/tests/unit/Install/Prerequisite/WritablePathsTest.php
index dd6703828..93be77bb4 100644
--- a/framework/core/tests/unit/Install/Prerequisite/WritablePathsTest.php
+++ b/framework/core/tests/unit/Install/Prerequisite/WritablePathsTest.php
@@ -41,7 +41,7 @@ class WritablePathsTest extends TestCase
 
         $problems = $writable->problems();
         $this->assertCount(1, $problems);
-        $this->assertRegExp(
+        $this->assertMatchesRegularExpression(
             "%^The .+/tests/fixtures/writable_paths/missing directory doesn't exist$%",
             $problems[0]['message']
         );
@@ -59,7 +59,7 @@ class WritablePathsTest extends TestCase
 
         $problems = $writable->problems();
         $this->assertCount(1, $problems);
-        $this->assertRegExp(
+        $this->assertMatchesRegularExpression(
             "%^The .+/tests/fixtures/writable_paths/missing directory doesn't exist$%",
             $problems[0]['message']
         );
diff --git a/framework/core/tests/unit/User/AvatarUploaderTest.php b/framework/core/tests/unit/User/AvatarUploaderTest.php
index a85e3ed9a..23a6148c0 100644
--- a/framework/core/tests/unit/User/AvatarUploaderTest.php
+++ b/framework/core/tests/unit/User/AvatarUploaderTest.php
@@ -56,7 +56,7 @@ class AvatarUploaderTest extends TestCase
         }
         $user->syncOriginal();
 
-        $this->assertEquals(null, $user->getOriginal('avatar_url'));
+        $this->assertEquals(null, $user->getRawOriginal('avatar_url'));
     }
 
     public function test_removing_url_avatar_removes_no_file()
@@ -76,7 +76,7 @@ class AvatarUploaderTest extends TestCase
         }
         $user->syncOriginal();
 
-        $this->assertEquals(null, $user->getOriginal('avatar_url'));
+        $this->assertEquals(null, $user->getRawOriginal('avatar_url'));
     }
 
     public function test_changing_avatar_removes_file()
@@ -97,6 +97,6 @@ class AvatarUploaderTest extends TestCase
         }
         $user->syncOriginal();
 
-        $this->assertNotEquals('ABCDEFGHabcdefgh.png', $user->getOriginal('avatar_url'));
+        $this->assertNotEquals('ABCDEFGHabcdefgh.png', $user->getRawOriginal('avatar_url'));
     }
 }