tourze/file-storage-bundle

Symfony bundle for file storage management with upload, validation, and cleanup features

Installs: 61

Dependents: 6

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

Type:symfony-bundle

pkg:composer/tourze/file-storage-bundle

1.0.0 2025-11-01 14:17 UTC

This package is auto-updated.

Last update: 2025-11-02 05:56:35 UTC


README

English | δΈ­ζ–‡

PHP Version License

Build Status

Code Coverage

Symfony Bundle for file upload and storage management with Flysystem integration.

Table of Contents

Features

  • Flysystem integration for flexible file storage (local, S3, FTP, etc.)
  • Separate upload endpoints for anonymous and authenticated users
  • Database-driven file type management with configurable permissions
  • File metadata storage in database with user tracking
  • Automatic file hash calculation (MD5, SHA1)
  • File organization by year/month structure
  • Soft delete support
  • Automatic cleanup of anonymous files
  • File statistics
  • Duplicate detection by hash
  • EasyAdmin integration ready

Installation

composer require tourze/file-storage-bundle

Configuration

Add the bundle to your config/bundles.php:

return [
    // ...
    Tourze\FileStorageBundle\FileStorageBundle::class => ['all' => true],
];

The bundle automatically configures Flysystem with a local adapter. No additional configuration is needed for basic usage.

Database Setup

Run migrations to create the required tables:

bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate

Load default file types:

bin/console doctrine:fixtures:load --append --group=file-types

Usage

Anonymous Upload

POST /api/files/upload/anonymous
Content-Type: multipart/form-data

file: <file>

Response:

{
  "success": true,
  "file": {
    "id": 1,
    "originalName": "document.pdf",
    "fileName": "document-65abc123.pdf",
    "mimeType": "application/pdf",
    "fileSize": 1048576,
    "md5Hash": "098f6bcd4621d373cade4e832627b4f6",
    "createdAt": "2024-01-10 10:30:00"
  }
}

Note: Anonymous files are automatically deleted after 1 hour.

Member Upload

POST /api/files/upload/member
Authorization: Bearer <token>
Content-Type: multipart/form-data

file: <file>

Response:

{
  "success": true,
  "file": {
    "id": 2,
    "originalName": "report.xlsx",
    "fileName": "report-65abc456.xlsx",
    "mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "fileSize": 2097152,
    "md5Hash": "5d41402abc4b2a76b9719d911017c592",
    "createdAt": "2024-01-10 11:00:00",
    "userId": 123
  }
}

Get Allowed File Types

# For anonymous users
GET /api/files/allowed-types/anonymous

# For members
GET /api/files/allowed-types/member

Response:

{
  "allowedTypes": [
    {
      "name": "JPEG Image",
      "mimeType": "image/jpeg",
      "extension": "jpg",
      "maxSize": 10485760
    },
    {
      "name": "PDF Document",
      "mimeType": "application/pdf",
      "extension": "pdf",
      "maxSize": 20971520
    }
  ]
}

Get File Info

GET /api/files/{id}

File Type Management

File types are stored in the database and can be managed via EasyAdmin or programmatically:

// Create a new file type
$fileType = new FileType();
$fileType->setName('PowerPoint Presentation')
    ->setMimeType('application/vnd.ms-powerpoint')
    ->setExtension('ppt')
    ->setMaxSize(30 * 1024 * 1024) // 30MB
    ->setUploadType('member') // 'anonymous', 'member', or 'both'
    ->setIsActive(true);

$entityManager->persist($fileType);
$entityManager->flush();

Console Commands

Clean Anonymous Files

Remove anonymous files older than specified hours:

# Delete anonymous files older than 1 hour (default)
bin/console file-storage:clean-anonymous

# Delete anonymous files older than 3 hours
bin/console file-storage:clean-anonymous --hours=3

# Dry run - show what would be deleted without actually deleting
bin/console file-storage:clean-anonymous --dry-run

# Verbose output showing file details
bin/console file-storage:clean-anonymous --dry-run -v

Cron Job Example

Add to your crontab to run every hour:

0 * * * * cd /path/to/project && bin/console file-storage:clean-anonymous

File Statistics

Display file storage statistics:

# Show file statistics
bin/console file-storage:stats

# Output example:
# File Storage Statistics
# =======================
# Total files: 1,234
# Total size: 5.67 GB
# Average file size: 4.71 MB
# Anonymous files: 456
# Member files: 778

Advanced Storage Configuration

The bundle uses Flysystem for file storage abstraction. By default, it uses a local filesystem adapter.

Custom Storage Adapter

To use a different storage backend (e.g., AWS S3, FTP, etc.), create your own factory:

// src/Factory/S3FilesystemFactory.php
namespace App\Factory;

use Aws\S3\S3Client;
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemOperator;

class S3FilesystemFactory
{
    public function __construct(
        private readonly S3Client $s3Client,
        private readonly string $bucket,
    ) {
    }

    public function createFilesystem(): FilesystemOperator
    {
        $adapter = new AwsS3V3Adapter($this->s3Client, $this->bucket);
        return new Filesystem($adapter);
    }
}

Then override the factory service in your application:

# config/services.yaml
services:
    Tourze\FileStorageBundle\Factory\FilesystemFactory:
        class: App\Factory\S3FilesystemFactory
        arguments:
            $s3Client: '@aws.s3.client'
            $bucket: '%env(S3_BUCKET)%'

Entity Structure

File Entity

  • originalName - Original uploaded filename
  • fileName - Generated unique filename
  • filePath - Relative path to file
  • mimeType - File MIME type
  • fileSize - File size in bytes
  • md5Hash - MD5 hash of file content
  • sha1Hash - SHA1 hash of file content
  • createTime - Upload timestamp
  • updateTime - Last update timestamp
  • metadata - JSON field for additional data
  • storageType - Storage backend type (default: local)
  • isActive - Soft delete flag
  • userId - User ID for member uploads (null for anonymous)

FileType Entity

  • name - Display name (e.g., "JPEG Image")
  • mimeType - MIME type (e.g., "image/jpeg")
  • extension - File extension (e.g., "jpg")
  • maxSize - Maximum file size in bytes
  • uploadType - Who can upload ('anonymous', 'member', 'both')
  • isActive - Whether this type is enabled
  • createTime - Creation timestamp
  • updateTime - Last update timestamp

Security

Access Control

  • Anonymous uploads have separate endpoints and permissions
  • Member uploads require authentication
  • File types can be restricted by upload type (anonymous/member/both)
  • File size limits are enforced per file type

Data Protection

  • File hashes (MD5, SHA1) are calculated for integrity verification
  • Files are stored with unique generated names to prevent collisions
  • Original filenames are preserved in the database
  • User tracking for authenticated uploads
  • IP address tracking for security auditing

Best Practices

  1. Always validate file types on the server side
  2. Set appropriate file size limits
  3. Regularly clean up anonymous files
  4. Monitor file statistics for unusual activity
  5. Use HTTPS for file uploads
  6. Consider implementing virus scanning for uploaded files

Dependencies

This bundle requires:

  • PHP ^8.1
  • Symfony ^6.4
  • Doctrine ORM ^2.0 || ^3.0
  • League Flysystem ^3.0
  • Symfony String Component

Bundle dependencies:

  • tourze/doctrine-ip-bundle
  • tourze/doctrine-timestamp-bundle
  • tourze/doctrine-track-bundle
  • tourze/doctrine-user-bundle
  • tourze/doctrine-snowflake-bundle

Advanced Usage

Event Listeners

You can listen to file upload events:

namespace App\EventListener;

use Tourze\FileStorageBundle\Event\FileUploadedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: FileUploadedEvent::class)]
class FileUploadListener
{
    public function __invoke(FileUploadedEvent $event): void
    {
        $file = $event->getFile();
        // Custom logic: virus scan, image optimization, etc.
    }
}

Custom File Validators

Extend the validation logic:

namespace App\Validator;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Tourze\FileStorageBundle\Validator\FileValidatorInterface;

class VirusScanValidator implements FileValidatorInterface
{
    public function validate(UploadedFile $file): void
    {
        // Implement virus scanning logic
        if ($this->hasVirus($file)) {
            throw new FileValidationException('File contains malware');
        }
    }
}

Testing

The bundle includes comprehensive tests with full coverage:

# Run all tests
./vendor/bin/phpunit packages/file-storage-bundle/tests

# Run with coverage
./vendor/bin/phpunit packages/file-storage-bundle/tests --coverage-html coverage

Test Suite Results:

  • πŸ“Š 128 tests, 424 assertions
  • βœ… All tests passing
  • πŸ§ͺ 100% test coverage for critical components
  • 🎯 Unit + Integration tests for controllers, services, repositories, and commands

PHPStan Analysis

Static analysis with PHPStan level 5:

php -d memory_limit=2G ./vendor/bin/phpstan analyse packages/file-storage-bundle

Analysis Results:

  • βœ… 0 errors - Clean code with proper type declarations
  • πŸ”’ Level 5 - Strict type checking enabled
  • πŸ“ Full documentation - All public methods documented

License

This bundle is released under the MIT License. See the LICENSE file for details.