eduvl/laravel-filekit

Typed file upload services for Laravel (images, audio, video, generic files) with signed Urls

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/eduvl/laravel-filekit

v0.1.0 2025-11-01 18:36 UTC

This package is auto-updated.

Last update: 2025-11-01 18:49:44 UTC


README

Typed upload services for Laravel: images, audio, video and generic files with safe paths, MIME whitelists, and signed URLs for private disks.

PHP Laravel License: MIT

✨ Features

  • ✅ Upload from UploadedFile, local path, HTTP URL, data:base64, or raw bytes
  • ✅ Per-type MIME whitelists and size limits
  • Signed routes for private disks (filekit.show, filekit.download)
  • ✅ Clean result object: UploadResult { path, url, disk, mime, size }
  • ✅ Extensible (add your own VideoService, etc.)
  • ✅ Laravel auto-discovery and publishable config

📚 Contents

✅ Requirements

Component Version
PHP ^8.2 / ^8.3 / ^8.4
Laravel 10.x or 11.x
Guzzle ^7.8 (installed automatically)

📦 Installation

Packagist (when published)

composer require eduvl/laravel-filekit

From GitHub (VCS)

Add to your app’s composer.json:

"repositories": [
{ "type": "vcs", "url": "https://github.com/eduvl/laravel-filekit" }
]

Then:

composer require eduvl/laravel-filekit:*@dev

Local path (for development)

"repositories": [
{ "type": "path", "url": "../laravel-filekit", "options": { "symlink": true } }
]
composer require eduvl/laravel-filekit:*@dev

Uses Laravel auto-discovery — no manual provider registration.

⚙️ Publish Config

php artisan vendor:publish --provider="EduVl\FileKit\FileKitServiceProvider" --tag=filekit-config

This creates config/filekit.php:

return [
'disk' => env('FILEKIT_DISK', 'public'),

    'base_dirs' => [
        'files'  => 'uploads',
        'images' => 'uploads/images',
        'audio'  => 'uploads/audio',
    ],

    'signed_url_ttl' => env('FILEKIT_SIGNED_TTL', 60), // minutes
    'max_size_bytes' => env('FILEKIT_MAX_SIZE', 50 * 1024 * 1024),

    'allowed' => [
        'images' => [
            'image/jpeg','image/png','image/gif','image/bmp','image/webp',
            'image/svg+xml','image/x-icon','image/tiff','image/heic','image/heif',
        ],
        'audio'  => [
            'audio/mpeg','audio/wav','audio/ogg','audio/webm','audio/aac',
            'audio/flac','audio/midi','audio/amr',
        ],
        'files'  => [
            'application/pdf','application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/vnd.ms-excel',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'application/vnd.ms-powerpoint',
            'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'application/rtf','application/vnd.oasis.opendocument.text',
            'application/vnd.oasis.opendocument.spreadsheet',
            'application/vnd.oasis.opendocument.presentation',
            'application/epub+zip','text/plain','application/json','text/csv',
            'application/zip','application/x-7z-compressed','application/x-rar-compressed',
            'application/x-tar','application/gzip','application/x-bzip2',
            'image/jpeg','image/png','image/webp',
            'video/mp4','video/webm','video/quicktime',
        ],
    ],
];

Using the local public disk? Run:

php artisan storage:link

🚀 Quick Start

use Illuminate\Http\Request;
use FileKit;

class ProfilePhotoController
{
public function store(Request $request)
{
$request->validate([
'photo' => ['required','image','max:5120'], // 5MB
]);

        $res = FileKit::images()->upload(
            $request->file('photo'),
            directory: 'users/'.auth()->id()
        );

        return response()->json([
            'path' => $res->path, // uploads/images/users/123/uuid.jpg
            'url'  => $res->url,  // public or signed URL
            'mime' => $res->mime, // image/jpeg
            'size' => $res->size, // bytes
        ]);
    }
}

🧰 API

Facade → manager → services:

FileKit::images(); // ImageService
FileKit::audio();  // AudioService
FileKit::video();  // VideoService
FileKit::files();  // Generic FileService

Upload

/** @var \EduVl\FileKit\Contracts\UploadResult $res */
$res = FileKit::images()->upload(
$input,               // UploadedFile | string (see below)
filename: null,       // optional, e.g. "avatar.jpg"
directory: 'users/42' // subfolder appended to base_dir
);

Allowed $input types

  • UploadedFile (form upload)
  • Local path string (/tmp/x.png)
  • HTTP URL (https://…) — downloaded via Laravel HTTP Client
  • Data URL (data:image/png;base64,…)
  • Raw bytes string

Result object

$res->path; // disk-relative path
$res->url;  // public URL or temporary signed route
$res->disk; // disk name
$res->mime; // MIME type
$res->size; // bytes

Delete

FileKit::files()->remove($res->path); // true (also true if already missing)

🔏 Signed URLs

  • Public disk → direct Storage::url($path).
  • Private disk → temporary signed routes:
    • filekit.show — inline stream
    • filekit.download — attachment

TTL is filekit.signed_url_ttl (minutes).

Manual download URL:

use Illuminate\Support\Facades\URL;

$url = URL::temporarySignedRoute(
'filekit.download',
now()->addMinutes(60),
['path' => $res->path]
);

✅ Validation

Still validate requests at the controller level:

$request->validate([
'file' => ['required', 'mimetypes:image/jpeg,image/png', 'max:10240'] // 10MB
]);

The package also enforces its own MIME whitelist and size caps.

🧩 Extending

Example: add ExcelService in your app:

namespace App\Services;

use EduVl\FileKit\Services\FileService;

class ExcelService extends FileService {}
  • Add Exel MIME list and base dir in config/filekit.php.
  • Optionally expose a factory method in your own manager wrapper.

🔐 Security Notes

  • MIME whitelists per type
  • Filename sanitization (no dangerous chars)
  • No ../ or absolute paths allowed in directory
  • Size limit (max_size_bytes)
  • HTTP downloads via Laravel HTTP Client (with timeout)
    Recommended: domain allow-list, block local/loopback IPs (SSRF), verify Content-Length.

🧪 Testing (Testbench)

This package targets Laravel; use Orchestra Testbench for package tests.

Install (in the package repo):

composer require --dev "orchestra/testbench:^9.0"

tests/TestCase.php

<?php

namespace EduVl\FileKit\Tests;

use EduVl\FileKit\FileKitServiceProvider;
use Orchestra\Testbench\TestCase as Base;

abstract class TestCase extends Base
{
    protected function getPackageProviders($app)
    {
        return [FileKitServiceProvider::class];
    }

    protected function defineEnvironment($app)
    {
        $app['config']->set('filekit.disk', 'public');
    }
}

Route test example:

<?php

namespace EduVl\FileKit\Tests;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;

class RoutesTest extends TestCase
{
    public function test_signed_show_serves_file()
    {
        Storage::fake('public');
        Storage::disk('public')->put('uploads/test.txt', 'hello');

        $url = URL::temporarySignedRoute('filekit.show', now()->addMinutes(5), [
            'path' => 'uploads/test.txt',
        ]);

        $this->get($url)->assertOk()->assertSee('hello');
    }
}

Run:

vendor/bin/phpunit

🛠️ Troubleshooting

  • Windows & | in Composer constraints
    On cmd.exe use ranges instead of pipes:
    "illuminate/support:>=10.0 <12.0".

  • 404 when accessing URL
    Ensure the file exists on the configured disk and the path matches.
    For public disk: php artisan storage:link.

  • IDE highlights helpers in the package
    Outside a Laravel app those helpers aren’t loaded — it’s fine.
    Keeping Testbench in require-dev restores helper awareness for IDE/tests.

🔢 Versioning

  • 0.x — development, API may change.
  • 1.x — stable; breaking changes only in 2.0.

🤝 Contributing

PRs and issues are welcome!

  1. git clone
  2. composer install
  3. vendor/bin/phpunit
  4. Don’t commit vendor/ or composer.lock (libraries usually don’t ship lockfiles)

📄 License

MIT — see LICENSE.