Skip to content

b13/db_file_storage

Repository files navigation

db_file_storage

Store uploaded files directly in the TYPO3 database.

A tiny, injectable service that persists files into a single DB table (with a LONGBLOB content column) and hands them back out as PSR-7 responses. Works from Extbase controllers, PSR-15 middlewares, CLI commands, or anything the DI container builds.

  • TYPO3: v13 LTS / v14
  • PHP: 8.2+
  • Composer: b13/db-file-storage

Installation

composer require b13/db-file-storage
vendor/bin/typo3 extension:setup

API

Service — DatabaseFileStorage

The main entry point. Inject via constructor:

public function store(UploadedFileInterface $file): StoredFile;
public function storeContents(string $filename, string $contents, ?string $mimeType = null): StoredFile;
public function get(int $uid): ?StoredFile;
public function require(int $uid): StoredFile; // throws FileNotFoundException
public function delete(int $uid): bool; // soft-delete (sets deleted=1)
public function createResponse(int $uid, bool $forceDownload = false): ResponseInterface;

StoredFile is an immutable value object (uid, filename, mimeType, size, sha1, contents, createdAt). Every method after store*() takes the uid returned by it.

Extbase entity — StoredFileReference

A metadata-only Extbase entity for the same table, suitable for ObjectStorage<StoredFileReference> properties and MM relations. Does not map the content LONGBLOB — bytes stay behind DatabaseFileStorage::get().

Ships with a repository (StoredFileReferenceRepository), a minimal hideTable / readOnly TCA stub, and a Persistence/Classes.php mapping that's auto-merged into every consumer extension.

Example: Extbase controller

use B13\DbFileStorage\Service\DatabaseFileStorage;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

final class InvoiceController extends ActionController
{
    public function __construct(
        private readonly DatabaseFileStorage $databaseFileStorage,
    ) {}

    public function uploadAction(): ResponseInterface
    {
        $uploadedFile = $this->request->getUploadedFiles()['invoice'] ?? null;
        if ($uploadedFile === null) {
            return (new \TYPO3\CMS\Core\Http\Response())->withStatus(400);
        }

        $stored = $this->databaseFileStorage->store($uploadedFile);

        return $this->redirect('show', null, null, ['file' => $stored->uid]);
    }

    public function downloadAction(int $file): ResponseInterface
    {
        return $this->databaseFileStorage->createResponse($file, forceDownload: true);
    }
}

Example: storing generated bytes

$stored = $this->databaseFileStorage->storeContents(
    filename: 'invoice-' . $invoice->getNumber() . '.pdf',
    contents: $pdfBytes,
    mimeType: 'application/pdf',
);
$invoice->setFileUid($stored->uid);

Example: MM relation to a domain model

Add a group + MM column in your consumer TCA:

'attachments' => [
    'label' => 'Attachments',
    'config' => [
        'type' => 'group',
        'relationship' => 'manyToMany',
        'allowed' => 'tx_dbfilestorage_domain_model_file',
        'foreign_table' => 'tx_dbfilestorage_domain_model_file',
        'MM' => 'tx_myext_task_file_mm',
    ],
],

The MM table is auto-created by TYPO3's schema analyzer. On the Extbase side, use StoredFileReference in your model:

use B13\DbFileStorage\Domain\Model\StoredFileReference;
use B13\DbFileStorage\Domain\Repository\StoredFileReferenceRepository;

// In your model:
/** @param ObjectStorage<StoredFileReference> $attachments */
protected ObjectStorage $attachments;

// In your upload action:
$stored = $this->databaseFileStorage->store($uploadedFile);
$ref = $this->storedFileReferenceRepository->findByUid($stored->uid);
$task->addAttachment($ref);
$this->taskRepository->update($task);

Extbase writes the MM rows automatically on persistAll().

Note on cascade delete: Extbase's #[Cascade('remove')] only works for 1:n relations (HAS_MANY), not for M:N (HAS_AND_BELONGS_TO_MANY). To soft-delete the file row when removing a relation, call $databaseFileStorage->delete($ref->getUid()) explicitly in your controller.

Schema

One table — ext_tables.sql:

Column Type Notes
uid int auto_increment Primary key
deleted tinyint Soft-delete flag
filename varchar(255) Original client filename
mime_type varchar(127) Detected via finfo / TYPO3 map
size bigint Size in bytes
sha1 varchar(40) Content hash, indexed
content longblob The file bytes
crdate int Creation timestamp

MariaDB/MySQL's max_allowed_packet governs the maximum insert size.

Development

ddev start          # installs TYPO3, creates tables, ready to use
  • Backend: https://db-file-storage.ddev.site/typo3admin / Password.1
  • Tests:
    ddev exec --dir /var/www/html/Build \
      typo3DatabaseDriver=pdo_sqlite \
      vendor/bin/phpunit -c ../Build/phpunit/FunctionalTests.xml

Credits

This extension was created by Jochen Roth in 2025 for b13 GmbH, Stuttgart.

About

TYPO3 Extension to store files in the database

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors