From 09a41b15ac3a4e87c40bccdcc78022be08aeac55 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Wed, 5 Aug 2020 11:18:26 +0200
Subject: [PATCH] [!!!][TASK] FAL: Decouple logic of ResourceFactory into
 StorageRepository

The "ResourceFactory" class was introduced as a "god class" to build
any File/Folder object (from a string, DB row or integer), but is also
there for building sys_file_storage (ResourceStorage) objects, so it is
mixing a lot of concerns (also driver setup).

However, all creation of ResourceStorage objects should be moved into
the StorageRepository (which already exists), thus reducing
the dependencies towards ResourceFactory.

This makes it much easier to not juggle with storage UIDs anymore
but actually work with storage objects throughout the internal FAL
API.

The StorageRepository is not extending from the AbstractRepository
anymore, which is breaking.

Resolves: #92289
Releases: master
Change-Id: Ib367164535550e280a6907f4d4473aa0417c3813
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65184
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Helmut Hummel <typo3@helhum.io>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Helmut Hummel <typo3@helhum.io>
---
 .../UserSysFileStorageIsPublicElement.php     |   4 +-
 typo3/sysext/core/Classes/Resource/File.php   |   2 +-
 .../Resource/Index/FileIndexRepository.php    |  57 +---
 .../core/Classes/Resource/Index/Indexer.php   |   2 +-
 .../core/Classes/Resource/ProcessedFile.php   |   2 +-
 .../Resource/ProcessedFileRepository.php      |   1 -
 .../core/Classes/Resource/ResourceFactory.php | 232 ++-------------
 .../core/Classes/Resource/ResourceStorage.php |  70 ++++-
 .../Classes/Resource/StorageRepository.php    | 274 +++++++++++++++---
 typo3/sysext/core/Classes/ServiceProvider.php |   7 +-
 .../Utility/File/ExtendedFileUtility.php      |   2 -
 ...OfResourceFactoryIntoStorageRepository.rst |  64 ++++
 .../Resource/ResourceStorageTest.php          |  18 +-
 .../core/Tests/Unit/Resource/FileTest.php     |  10 +-
 .../Unit/Resource/ResourceFactoryTest.php     | 104 +------
 .../Unit/Resource/ResourceStorageTest.php     |   7 +-
 .../Unit/Resource/StorageRepositoryTest.php   | 127 ++++++++
 .../Classes/Controller/FileListController.php |   7 +-
 .../Hooks/FormFileExtensionUpdateTest.php     |   4 +-
 .../Functional/Imaging/GifBuilderTest.php     |   3 +-
 .../SiteHandling/TypoLinkGeneratorTest.php    |   2 +-
 typo3/sysext/impexp/Classes/Import.php        |  13 +-
 .../Php/MethodCallMatcher.php                 |   7 +
 ...orageExtractionAdditionalFieldProvider.php |   3 +-
 .../Task/FileStorageExtractionTask.php        |   4 +-
 ...StorageIndexingAdditionalFieldProvider.php |   3 +-
 .../Classes/Task/FileStorageIndexingTask.php  |   4 +-
 27 files changed, 585 insertions(+), 448 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Breaking-92289-DecoupleLogicOfResourceFactoryIntoStorageRepository.rst
 create mode 100644 typo3/sysext/core/Tests/Unit/Resource/StorageRepositoryTest.php

diff --git a/typo3/sysext/backend/Classes/Form/Element/UserSysFileStorageIsPublicElement.php b/typo3/sysext/backend/Classes/Form/Element/UserSysFileStorageIsPublicElement.php
index 620b1c4d313e..203254d8f217 100644
--- a/typo3/sysext/backend/Classes/Form/Element/UserSysFileStorageIsPublicElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/UserSysFileStorageIsPublicElement.php
@@ -21,7 +21,7 @@ use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Resource\Exception\InvalidPathException;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -61,7 +61,7 @@ class UserSysFileStorageIsPublicElement extends AbstractFormElement
             $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
             $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
             try {
-                $storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject((int)$row['uid']);
+                $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid((int)$row['uid']);
                 $storageRecord = $storage->getStorageRecord();
                 $isPublic = $storage->isPublic() && $storageRecord['is_public'];
 
diff --git a/typo3/sysext/core/Classes/Resource/File.php b/typo3/sysext/core/Classes/Resource/File.php
index e4607c041ca4..1863dd28ad4e 100644
--- a/typo3/sysext/core/Classes/Resource/File.php
+++ b/typo3/sysext/core/Classes/Resource/File.php
@@ -195,7 +195,7 @@ class File extends AbstractFile
             $this->getType();
         }
         if (array_key_exists('storage', $properties) && in_array('storage', $this->updatedProperties)) {
-            $this->storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($properties['storage']);
+            $this->storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid((int)$properties['storage']);
         }
     }
 
diff --git a/typo3/sysext/core/Classes/Resource/Index/FileIndexRepository.php b/typo3/sysext/core/Classes/Resource/Index/FileIndexRepository.php
index fa3a17d1cb17..afe14c8dcd03 100644
--- a/typo3/sysext/core/Classes/Resource/Index/FileIndexRepository.php
+++ b/typo3/sysext/core/Classes/Resource/Index/FileIndexRepository.php
@@ -26,7 +26,6 @@ use TYPO3\CMS\Core\Resource\Event\AfterFileUpdatedInIndexEvent;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\FileInterface;
 use TYPO3\CMS\Core\Resource\Folder;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -61,16 +60,6 @@ class FileIndexRepository implements SingletonInterface
         'mime_type', 'name', 'sha1', 'size', 'creation_date', 'modification_date', 'folder_hash'
     ];
 
-    /**
-     * Gets the Resource Factory
-     *
-     * @return ResourceFactory
-     */
-    protected function getResourceFactory()
-    {
-        return GeneralUtility::makeInstance(ResourceFactory::class);
-    }
-
     /**
      * Returns an Instance of the Repository
      *
@@ -86,18 +75,6 @@ class FileIndexRepository implements SingletonInterface
         $this->eventDispatcher = $eventDispatcher;
     }
 
-    /**
-     * Retrieves Index record for a given $combinedIdentifier
-     *
-     * @param string $combinedIdentifier
-     * @return array|bool
-     */
-    public function findOneByCombinedIdentifier($combinedIdentifier)
-    {
-        [$storageUid, $identifier] = GeneralUtility::trimExplode(':', $combinedIdentifier, false, 2);
-        return $this->findOneByStorageUidAndIdentifier($storageUid, $identifier);
-    }
-
     /**
      * Retrieves Index record for a given $fileUid
      *
@@ -121,21 +98,6 @@ class FileIndexRepository implements SingletonInterface
         return is_array($row) ? $row : false;
     }
 
-    /**
-     * Retrieves Index record for a given $storageUid and $identifier
-     *
-     * @param int $storageUid
-     * @param string $identifier
-     * @return array|bool
-     *
-     * @internal only for use from FileRepository
-     */
-    public function findOneByStorageUidAndIdentifier($storageUid, $identifier)
-    {
-        $identifierHash = $this->getResourceFactory()->getStorageObject($storageUid)->hashFileIdentifier($identifier);
-        return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
-    }
-
     /**
      * Retrieves Index record for a given $storageUid and $identifier
      *
@@ -163,6 +125,21 @@ class FileIndexRepository implements SingletonInterface
         return is_array($row) ? $row : false;
     }
 
+    /**
+     * Retrieves Index record for a given $storageUid and $identifier
+     *
+     * @param ResourceStorage $storage
+     * @param string $identifier
+     * @return array|bool
+     *
+     * @internal only for use from FileRepository
+     */
+    public function findOneByStorageAndIdentifier(ResourceStorage $storage, $identifier)
+    {
+        $identifierHash = $storage->hashFileIdentifier($identifier);
+        return $this->findOneByStorageUidAndIdentifierHash($storage->getUid(), $identifierHash);
+    }
+
     /**
      * Retrieves Index record for a given $fileObject
      *
@@ -173,9 +150,7 @@ class FileIndexRepository implements SingletonInterface
      */
     public function findOneByFileObject(FileInterface $fileObject)
     {
-        $storageUid = $fileObject->getStorage()->getUid();
-        $identifierHash = $fileObject->getHashedIdentifier();
-        return $this->findOneByStorageUidAndIdentifierHash($storageUid, $identifierHash);
+        return $this->findOneByStorageAndIdentifier($fileObject->getStorage(), $fileObject->getIdentifier());
     }
 
     /**
diff --git a/typo3/sysext/core/Classes/Resource/Index/Indexer.php b/typo3/sysext/core/Classes/Resource/Index/Indexer.php
index c9a156fa2b20..c72d91d7a3ac 100644
--- a/typo3/sysext/core/Classes/Resource/Index/Indexer.php
+++ b/typo3/sysext/core/Classes/Resource/Index/Indexer.php
@@ -213,7 +213,7 @@ class Indexer implements LoggerAwareInterface
             // Get the modification time for file-identifier from the storage
             $modificationTime = $this->storage->getFileInfoByIdentifier($fileIdentifier, ['mtime']);
             // Look if the the modification time in FS is higher than the one in database (key needed on timestamps)
-            $indexRecord = $this->getFileIndexRepository()->findOneByStorageUidAndIdentifier($this->storage->getUid(), $fileIdentifier);
+            $indexRecord = $this->getFileIndexRepository()->findOneByStorageAndIdentifier($this->storage, $fileIdentifier);
 
             if ($indexRecord !== false) {
                 $this->identifiedFileUids[] = $indexRecord['uid'];
diff --git a/typo3/sysext/core/Classes/Resource/ProcessedFile.php b/typo3/sysext/core/Classes/Resource/ProcessedFile.php
index db2990e81926..48563f109b32 100644
--- a/typo3/sysext/core/Classes/Resource/ProcessedFile.php
+++ b/typo3/sysext/core/Classes/Resource/ProcessedFile.php
@@ -143,7 +143,7 @@ class ProcessedFile extends AbstractFile
         $this->properties = $databaseRow;
 
         if (!empty($databaseRow['storage']) && (int)$this->storage->getUid() !== (int)$databaseRow['storage']) {
-            $this->storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($databaseRow['storage']);
+            $this->storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid($databaseRow['storage']);
         }
     }
 
diff --git a/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php b/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php
index 7a3c2f31d08a..8f1e2cf6fe2c 100644
--- a/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php
+++ b/typo3/sysext/core/Classes/Resource/ProcessedFileRepository.php
@@ -88,7 +88,6 @@ class ProcessedFileRepository extends AbstractRepository implements LoggerAwareI
     protected function createDomainObject(array $databaseRow)
     {
         $originalFile = $this->factory->getFileObject((int)$databaseRow['original']);
-        $originalFile->setStorage($this->factory->getStorageObject($originalFile->getProperty('storage')));
         $taskType = $databaseRow['task_type'];
         // Allow deserialization of Area class, since Area objects get serialized in configuration
         // TODO: This should be changed to json encode and decode at some point
diff --git a/typo3/sysext/core/Classes/Resource/ResourceFactory.php b/typo3/sysext/core/Classes/Resource/ResourceFactory.php
index 926de99a915a..9e8b325a3fcf 100644
--- a/typo3/sysext/core/Classes/Resource/ResourceFactory.php
+++ b/typo3/sysext/core/Classes/Resource/ResourceFactory.php
@@ -15,19 +15,15 @@
 
 namespace TYPO3\CMS\Core\Resource;
 
-use Psr\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Collection\CollectionInterface;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Resource\Collection\FileCollectionRegistry;
-use TYPO3\CMS\Core\Resource\Driver\DriverRegistry;
-use TYPO3\CMS\Core\Resource\Event\AfterResourceStorageInitializationEvent;
-use TYPO3\CMS\Core\Resource\Event\BeforeResourceStorageInitializationEvent;
 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
 use TYPO3\CMS\Core\Resource\Index\FileIndexRepository;
-use TYPO3\CMS\Core\Resource\Index\Indexer;
 use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -39,11 +35,6 @@ use TYPO3\CMS\Core\Utility\PathUtility;
  */
 class ResourceFactory implements SingletonInterface
 {
-    /**
-     * @var ResourceStorage[]
-     */
-    protected $storageInstances = [];
-
     /**
      * @var Collection\AbstractFileCollection[]
      */
@@ -60,40 +51,13 @@ class ResourceFactory implements SingletonInterface
     protected $fileReferenceInstances = [];
 
     /**
-     * A list of the base paths of "local" driver storages. Used to make the detection of base paths easier.
-     *
-     * @var array
-     */
-    protected $localDriverStorageCache;
-
-    /**
-     * @var EventDispatcherInterface
-     */
-    protected $eventDispatcher;
-
-    /**
-     * @param EventDispatcherInterface $eventDispatcher
+     * @var StorageRepository
      */
-    public function __construct(EventDispatcherInterface $eventDispatcher)
-    {
-        $this->eventDispatcher = $eventDispatcher;
-    }
+    protected $storageRepository;
 
-    /**
-     * Creates a driver object for a specified storage object.
-     *
-     * @param string $driverIdentificationString The driver class (or identifier) to use.
-     * @param array $driverConfiguration The configuration of the storage
-     * @return Driver\DriverInterface
-     * @throws \InvalidArgumentException
-     */
-    public function getDriverObject($driverIdentificationString, array $driverConfiguration)
+    public function __construct(StorageRepository $storageRepository)
     {
-        /** @var Driver\DriverRegistry $driverRegistry */
-        $driverRegistry = GeneralUtility::makeInstance(DriverRegistry::class);
-        $driverClass = $driverRegistry->getDriverClass($driverIdentificationString);
-        $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
-        return $driverObject;
+        $this->storageRepository = $storageRepository;
     }
 
     /**
@@ -105,20 +69,13 @@ class ResourceFactory implements SingletonInterface
      * TYPO3 installation.
      *
      * @return ResourceStorage|null
+     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
      */
     public function getDefaultStorage()
     {
-        /** @var StorageRepository $storageRepository */
-        $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
-
-        $allStorages = $storageRepository->findAll();
-        foreach ($allStorages as $storage) {
-            if ($storage->isDefault()) {
-                return $storage;
-            }
-        }
-        return null;
+        return $this->storageRepository->getDefaultStorage();
     }
+
     /**
      * Creates an instance of the storage from given UID. The $recordData can
      * be supplied to increase performance.
@@ -129,114 +86,22 @@ class ResourceFactory implements SingletonInterface
      *
      * @throws \InvalidArgumentException
      * @return ResourceStorage
+     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
      */
     public function getStorageObject($uid, array $recordData = [], &$fileIdentifier = null)
     {
-        if (!is_numeric($uid)) {
-            throw new \InvalidArgumentException('The UID of storage has to be numeric. UID given: "' . $uid . '"', 1314085991);
-        }
-        $uid = (int)$uid;
-        if ($uid === 0 && $fileIdentifier !== null) {
-            $uid = $this->findBestMatchingStorageByLocalPath($fileIdentifier);
-        }
-        if (empty($this->storageInstances[$uid])) {
-            $storageConfiguration = null;
-            /** @var BeforeResourceStorageInitializationEvent $event */
-            $event = $this->eventDispatcher->dispatch(new BeforeResourceStorageInitializationEvent($uid, $recordData, $fileIdentifier));
-            $recordData = $event->getRecord();
-            $uid = $event->getStorageUid();
-            $fileIdentifier = $event->getFileIdentifier();
-            // If the built-in storage with UID=0 is requested:
-            if ($uid === 0) {
-                $recordData = [
-                    'uid' => 0,
-                    'pid' => 0,
-                    'name' => 'Fallback Storage',
-                    'description' => 'Internal storage, mounting the main TYPO3_site directory.',
-                    'driver' => 'Local',
-                    'processingfolder' => 'typo3temp/assets/_processed_/',
-                    // legacy code
-                    'configuration' => '',
-                    'is_online' => true,
-                    'is_browsable' => true,
-                    'is_public' => true,
-                    'is_writable' => true,
-                    'is_default' => false,
-                ];
-                $storageConfiguration = [
-                    'basePath' => '/',
-                    'pathType' => 'relative'
-                ];
-            } elseif ($recordData === [] || (int)$recordData['uid'] !== $uid) {
-                /** @var StorageRepository $storageRepository */
-                $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
-                $recordData = $storageRepository->fetchRowByUid($uid);
-            }
-            $storageObject = $this->createStorageObject($recordData, $storageConfiguration);
-            $storageObject = $this->eventDispatcher
-                ->dispatch(new AfterResourceStorageInitializationEvent($storageObject))
-                ->getStorage();
-            $this->storageInstances[$uid] = $storageObject;
-        }
-        return $this->storageInstances[$uid];
+        return $this->storageRepository->getStorageObject($uid, $recordData, $fileIdentifier);
     }
 
     /**
-     * Checks whether a file resides within a real storage in local file system.
-     * If no match is found, uid 0 is returned which is a fallback storage pointing to fileadmin in public web path.
-     *
-     * The file identifier is adapted accordingly to match the new storage's base path.
+     * Converts a flexform data string to a flat array with key value pairs.
      *
-     * @param string $localPath
-     *
-     * @return int
-     */
-    protected function findBestMatchingStorageByLocalPath(&$localPath)
-    {
-        if ($this->localDriverStorageCache === null) {
-            $this->initializeLocalStorageCache();
-        }
-
-        $bestMatchStorageUid = 0;
-        $bestMatchLength = 0;
-        foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
-            $matchLength = strlen(PathUtility::getCommonPrefix([$basePath, $localPath]));
-            $basePathLength = strlen($basePath);
-
-            if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
-                $bestMatchStorageUid = (int)$storageUid;
-                $bestMatchLength = $matchLength;
-            }
-        }
-        if ($bestMatchStorageUid !== 0) {
-            $localPath = substr($localPath, $bestMatchLength);
-        }
-        return $bestMatchStorageUid;
-    }
-
-    /**
-     * Creates an array mapping all uids to the basePath of storages using the "local" driver.
-     */
-    protected function initializeLocalStorageCache()
-    {
-        /** @var StorageRepository $storageRepository */
-        $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
-        /** @var ResourceStorage[] $storageObjects */
-        $storageObjects = $storageRepository->findByStorageType('Local');
-
-        $storageCache = [];
-        foreach ($storageObjects as $localStorage) {
-            $configuration = $localStorage->getConfiguration();
-            $storageCache[$localStorage->getUid()] = $configuration['basePath'];
-        }
-        $this->localDriverStorageCache = $storageCache;
-    }
-
-    /**
-     * Converts a flexform data string to a flat array with key value pairs
+     * It is recommended to not use this functionality directly, and instead implement this code yourself, as this
+     * code has nothing to do with a Public API for Resources.
      *
      * @param string $flexFormData
      * @return array Array with key => value pairs of the field data in the FlexForm
+     * @internal
      */
     public function convertFlexFormDataToConfigurationArray($flexFormData)
     {
@@ -291,7 +156,7 @@ class ResourceFactory implements SingletonInterface
      * Creates a collection object.
      *
      * @param array $collectionData The database row of the sys_file_collection record.
-     * @return Collection\AbstractFileCollection
+     * @return Collection\AbstractFileCollection|CollectionInterface
      */
     public function createCollectionObject(array $collectionData)
     {
@@ -308,17 +173,13 @@ class ResourceFactory implements SingletonInterface
      * Creates a storage object from a storage database row.
      *
      * @param array $storageRecord
-     * @param array $storageConfiguration Storage configuration (if given, this won't be extracted from the FlexForm value but the supplied array used instead)
+     * @param array|null $storageConfiguration Storage configuration (if given, this won't be extracted from the FlexForm value but the supplied array used instead)
      * @return ResourceStorage
+     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
      */
     public function createStorageObject(array $storageRecord, array $storageConfiguration = null)
     {
-        if (!$storageConfiguration) {
-            $storageConfiguration = $this->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
-        }
-        $driverType = $storageRecord['driver'];
-        $driverObject = $this->getDriverObject($driverType, $storageConfiguration);
-        return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->eventDispatcher);
+        return $this->storageRepository->createStorageObject($storageRecord, $storageConfiguration);
     }
 
     /**
@@ -328,6 +189,7 @@ class ResourceFactory implements SingletonInterface
      * @param string $identifier The path to the folder. Might also be a simple unique string, depending on the storage driver.
      * @param string $name The name of the folder (e.g. the folder name)
      * @return Folder
+     * @internal it is recommended to access the ResourceStorage object directly and access ->getFolder($identifier) this method is kept for backwards compatibility
      */
     public function createFolderObject(ResourceStorage $storage, $identifier, $name)
     {
@@ -385,10 +247,8 @@ class ResourceFactory implements SingletonInterface
             $storageUid = 0;
             $fileIdentifier = $parts[0];
         }
-
-        // please note that getStorageObject() might modify $fileIdentifier when
-        // auto-detecting the best-matching storage to use
-        return $this->getFileObjectByStorageAndIdentifier($storageUid, $fileIdentifier);
+        return $this->storageRepository->getStorageObject($storageUid, [], $fileIdentifier)
+            ->getFileByIdentifier($fileIdentifier);
     }
 
     /**
@@ -396,25 +256,17 @@ class ResourceFactory implements SingletonInterface
      * If the file is outside of the process folder, it gets indexed and returned as file object afterwards
      * If the file is within processing folder, the file object will be directly returned
      *
-     * @param int $storageUid
+     * @param ResourceStorage|int $storage
      * @param string $fileIdentifier
      * @return File|ProcessedFile|null
+     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
      */
-    public function getFileObjectByStorageAndIdentifier($storageUid, &$fileIdentifier)
+    public function getFileObjectByStorageAndIdentifier($storage, &$fileIdentifier)
     {
-        $storage = $this->getStorageObject($storageUid, [], $fileIdentifier);
-        if (!$storage->isWithinProcessingFolder($fileIdentifier)) {
-            $fileData = $this->getFileIndexRepository()->findOneByStorageUidAndIdentifier($storage->getUid(), $fileIdentifier);
-            if ($fileData === false) {
-                $fileObject = $this->getIndexer($storage)->createIndexEntry($fileIdentifier);
-            } else {
-                $fileObject = $this->getFileObject($fileData['uid'], $fileData);
-            }
-        } else {
-            $fileObject = $this->getProcessedFileRepository()->findByStorageAndIdentifier($storage, $fileIdentifier);
+        if (!($storage instanceof ResourceStorage)) {
+            $storage = $this->storageRepository->getStorageObject($storage, [], $fileIdentifier);
         }
-
-        return $fileObject;
+        return $storage->getFileByIdentifier($fileIdentifier);
     }
 
     /**
@@ -504,7 +356,7 @@ class ResourceFactory implements SingletonInterface
                 $folderIdentifier = PathUtility::stripPathSitePrefix($parts[0]);
             }
         }
-        return $this->getStorageObject($storageUid, [], $folderIdentifier)->getFolder($folderIdentifier);
+        return $this->storageRepository->getStorageObject($storageUid, [], $folderIdentifier)->getFolder($folderIdentifier);
     }
 
     /**
@@ -512,12 +364,13 @@ class ResourceFactory implements SingletonInterface
      *
      * @param string $identifier An identifier of the form [storage uid]:[object identifier]
      * @return ResourceStorage
+     * @internal It is recommended to use the StorageRepository in the future, and this is only kept as backwards-compat layer
      */
     public function getStorageObjectFromCombinedIdentifier($identifier)
     {
         $parts = GeneralUtility::trimExplode(':', $identifier);
         $storageUid = count($parts) === 2 ? $parts[0] : null;
-        return $this->getStorageObject($storageUid);
+        return $this->storageRepository->findByUid($storageUid);
     }
 
     /**
@@ -531,7 +384,7 @@ class ResourceFactory implements SingletonInterface
     public function getObjectFromCombinedIdentifier($identifier)
     {
         [$storageId, $objectIdentifier] = GeneralUtility::trimExplode(':', $identifier);
-        $storage = $this->getStorageObject($storageId);
+        $storage = $this->storageRepository->findByUid($storageId);
         if ($storage->hasFile($objectIdentifier)) {
             return $storage->getFile($objectIdentifier);
         }
@@ -552,7 +405,7 @@ class ResourceFactory implements SingletonInterface
     public function createFileObject(array $fileData, ResourceStorage $storage = null)
     {
         if (array_key_exists('storage', $fileData) && MathUtility::canBeInterpretedAsInteger($fileData['storage'])) {
-            $storageObject = $this->getStorageObject((int)$fileData['storage']);
+            $storageObject = $this->storageRepository->findByUid((int)$fileData['storage']);
         } elseif ($storage !== null) {
             $storageObject = $storage;
             $fileData['storage'] = $storage->getUid();
@@ -653,25 +506,4 @@ class ResourceFactory implements SingletonInterface
     {
         return FileIndexRepository::getInstance();
     }
-
-    /**
-     * Returns an instance of the ProcessedFileRepository
-     *
-     * @return ProcessedFileRepository
-     */
-    protected function getProcessedFileRepository()
-    {
-        return GeneralUtility::makeInstance(ProcessedFileRepository::class);
-    }
-
-    /**
-     * Returns an instance of the Indexer
-     *
-     * @param ResourceStorage $storage
-     * @return Index\Indexer
-     */
-    protected function getIndexer(ResourceStorage $storage)
-    {
-        return GeneralUtility::makeInstance(Indexer::class, $storage);
-    }
 }
diff --git a/typo3/sysext/core/Classes/Resource/ResourceStorage.php b/typo3/sysext/core/Classes/Resource/ResourceStorage.php
index 5edbefa2e45c..d1667f594786 100644
--- a/typo3/sysext/core/Classes/Resource/ResourceStorage.php
+++ b/typo3/sysext/core/Classes/Resource/ResourceStorage.php
@@ -80,6 +80,7 @@ use TYPO3\CMS\Core\Resource\Search\Result\FileSearchResult;
 use TYPO3\CMS\Core\Resource\Search\Result\FileSearchResultInterface;
 use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
 use TYPO3\CMS\Core\Resource\Service\FileProcessingService;
+use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\Utility\Exception\NotImplementedMethodException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
@@ -230,7 +231,13 @@ class ResourceStorage implements ResourceStorageInterface
     {
         $this->storageRecord = $storageRecord;
         $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
-        $this->configuration = $this->getResourceFactoryInstance()->convertFlexFormDataToConfigurationArray($storageRecord['configuration'] ?? '');
+        if (is_array($storageRecord['configuration'] ?? null)) {
+            $this->configuration = $storageRecord['configuration'];
+        } elseif (!empty($storageRecord['configuration'] ?? '')) {
+            $this->configuration = GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($storageRecord['configuration']);
+        } else {
+            $this->configuration = [];
+        }
         $this->capabilities =
             ($this->storageRecord['is_browsable'] ?? null ? self::CAPABILITY_BROWSABLE : 0) |
             ($this->storageRecord['is_public'] ?? null ? self::CAPABILITY_PUBLIC : 0) |
@@ -555,7 +562,7 @@ class ResourceStorage implements ResourceStorageInterface
             throw new FolderDoesNotExistException('Folder for file mount ' . $folderIdentifier . ' does not exist.', 1334427099);
         }
         $data = $this->driver->getFolderInfoByIdentifier($folderIdentifier);
-        $folderObject = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
+        $folderObject = $this->createFolderObject($data['identifier'], $data['name']);
         // Use the canonical identifier instead of the user provided one!
         $folderIdentifier = $folderObject->getIdentifier();
         if (
@@ -1260,7 +1267,7 @@ class ResourceStorage implements ResourceStorageInterface
         }
 
         $fileIdentifier = $this->driver->addFile($localFilePath, $targetFolder->getIdentifier(), $targetFileName, $removeOriginal);
-        $file = $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $fileIdentifier);
+        $file = $this->getFileByIdentifier($fileIdentifier);
 
         if ($replaceExisting && $file instanceof File) {
             $this->getIndexer()->updateIndexEntry($file);
@@ -1443,13 +1450,38 @@ class ResourceStorage implements ResourceStorageInterface
      */
     public function getFile($identifier)
     {
-        $file = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
+        $file = $this->getFileByIdentifier($identifier);
         if (!$this->driver->fileExists($identifier)) {
             $file->setMissing(true);
         }
         return $file;
     }
 
+    /**
+     * Gets a file object from storage by file identifier
+     * If the file is outside of the process folder, it gets indexed and returned as file object afterwards
+     * If the file is within processing folder, the file object will be directly returned
+     *
+     * @param string $fileIdentifier
+     * @return File|ProcessedFile|null
+     */
+    public function getFileByIdentifier(string $fileIdentifier)
+    {
+        if (!$this->isWithinProcessingFolder($fileIdentifier)) {
+            $fileData = $this->getFileIndexRepository()->findOneByStorageAndIdentifier($this, $fileIdentifier);
+            if ($fileData === false) {
+                return $this->getIndexer()->createIndexEntry($fileIdentifier);
+            }
+            return $this->getResourceFactoryInstance()->getFileObject($fileData['uid'], $fileData);
+        }
+        return $this->getProcessedFileRepository()->findByStorageAndIdentifier($this, $fileIdentifier);
+    }
+
+    protected function getProcessedFileRepository(): ProcessedFileRepository
+    {
+        return GeneralUtility::makeInstance(ProcessedFileRepository::class);
+    }
+
     /**
      * Gets information about a file.
      *
@@ -1539,7 +1571,7 @@ class ResourceStorage implements ResourceStorageInterface
     public function getFileInFolder($fileName, Folder $folder)
     {
         $identifier = $this->driver->getFileInFolder($fileName, $folder->getIdentifier());
-        return $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
+        return $this->getFileByIdentifier($identifier);
     }
 
     /**
@@ -1571,7 +1603,7 @@ class ResourceStorage implements ResourceStorageInterface
             if (isset($rows[$identifier])) {
                 $fileObject = $this->getFileFactory()->getFileObject($rows[$identifier]['uid'], $rows[$identifier]);
             } else {
-                $fileObject = $this->getFileFactory()->getFileObjectByStorageAndIdentifier($this->getUid(), $identifier);
+                $fileObject = $this->getFileByIdentifier($identifier);
             }
             if ($fileObject instanceof FileInterface) {
                 $key = $fileObject->getName();
@@ -1658,7 +1690,7 @@ class ResourceStorage implements ResourceStorageInterface
                 if (empty($processingFolderIdentifier) || (int)$storageUid !== $this->getUid()) {
                     continue;
                 }
-                $potentialProcessingFolder = $this->getResourceFactoryInstance()->createFolderObject($this, $processingFolderIdentifier, $processingFolderIdentifier);
+                $potentialProcessingFolder = $this->createFolderObject($processingFolderIdentifier, $processingFolderIdentifier);
                 if ($potentialProcessingFolder->getStorage() === $this && $potentialProcessingFolder->getIdentifier() !== $this->getProcessingFolder()->getIdentifier()) {
                     $this->processingFolders[] = $potentialProcessingFolder;
                 }
@@ -1824,7 +1856,7 @@ class ResourceStorage implements ResourceStorageInterface
         $this->eventDispatcher->dispatch(
             new AfterFileCreatedEvent($newFileIdentifier, $targetFolderObject)
         );
-        return $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileIdentifier);
+        return $this->getFileByIdentifier($newFileIdentifier);
     }
 
     /**
@@ -1919,7 +1951,7 @@ class ResourceStorage implements ResourceStorageInterface
             $tempPath = $file->getForLocalProcessing();
             $newFileObjectIdentifier = $this->driver->addFile($tempPath, $targetFolder->getIdentifier(), $sanitizedTargetFileName);
         }
-        $newFileObject = $this->getResourceFactoryInstance()->getFileObjectByStorageAndIdentifier($this->getUid(), $newFileObjectIdentifier);
+        $newFileObject = $this->getFileByIdentifier($newFileObjectIdentifier);
 
         $this->eventDispatcher->dispatch(
             new AfterFileCopiedEvent($file, $targetFolder, $newFileObjectIdentifier, $newFileObject)
@@ -2508,7 +2540,7 @@ class ResourceStorage implements ResourceStorageInterface
     public function getFolder($identifier, $returnInaccessibleFolderObject = false)
     {
         $data = $this->driver->getFolderInfoByIdentifier($identifier);
-        $folder = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'] ?? null, $data['name'] ?? null);
+        $folder = $this->createFolderObject($data['identifier'] ?? '', $data['name'] ?? '');
 
         try {
             $this->assureFolderReadPermission($folder);
@@ -2585,7 +2617,7 @@ class ResourceStorage implements ResourceStorageInterface
             $mount = reset($this->fileMounts);
             return $mount['folder'];
         }
-        return $this->getResourceFactoryInstance()->createFolderObject($this, $this->driver->getRootLevelFolder(), '');
+        return $this->createFolderObject($this->driver->getRootLevelFolder(), '');
     }
 
     /**
@@ -2716,7 +2748,7 @@ class ResourceStorage implements ResourceStorageInterface
             try {
                 if (strpos($processingFolder, ':') !== false) {
                     [$storageUid, $processingFolderIdentifier] = explode(':', $processingFolder, 2);
-                    $storage = $this->getResourceFactoryInstance()->getStorageObject($storageUid);
+                    $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid((int)$storageUid);
                     if ($storage->hasFolder($processingFolderIdentifier)) {
                         $this->processingFolder = $storage->getFolder($processingFolderIdentifier);
                     } else {
@@ -2750,7 +2782,7 @@ class ResourceStorage implements ResourceStorageInterface
                         }
                     } else {
                         $data = $this->driver->getFolderInfoByIdentifier($processingFolder);
-                        $this->processingFolder = $this->getResourceFactoryInstance()->createFolderObject($this, $data['identifier'], $data['name']);
+                        $this->processingFolder = $this->createFolderObject($data['identifier'], $data['name']);
                     }
                 }
             } catch (InsufficientFolderWritePermissionsException|ResourcePermissionsUnavailableException $e) {
@@ -2921,4 +2953,16 @@ class ResourceStorage implements ResourceStorageInterface
 
         return $recyclerFolder;
     }
+
+    /**
+     * Creates a folder to directly access (a part of) a storage.
+     *
+     * @param string $identifier The path to the folder. Might also be a simple unique string, depending on the storage driver.
+     * @param string $name The name of the folder (e.g. the folder name)
+     * @return Folder
+     */
+    protected function createFolderObject(string $identifier, string $name)
+    {
+        return GeneralUtility::makeInstance(Folder::class, $this, $identifier, $name);
+    }
 }
diff --git a/typo3/sysext/core/Classes/Resource/StorageRepository.php b/typo3/sysext/core/Classes/Resource/StorageRepository.php
index 78d4ffa78ca7..aa20cabc26b4 100644
--- a/typo3/sysext/core/Classes/Resource/StorageRepository.php
+++ b/typo3/sysext/core/Classes/Resource/StorageRepository.php
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 /*
  * This file is part of the TYPO3 CMS project.
  *
@@ -15,19 +17,24 @@
 
 namespace TYPO3\CMS\Core\Resource;
 
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
+use TYPO3\CMS\Core\Resource\Driver\DriverInterface;
 use TYPO3\CMS\Core\Resource\Driver\DriverRegistry;
+use TYPO3\CMS\Core\Resource\Event\AfterResourceStorageInitializationEvent;
+use TYPO3\CMS\Core\Resource\Event\BeforeResourceStorageInitializationEvent;
+use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
- * Repository for accessing the file mounts
+ * Repository for accessing the file storages
  */
-class StorageRepository extends AbstractRepository implements LoggerAwareInterface
+class StorageRepository implements LoggerAwareInterface
 {
     use LoggerAwareTrait;
 
@@ -37,9 +44,9 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
     protected $storageRowCache;
 
     /**
-     * @var string
+     * @var array|null
      */
-    protected $objectType = ResourceStorage::class;
+    protected $localDriverStorageCache;
 
     /**
      * @var string
@@ -47,38 +54,73 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
     protected $table = 'sys_file_storage';
 
     /**
-     * @var string
+     * @var DriverRegistry
      */
-    protected $typeField = 'driver';
+    protected $driverRegistry;
 
     /**
-     * @var string
+     * @var EventDispatcherInterface
      */
-    protected $driverField = 'driver';
+    protected $eventDispatcher;
 
     /**
-     * @param int $uid
+     * @var ResourceStorage[]
+     */
+    protected $storageInstances;
+
+    public function __construct(EventDispatcherInterface $eventDispatcher, DriverRegistry $driverRegistry)
+    {
+        $this->eventDispatcher = $eventDispatcher;
+        $this->driverRegistry = $driverRegistry;
+    }
+
+    /**
+     * Returns the Default Storage
+     *
+     * The Default Storage is considered to be the replacement for the fileadmin/ construct.
+     * It is automatically created with the setting fileadminDir from install tool.
+     * getDefaultStorage->getDefaultFolder() will get you fileadmin/user_upload/ in a standard
+     * TYPO3 installation.
      *
      * @return ResourceStorage|null
      */
-    public function findByUid($uid)
+    public function getDefaultStorage(): ?ResourceStorage
+    {
+        $allStorages = $this->findAll();
+        foreach ($allStorages as $storage) {
+            if ($storage->isDefault()) {
+                return $storage;
+            }
+        }
+        return null;
+    }
+
+    public function findByUid(int $uid): ?ResourceStorage
     {
         $this->initializeLocalCache();
-        if (isset($this->storageRowCache[$uid])) {
-            return $this->factory->getStorageObject($uid, $this->storageRowCache[$uid]);
+        if (isset($this->storageRowCache[$uid]) || $uid === 0) {
+            return $this->getStorageObject($uid, $this->storageRowCache[$uid] ?? []);
         }
         return null;
     }
 
     /**
-     * Only for use in ResourceFactory::getStorageObject
+     * Gets a storage object from a combined identifier
      *
-     * @internal
+     * @param string $identifier An identifier of the form [storage uid]:[object identifier]
+     * @return ResourceStorage|null
+     */
+    public function findByCombinedIdentifier(string $identifier): ?ResourceStorage
+    {
+        $parts = GeneralUtility::trimExplode(':', $identifier);
+        return count($parts) === 2 ? $this->findByUid((int)$parts[0]) : null;
+    }
+
+    /**
      * @param int $uid
-     *
      * @return array
      */
-    public function fetchRowByUid(int $uid): array
+    protected function fetchRecordDataByUid(int $uid): array
     {
         $this->initializeLocalCache();
         if (!isset($this->storageRowCache[$uid])) {
@@ -97,10 +139,6 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                 ->getQueryBuilderForTable($this->table);
 
-            if ($this->getEnvironmentMode() === 'FE') {
-                $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
-            }
-
             $result = $queryBuilder
                 ->select('*')
                 ->from($this->table)
@@ -153,16 +191,13 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
     {
         $this->initializeLocalCache();
 
-        /** @var Driver\DriverRegistry $driverRegistry */
-        $driverRegistry = GeneralUtility::makeInstance(DriverRegistry::class);
-
         $storageObjects = [];
         foreach ($this->storageRowCache as $storageRow) {
             if ($storageRow['driver'] !== $storageType) {
                 continue;
             }
-            if ($driverRegistry->driverExists($storageRow['driver'])) {
-                $storageObjects[] = $this->factory->getStorageObject($storageRow['uid'], $storageRow);
+            if ($this->driverRegistry->driverExists($storageRow['driver'])) {
+                $storageObjects[] = $this->getStorageObject($storageRow['uid'], $storageRow);
             } else {
                 $this->logger->warning(
                     sprintf('Could not instantiate storage "%s" because of missing driver.', [$storageRow['name']]),
@@ -183,13 +218,10 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
     {
         $this->initializeLocalCache();
 
-        /** @var Driver\DriverRegistry $driverRegistry */
-        $driverRegistry = GeneralUtility::makeInstance(DriverRegistry::class);
-
         $storageObjects = [];
         foreach ($this->storageRowCache as $storageRow) {
-            if ($driverRegistry->driverExists($storageRow['driver'])) {
-                $storageObjects[] = $this->factory->getStorageObject($storageRow['uid'], $storageRow);
+            if ($this->driverRegistry->driverExists($storageRow['driver'])) {
+                $storageObjects[] = $this->getStorageObject($storageRow['uid'], $storageRow);
             } else {
                 $this->logger->warning(
                     sprintf('Could not instantiate storage "%s" because of missing driver.', [$storageRow['name']]),
@@ -226,9 +258,7 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
             ]
         ];
 
-        /** @var FlexFormTools $flexObj */
-        $flexObj = GeneralUtility::makeInstance(FlexFormTools::class);
-        $flexFormXml = $flexObj->flexArray2Xml($flexFormData, true);
+        $flexFormXml = GeneralUtility::makeInstance(FlexFormTools::class)->flexArray2Xml($flexFormData, true);
 
         // create the record
         $field_values = [
@@ -256,17 +286,6 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
         return (int)$dbConnection->lastInsertId($this->table);
     }
 
-    /**
-     * Creates an object managed by this repository.
-     *
-     * @param array $databaseRow
-     * @return ResourceStorage
-     */
-    protected function createDomainObject(array $databaseRow)
-    {
-        return $this->factory->getStorageObject($databaseRow['uid'], $databaseRow);
-    }
-
     /**
      * Test if the local filesystem is case sensitive
      *
@@ -296,4 +315,169 @@ class StorageRepository extends AbstractRepository implements LoggerAwareInterfa
 
         return $caseSensitive;
     }
+
+    /**
+     * Creates an instance of the storage from given UID. The $recordData can
+     * be supplied to increase performance.
+     *
+     * @param int $uid The uid of the storage to instantiate.
+     * @param array $recordData The record row from database.
+     * @param mixed|null $fileIdentifier Identifier for a file. Used for auto-detection of a storage, but only if $uid === 0 (Local default storage) is used
+     * @throws \InvalidArgumentException
+     * @return ResourceStorage
+     */
+    public function getStorageObject($uid, array $recordData = [], &$fileIdentifier = null): ResourceStorage
+    {
+        if (!is_numeric($uid)) {
+            throw new \InvalidArgumentException('The UID of storage has to be numeric. UID given: "' . $uid . '"', 1314085991);
+        }
+        $uid = (int)$uid;
+        if ($uid === 0 && $fileIdentifier !== null) {
+            $uid = $this->findBestMatchingStorageByLocalPath($fileIdentifier);
+        }
+        if (empty($this->storageInstances[$uid])) {
+            $storageConfiguration = null;
+            $storageObject = null;
+            /** @var BeforeResourceStorageInitializationEvent $event */
+            $event = $this->eventDispatcher->dispatch(new BeforeResourceStorageInitializationEvent($uid, $recordData, $fileIdentifier));
+            $recordData = $event->getRecord();
+            $uid = $event->getStorageUid();
+            $fileIdentifier = $event->getFileIdentifier();
+            // If the built-in storage with UID=0 is requested:
+            if ($uid === 0) {
+                $recordData = [
+                    'uid' => 0,
+                    'pid' => 0,
+                    'name' => 'Fallback Storage',
+                    'description' => 'Internal storage, mounting the main TYPO3_site directory.',
+                    'driver' => 'Local',
+                    'processingfolder' => 'typo3temp/assets/_processed_/',
+                    // legacy code
+                    'configuration' => '',
+                    'is_online' => true,
+                    'is_browsable' => true,
+                    'is_public' => true,
+                    'is_writable' => true,
+                    'is_default' => false,
+                ];
+                $storageConfiguration = [
+                    'basePath' => '/',
+                    'pathType' => 'relative'
+                ];
+            } elseif ($recordData === [] || (int)$recordData['uid'] !== $uid) {
+                $recordData = $this->fetchRecordDataByUid($uid);
+            }
+            $storageObject = $this->createStorageObject($recordData, $storageConfiguration);
+            $storageObject = $this->eventDispatcher
+                ->dispatch(new AfterResourceStorageInitializationEvent($storageObject))
+                ->getStorage();
+            $this->storageInstances[$uid] = $storageObject;
+        }
+        return $this->storageInstances[$uid];
+    }
+
+    /**
+     * Checks whether a file resides within a real storage in local file system.
+     * If no match is found, uid 0 is returned which is a fallback storage pointing to fileadmin in public web path.
+     *
+     * The file identifier is adapted accordingly to match the new storage's base path.
+     *
+     * @param string $localPath
+     * @return int
+     */
+    protected function findBestMatchingStorageByLocalPath(&$localPath): int
+    {
+        if ($this->localDriverStorageCache === null) {
+            $this->initializeLocalStorageCache();
+        }
+
+        $bestMatchStorageUid = 0;
+        $bestMatchLength = 0;
+        foreach ($this->localDriverStorageCache as $storageUid => $basePath) {
+            $matchLength = strlen((string)PathUtility::getCommonPrefix([$basePath, $localPath]));
+            $basePathLength = strlen($basePath);
+
+            if ($matchLength >= $basePathLength && $matchLength > $bestMatchLength) {
+                $bestMatchStorageUid = (int)$storageUid;
+                $bestMatchLength = $matchLength;
+            }
+        }
+        if ($bestMatchStorageUid !== 0) {
+            $localPath = substr($localPath, $bestMatchLength);
+        }
+        return $bestMatchStorageUid;
+    }
+
+    /**
+     * Creates an array mapping all uids to the basePath of storages using the "local" driver.
+     */
+    protected function initializeLocalStorageCache(): void
+    {
+        $storageObjects = $this->findByStorageType('Local');
+
+        $storageCache = [];
+        foreach ($storageObjects as $localStorage) {
+            $configuration = $localStorage->getConfiguration();
+            $storageCache[$localStorage->getUid()] = $configuration['basePath'];
+        }
+        $this->localDriverStorageCache = $storageCache;
+    }
+
+    /**
+     * Creates a storage object from a storage database row.
+     *
+     * @param array $storageRecord
+     * @param array|null $storageConfiguration Storage configuration (if given, this won't be extracted from the FlexForm value but the supplied array used instead)
+     * @return ResourceStorage
+     * @internal this method is only public for having access to ResourceFactory->createStorageObject(). In TYPO3 v12 this method can be changed to protected again.
+     */
+    public function createStorageObject(array $storageRecord, array $storageConfiguration = null): ResourceStorage
+    {
+        if (!$storageConfiguration && !empty($storageRecord['configuration'])) {
+            $storageConfiguration = $this->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
+        }
+        $driverType = $storageRecord['driver'];
+        $driverObject = $this->getDriverObject($driverType, $storageConfiguration);
+        $storageRecord['configuration'] = $storageConfiguration;
+        return GeneralUtility::makeInstance(ResourceStorage::class, $driverObject, $storageRecord, $this->eventDispatcher);
+    }
+
+    /**
+     * Converts a flexform data string to a flat array with key value pairs
+     *
+     * @param string $flexFormData
+     * @return array Array with key => value pairs of the field data in the FlexForm
+     */
+    protected function convertFlexFormDataToConfigurationArray(string $flexFormData): array
+    {
+        if ($flexFormData) {
+            return GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($flexFormData);
+        }
+        return [];
+    }
+
+    /**
+     * Creates a driver object for a specified storage object.
+     *
+     * @param string $driverIdentificationString The driver class (or identifier) to use.
+     * @param array $driverConfiguration The configuration of the storage
+     * @return DriverInterface
+     */
+    protected function getDriverObject(string $driverIdentificationString, array $driverConfiguration): DriverInterface
+    {
+        $driverClass = $this->driverRegistry->getDriverClass($driverIdentificationString);
+        /** @var DriverInterface $driverObject */
+        $driverObject = GeneralUtility::makeInstance($driverClass, $driverConfiguration);
+        return $driverObject;
+    }
+
+    /**
+     * @param array $storageRecord
+     * @return ResourceStorage
+     * @internal
+     */
+    public function createFromRecord(array $storageRecord): ResourceStorage
+    {
+        return $this->createStorageObject($storageRecord);
+    }
 }
diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php
index b2083eb43f3c..db4fd70a1a37 100644
--- a/typo3/sysext/core/Classes/ServiceProvider.php
+++ b/typo3/sysext/core/Classes/ServiceProvider.php
@@ -265,13 +265,16 @@ class ServiceProvider extends AbstractServiceProvider
     public static function getResourceFactory(ContainerInterface $container): Resource\ResourceFactory
     {
         return self::new($container, Resource\ResourceFactory::class, [
-            $container->get(EventDispatcherInterface::class)
+            $container->get(Resource\StorageRepository::class)
         ]);
     }
 
     public static function getStorageRepository(ContainerInterface $container): Resource\StorageRepository
     {
-        return self::new($container, Resource\StorageRepository::class);
+        return self::new($container, Resource\StorageRepository::class, [
+            $container->get(EventDispatcherInterface::class),
+            $container->get(Resource\Driver\DriverRegistry::class),
+        ]);
     }
 
     public static function getDependencyOrderingService(ContainerInterface $container): Service\DependencyOrderingService
diff --git a/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php b/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php
index 2216cd5fba5d..4037d029b2ca 100644
--- a/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php
+++ b/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php
@@ -1090,9 +1090,7 @@ class ExtendedFileUtility extends BasicFileUtility
                 'size' => $uploadedFileData['size'][$i]
             ];
             try {
-                /** @var File $fileObject */
                 $fileObject = $targetFolderObject->addUploadedFile($fileInfo, (string)$this->existingFilesConflictMode);
-                $fileObject = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObjectByStorageAndIdentifier($targetFolderObject->getStorage()->getUid(), $fileObject->getIdentifier());
                 if ($this->existingFilesConflictMode->equals(DuplicationBehavior::REPLACE)) {
                     $this->getIndexer($fileObject->getStorage())->updateIndexEntry($fileObject);
                 }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-92289-DecoupleLogicOfResourceFactoryIntoStorageRepository.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-92289-DecoupleLogicOfResourceFactoryIntoStorageRepository.rst
new file mode 100644
index 000000000000..e7a0e3e181f3
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-92289-DecoupleLogicOfResourceFactoryIntoStorageRepository.rst
@@ -0,0 +1,64 @@
+.. include:: ../../Includes.txt
+
+===========================================================================
+Breaking: #92289 - Decouple logic of ResourceFactory into StorageRepository
+===========================================================================
+
+See :issue:`92289`
+
+Description
+===========
+
+The ResourceFactory was initially created for the File Abstraction Layer (FAL)
+as a Factory class, which created PHP objects.
+
+However, in the recent years, it became more apparent that it is more useful to
+separate the concerns of the creation and retrieving of existing information.
+
+For this reason, the StorageRepository is now handling the creation of
+ResourceStorage objects. This layer accesses the Database and the needed Driver
+objects and configuration.
+
+The StorageRepository class does not extend from AbstractRepository anymore,
+and is available standalone.
+
+Most of the logic in the ResourceFactory concerning Storages has been moved to
+StorageRepository, which has a lot of options available now.
+
+The following methods within ResourceFactory have been marked
+as internal, and are kept for backwards-compatibility without deprecation:
+
+* :php:`ResourceFactory->getDefaultStorage()`
+* :php:`ResourceFactory->getStorageObject()`
+* :php:`ResourceFactory->convertFlexFormDataToConfigurationArray()`
+* :php:`ResourceFactory->createStorageObject()`
+* :php:`ResourceFactory->createFolderObject()`
+* :php:`ResourceFactory->getFileObjectByStorageAndIdentifier()`
+* :php:`ResourceFactory->getStorageObjectFromCombinedIdentifier()`
+
+The following method has been removed
+* :php:`ResourceFactory->getDriverObject()`
+
+
+Impact
+======
+
+Calling the removed method will throw a fatal error.
+
+Checking StorageRepository for an instance of AbstractRepository
+will have different results.
+
+
+Affected Installations
+======================
+
+TYPO3 installations with specific third-party extensions working with the FAL
+API directly might use the existing functionality.
+
+
+Migration
+=========
+
+Migrate to the StorageRepository API in the third-party extension code.
+
+.. index:: FAL, PHP-API, FullyScanned, ext:core
diff --git a/typo3/sysext/core/Tests/Functional/Resource/ResourceStorageTest.php b/typo3/sysext/core/Tests/Functional/Resource/ResourceStorageTest.php
index 4006006ff299..04ef0f206412 100644
--- a/typo3/sysext/core/Tests/Functional/Resource/ResourceStorageTest.php
+++ b/typo3/sysext/core/Tests/Functional/Resource/ResourceStorageTest.php
@@ -47,7 +47,7 @@ class ResourceStorageTest extends FunctionalTestCase
     {
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
         $subject->setEvaluatePermissions(false);
 
         GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin/_processed_');
@@ -93,7 +93,7 @@ class ResourceStorageTest extends FunctionalTestCase
         clearstatcache();
         $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObjectFromCombinedIdentifier('1:/' . $targetDirectory . '/' . $fileName);
 
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
         $subject->setEvaluatePermissions(true);
 
         // read_only = true -> no write access for user, so checking for second argument true should assert false
@@ -160,7 +160,7 @@ class ResourceStorageTest extends FunctionalTestCase
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
 
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
         $processingFolder = $subject->getProcessingFolder();
 
         self::assertInstanceOf(Folder::class, $processingFolder);
@@ -175,7 +175,7 @@ class ResourceStorageTest extends FunctionalTestCase
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
 
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
         $folder = new Folder($subject, '/foo/' . $folderIdentifier . '/', $folderIdentifier);
 
         $role = $subject->getRole($folder);
@@ -190,7 +190,7 @@ class ResourceStorageTest extends FunctionalTestCase
     {
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
 
         GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin/foo');
         file_put_contents(Environment::getPublicPath() . '/fileadmin/foo/bar.txt', 'myData');
@@ -209,7 +209,7 @@ class ResourceStorageTest extends FunctionalTestCase
     {
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
 
         $this->expectException(\InvalidArgumentException::class);
         $this->expectExceptionCode(1325689164);
@@ -223,7 +223,7 @@ class ResourceStorageTest extends FunctionalTestCase
     {
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
 
         GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin/foo');
         GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin/_recycler_');
@@ -244,7 +244,7 @@ class ResourceStorageTest extends FunctionalTestCase
     {
         $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
         $this->setUpBackendUserFromFixture(1);
-        $subject = (new StorageRepository())->findByUid(1);
+        $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
 
         GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin/foo');
         file_put_contents(Environment::getPublicPath() . '/fileadmin/foo/bar.txt', 'myData');
@@ -394,7 +394,7 @@ class ResourceStorageTest extends FunctionalTestCase
             $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
             $this->importDataSet(__DIR__ . '/Fixtures/FileSearch.xml');
             $this->setUpBackendUserFromFixture(1);
-            $subject = (new StorageRepository())->findByUid(1);
+            $subject = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
             $subject->setFileAndFolderNameFilters($filters);
 
             GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/fileadmin/bar/bla');
diff --git a/typo3/sysext/core/Tests/Unit/Resource/FileTest.php b/typo3/sysext/core/Tests/Unit/Resource/FileTest.php
index 2479ceed0e6b..7582c1627e3d 100644
--- a/typo3/sysext/core/Tests/Unit/Resource/FileTest.php
+++ b/typo3/sysext/core/Tests/Unit/Resource/FileTest.php
@@ -21,8 +21,8 @@ use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\Index\MetaDataRepository;
 use TYPO3\CMS\Core\Resource\MetaDataAspect;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
@@ -166,12 +166,12 @@ class FileTest extends UnitTestCase
             ->setConstructorArgs([$fileProperties, $this->storageMock])
             ->getMock();
         $mockedNewStorage = $this->createMock(ResourceStorage::class);
-        $mockedResourceFactory = $this->createMock(ResourceFactory::class);
-        $mockedResourceFactory
+        $mockedStorageRepository = $this->createMock(StorageRepository::class);
+        $mockedStorageRepository
             ->expects(self::once())
-            ->method('getStorageObject')
+            ->method('findByUid')
             ->willReturn($mockedNewStorage);
-        GeneralUtility::setSingletonInstance(ResourceFactory::class, $mockedResourceFactory);
+        GeneralUtility::addInstance(StorageRepository::class, $mockedStorageRepository);
 
         $subject->updateProperties(['storage' => 'different']);
         self::assertSame($mockedNewStorage, $subject->getStorage());
diff --git a/typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php b/typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php
index 051e2727e776..83532bc5f343 100644
--- a/typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php
+++ b/typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php
@@ -15,10 +15,7 @@
 
 namespace TYPO3\CMS\Core\Tests\Unit\Resource;
 
-use Psr\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Core\Core\Environment;
-use TYPO3\CMS\Core\Resource\Driver\AbstractDriver;
-use TYPO3\CMS\Core\Resource\Driver\DriverRegistry;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -65,39 +62,18 @@ class ResourceFactoryTest extends UnitTestCase
         parent::tearDown();
     }
 
-    /**********************************
-     * Storage Collections
-     **********************************/
     /**
      * @test
      */
-    public function createStorageCollectionObjectCreatesCollectionWithCorrectArguments()
+    public function createFolderCreatesObjectWithCorrectArguments()
     {
         $mockedMount = $this->createMock(ResourceStorage::class);
         $path = StringUtility::getUniqueId('path_');
         $name = StringUtility::getUniqueId('name_');
-        $storageCollection = $this->subject->createFolderObject($mockedMount, $path, $name, 0);
-        self::assertSame($mockedMount, $storageCollection->getStorage());
-        self::assertEquals($path, $storageCollection->getIdentifier());
-        self::assertEquals($name, $storageCollection->getName());
-    }
-
-    /**********************************
-     * Drivers
-     **********************************/
-    /**
-     * @test
-     */
-    public function getDriverObjectAcceptsDriverClassName()
-    {
-        $mockedDriver = $this->getMockForAbstractClass(AbstractDriver::class);
-        $driverFixtureClass = get_class($mockedDriver);
-        GeneralUtility::addInstance($driverFixtureClass, $mockedDriver);
-        $mockedRegistry = $this->createMock(DriverRegistry::class);
-        $mockedRegistry->expects(self::once())->method('getDriverClass')->with(self::equalTo($driverFixtureClass))->willReturn($driverFixtureClass);
-        GeneralUtility::setSingletonInstance(DriverRegistry::class, $mockedRegistry);
-        $obj = $this->subject->getDriverObject($driverFixtureClass, []);
-        self::assertInstanceOf(AbstractDriver::class, $obj);
+        $folderObject = $this->subject->createFolderObject($mockedMount, $path, $name);
+        self::assertSame($mockedMount, $folderObject->getStorage());
+        self::assertEquals($path, $folderObject->getIdentifier());
+        self::assertEquals($name, $folderObject->getName());
     }
 
     /***********************************
@@ -159,74 +135,4 @@ class ResourceFactoryTest extends UnitTestCase
         $this->filesCreated[] = Environment::getPublicPath() . '/' . $filename;
         $this->subject->retrieveFileOrFolderObject($filename);
     }
-
-    /***********************************
-     * Storage AutoDetection
-     ***********************************/
-
-    /**
-     * @param array $storageConfiguration
-     * @param string $path
-     * @param int $expectedStorageId
-     * @test
-     * @dataProvider storageDetectionDataProvider
-     */
-    public function findBestMatchingStorageByLocalPathReturnsDefaultStorageIfNoMatchIsFound(array $storageConfiguration, $path, $expectedStorageId)
-    {
-        $resourceFactory = new ResourceFactory($this->prophesize(EventDispatcherInterface::class)->reveal());
-        $mock = \Closure::bind(static function (ResourceFactory $resourceFactory) use (&$path, $storageConfiguration) {
-            $resourceFactory->localDriverStorageCache = $storageConfiguration;
-            return $resourceFactory->findBestMatchingStorageByLocalPath($path);
-        }, null, ResourceFactory::class);
-        self::assertSame($expectedStorageId, $mock($resourceFactory));
-    }
-
-    /**
-     * @return array
-     */
-    public function storageDetectionDataProvider()
-    {
-        return [
-            'NoLocalStoragesReturnDefaultStorage' => [
-                [],
-                'my/dummy/Image.png',
-                0
-            ],
-            'NoMatchReturnsDefaultStorage' => [
-                [1 => 'fileadmin/', 2 => 'fileadmin2/public/'],
-                'my/dummy/Image.png',
-                0
-            ],
-            'MatchReturnsTheMatch' => [
-                [1 => 'fileadmin/', 2 => 'other/public/'],
-                'fileadmin/dummy/Image.png',
-                1
-            ],
-            'TwoFoldersWithSameStartReturnsCorrect' => [
-                [1 => 'fileadmin/', 2 => 'fileadmin/public/'],
-                'fileadmin/dummy/Image.png',
-                1
-            ],
-            'NestedStorageReallyReturnsTheBestMatching' => [
-                [1 => 'fileadmin/', 2 => 'fileadmin/public/'],
-                'fileadmin/public/Image.png',
-                2
-            ],
-            'CommonPrefixButWrongPath' => [
-                [1 => 'fileadmin/', 2 => 'uploads/test/'],
-                'uploads/bogus/dummy.png',
-                0
-            ],
-            'CommonPrefixRightPath' => [
-                [1 => 'fileadmin/', 2 => 'uploads/test/'],
-                'uploads/test/dummy.png',
-                2
-            ],
-            'FindStorageFromWindowsPath' => [
-                [1 => 'fileadmin/', 2 => 'uploads/test/'],
-                'uploads\\test\\dummy.png',
-                2
-            ],
-        ];
-    }
 }
diff --git a/typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php b/typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
index 55527c40d787..683b8df18728 100644
--- a/typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
+++ b/typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
@@ -836,16 +836,15 @@ class ResourceStorageTest extends BaseTestCase
             'bar',
             'bar_01'
         ));
-        $resourceFactory = $this->createMock(ResourceFactory::class);
         $this->prepareSubject(
             [],
             true,
             $mockedDriver,
-            $resourceFactory,
+            null,
             [],
-            ['getUniqueName']
+            ['getUniqueName', 'createFolderObject']
         );
-        $resourceFactory->expects(self::once())->method('createFolderObject')->willReturn(new Folder($this->subject, '', ''));
+        $this->subject->expects(self::once())->method('createFolderObject')->willReturn(new Folder($this->subject, '', ''));
         /** @var File $file */
         $file = new File(['identifier' => 'foo', 'name' => 'foo'], $this->subject);
         $this->subject->expects(self::any())->method('getUniqueName')->willReturn('bar_01');
diff --git a/typo3/sysext/core/Tests/Unit/Resource/StorageRepositoryTest.php b/typo3/sysext/core/Tests/Unit/Resource/StorageRepositoryTest.php
new file mode 100644
index 000000000000..54828cfdcde9
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Resource/StorageRepositoryTest.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Unit\Resource;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use TYPO3\CMS\Core\Resource\Driver\AbstractDriver;
+use TYPO3\CMS\Core\Resource\Driver\DriverRegistry;
+use TYPO3\CMS\Core\Resource\StorageRepository;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class StorageRepositoryTest extends UnitTestCase
+{
+    /**********************************
+     * Drivers
+     **********************************/
+    /**
+     * @test
+     */
+    public function getDriverObjectAcceptsDriverClassName()
+    {
+        $mockedDriver = $this->getMockForAbstractClass(AbstractDriver::class);
+        $driverFixtureClass = get_class($mockedDriver);
+        $registry = new DriverRegistry();
+        $registry->registerDriverClass($driverFixtureClass);
+        $subject = $this->getAccessibleMock(
+            StorageRepository::class,
+            ['dummy'],
+            [
+                $this->prophesize(EventDispatcherInterface::class)->reveal(),
+                $registry
+            ]
+        );
+        $obj = $subject->_call('getDriverObject', $driverFixtureClass, []);
+        self::assertInstanceOf(AbstractDriver::class, $obj);
+    }
+
+    /***********************************
+     * Storage AutoDetection
+     ***********************************/
+
+    /**
+     * @param array $storageConfiguration
+     * @param string $path
+     * @param int $expectedStorageId
+     * @test
+     * @dataProvider storageDetectionDataProvider
+     */
+    public function findBestMatchingStorageByLocalPathReturnsDefaultStorageIfNoMatchIsFound(array $storageConfiguration, $path, $expectedStorageId)
+    {
+        $subject = new StorageRepository(
+            $this->prophesize(EventDispatcherInterface::class)->reveal(),
+            $this->prophesize(DriverRegistry::class)->reveal()
+        );
+        $mock = \Closure::bind(static function (StorageRepository $storageRepository) use (&$path, $storageConfiguration) {
+            $storageRepository->localDriverStorageCache = $storageConfiguration;
+            return $storageRepository->findBestMatchingStorageByLocalPath($path);
+        }, null, StorageRepository::class);
+        self::assertSame($expectedStorageId, $mock($subject));
+    }
+
+    /**
+     * @return array
+     */
+    public function storageDetectionDataProvider()
+    {
+        return [
+            'NoLocalStoragesReturnDefaultStorage' => [
+                [],
+                'my/dummy/Image.png',
+                0
+            ],
+            'NoMatchReturnsDefaultStorage' => [
+                [1 => 'fileadmin/', 2 => 'fileadmin2/public/'],
+                'my/dummy/Image.png',
+                0
+            ],
+            'MatchReturnsTheMatch' => [
+                [1 => 'fileadmin/', 2 => 'other/public/'],
+                'fileadmin/dummy/Image.png',
+                1
+            ],
+            'TwoFoldersWithSameStartReturnsCorrect' => [
+                [1 => 'fileadmin/', 2 => 'fileadmin/public/'],
+                'fileadmin/dummy/Image.png',
+                1
+            ],
+            'NestedStorageReallyReturnsTheBestMatching' => [
+                [1 => 'fileadmin/', 2 => 'fileadmin/public/'],
+                'fileadmin/public/Image.png',
+                2
+            ],
+            'CommonPrefixButWrongPath' => [
+                [1 => 'fileadmin/', 2 => 'uploads/test/'],
+                'uploads/bogus/dummy.png',
+                0
+            ],
+            'CommonPrefixRightPath' => [
+                [1 => 'fileadmin/', 2 => 'uploads/test/'],
+                'uploads/test/dummy.png',
+                2
+            ],
+            'FindStorageFromWindowsPath' => [
+                [1 => 'fileadmin/', 2 => 'uploads/test/'],
+                'uploads\\test\\dummy.png',
+                2
+            ],
+        ];
+    }
+}
diff --git a/typo3/sysext/filelist/Classes/Controller/FileListController.php b/typo3/sysext/filelist/Classes/Controller/FileListController.php
index 5191b6a69562..1fcaa5ebe8fd 100644
--- a/typo3/sysext/filelist/Classes/Controller/FileListController.php
+++ b/typo3/sysext/filelist/Classes/Controller/FileListController.php
@@ -35,6 +35,7 @@ use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Resource\Search\FileSearchDemand;
+use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Resource\Utility\ListUtility;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
 use TYPO3\CMS\Core\Utility\File\ExtendedFileUtility;
@@ -173,14 +174,12 @@ class FileListController extends ActionController implements LoggerAwareInterfac
         try {
             if ($combinedIdentifier) {
                 $this->getBackendUser()->evaluateUserSpecificFileFilterSettings();
-                $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
-                $storage = $resourceFactory->getStorageObjectFromCombinedIdentifier($combinedIdentifier);
+                $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByCombinedIdentifier($combinedIdentifier);
                 $identifier = substr($combinedIdentifier, strpos($combinedIdentifier, ':') + 1);
                 if (!$storage->hasFolder($identifier)) {
                     $identifier = $storage->getFolderIdentifierFromFileIdentifier($identifier);
                 }
-
-                $this->folderObject = $resourceFactory->getFolderObjectFromCombinedIdentifier($storage->getUid() . ':' . $identifier);
+                $this->folderObject = $storage->getFolder($identifier);
                 // Disallow access to fallback storage 0
                 if ($storage->getUid() === 0) {
                     throw new InsufficientFolderAccessPermissionsException(
diff --git a/typo3/sysext/form/Tests/Functional/Hooks/FormFileExtensionUpdateTest.php b/typo3/sysext/form/Tests/Functional/Hooks/FormFileExtensionUpdateTest.php
index fb1efde8bbd6..f33adedd0c53 100644
--- a/typo3/sysext/form/Tests/Functional/Hooks/FormFileExtensionUpdateTest.php
+++ b/typo3/sysext/form/Tests/Functional/Hooks/FormFileExtensionUpdateTest.php
@@ -24,7 +24,7 @@ use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Resource\Folder;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
@@ -81,7 +81,7 @@ class FormFileExtensionUpdateTest extends FunctionalTestCase
         Bootstrap::initializeLanguageObject();
 
         $folderIdentifier = 'form_definitions';
-        $storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject(1);
+        $storage = GeneralUtility::makeInstance(StorageRepository::class)->getStorageObject(1);
 
         if ($storage->hasFolder($folderIdentifier)) {
             $storage->getFolder($folderIdentifier)->delete(true);
diff --git a/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php b/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php
index 8436de9f8ea8..07cfdc9596b5 100644
--- a/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/Imaging/GifBuilderTest.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Imaging;
 
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Resource\StorageRepository;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Imaging\GifBuilder;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
@@ -42,7 +43,7 @@ class GifBuilderTest extends FunctionalTestCase
             Environment::getPublicPath() . '/fileadmin/kasper-skarhoj1.jpg'
         );
 
-        $storageRepository = (new StorageRepository())->findByUid(1);
+        $storageRepository = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(1);
         $file = $storageRepository->getFile('kasper-skarhoj1.jpg');
 
         self::assertFalse($file->isMissing());
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php
index bc6eac335cc4..d3651e20a10f 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/TypoLinkGeneratorTest.php
@@ -126,7 +126,7 @@ class TypoLinkGeneratorTest extends AbstractTestCase
      */
     private function setUpFileStorage()
     {
-        $storageRepository = new StorageRepository();
+        $storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
         $storageId = $storageRepository->createLocalStorage(
             'fileadmin',
             'fileadmin/',
diff --git a/typo3/sysext/impexp/Classes/Import.php b/typo3/sysext/impexp/Classes/Import.php
index faa726f72c10..1f1d470a54b2 100644
--- a/typo3/sysext/impexp/Classes/Import.php
+++ b/typo3/sysext/impexp/Classes/Import.php
@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
 use TYPO3\CMS\Core\Resource\StorageRepository;
+use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -219,7 +220,7 @@ class Import extends ImportExport
 
         $defaultStorageUid = null;
         // get default storage
-        $defaultStorage = GeneralUtility::makeInstance(ResourceFactory::class)->getDefaultStorage();
+        $defaultStorage = GeneralUtility::makeInstance(StorageRepository::class)->getDefaultStorage();
         if ($defaultStorage !== null) {
             $defaultStorageUid = $defaultStorage->getUid();
         }
@@ -246,7 +247,7 @@ class Import extends ImportExport
             && (bool)$storageObject->isWritable() === (bool)$storageRecord['is_writable']
             && (bool)$storageObject->isOnline() === (bool)$storageRecord['is_online']
         ) {
-            $storageRecordConfiguration = GeneralUtility::makeInstance(ResourceFactory::class)->convertFlexFormDataToConfigurationArray($storageRecord['configuration']);
+            $storageRecordConfiguration = GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($storageRecord['configuration'] ?? '');
             $storageObjectConfiguration = $storageObject->getConfiguration();
             // compare the properties: pathType and basePath
             if ($storageRecordConfiguration['pathType'] === $storageObjectConfiguration['pathType']
@@ -305,7 +306,7 @@ class Import extends ImportExport
                         // storage object will check whether the target folder exists and set the
                         // isOnline flag depending on the outcome.
                         $storageRecord['uid'] = 0;
-                        $resourceStorage = GeneralUtility::makeInstance(ResourceFactory::class)->createStorageObject($storageRecord);
+                        $resourceStorage = GeneralUtility::makeInstance(StorageRepository::class)->createStorageObject($storageRecord);
                         if (!$resourceStorage->isOnline()) {
                             $configuration = $resourceStorage->getConfiguration();
                             $messages['resourceStorageFolderMissing_' . $storageRecordUid] =
@@ -335,7 +336,7 @@ class Import extends ImportExport
         // fetch fresh storage records from database
         $storageRecords = $this->fetchStorageRecords();
 
-        $defaultStorage = GeneralUtility::makeInstance(ResourceFactory::class)->getDefaultStorage();
+        $defaultStorage = GeneralUtility::makeInstance(StorageRepository::class)->getDefaultStorage();
 
         $sanitizedFolderMappings = [];
 
@@ -379,9 +380,9 @@ class Import extends ImportExport
             // mapping. Only in this case we could be sure, that it's a local, online and writable storage.
             if ($useStorageFromStorageRecords && isset($storageRecords[$fileRecord['storage']])) {
                 /** @var \TYPO3\CMS\Core\Resource\ResourceStorage $storage */
-                $storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($fileRecord['storage'], $storageRecords[$fileRecord['storage']]);
+                $storage = GeneralUtility::makeInstance(StorageRepository::class)->getStorageObject($fileRecord['storage'], $storageRecords[$fileRecord['storage']]);
             } elseif ($this->isFallbackStorage($fileRecord['storage'])) {
-                $storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject(0);
+                $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid(0);
             } elseif ($defaultStorage !== null) {
                 $storage = $defaultStorage;
             } else {
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
index 786b02a316a9..83821fb4cb13 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
@@ -4493,4 +4493,11 @@ return [
             'Deprecation-92132-DeprecatedShortcutPHPAPI.rst'
         ],
     ],
+    'TYPO3\CMS\Core\Resource\ResourceFactory->getDriverObject' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Breaking-92289-DecoupleLogicOfResourceFactoryIntoStorageRepository.rst'
+        ],
+    ],
 ];
diff --git a/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionAdditionalFieldProvider.php b/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionAdditionalFieldProvider.php
index 5bf48ab3525c..2f8be8c41195 100644
--- a/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionAdditionalFieldProvider.php
+++ b/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionAdditionalFieldProvider.php
@@ -17,7 +17,6 @@ namespace TYPO3\CMS\Scheduler\Task;
 
 use TYPO3\CMS\Core\Resource\Index\ExtractorInterface;
 use TYPO3\CMS\Core\Resource\Index\ExtractorRegistry;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -163,7 +162,7 @@ class FileStorageExtractionAdditionalFieldProvider implements AdditionalFieldPro
         ) {
             return false;
         }
-        if (GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($submittedData['scheduler_fileStorageIndexing_storage']) === null) {
+        if (GeneralUtility::makeInstance(StorageRepository::class)->findByUid($submittedData['scheduler_fileStorageIndexing_storage']) === null) {
             return false;
         }
         if (!MathUtility::isIntegerInRange($submittedData['scheduler_fileStorageIndexing_fileCount'], 1, 9999)) {
diff --git a/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionTask.php b/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionTask.php
index 1ad0059a807d..0d2cb9008444 100644
--- a/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionTask.php
+++ b/typo3/sysext/scheduler/Classes/Task/FileStorageExtractionTask.php
@@ -16,8 +16,8 @@
 namespace TYPO3\CMS\Scheduler\Task;
 
 use TYPO3\CMS\Core\Resource\Index\Indexer;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -49,7 +49,7 @@ class FileStorageExtractionTask extends AbstractTask
     {
         $success = false;
         if ((int)$this->storageUid > 0) {
-            $storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($this->storageUid);
+            $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid($this->storageUid);
             $currentEvaluatePermissionsValue = $storage->getEvaluatePermissions();
             $storage->setEvaluatePermissions(false);
             $indexer = $this->getIndexer($storage);
diff --git a/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingAdditionalFieldProvider.php b/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingAdditionalFieldProvider.php
index 2d38b08e8518..67f7426773cc 100644
--- a/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingAdditionalFieldProvider.php
+++ b/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingAdditionalFieldProvider.php
@@ -15,7 +15,6 @@
 
 namespace TYPO3\CMS\Scheduler\Task;
 
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -95,7 +94,7 @@ class FileStorageIndexingAdditionalFieldProvider implements AdditionalFieldProvi
         if (!MathUtility::canBeInterpretedAsInteger($value)) {
             return false;
         }
-        if (GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($submittedData['scheduler_fileStorageIndexing_storage']) !== null) {
+        if (GeneralUtility::makeInstance(StorageRepository::class)->findByUid($submittedData['scheduler_fileStorageIndexing_storage']) !== null) {
             return true;
         }
         return false;
diff --git a/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingTask.php b/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingTask.php
index 6ff91cbafd6d..175dc630cafd 100644
--- a/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingTask.php
+++ b/typo3/sysext/scheduler/Classes/Task/FileStorageIndexingTask.php
@@ -16,8 +16,8 @@
 namespace TYPO3\CMS\Scheduler\Task;
 
 use TYPO3\CMS\Core\Resource\Index\Indexer;
-use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
+use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -41,7 +41,7 @@ class FileStorageIndexingTask extends AbstractTask
     public function execute()
     {
         if ((int)$this->storageUid > 0) {
-            $storage = GeneralUtility::makeInstance(ResourceFactory::class)->getStorageObject($this->storageUid);
+            $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid($this->storageUid);
             $currentEvaluatePermissionsValue = $storage->getEvaluatePermissions();
             $storage->setEvaluatePermissions(false);
             $indexer = $this->getIndexer($storage);
-- 
GitLab