diff --git a/composer.json b/composer.json
index c4e1e7ecb8b..f982eb36dce 100644
--- a/composer.json
+++ b/composer.json
@@ -24,17 +24,17 @@
     "require": {
         "php": "^8.3",
         "composer-runtime-api": "^2.0",
-        "composer/installers": "^2.2",
-        "guzzlehttp/guzzle": "^7.5.0",
-        "guzzlehttp/psr7": "^2.4.0",
+        "composer/installers": "^2.3",
+        "guzzlehttp/guzzle": "^7.9",
+        "guzzlehttp/psr7": "^2.7",
         "embed/embed": "^4.4.7",
-        "league/csv": "^9.8.0",
+        "league/csv": "^9.18",
         "m1/env": "^2.2.0",
-        "masterminds/html5": "^2.7.6",
-        "monolog/monolog": "^3.2.0",
-        "nikic/php-parser": "^5.1.0",
-        "psr/container": "^1.1 || ^2.0",
-        "psr/http-message": "^1",
+        "masterminds/html5": "^2.9",
+        "monolog/monolog": "^3.8",
+        "nikic/php-parser": "^5.3",
+        "psr/container": "^2.0",
+        "psr/http-message": "^2.0",
         "sebastian/diff": "^6.0",
         "sensiolabs/ansi-to-html": "^1.2",
         "silverstripe/config": "^3",
diff --git a/src/Forms/FieldsValidator.php b/src/Forms/FieldsValidator.php
deleted file mode 100644
index 5a70dd76f7e..00000000000
--- a/src/Forms/FieldsValidator.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-namespace SilverStripe\Forms;
-
-use SilverStripe\Dev\Deprecation;
-
-/**
- * Validates the internal state of all fields in the form.
- *
- * @deprecated 5.4.0 Will be replaced with functionality inside SilverStripe\Forms\Form::validate()
- */
-class FieldsValidator extends Validator
-{
-    public function __construct()
-    {
-        Deprecation::noticeWithNoReplacment(
-            '5.4.0',
-            'Will be replaced with functionality inside SilverStripe\Forms\Form::validate()',
-            Deprecation::SCOPE_CLASS
-        );
-        parent::__construct();
-    }
-
-    public function php($data): bool
-    {
-        $fields = $this->form->Fields();
-        foreach ($fields as $field) {
-            $this->result->combineAnd($field->validate());
-        }
-        return $this->result->isValid();
-    }
-
-    public function canBeCached(): bool
-    {
-        return true;
-    }
-}
diff --git a/src/Forms/FileField.php b/src/Forms/FileField.php
index a4db0fba7c3..fdeed0a59d7 100644
--- a/src/Forms/FileField.php
+++ b/src/Forms/FileField.php
@@ -33,7 +33,7 @@
  *      $actions = new FieldList(
  *          new FormAction('doUpload', 'Upload file')
  *      );
- *      $validator = new RequiredFields(['MyName', 'MyFile']);
+ *      $validator = new RequiredFieldsValidator(['MyName', 'MyFile']);
  *
  *      return new Form($this, 'Form', $fields, $actions, $validator);
  *  }
diff --git a/src/Forms/Form.php b/src/Forms/Form.php
index 5d25b4f543f..40d6f8c252a 100644
--- a/src/Forms/Form.php
+++ b/src/Forms/Form.php
@@ -20,6 +20,8 @@
 use SilverStripe\View\AttributesHTML;
 use SilverStripe\View\SSViewer;
 use SilverStripe\Model\ModelData;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
+use SilverStripe\Forms\Validation\Validator;
 
 /**
  * Base class for all forms.
@@ -297,8 +299,10 @@ public function __construct(
         $this->setName($name);
 
         // Form validation
-        $this->validator = ($validator) ? $validator : new RequiredFields();
-        $this->validator->setForm($this);
+        if ($validator) {
+            $this->validator = $validator;
+            $this->validator->setForm($this);
+        }
 
         // Form error controls
         $this->restoreFormState();
@@ -1262,17 +1266,23 @@ public function getLegend()
      */
     public function validationResult()
     {
-        // Automatically pass if there is no validator, or the clicked button is exempt
+        $result = ValidationResult::create();
+        // Automatically pass if the clicked button is exempt
         // Note: Soft support here for validation with absent request handler
         $handler = $this->getRequestHandler();
         $action = $handler ? $handler->buttonClicked() : null;
-        $validator = $this->getValidator();
-        if (!$validator || $this->actionIsValidationExempt($action)) {
-            return ValidationResult::create();
+        if ($this->actionIsValidationExempt($action)) {
+            return $result;
+        }
+        // Invoke FormField validation
+        foreach ($this->Fields() as $field) {
+            $result->combineAnd($field->validate());
         }
-
         // Invoke validator
-        $result = $validator->validate();
+        $validator = $this->getValidator();
+        if ($validator) {
+            $result->combineAnd($validator->validate());
+        }
         $this->loadMessagesFrom($result);
         return $result;
     }
diff --git a/src/Forms/GridField/GridFieldDetailForm.php b/src/Forms/GridField/GridFieldDetailForm.php
index 544f6221f3c..d80961e16dd 100644
--- a/src/Forms/GridField/GridFieldDetailForm.php
+++ b/src/Forms/GridField/GridFieldDetailForm.php
@@ -14,8 +14,7 @@
 use SilverStripe\Core\Extensible;
 use SilverStripe\Core\Injector\Injector;
 use SilverStripe\Forms\FieldList;
-use SilverStripe\Forms\FieldsValidator;
-use SilverStripe\Forms\Validator;
+use SilverStripe\Forms\Validation\Validator;
 use SilverStripe\ORM\DataList;
 use SilverStripe\ORM\DataObject;
 use SilverStripe\Model\ModelData;
@@ -147,10 +146,8 @@ public function handleItem($gridField, $request)
         if (!$this->getValidator()) {
             if ($record->hasMethod('getCMSCompositeValidator')) {
                 $validator = $record->getCMSCompositeValidator();
-            } else {
-                $validator = FieldsValidator::create();
+                $this->setValidator($validator);
             }
-            $this->setValidator($validator);
         }
 
         return $handler->handleRequest($request);
diff --git a/src/Forms/GridField/GridFieldExportButton.php b/src/Forms/GridField/GridFieldExportButton.php
index 6b543b8c49e..3f77b649236 100644
--- a/src/Forms/GridField/GridFieldExportButton.php
+++ b/src/Forms/GridField/GridFieldExportButton.php
@@ -2,6 +2,7 @@
 
 namespace SilverStripe\Forms\GridField;
 
+use League\Csv\Bom;
 use League\Csv\Writer;
 use LogicException;
 use SilverStripe\Control\HTTPRequest;
@@ -180,8 +181,8 @@ public function generateExportFileData($gridField)
         $csvWriter = Writer::createFromFileObject(new \SplTempFileObject());
         $csvWriter->setDelimiter($this->getCsvSeparator());
         $csvWriter->setEnclosure($this->getCsvEnclosure());
-        $csvWriter->setNewline("\r\n"); //use windows line endings for compatibility with some csv libraries
-        $csvWriter->setOutputBOM(Writer::BOM_UTF8);
+        $csvWriter->setEndOfLine("\r\n"); //use windows line endings for compatibility with some csv libraries
+        $csvWriter->setOutputBOM(Bom::Utf8);
 
         if (!Config::inst()->get(get_class($this), 'xls_export_disabled')) {
             $csvWriter->addFormatter(function (array $row) {
@@ -269,11 +270,7 @@ public function generateExportFileData($gridField)
             }
         }
 
-        if (method_exists($csvWriter, 'getContent')) {
-            return $csvWriter->getContent();
-        }
-
-        return (string)$csvWriter;
+        return $csvWriter->toString();
     }
 
     /**
diff --git a/src/Forms/HasOneRelationFieldInterface.php b/src/Forms/HasOneRelationFieldInterface.php
index b520cc9592a..e02451faf7c 100644
--- a/src/Forms/HasOneRelationFieldInterface.php
+++ b/src/Forms/HasOneRelationFieldInterface.php
@@ -4,7 +4,7 @@
 
 /**
  * Added to form fields whose values are the ID of a has_one relation
- * This is used in RequiredFields validation to check if the value is set
+ * This is used in RequiredFieldsValidator validation to check if the value is set
  */
 interface HasOneRelationFieldInterface
 {
diff --git a/src/Forms/CompositeValidator.php b/src/Forms/Validation/CompositeValidator.php
similarity index 91%
rename from src/Forms/CompositeValidator.php
rename to src/Forms/Validation/CompositeValidator.php
index 8864168d348..b77bed9559b 100644
--- a/src/Forms/CompositeValidator.php
+++ b/src/Forms/Validation/CompositeValidator.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace SilverStripe\Forms;
+namespace SilverStripe\Forms\Validation;
 
 use InvalidArgumentException;
 use SilverStripe\Core\Validation\ValidationResult;
@@ -20,7 +20,7 @@
  * {
  *   $compositeValidator = parent::getCMSCompositeValidator();
  *
- *   $compositeValidator->addValidator(RequiredFields::create(['MyRequiredField']));
+ *   $compositeValidator->addValidator(RequiredFieldsValidator::create(['MyRequiredField']));
  *
  *   return $compositeValidator
  * }
@@ -29,9 +29,8 @@
  *
  * protected function updateCMSCompositeValidator(CompositeValidator $compositeValidator): void
  * {
- *   $compositeValidator->addValidator(RequiredFields::create(['AdditionalContent']));
+ *   $compositeValidator->addValidator(RequiredFieldsValidator::create(['AdditionalContent']));
  * }
- * @deprecated 5.4.0 Will be renamed to SilverStripe\Forms\Validation\CompositeValidator
  */
 class CompositeValidator extends Validator
 {
@@ -47,11 +46,6 @@ class CompositeValidator extends Validator
      */
     public function __construct(array $validators = [])
     {
-        Deprecation::noticeWithNoReplacment(
-            '5.4.0',
-            'Will be renamed to SilverStripe\\Forms\\Validation\\CompositeValidator',
-            Deprecation::SCOPE_CLASS
-        );
         $this->validators = array_values($validators ?? []);
         parent::__construct();
     }
@@ -106,7 +100,7 @@ public function validate()
     }
 
     /**
-     * Note: The existing implementations for the php() method (@see RequiredFields) does not check whether the
+     * Note: The existing implementations for the php() method (@see RequiredFieldsValidator) does not check whether the
      * Validator is enabled or not, and it also does not reset the validation result - so, neither does this.
      *
      * @param array $data
@@ -156,7 +150,7 @@ public function getValidators(): array
     }
 
     /**
-     * Return all Validators that match a certain class name. EG: RequiredFields::class
+     * Return all Validators that match a certain class name. EG: RequiredFieldsValidator::class
      *
      * The keys for the return array match the keys in the unfiltered array. You cannot assume the keys will be
      * sequential or that the first key will be ZERO.
@@ -181,7 +175,7 @@ public function getValidatorsByType(string $className): array
     }
 
     /**
-     * Remove all Validators that match a certain class name. EG: RequiredFields::class
+     * Remove all Validators that match a certain class name. EG: RequiredFieldsValidator::class
      *
      * @param string $className
      * @return CompositeValidator
diff --git a/src/Forms/RequiredFields.php b/src/Forms/Validation/RequiredFieldsValidator.php
similarity index 91%
rename from src/Forms/RequiredFields.php
rename to src/Forms/Validation/RequiredFieldsValidator.php
index 0ec173f5c1e..aeadf8974b9 100644
--- a/src/Forms/RequiredFields.php
+++ b/src/Forms/Validation/RequiredFieldsValidator.php
@@ -1,9 +1,11 @@
 <?php
 
-namespace SilverStripe\Forms;
+namespace SilverStripe\Forms\Validation;
 
 use SilverStripe\Core\ArrayLib;
-use SilverStripe\Dev\Deprecation;
+use SilverStripe\Forms\HasOneRelationFieldInterface;
+use SilverStripe\Forms\FileField;
+use SilverStripe\Forms\FormField;
 
 /**
  * Required Fields allows you to set which fields need to be present before
@@ -12,10 +14,8 @@
  *
  * Validation is performed on a field by field basis through
  * {@link FormField::validate}.
- *
- * @deprecated 5.4.0 Will be renamed to SilverStripe\Forms\Validation\RequiredFieldsValidator
  */
-class RequiredFields extends Validator
+class RequiredFieldsValidator extends Validator
 {
     /**
      * Whether to globally allow whitespace only as a valid value for a required field
@@ -42,11 +42,6 @@ class RequiredFields extends Validator
      */
     public function __construct()
     {
-        Deprecation::noticeWithNoReplacment(
-            '5.4.0',
-            'Will be renamed to SilverStripe\\Forms\\Validation\\RequiredFieldsValidator',
-            Deprecation::SCOPE_CLASS
-        );
         $required = func_get_args();
         if (isset($required[0]) && is_array($required[0])) {
             $required = $required[0];
@@ -121,12 +116,6 @@ public function php($data)
         $valid = true;
         $fields = $this->form->Fields();
 
-        foreach ($fields as $field) {
-            $result = $field->validate();
-            $valid = $result->isValid() && $valid;
-            $this->result->combineAnd($result);
-        }
-
         if (!$this->required) {
             return $valid;
         }
@@ -232,7 +221,7 @@ public function removeRequiredField($field)
     /**
      * Add {@link RequiredField} objects together
      *
-     * @param RequiredFields $requiredFields
+     * @param RequiredFieldsValidator $requiredFields
      * @return $this
      */
     public function appendRequiredFields($requiredFields)
diff --git a/src/Forms/Validator.php b/src/Forms/Validation/Validator.php
similarity index 91%
rename from src/Forms/Validator.php
rename to src/Forms/Validation/Validator.php
index 4a88c447412..04df3d263cb 100644
--- a/src/Forms/Validator.php
+++ b/src/Forms/Validation/Validator.php
@@ -1,19 +1,16 @@
 <?php
 
-namespace SilverStripe\Forms;
+namespace SilverStripe\Forms\Validation;
 
 use SilverStripe\Core\Config\Configurable;
 use SilverStripe\Core\Extensible;
 use SilverStripe\Core\Injector\Injectable;
 use SilverStripe\Core\Validation\ValidationResult;
-use SilverStripe\Dev\Deprecation;
 
 /**
  * This validation class handles all form and custom form validation through the use of Required
  * fields. It relies on javascript for client-side validation, and marking fields after server-side
  * validation. It acts as a visitor to individual form fields.
- *
- * @deprecated 5.4.0 Will be renamed to SilverStripe\Forms\Validation\Validator
  */
 abstract class Validator
 {
@@ -23,11 +20,6 @@ abstract class Validator
 
     public function __construct()
     {
-        Deprecation::noticeWithNoReplacment(
-            '5.4.0',
-            'Will be renamed to SilverStripe\\Forms\\Validation\\Validator',
-            Deprecation::SCOPE_CLASS
-        );
         $this->resetResult();
     }
 
@@ -179,7 +171,7 @@ public function removeValidation()
     /**
      * When Validators are set on the form, it can affect whether or not the form cannot be cached.
      *
-     * @see RequiredFields for an example of when you might be able to cache your form.
+     * @see RequiredFieldsValidator for an example of when you might be able to cache your form.
      *
      * @return bool
      */
diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php
index 586db64a941..8cbf4f9575d 100644
--- a/src/ORM/DataObject.php
+++ b/src/ORM/DataObject.php
@@ -18,8 +18,7 @@
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\FormField;
 use SilverStripe\Forms\FormScaffolder;
-use SilverStripe\Forms\CompositeValidator;
-use SilverStripe\Forms\FieldsValidator;
+use SilverStripe\Forms\Validation\CompositeValidator;
 use SilverStripe\Forms\GridField\GridField;
 use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
 use SilverStripe\Forms\HiddenField;
@@ -2686,7 +2685,7 @@ public function getCMSActions()
      */
     public function getCMSCompositeValidator(): CompositeValidator
     {
-        $compositeValidator = CompositeValidator::create([FieldsValidator::create()]);
+        $compositeValidator = CompositeValidator::create();
 
         // Support for the old method during the deprecation period
         if ($this->hasMethod('getCMSValidator')) {
diff --git a/src/ORM/Hierarchy/Hierarchy.php b/src/ORM/Hierarchy/Hierarchy.php
index 4ae4014668d..c9eff0c8183 100644
--- a/src/ORM/Hierarchy/Hierarchy.php
+++ b/src/ORM/Hierarchy/Hierarchy.php
@@ -617,6 +617,30 @@ public function showingCMSTree()
             && in_array($controller->getAction(), ["treeview", "listview", "getsubtree"]);
     }
 
+    /**
+     * Return the CSS classes to apply to this node in the CMS tree.
+     */
+    public function CMSTreeClasses(): string
+    {
+        $owner = $this->getOwner();
+        $classes = sprintf('class-%s', Convert::raw2htmlid(get_class($owner)));
+
+        if (!$owner->canAddChildren()) {
+            $classes .= " nochildren";
+        }
+
+        if (!$owner->canEdit() && !$owner->canAddChildren()) {
+            if (!$owner->canView()) {
+                $classes .= " disabled";
+            } else {
+                $classes .= " edit-disabled";
+            }
+        }
+
+        $owner->invokeWithExtensions('updateCMSTreeClasses', $classes);
+        return $classes;
+    }
+
     /**
      * Find the first class in the inheritance chain that has Hierarchy extension applied
      *
@@ -777,6 +801,17 @@ public function getBreadcrumbs($separator = ' &raquo; ')
         return implode($separator ?? '', $crumbs);
     }
 
+    /**
+     * Get the title that will be used in TreeDropdownField and other tree structures.
+     */
+    public function getTreeTitle(): string
+    {
+        $owner = $this->getOwner();
+        $title = $owner->MenuTitle ?: $owner->Title;
+        $owner->extend('updateTreeTitle', $title);
+        return Convert::raw2xml($title ?? '');
+    }
+
     /**
      * Get the name of the dedicated sort field, if there is one.
      */
diff --git a/src/Security/Group.php b/src/Security/Group.php
index 6c10d32c073..4298c11dd89 100755
--- a/src/Security/Group.php
+++ b/src/Security/Group.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Admin\SecurityAdmin;
 use SilverStripe\Core\Convert;
-use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\Validation\CompositeValidator;
 use SilverStripe\Forms\DropdownField;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
@@ -23,7 +23,7 @@
 use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
 use SilverStripe\Forms\ListboxField;
 use SilverStripe\Forms\LiteralField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Tab;
 use SilverStripe\Forms\TabSet;
 use SilverStripe\Forms\TextareaField;
@@ -494,16 +494,6 @@ public function stageChildren()
             ->sort('"Sort"');
     }
 
-    /**
-     * @return string
-     */
-    public function getTreeTitle()
-    {
-        $title = htmlspecialchars($this->Title ?? '', ENT_QUOTES);
-        $this->extend('updateTreeTitle', $title);
-        return $title;
-    }
-
     /**
      * Overloaded to ensure the code is always descent.
      *
@@ -559,7 +549,7 @@ public function getCMSCompositeValidator(): CompositeValidator
     {
         $validator = parent::getCMSCompositeValidator();
 
-        $validator->addValidator(RequiredFields::create([
+        $validator->addValidator(RequiredFieldsValidator::create([
             'Title'
         ]));
 
diff --git a/src/Security/LogoutForm.php b/src/Security/LogoutForm.php
index 5b12c2c3563..6f4588d9d11 100644
--- a/src/Security/LogoutForm.php
+++ b/src/Security/LogoutForm.php
@@ -9,7 +9,7 @@
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\FormAction;
 use SilverStripe\Forms\HiddenField;
-use SilverStripe\Forms\Validator;
+use SilverStripe\Forms\Validation\Validator;
 
 /**
  * Log out form to display to users who arrive at 'Security/logout' without a
diff --git a/src/Security/Member.php b/src/Security/Member.php
index 1e025d5a159..7707df74ee2 100644
--- a/src/Security/Member.php
+++ b/src/Security/Member.php
@@ -15,7 +15,7 @@
 use SilverStripe\Core\Injector\Injector;
 use SilverStripe\Dev\TestMailer;
 use SilverStripe\Forms\CheckboxField;
-use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\Validation\CompositeValidator;
 use SilverStripe\Forms\ConfirmedPasswordField;
 use SilverStripe\Forms\DropdownField;
 use SilverStripe\Forms\FieldList;
@@ -682,7 +682,7 @@ public function getMemberPasswordField()
 
 
     /**
-     * Returns the {@link RequiredFields} instance for the Member object. This
+     * Returns the {@link RequiredFieldsValidator} instance for the Member object. This
      * Validator is used when saving a {@link CMSProfileController} or added to
      * any form responsible for saving a users data.
      *
diff --git a/src/Security/MemberAuthenticator/MemberLoginForm.php b/src/Security/MemberAuthenticator/MemberLoginForm.php
index c88df53dc87..8273020748f 100644
--- a/src/Security/MemberAuthenticator/MemberLoginForm.php
+++ b/src/Security/MemberAuthenticator/MemberLoginForm.php
@@ -10,7 +10,7 @@
 use SilverStripe\Forms\HiddenField;
 use SilverStripe\Forms\LiteralField;
 use SilverStripe\Forms\PasswordField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\TextField;
 use SilverStripe\Core\Validation\ValidationResult;
 use SilverStripe\Security\LoginForm as BaseLoginForm;
@@ -111,7 +111,7 @@ public function __construct(
         if (isset($logoutAction)) {
             $this->setFormAction($logoutAction);
         }
-        $this->setValidator(RequiredFields::create(static::config()->get('required_fields')));
+        $this->setValidator(RequiredFieldsValidator::create(static::config()->get('required_fields')));
     }
 
     /**
diff --git a/src/Security/Member_Validator.php b/src/Security/Member_Validator.php
index 6b979847dd7..7ad2bc354a5 100644
--- a/src/Security/Member_Validator.php
+++ b/src/Security/Member_Validator.php
@@ -3,7 +3,7 @@
 namespace SilverStripe\Security;
 
 use SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 
 /**
  * Member Validator
@@ -22,7 +22,7 @@
  *     - Surname
  * </code>
  */
-class Member_Validator extends RequiredFields
+class Member_Validator extends RequiredFieldsValidator
 {
     /**
      * Fields that are required by this validator
diff --git a/src/Security/PermissionCheckable.php b/src/Security/PermissionCheckable.php
new file mode 100644
index 00000000000..1e5348f53a2
--- /dev/null
+++ b/src/Security/PermissionCheckable.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace SilverStripe\Security;
+
+/**
+ * Model with permissions that can be checked using PermissionChecker
+ */
+interface PermissionCheckable
+{
+    /**
+     * Get the permission checker for this model
+     */
+    public function getPermissionChecker(): PermissionChecker;
+}
diff --git a/tests/php/Forms/CheckboxFieldTest.php b/tests/php/Forms/CheckboxFieldTest.php
index bbd3603492b..574106976f0 100644
--- a/tests/php/Forms/CheckboxFieldTest.php
+++ b/tests/php/Forms/CheckboxFieldTest.php
@@ -9,7 +9,7 @@
 use SilverStripe\ORM\DB;
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\CheckboxField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 
 class CheckboxFieldTest extends SapphireTest
 {
@@ -200,7 +200,7 @@ public function testNoAriaRequired()
             "form",
             new FieldList($field),
             new FieldList(),
-            new RequiredFields(["RequiredField"])
+            new RequiredFieldsValidator(["RequiredField"])
         );
         $this->assertTrue($field->Required());
 
diff --git a/tests/php/Forms/CheckboxSetFieldTest.php b/tests/php/Forms/CheckboxSetFieldTest.php
index a9c717ae753..2c7e75a3775 100644
--- a/tests/php/Forms/CheckboxSetFieldTest.php
+++ b/tests/php/Forms/CheckboxSetFieldTest.php
@@ -15,7 +15,7 @@
 use SilverStripe\Forms\CheckboxSetField;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Model\ArrayData;
 
 class CheckboxSetFieldTest extends SapphireTest
@@ -355,7 +355,7 @@ public function testNoAriaRequired()
             "form",
             new FieldList($field),
             new FieldList(),
-            new RequiredFields(["RequiredField"])
+            new RequiredFieldsValidator(["RequiredField"])
         );
         $this->assertTrue($field->Required());
 
diff --git a/tests/php/Forms/CompositeFieldTest.php b/tests/php/Forms/CompositeFieldTest.php
index f3b22dc711a..d15f60599ab 100644
--- a/tests/php/Forms/CompositeFieldTest.php
+++ b/tests/php/Forms/CompositeFieldTest.php
@@ -7,7 +7,7 @@
 use SilverStripe\Forms\CompositeField;
 use SilverStripe\Forms\DropdownField;
 use SilverStripe\Forms\FieldList;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\TextField;
 
 class CompositeFieldTest extends SapphireTest
diff --git a/tests/php/Forms/CompositeValidatorTest.php b/tests/php/Forms/CompositeValidatorTest.php
index bcd79a41caf..da181e9bbd8 100644
--- a/tests/php/Forms/CompositeValidatorTest.php
+++ b/tests/php/Forms/CompositeValidatorTest.php
@@ -8,10 +8,10 @@
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Tests\ValidatorTest\TestValidator;
 use SilverStripe\Forms\TextField;
-use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\Validation\CompositeValidator;
 
 /**
  * @package framework
@@ -40,8 +40,8 @@ protected function getForm(array $fieldNames = []): Form
     public function testAddValidator(): void
     {
         $compositeValidator = new CompositeValidator();
-        $compositeValidator->addValidator(new RequiredFields());
-        $compositeValidator->addValidator(new RequiredFields());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
 
         $this->assertCount(2, $compositeValidator->getValidators());
     }
@@ -76,38 +76,38 @@ public function testSetForm(): void
     public function testGetValidatorsByType(): void
     {
         $compositeValidator = new CompositeValidator();
-        $compositeValidator->addValidator(new RequiredFields());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
         $compositeValidator->addValidator(new TestValidator());
-        $compositeValidator->addValidator(new RequiredFields());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
         $compositeValidator->addValidator(new TestValidator());
 
         $this->assertCount(4, $compositeValidator->getValidators());
-        $this->assertCount(2, $compositeValidator->getValidatorsByType(RequiredFields::class));
+        $this->assertCount(2, $compositeValidator->getValidatorsByType(RequiredFieldsValidator::class));
     }
 
     public function testRemoveValidatorsByType(): void
     {
         $compositeValidator = new CompositeValidator();
-        $compositeValidator->addValidator(new RequiredFields());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
         $compositeValidator->addValidator(new TestValidator());
-        $compositeValidator->addValidator(new RequiredFields());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
         $compositeValidator->addValidator(new TestValidator());
 
         $this->assertCount(4, $compositeValidator->getValidators());
 
-        $compositeValidator->removeValidatorsByType(RequiredFields::class);
+        $compositeValidator->removeValidatorsByType(RequiredFieldsValidator::class);
         $this->assertCount(2, $compositeValidator->getValidators());
     }
 
     public function testCanBeCached(): void
     {
         $compositeValidator = new CompositeValidator();
-        $compositeValidator->addValidator(new RequiredFields());
+        $compositeValidator->addValidator(new RequiredFieldsValidator());
 
         $this->assertTrue($compositeValidator->canBeCached());
 
         $compositeValidator = new CompositeValidator();
-        $compositeValidator->addValidator(new RequiredFields(['Foor']));
+        $compositeValidator->addValidator(new RequiredFieldsValidator(['Foor']));
 
         $this->assertFalse($compositeValidator->canBeCached());
     }
@@ -121,12 +121,12 @@ public function testFieldIsRequired(): void
             'Content',
         ];
 
-        $requiredFieldsFirst = new RequiredFields(
+        $requiredFieldsFirst = new RequiredFieldsValidator(
             [
                 $fieldNames[0],
             ]
         );
-        $requiredFieldsSecond = new RequiredFields(
+        $requiredFieldsSecond = new RequiredFieldsValidator(
             [
                 $fieldNames[1],
             ]
@@ -147,8 +147,8 @@ public function testValidate(): void
     {
         $compositeValidator = new CompositeValidator();
         // Add two separate validators, each with one required field
-        $compositeValidator->addValidator(new RequiredFields(['Foo']));
-        $compositeValidator->addValidator(new RequiredFields(['Bar']));
+        $compositeValidator->addValidator(new RequiredFieldsValidator(['Foo']));
+        $compositeValidator->addValidator(new RequiredFieldsValidator(['Bar']));
 
         // Setup a form with the fields/data we're testing (a form is a dependency for validation right now)
         // We'll add three empty fields, but only two of them should be required
diff --git a/tests/php/Forms/ConfirmedPasswordFieldTest.php b/tests/php/Forms/ConfirmedPasswordFieldTest.php
index 7fb6f643d3f..6c8cee8cf66 100644
--- a/tests/php/Forms/ConfirmedPasswordFieldTest.php
+++ b/tests/php/Forms/ConfirmedPasswordFieldTest.php
@@ -9,7 +9,7 @@
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\ReadonlyField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Security\Member;
 use SilverStripe\View\SSViewer;
 use Closure;
@@ -466,7 +466,7 @@ public function testChildFieldsAreRequired(bool $canBeEmpty, bool $required, boo
                 $requiredFields[] = 'Test[_Password]';
                 $requiredFields[] = 'Test[_ConfirmPassword]';
             }
-            $form->setValidator(new RequiredFields($requiredFields));
+            $form->setValidator(new RequiredFieldsValidator($requiredFields));
 
             $rendered = $field->Field();
             $fieldOneRegex = '<input\s+type="password"\s+name="Test\[_Password\]"\s[^>]*?required="required"\s+aria-required="true"\s[^>]*\/>';
diff --git a/tests/php/Forms/CurrencyFieldTest.php b/tests/php/Forms/CurrencyFieldTest.php
index af08d227595..13543e65751 100644
--- a/tests/php/Forms/CurrencyFieldTest.php
+++ b/tests/php/Forms/CurrencyFieldTest.php
@@ -6,7 +6,7 @@
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\CurrencyField;
 use SilverStripe\Forms\CurrencyField_Readonly;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\ORM\FieldType\DBCurrency;
 
 class CurrencyFieldTest extends SapphireTest
diff --git a/tests/php/Forms/DateFieldTest.php b/tests/php/Forms/DateFieldTest.php
index 9ac536bc2e3..7f60339a177 100644
--- a/tests/php/Forms/DateFieldTest.php
+++ b/tests/php/Forms/DateFieldTest.php
@@ -7,7 +7,7 @@
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\DateField;
 use SilverStripe\Forms\DateField_Disabled;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\i18n\i18n;
 use SilverStripe\ORM\FieldType\DBDate;
 use SilverStripe\ORM\FieldType\DBDatetime;
diff --git a/tests/php/Forms/DatetimeFieldTest.php b/tests/php/Forms/DatetimeFieldTest.php
index 68e31a4eac8..4dc5da9e8b4 100644
--- a/tests/php/Forms/DatetimeFieldTest.php
+++ b/tests/php/Forms/DatetimeFieldTest.php
@@ -9,7 +9,7 @@
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\FormAction;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Tests\DatetimeFieldTest\Model;
 use SilverStripe\i18n\i18n;
 use SilverStripe\ORM\FieldType\DBDatetime;
diff --git a/tests/php/Forms/DropdownFieldTest.php b/tests/php/Forms/DropdownFieldTest.php
index 8e4de0e2fbe..2e87284a7c3 100644
--- a/tests/php/Forms/DropdownFieldTest.php
+++ b/tests/php/Forms/DropdownFieldTest.php
@@ -7,7 +7,7 @@
 use SilverStripe\Dev\CSSContentParser;
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\DropdownField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\FormTemplateHelper;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
@@ -574,7 +574,7 @@ public function testRequiredDropdownHasEmptyDefault()
             "form",
             new FieldList($field),
             new FieldList(),
-            new RequiredFields(["RequiredField"])
+            new RequiredFieldsValidator(["RequiredField"])
         );
 
         $this->assertTrue($field->getHasEmptyDefault());
diff --git a/tests/php/Forms/EmailFieldTest.php b/tests/php/Forms/EmailFieldTest.php
index d46ceae1214..9c5064812dd 100644
--- a/tests/php/Forms/EmailFieldTest.php
+++ b/tests/php/Forms/EmailFieldTest.php
@@ -4,7 +4,6 @@
 
 use SilverStripe\Dev\FunctionalTest;
 use SilverStripe\Forms\EmailField;
-use SilverStripe\Forms\FieldsValidator;
 
 class EmailFieldTest extends FunctionalTest
 {
diff --git a/tests/php/Forms/EmailFieldTest/TestController.php b/tests/php/Forms/EmailFieldTest/TestController.php
index fd01418e34f..a0ae0ca3dc1 100644
--- a/tests/php/Forms/EmailFieldTest/TestController.php
+++ b/tests/php/Forms/EmailFieldTest/TestController.php
@@ -9,7 +9,7 @@
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\FormAction;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\View\SSViewer;
 
 class TestController extends Controller implements TestOnly
@@ -54,7 +54,7 @@ public function Form()
             new FieldList(
                 new FormAction('doSubmit')
             ),
-            new RequiredFields(
+            new RequiredFieldsValidator(
                 'Email'
             )
         );
diff --git a/tests/php/Forms/FieldsValidatorTest.php b/tests/php/Forms/FieldsValidatorTest.php
deleted file mode 100644
index d5eac24d9c4..00000000000
--- a/tests/php/Forms/FieldsValidatorTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-namespace SilverStripe\Forms\Tests;
-
-use SilverStripe\Dev\SapphireTest;
-use SilverStripe\Forms\EmailField;
-use SilverStripe\Forms\FieldList;
-use SilverStripe\Forms\FieldsValidator;
-use SilverStripe\Forms\Form;
-use PHPUnit\Framework\Attributes\DataProvider;
-
-class FieldsValidatorTest extends SapphireTest
-{
-    protected $usesDatabase = false;
-
-    public static function provideValidation()
-    {
-        return [
-            'missing values arent invalid' => [
-                'values' => [],
-                'isValid' => true,
-            ],
-            'empty values arent invalid' => [
-                'values' => [
-                    'EmailField1' => '',
-                    'EmailField2' => null,
-                ],
-                'isValid' => true,
-            ],
-            'any invalid is invalid' => [
-                'values' => [
-                    'EmailField1' => 'email@example.com',
-                    'EmailField2' => 'not email',
-                ],
-                'isValid' => false,
-            ],
-            'all invalid is invalid' => [
-                'values' => [
-                    'EmailField1' => 'not email',
-                    'EmailField2' => 'not email',
-                ],
-                'isValid' => false,
-            ],
-            'all valid is valid' => [
-                'values' => [
-                    'EmailField1' => 'email@example.com',
-                    'EmailField2' => 'email@example.com',
-                ],
-                'isValid' => true,
-            ],
-        ];
-    }
-
-    #[DataProvider('provideValidation')]
-    public function testValidation(array $values, bool $isValid)
-    {
-        $fieldList = new FieldList([
-            $field1 = new EmailField('EmailField1'),
-            $field2 = new EmailField('EmailField2'),
-        ]);
-        if (array_key_exists('EmailField1', $values)) {
-            $field1->setValue($values['EmailField1']);
-        }
-        if (array_key_exists('EmailField2', $values)) {
-            $field2->setValue($values['EmailField2']);
-        }
-        $form = new Form(null, 'testForm', $fieldList, new FieldList([/* no actions */]), new FieldsValidator());
-
-        $result = $form->validationResult();
-        $this->assertSame($isValid, $result->isValid());
-        $messages = $result->getMessages();
-        if ($isValid) {
-            $this->assertEmpty($messages);
-        } else {
-            $this->assertNotEmpty($messages);
-        }
-    }
-}
diff --git a/tests/php/Forms/FileFieldTest.php b/tests/php/Forms/FileFieldTest.php
index 3529b5eea1f..aa863cf049d 100644
--- a/tests/php/Forms/FileFieldTest.php
+++ b/tests/php/Forms/FileFieldTest.php
@@ -10,7 +10,7 @@
 use SilverStripe\Forms\FileField;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 
 class FileFieldTest extends FunctionalTest
 {
@@ -126,7 +126,7 @@ public function testUploadMissingRequiredFile()
                 $fileField = new FileField('cv', 'Upload your CV')
             ),
             new FieldList(),
-            new RequiredFields('cv')
+            new RequiredFieldsValidator('cv')
         );
         // All fields are filled but for some reason an error occurred when uploading the file => fails
         $fileFieldValue = [
diff --git a/tests/php/Forms/FormFieldTest.php b/tests/php/Forms/FormFieldTest.php
index 647c1adcfec..4ccc0039400 100644
--- a/tests/php/Forms/FormFieldTest.php
+++ b/tests/php/Forms/FormFieldTest.php
@@ -20,7 +20,7 @@
 use SilverStripe\Forms\NullableField;
 use SilverStripe\Forms\PopoverField;
 use SilverStripe\Forms\PrintableTransformation_TabSet;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\SelectionGroup;
 use SilverStripe\Forms\SelectionGroup_Item;
 use SilverStripe\Forms\Tab;
@@ -41,7 +41,6 @@
 use PHPUnit\Framework\Attributes\DataProvider;
 use SilverStripe\Core\Injector\Injector;
 use SilverStripe\Forms\ConfirmedPasswordField;
-use SilverStripe\Forms\FieldsValidator;
 use SilverStripe\Forms\NumericField;
 use SilverStripe\Forms\CheckboxField_Readonly;
 use SilverStripe\VersionedAdmin\Forms\DiffField;
@@ -533,7 +532,7 @@ public function testSetSchemaState()
     public function testGetSchemaStateWithFormValidation()
     {
         $field = new FormField('MyField', 'My Field');
-        $validator = new RequiredFields('MyField');
+        $validator = new RequiredFieldsValidator('MyField');
         $form = new Form(null, 'TestForm', new FieldList($field), new FieldList(), $validator);
         $form->validationResult();
         $schema = $field->getSchemaState();
@@ -548,7 +547,7 @@ public function testValidationExtensionHooks()
         /** @var TextField|FieldValidationExtension $field */
         $field = new TextField('Test');
         $field->setMaxLength(5);
-        $form = new Form(null, 'test', new FieldList($field), new FieldList(), new FieldsValidator());
+        $form = new Form(null, 'test', new FieldList($field), new FieldList());
         $form->disableSecurityToken();
 
         $field->setValue('IAmLongerThan5Characters');
diff --git a/tests/php/Forms/FormSchemaTest.php b/tests/php/Forms/FormSchemaTest.php
index ed78a8ef70c..1cc3a46fbd6 100644
--- a/tests/php/Forms/FormSchemaTest.php
+++ b/tests/php/Forms/FormSchemaTest.php
@@ -11,7 +11,7 @@
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\TextField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\FormAction;
 use SilverStripe\Forms\PopoverField;
 use SilverStripe\Forms\FormField;
@@ -141,7 +141,7 @@ public function testGetStateWithFieldValidationErrors()
     {
         $fields = new FieldList(new TextField('Title'));
         $actions = new FieldList();
-        $validator = new RequiredFields('Title');
+        $validator = new RequiredFieldsValidator('Title');
         $form = new Form(null, 'TestForm', $fields, $actions, $validator);
         $form->clearMessage();
         $form->loadDataFrom(
@@ -240,7 +240,7 @@ public function testSchemaValidation()
                 new CurrencyField("Money")
             ),
             new FieldList(),
-            new RequiredFields('Name')
+            new RequiredFieldsValidator('Name')
         );
         $formSchema = new FormSchema();
         $schema = $formSchema->getSchema($form);
diff --git a/tests/php/Forms/FormTest.php b/tests/php/Forms/FormTest.php
index 30816b0feb9..7e30a1b5fe3 100644
--- a/tests/php/Forms/FormTest.php
+++ b/tests/php/Forms/FormTest.php
@@ -34,6 +34,7 @@
 use SilverStripe\Model\ArrayData;
 use SilverStripe\View\SSViewer;
 use PHPUnit\Framework\Attributes\DataProvider;
+use SilverStripe\Forms\EmailField;
 
 class FormTest extends FunctionalTest
 {
@@ -1309,4 +1310,67 @@ protected function clean($input)
             trim($input ?? '')
         );
     }
+
+    public static function provideValidateFormFields()
+    {
+        return [
+            'missing values arent invalid' => [
+                'values' => [],
+                'isValid' => true,
+            ],
+            'empty values arent invalid' => [
+                'values' => [
+                    'EmailField1' => '',
+                    'EmailField2' => null,
+                ],
+                'isValid' => true,
+            ],
+            'any invalid is invalid' => [
+                'values' => [
+                    'EmailField1' => 'email@example.com',
+                    'EmailField2' => 'not email',
+                ],
+                'isValid' => false,
+            ],
+            'all invalid is invalid' => [
+                'values' => [
+                    'EmailField1' => 'not email',
+                    'EmailField2' => 'not email',
+                ],
+                'isValid' => false,
+            ],
+            'all valid is valid' => [
+                'values' => [
+                    'EmailField1' => 'email@example.com',
+                    'EmailField2' => 'email@example.com',
+                ],
+                'isValid' => true,
+            ],
+        ];
+    }
+
+    #[DataProvider('provideValidateFormFields')]
+    public function testValidateFormFields(array $values, bool $isValid)
+    {
+        $fieldList = new FieldList([
+            $field1 = new EmailField('EmailField1'),
+            $field2 = new EmailField('EmailField2'),
+        ]);
+        if (array_key_exists('EmailField1', $values)) {
+            $field1->setValue($values['EmailField1']);
+        }
+        if (array_key_exists('EmailField2', $values)) {
+            $field2->setValue($values['EmailField2']);
+        }
+        $form = new Form(null, 'testForm', $fieldList, new FieldList([/* no actions */]));
+
+        $result = $form->validationResult();
+        $this->assertSame($isValid, $result->isValid());
+        $messages = $result->getMessages();
+        if ($isValid) {
+            $this->assertEmpty($messages);
+        } else {
+            $this->assertNotEmpty($messages);
+        }
+    }
 }
diff --git a/tests/php/Forms/FormTest/ControllerWithSpecialSubmittedValueFields.php b/tests/php/Forms/FormTest/ControllerWithSpecialSubmittedValueFields.php
index 538261bd6c9..d16d59a8034 100644
--- a/tests/php/Forms/FormTest/ControllerWithSpecialSubmittedValueFields.php
+++ b/tests/php/Forms/FormTest/ControllerWithSpecialSubmittedValueFields.php
@@ -12,7 +12,7 @@
 use SilverStripe\Forms\FormAction;
 use SilverStripe\Forms\MoneyField;
 use SilverStripe\Forms\NumericField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\TextField;
 use SilverStripe\Core\Validation\ValidationException;
 use SilverStripe\Core\Validation\ValidationResult;
@@ -69,7 +69,7 @@ public function Form()
             new FieldList(
                 FormAction::create('doSubmit')
             ),
-            new RequiredFields(
+            new RequiredFieldsValidator(
                 'SomeRequiredField'
             )
         );
diff --git a/tests/php/Forms/FormTest/TestController.php b/tests/php/Forms/FormTest/TestController.php
index a9ca0a1259d..1aac455062d 100644
--- a/tests/php/Forms/FormTest/TestController.php
+++ b/tests/php/Forms/FormTest/TestController.php
@@ -11,7 +11,7 @@
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\FormAction;
 use SilverStripe\Forms\NumericField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\TextField;
 use SilverStripe\Core\Validation\ValidationException;
 use SilverStripe\Core\Validation\ValidationResult;
@@ -66,7 +66,7 @@ public function Form()
                 FormAction::create('doSubmitActionExempt')
                     ->setValidationExempt(true)
             ),
-            new RequiredFields(
+            new RequiredFieldsValidator(
                 'Email',
                 'SomeRequiredField'
             )
diff --git a/tests/php/Forms/GridField/GridFieldDetailFormTest/Person.php b/tests/php/Forms/GridField/GridFieldDetailFormTest/Person.php
index c5f33364065..ce97268c0e6 100644
--- a/tests/php/Forms/GridField/GridFieldDetailFormTest/Person.php
+++ b/tests/php/Forms/GridField/GridFieldDetailFormTest/Person.php
@@ -5,7 +5,7 @@
 use SilverStripe\Dev\TestOnly;
 use SilverStripe\Forms\GridField\GridField;
 use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\ORM\DataObject;
 use SilverStripe\ORM\DataObjectSchema;
 
@@ -67,7 +67,7 @@ public function getCMSFields()
 
     public function getCMSValidator()
     {
-        return new RequiredFields(
+        return new RequiredFieldsValidator(
             [
             'FirstName',
             'Surname'
diff --git a/tests/php/Forms/GridField/GridFieldExportButtonTest.php b/tests/php/Forms/GridField/GridFieldExportButtonTest.php
index 9ec0d38ce48..255ba66edfe 100644
--- a/tests/php/Forms/GridField/GridFieldExportButtonTest.php
+++ b/tests/php/Forms/GridField/GridFieldExportButtonTest.php
@@ -2,6 +2,7 @@
 
 namespace SilverStripe\Forms\Tests\GridField;
 
+use League\Csv\Bom;
 use League\Csv\Reader;
 use LogicException;
 use ReflectionMethod;
@@ -64,7 +65,7 @@ public function testCanView()
 
         $this->assertEquals(
             "$bom\"My Name\"\r\n",
-            (string) $csvReader
+            (string) $csvReader->toString()
         );
     }
 
@@ -78,7 +79,7 @@ public function testGenerateFileDataBasicFields()
 
         $this->assertEquals(
             $bom . '"My Name"' . "\r\n" . 'Test' . "\r\n" . 'Test2' . "\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -98,7 +99,7 @@ public function testXLSSanitisation()
 
         $this->assertEquals(
             "$bom\"My Name\"\r\n\"\t=SUM(1, 2)\"\r\nTest\r\nTest2\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -117,7 +118,7 @@ public function testGenerateFileDataAnonymousFunctionField()
 
         $this->assertEquals(
             $bom . 'Name,City' . "\r\n" . 'Test,"City city"' . "\r\n" . 'Test2,"Quoted ""City"" 2 city"' . "\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -134,7 +135,7 @@ public function testBuiltInFunctionNameCanBeUsedAsHeader()
 
         $this->assertEquals(
             $bom . 'Name,strtolower' . "\r\n" . 'Test,City' . "\r\n" . 'Test2,"Quoted ""City"" 2"' . "\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -152,7 +153,7 @@ public function testNoCsvHeaders()
 
         $this->assertEquals(
             $bom . 'Test,City' . "\r\n" . 'Test2,"Quoted ""City"" 2"' . "\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -179,7 +180,7 @@ public function testArrayListInput()
 
         $this->assertEquals(
             $bom . "ID\r\n" . "1\r\n" . "2\r\n" . "3\r\n" . "4\r\n" . "5\r\n" . "6\r\n" . "7\r\n" . "8\r\n" . "9\r\n" . "10\r\n" . "11\r\n" . "12\r\n" . "13\r\n" . "14\r\n" . "15\r\n" . "16\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -195,7 +196,7 @@ public function testZeroValue()
 
         $this->assertEquals(
             "$bom\"Rugby Team Number\"\r\n2\r\n0\r\n",
-            (string) $csvReader
+            $csvReader->toString()
         );
     }
 
@@ -224,7 +225,7 @@ protected function createReader($string)
 
         // Explicitly set the output BOM in league/csv 9
         if (method_exists($reader, 'getContent')) {
-            $reader->setOutputBOM(Reader::BOM_UTF8);
+            $reader->setOutputBOM(Bom::Utf8);
         }
 
         return $reader;
diff --git a/tests/php/Forms/GridField/GridFieldTest.php b/tests/php/Forms/GridField/GridFieldTest.php
index d80a9a70c62..8847ee255f8 100644
--- a/tests/php/Forms/GridField/GridFieldTest.php
+++ b/tests/php/Forms/GridField/GridFieldTest.php
@@ -22,7 +22,7 @@
 use SilverStripe\Forms\GridField\GridState;
 use SilverStripe\Forms\GridField\GridState_Component;
 use SilverStripe\Forms\GridField\GridState_Data;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Tests\GridField\GridFieldTest\Cheerleader;
 use SilverStripe\Forms\Tests\GridField\GridFieldTest\Component;
 use SilverStripe\Forms\Tests\GridField\GridFieldTest\Component2;
@@ -551,7 +551,7 @@ public function testValidationMessageInOutput()
         $gridField->setMessage(null);
 
         // A form that passes validation should not display a validation error in the FieldHolder output.
-        $form->setValidator(new RequiredFields());
+        $form->setValidator(null);
         $form->validationResult();
         $gridfieldOutput = $gridField->FieldHolder();
         $this->assertStringNotContainsString('<p class="message ' . ValidationResult::TYPE_ERROR . '">', $gridfieldOutput);
diff --git a/tests/php/Forms/GroupedDropdownFieldTest.php b/tests/php/Forms/GroupedDropdownFieldTest.php
index d382afcbe6d..22207dfd9ff 100644
--- a/tests/php/Forms/GroupedDropdownFieldTest.php
+++ b/tests/php/Forms/GroupedDropdownFieldTest.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\GroupedDropdownField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 
 class GroupedDropdownFieldTest extends SapphireTest
 {
diff --git a/tests/php/Forms/ListboxFieldTest.php b/tests/php/Forms/ListboxFieldTest.php
index b451f97b590..5ef0a1b3832 100644
--- a/tests/php/Forms/ListboxFieldTest.php
+++ b/tests/php/Forms/ListboxFieldTest.php
@@ -9,7 +9,7 @@
 use SilverStripe\Dev\CSSContentParser;
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\ListboxField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Model\ArrayData;
 use PHPUnit\Framework\Attributes\DataProvider;
 
diff --git a/tests/php/Forms/MoneyFieldTest.php b/tests/php/Forms/MoneyFieldTest.php
index d48b393301b..ea136e8be69 100644
--- a/tests/php/Forms/MoneyFieldTest.php
+++ b/tests/php/Forms/MoneyFieldTest.php
@@ -5,7 +5,7 @@
 use SilverStripe\Forms\DropdownField;
 use SilverStripe\Forms\HiddenField;
 use SilverStripe\Forms\NumericField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Tests\MoneyFieldTest\CustomSetter_Object;
 use SilverStripe\Forms\Tests\MoneyFieldTest\TestObject;
 use SilverStripe\Forms\TextField;
diff --git a/tests/php/Forms/NumericFieldTest.php b/tests/php/Forms/NumericFieldTest.php
index 6637f3d55b6..641a8153703 100644
--- a/tests/php/Forms/NumericFieldTest.php
+++ b/tests/php/Forms/NumericFieldTest.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\NumericField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\i18n\i18n;
 use PHPUnit\Framework\Attributes\DataProvider;
 
diff --git a/tests/php/Forms/OptionsetFieldTest.php b/tests/php/Forms/OptionsetFieldTest.php
index 0528e7ed268..04c188d6e5c 100644
--- a/tests/php/Forms/OptionsetFieldTest.php
+++ b/tests/php/Forms/OptionsetFieldTest.php
@@ -7,7 +7,7 @@
 use SilverStripe\Dev\CSSContentParser;
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\OptionsetField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
 
@@ -46,7 +46,7 @@ public function testValidation()
             "Five" => "Five"
             ]
         );
-        $validator = new RequiredFields('Test');
+        $validator = new RequiredFieldsValidator('Test');
         $form = new Form(null, 'Form', new FieldList($field), new FieldList(), $validator);
 
         $field->setValue("One");
@@ -60,14 +60,14 @@ public function testValidation()
         $field->setValue('');
         $this->assertFalse($field->validate()->isValid());
 
-        // ... and should not pass "RequiredFields" validation
+        // ... and should not pass "RequiredFieldsValidator" validation
         $this->assertFalse($form->validationResult()->isValid());
 
         // null value should pass field-level validation...
         $field->setValue(null);
         $this->assertTrue($field->validate()->isValid());
 
-        // ... but should not pass "RequiredFields" validation
+        // ... but should not pass "RequiredFieldsValidator" validation
         $this->assertFalse($form->validationResult()->isValid());
 
         // disabled items shouldn't validate
@@ -118,7 +118,7 @@ public function testNoAriaRequired()
             "form",
             new FieldList($field),
             new FieldList(),
-            new RequiredFields(["RequiredField"])
+            new RequiredFieldsValidator(["RequiredField"])
         );
         $this->assertTrue($field->Required());
 
diff --git a/tests/php/Forms/RequiredFieldsTest.php b/tests/php/Forms/RequiredFieldsValidatorTest.php
similarity index 92%
rename from tests/php/Forms/RequiredFieldsTest.php
rename to tests/php/Forms/RequiredFieldsValidatorTest.php
index 1f52c0cb46c..188a6f1831f 100644
--- a/tests/php/Forms/RequiredFieldsTest.php
+++ b/tests/php/Forms/RequiredFieldsValidatorTest.php
@@ -3,7 +3,7 @@
 namespace SilverStripe\Forms\Tests;
 
 use SilverStripe\Dev\SapphireTest;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Form;
 use SilverStripe\Forms\SearchableDropdownField;
 use SilverStripe\Forms\TreeDropdownField;
@@ -12,7 +12,7 @@
 use SilverStripe\Forms\TextField;
 use SilverStripe\Forms\FieldList;
 
-class RequiredFieldsTest extends SapphireTest
+class RequiredFieldsValidatorTest extends SapphireTest
 {
     public function testConstructingWithArray()
     {
@@ -23,7 +23,7 @@ public function testConstructingWithArray()
             'Image',
             'AnotherField'
         ];
-        $requiredFields = new RequiredFields($fields);
+        $requiredFields = new RequiredFieldsValidator($fields);
         //check the fields and the array match
         $this->assertEquals(
             $fields,
@@ -35,7 +35,7 @@ public function testConstructingWithArray()
     public function testConstructingWithArguments()
     {
         //can we construct with arguments?
-        $requiredFields = new RequiredFields(
+        $requiredFields = new RequiredFieldsValidator(
             'Title',
             'Content',
             'Image',
@@ -57,7 +57,7 @@ public function testConstructingWithArguments()
     public function testRemoveValidation()
     {
         //can we remove all fields at once?
-        $requiredFields = new RequiredFields(
+        $requiredFields = new RequiredFieldsValidator(
             'Title',
             'Content',
             'Image',
@@ -74,7 +74,7 @@ public function testRemoveValidation()
     public function testRemoveRequiredField()
     {
         //set up the required fields
-        $requiredFields = new RequiredFields(
+        $requiredFields = new RequiredFieldsValidator(
             'Title',
             'Content',
             'Image',
@@ -117,7 +117,7 @@ public function testRemoveRequiredField()
     public function testAddRequiredField()
     {
         //set up the validator
-        $requiredFields = new RequiredFields(
+        $requiredFields = new RequiredFieldsValidator(
             'Title'
         );
         //add a field
@@ -183,14 +183,14 @@ public function testAddRequiredField()
     public function testAppendRequiredFields()
     {
         //get the validator
-        $requiredFields = new RequiredFields(
+        $requiredFields = new RequiredFieldsValidator(
             'Title',
             'Content',
             'Image',
             'AnotherField'
         );
         //create another validator with other fields
-        $otherRequiredFields = new RequiredFields(
+        $otherRequiredFields = new RequiredFieldsValidator(
             [
             'ExtraField1',
             'ExtraField2'
@@ -212,7 +212,7 @@ public function testAppendRequiredFields()
             "Merging of required fields failed to behave as expected"
         );
         // create the standard validator so we can check duplicates are ignored
-        $otherRequiredFields = new RequiredFields(
+        $otherRequiredFields = new RequiredFieldsValidator(
             'Title',
             'Content',
             'Image',
@@ -234,7 +234,7 @@ public function testAppendRequiredFields()
             "Merging of required fields with duplicates failed to behave as expected"
         );
         //add some new fields and some old ones in a strange order
-        $otherRequiredFields = new RequiredFields(
+        $otherRequiredFields = new RequiredFieldsValidator(
             'ExtraField3',
             'Title',
             'ExtraField4',
@@ -263,7 +263,7 @@ public function testAppendRequiredFields()
     public function testFieldIsRequired()
     {
         //get the validator
-        $requiredFields = new RequiredFields(
+        $requiredFields = new RequiredFieldsValidator(
             $fieldNames = [
             'Title',
             'Content',
@@ -312,7 +312,7 @@ public function testHasOneRelationFieldInterfaceValidation(string $className)
         $param = $className === TreeDropdownField::class ? Group::class : Group::get();
         $field = new $className('TestField', 'TestField', $param);
         $form->Fields()->push($field);
-        $validator = new RequiredFields('TestField');
+        $validator = new RequiredFieldsValidator('TestField');
         $validator->setForm($form);
         // blank string and 0 and '0' and array with value of 0 fail required field validation
         $this->assertFalse($validator->php(['TestField' => '']));
@@ -418,12 +418,12 @@ public function testAllowWhitespaceOnlyConfig(
         bool $allowWhitespaceOnly,
         bool $expected,
     ): void {
-        $validator = new RequiredFields(['TestField']);
+        $validator = new RequiredFieldsValidator(['TestField']);
         $this->assertSame(true, $validator->getAllowWhitespaceOnly());
         $field = new TextField('TestField');
         $field->setValue($value);
         $form = new Form(null, null, new FieldList([$field]), null, $validator);
-        RequiredFields::config()->set('allow_whitespace_only', $allowWhitespaceOnly);
+        RequiredFieldsValidator::config()->set('allow_whitespace_only', $allowWhitespaceOnly);
         $result = $validator->validate($form);
         $this->assertEquals($expected, $result->isValid());
     }
@@ -434,7 +434,7 @@ public function testAllowWhitespaceOnlySetter(
         bool $allowWhitespaceOnly,
         bool $expected,
     ): void {
-        $validator = new RequiredFields(['TestField']);
+        $validator = new RequiredFieldsValidator(['TestField']);
         $validator->setAllowWhitespaceOnly($allowWhitespaceOnly);
         $this->assertSame($allowWhitespaceOnly, $validator->getAllowWhitespaceOnly());
         $field = new TextField('TestField');
@@ -443,10 +443,10 @@ public function testAllowWhitespaceOnlySetter(
         $result = $validator->validate($form);
         $this->assertEquals($expected, $result->isValid());
         // assert that global config makes no difference
-        RequiredFields::config()->set('allow_whitespace_only', true);
+        RequiredFieldsValidator::config()->set('allow_whitespace_only', true);
         $result = $validator->validate($form);
         $this->assertEquals($expected, $result->isValid());
-        RequiredFields::config()->set('allow_whitespace_only', false);
+        RequiredFieldsValidator::config()->set('allow_whitespace_only', false);
         $result = $validator->validate($form);
         $this->assertEquals($expected, $result->isValid());
     }
diff --git a/tests/php/Forms/TextFieldTest.php b/tests/php/Forms/TextFieldTest.php
index 062867cad19..09d44ef9f22 100644
--- a/tests/php/Forms/TextFieldTest.php
+++ b/tests/php/Forms/TextFieldTest.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\TextField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\Tip;
 use PHPUnit\Framework\Attributes\DataProvider;
 
diff --git a/tests/php/Forms/TextareaFieldTest.php b/tests/php/Forms/TextareaFieldTest.php
index fa481b6a6c3..32ee7724aab 100644
--- a/tests/php/Forms/TextareaFieldTest.php
+++ b/tests/php/Forms/TextareaFieldTest.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\TextareaField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use PHPUnit\Framework\Attributes\DataProvider;
 
 class TextareaFieldTest extends SapphireTest
diff --git a/tests/php/Forms/TimeFieldTest.php b/tests/php/Forms/TimeFieldTest.php
index 35d0e9fdd27..ea915cceb6f 100644
--- a/tests/php/Forms/TimeFieldTest.php
+++ b/tests/php/Forms/TimeFieldTest.php
@@ -7,7 +7,7 @@
 use SilverStripe\Core\Config\Config;
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\TimeField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\i18n\i18n;
 use PHPUnit\Framework\Attributes\DataProvider;
 use ReflectionMethod;
diff --git a/tests/php/Forms/TreeDropdownFieldTest.php b/tests/php/Forms/TreeDropdownFieldTest.php
index 012b22b9a32..fbf15de4f1e 100644
--- a/tests/php/Forms/TreeDropdownFieldTest.php
+++ b/tests/php/Forms/TreeDropdownFieldTest.php
@@ -10,7 +10,7 @@
 use SilverStripe\Control\HTTPRequest;
 use SilverStripe\Forms\FieldList;
 use SilverStripe\Forms\Form;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Forms\TreeDropdownField;
 use SilverStripe\ORM\DataObject;
 use SilverStripe\ORM\Tests\HierarchyTest\HierarchyOnSubclassTestObject;
@@ -61,7 +61,7 @@ public function testGetSchemaValidation(): void
         $this->assertSame($expected, $field->getSchemaValidation());
         // field is required
         $fieldList = new FieldList([$field]);
-        $validator = new RequiredFields('TestTree');
+        $validator = new RequiredFieldsValidator('TestTree');
         new Form(null, null, $fieldList, null, $validator);
         $expected = [
             'required' => ['extraEmptyValues' => ['0']],
diff --git a/tests/php/Forms/UrlFieldTest.php b/tests/php/Forms/UrlFieldTest.php
index da55e05e993..03d73b3a728 100644
--- a/tests/php/Forms/UrlFieldTest.php
+++ b/tests/php/Forms/UrlFieldTest.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Dev\SapphireTest;
 use SilverStripe\Forms\UrlField;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use PHPUnit\Framework\Attributes\DataProvider;
 
 class UrlFieldTest extends SapphireTest
diff --git a/tests/php/Forms/ValidatorTest/TestValidator.php b/tests/php/Forms/ValidatorTest/TestValidator.php
index 141c3522112..f5ebf73826f 100644
--- a/tests/php/Forms/ValidatorTest/TestValidator.php
+++ b/tests/php/Forms/ValidatorTest/TestValidator.php
@@ -4,7 +4,7 @@
 
 use SilverStripe\Dev\TestOnly;
 use SilverStripe\Forms\Form;
-use SilverStripe\Forms\Validator;
+use SilverStripe\Forms\Validation\Validator;
 
 class TestValidator extends Validator implements TestOnly
 {
diff --git a/tests/php/Security/GroupTest.php b/tests/php/Security/GroupTest.php
index 1834f019ed8..0d5846c496a 100644
--- a/tests/php/Security/GroupTest.php
+++ b/tests/php/Security/GroupTest.php
@@ -5,7 +5,7 @@
 use InvalidArgumentException;
 use SilverStripe\Control\Controller;
 use SilverStripe\Dev\FunctionalTest;
-use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\Validation\RequiredFieldsValidator;
 use SilverStripe\Model\List\ArrayList;
 use SilverStripe\ORM\DataObject;
 use SilverStripe\Security\Group;
@@ -287,10 +287,10 @@ public function testGroupTitleValidation()
 
         $newGroup = new Group();
 
-        $validators = $newGroup->getCMSCompositeValidator()->getValidatorsByType(RequiredFields::class);
+        $validators = $newGroup->getCMSCompositeValidator()->getValidatorsByType(RequiredFieldsValidator::class);
         $this->assertCount(1, $validators);
         $validator = array_shift($validators);
-        $this->assertInstanceOf(RequiredFields::class, $validator);
+        $this->assertInstanceOf(RequiredFieldsValidator::class, $validator);
         $this->assertTrue(in_array('Title', $validator->getRequired() ?? []));
 
         $newGroup->Title = $group1->Title;
diff --git a/tests/php/View/Embed/MockRequest.php b/tests/php/View/Embed/MockRequest.php
index c92af57733b..0e3d2cec741 100644
--- a/tests/php/View/Embed/MockRequest.php
+++ b/tests/php/View/Embed/MockRequest.php
@@ -5,6 +5,7 @@
 use Psr\Http\Message\RequestInterface;
 use Psr\Http\Message\UriInterface;
 use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\MessageInterface;
 
 class MockRequest implements RequestInterface
 {
@@ -17,80 +18,88 @@ public function __construct(EmbedUnitTest $unitTest, MockUri $mockUri)
         $this->mockUri = $mockUri;
     }
 
-    public function getRequestTarget()
+    public function getRequestTarget(): string
     {
+        return '';
     }
 
-    public function getMethod()
+    public function getMethod(): string
     {
+        return '';
     }
 
-    public function getUri()
+    public function getUri(): UriInterface
     {
         $this->unitTest->setFirstRequest(false);
         return $this->mockUri;
     }
 
-    public function getProtocolVersion()
+    public function getProtocolVersion(): string
     {
+        return '';
     }
 
-    public function getHeaders()
+    public function getHeaders(): array
     {
+        return [];
     }
 
-    public function getHeader($name)
+    public function getHeader($name): array
     {
+        return [];
     }
 
-    public function getHeaderLine($name)
+    public function getHeaderLine($name): string
     {
+        return '';
     }
 
-    public function getBody()
+    public function getBody(): StreamInterface
     {
+        return MockUtil::createStreamInterface('');
     }
 
-    public function hasHeader($name)
+    public function hasHeader($name): bool
     {
+        return false;
     }
 
-    public function withHeader($name, $value)
+    public function withHeader($name, $value): MessageInterface
     {
         return $this;
     }
 
-    public function withAddedHeader($name, $value)
+    public function withAddedHeader($name, $value): MessageInterface
     {
         return $this;
     }
 
-    public function withoutHeader($name)
+    public function withoutHeader($name): MessageInterface
     {
         return $this;
     }
 
-    public function withBody(StreamInterface $body)
+    public function withBody(StreamInterface $body): MessageInterface
     {
         return $this;
     }
 
-    public function withProtocolVersion($version)
+    public function withProtocolVersion($version): MessageInterface
     {
         return $this;
     }
 
-    public function withRequestTarget($requestTarget)
+    public function withRequestTarget($requestTarget): RequestInterface
     {
         return $this;
     }
 
-    public function withMethod($method)
+    public function withMethod($method): RequestInterface
     {
         return $this;
     }
 
-    public function withUri(UriInterface $uri, $preserveHost = false)
+    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
     {
         return $this;
     }
diff --git a/tests/php/View/Embed/MockResponse.php b/tests/php/View/Embed/MockResponse.php
index 65785047afd..477ba360eeb 100644
--- a/tests/php/View/Embed/MockResponse.php
+++ b/tests/php/View/Embed/MockResponse.php
@@ -4,6 +4,7 @@
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\MessageInterface;
 
 class MockResponse implements ResponseInterface
 {
@@ -18,78 +19,78 @@ public function __construct(EmbedUnitTest $unitTest, string $firstResponse, stri
         $this->secondResponse = $secondResponse;
     }
 
-    public function getStatusCode()
+    public function getStatusCode(): int
     {
         return 200;
     }
 
-    public function getBody()
+    public function getBody(): StreamInterface
     {
         // first request is to the video HTML to get to find the oembed link
         // second request is to the oembed endpoint to fetch JSON
         if ($this->unitTest->getFirstRequest()) {
-            return $this->firstResponse;
+            return MockUtil::createStreamInterface($this->firstResponse);
         } else {
-            return $this->secondResponse;
+            return MockUtil::createStreamInterface($this->secondResponse);
         }
     }
 
-    public function getReasonPhrase()
+    public function getReasonPhrase(): string
     {
         return '';
     }
 
-    public function getProtocolVersion()
+    public function getProtocolVersion(): string
     {
         return '';
     }
 
-    public function getHeaders()
+    public function getHeaders(): array
     {
         return [];
     }
 
-    public function getHeader($name)
+    public function getHeader($name): array
     {
-        return '';
+        return [];
     }
 
-    public function getHeaderLine($name)
+    public function getHeaderLine($name): string
     {
         return '';
     }
 
-    public function hasHeader($name)
+    public function hasHeader($name): bool
     {
         return false;
     }
 
-    public function withHeader($name, $value)
+    public function withHeader($name, $value): MessageInterface
     {
         return $this;
     }
 
-    public function withAddedHeader($name, $value)
+    public function withAddedHeader($name, $value): MessageInterface
     {
         return $this;
     }
 
-    public function withBody(StreamInterface $body)
+    public function withBody(StreamInterface $body): MessageInterface
     {
         return $this;
     }
 
-    public function withoutHeader($name)
+    public function withoutHeader($name): MessageInterface
     {
         return $this;
     }
 
-    public function withProtocolVersion($version)
+    public function withProtocolVersion($version): MessageInterface
     {
         return $this;
     }
 
-    public function withStatus($code, $reasonPhrase = '')
+    public function withStatus($code, $reasonPhrase = ''): ResponseInterface
     {
         return $this;
     }
diff --git a/tests/php/View/Embed/MockUri.php b/tests/php/View/Embed/MockUri.php
index a374d1336d2..e7a692d72cf 100644
--- a/tests/php/View/Embed/MockUri.php
+++ b/tests/php/View/Embed/MockUri.php
@@ -7,10 +7,10 @@
 
 class MockUri implements UriInterface, Stringable
 {
-    private string $scheme;
-    private string $host;
-    private string $path;
-    private string $query;
+    private string $scheme = '';
+    private string $host = '';
+    private string $path = '';
+    private string $query = '';
 
     public function __construct(string $url)
     {
@@ -21,73 +21,77 @@ public function __construct(string $url)
         $this->query = $p['query'] ?? '';
     }
 
-    public function getScheme()
+    public function getScheme(): string
     {
         return $this->scheme;
     }
 
-    public function getHost()
+    public function getHost(): string
     {
         return $this->host;
     }
 
-    public function getPath()
+    public function getPath(): string
     {
         return $this->path;
     }
 
-    public function getQuery()
+    public function getQuery(): string
     {
         return $this->query;
     }
 
-    public function getPort()
+    public function getPort(): ?int
     {
+        return null;
     }
 
-    public function getAuthority()
+    public function getAuthority(): string
     {
+        return '';
     }
 
-    public function getUserInfo()
+    public function getUserInfo(): string
     {
+        return '';
     }
 
-    public function getFragment()
+    public function getFragment(): string
     {
+        return '';
     }
 
-    public function withPath($path)
+    public function withPath($path): UriInterface
     {
         return $this;
     }
 
-    public function withScheme($scheme)
+    public function withScheme($scheme): UriInterface
     {
         return $this;
     }
 
-    public function withUserInfo($user, $password = null)
+    public function withUserInfo($user, $password = null): UriInterface
     {
         return $this;
     }
 
-    public function withHost($host)
+    public function withHost($host): UriInterface
     {
         return $this;
     }
 
-    public function withPort($port)
+    public function withPort($port): UriInterface
     {
         return $this;
     }
 
-    public function withQuery($query)
+    public function withQuery($query): UriInterface
     {
         return $this;
     }
 
-    public function withFragment($fragment)
+    public function withFragment($fragment): UriInterface
     {
         return $this;
     }
diff --git a/tests/php/View/Embed/MockUtil.php b/tests/php/View/Embed/MockUtil.php
new file mode 100644
index 00000000000..82000a9f035
--- /dev/null
+++ b/tests/php/View/Embed/MockUtil.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace SilverStripe\View\Tests\Embed;
+
+use Psr\Http\Message\StreamInterface;
+
+class MockUtil
+{
+    public static function createStreamInterface(string $body)
+    {
+        return new class($body) implements StreamInterface {
+            private string $body;
+            public function __construct(string $body)
+            {
+                $this->body = $body;
+            }
+            public function __toString(): string
+            {
+                return $this->body;
+            }
+            public function close(): void
+            {
+                return;
+            }
+            public function detach()
+            {
+                return;
+            }
+            public function getSize(): ?int
+            {
+                return null;
+            }
+            public function tell(): int
+            {
+                return 0;
+            }
+            public function eof(): bool
+            {
+                return false;
+            }
+            public function isSeekable(): bool
+            {
+                return false;
+            }
+            public function seek(int $offset, int $whence = SEEK_SET): void
+            {
+                return;
+            }
+            public function rewind(): void
+            {
+                return;
+            }
+            public function isWritable(): bool
+            {
+                return false;
+            }
+            public function write(string $string): int
+            {
+                return 0;
+            }
+            public function isReadable(): bool
+            {
+                return false;
+            }
+            public function read(int $length): string
+            {
+                return '';
+            }
+            public function getContents(): string
+            {
+                return '';
+            }
+            public function getMetadata(?string $key = null)
+            {
+                return;
+            }
+        };
+    }
+}