youwe/file-manager-bundle
Youwe FileManagerBundle adds file management to Symfony apps: browse, upload, and organize files through a configurable interface, so you can handle assets without writing your own file-handling code.
First, the framing: youwe/file-manager-bundle is a Symfony2-era bundle, not a Laravel package. There is no service provider, no facade, no vendor:publish tag, and no ILLuminate dependency anywhere in it. composer require youwe/file-manager-bundle in a modern Laravel app will either fail on symfony/symfony constraints or drag in an abandoned dependency tree. So "leverage day-to-day" realistically means one of two things:
Both are legitimate. Orient yourself in this order:
DependencyInjection/Configuration.php — the config tree is the honest feature list. Upload root, allowed extensions, size limits, whether delete/rename are exposed. Read this before anything else.Resources/config/routing.yml — the routes you get for free, and therefore the attack surface.Controller/ — the endpoints. This is the file that matters; the "secure" claim in the description lives or dies here.Resources/views/ + Resources/public/ — the UI and its JS, which together define the JSON contract the backend must satisfy.Minimal Symfony2 wiring, if you're in that world:
// app/AppKernel.php
new Youwe\FileManagerBundle\YouweFileManagerBundle(),
# app/config/config.yml
youwe_file_manager:
upload_dir: %kernel.root_dir%/../web/uploads
allowed_extensions: [jpg, png, pdf]
# app/config/routing.yml
youwe_file_manager:
resource: "@YouweFileManagerBundle/Resources/config/routing.yml"
prefix: /admin/files
Put that prefix behind your firewall (access_control requiring ROLE_ADMIN) before you load the page once. First real use case: a back-office media browser — list a directory, upload, delete — with the bundle doing path validation.
For Laravel, the useful first step is not installation. It's reading the controller and reimplementing its validation in a FileManagerService, keeping the same request/response shapes so the bundle's front-end JS keeps working unmodified.
Config-first, not code-first. The bundle is driven almost entirely by its config tree. Resist the urge to subclass the controller to change the upload directory; almost everything you'd want to tweak (root path, extensions, size cap) is a parameter. Check Configuration.php for the exact key names before you guess — Symfony2-era bundles are inconsistent about snake_case vs camelCase here.
Route-shape compatibility when porting. The JS in Resources/public/ is coupled to the controller's response format. When you reimplement in Laravel, diff your JSON against the original controller's output field-by-field before touching the front end:
// routes/web.php — mirror the bundle's URL shape
Route::middleware(['auth', 'can:manage-files'])->prefix('admin/files')->group(function () {
Route::get('/', [FileManagerController::class, 'index']);
Route::get('/list', [FileManagerController::class, 'list']);
Route::post('/upload', [FileManagerController::class, 'upload']);
Route::delete('/delete', [FileManagerController::class, 'destroy']);
});
Separate "resolve a path" from "act on a path." The single most valuable pattern to lift from the bundle is a dedicated path-resolution method that takes a client-supplied relative path and returns an absolute, validated one — or throws. Every action (list, upload, delete, download) routes through it. In Laravel:
private function resolve(string $relative): string
{
$root = realpath(config('filemanager.root'));
$path = realpath($root . '/' . ltrim($relative, '/'));
if ($path === false || ! str_starts_with($path, $root . DIRECTORY_SEPARATOR)) {
abort(403);
}
return $path;
}
Keep the storage layer swappable. The bundle assumes a local filesystem. If you're porting, put a Flysystem disk behind your service from day one — the migration cost later is much higher than the abstraction cost now, and Storage::disk('uploads') gives you the same semantics for free.
Admin firewall is the integration point. In Symfony, the bundle is protected by access_control; in Laravel, the analogue is route middleware + a policy. Don't rely on the bundle's own checks for authorization — read the controller and confirm whether it does any role checking at all. Historically, bundles of this vintage did path validation but left authz entirely to the host app.
Test the traversal cases explicitly. Whatever you build, port the negative tests:
public function test_rejects_traversal(): void
{
$this->actingAs($admin)
->post('/admin/files/upload', ['path' => '../../.env'])
->assertForbidden();
}
Also test: absolute paths, URL-encoded ..%2f, symlinks pointing outside the root, and double extensions (shell.php.jpg).
realpath() is not enough on its own. It resolves the path but doesn't guarantee containment — you must compare against the realpath'd root with a trailing separator, or /uploads-evil will pass a naive str_starts_with($path, $root) check. Same bug class applies to the original controller; verify it.is_link() is true along the chain.[jpg, png, pdf] blocks shell.php but not a polyglot file or a .phtml on a misconfigured server. Validate content (finfo, mime_content_type) and extension, and store uploads outside the web root where possible.Shell.PHP and shell.php.jpg both defeat sloppy checks. Normalize case, take the last extension, and compare against a lowercased allowlist.upload_dir. Concatenating $root . '/' . $relative with a root that already ends in / produces //, which breaks naive prefix comparisons. rtrim($root, '/') on the way in.%kernel.root_dir% only resolves inside the bundle's Configuration.php if it's declared as a parameter in setParameter. Hardcoded-looking paths in the sample config are frequently wrong in practice — dump the resolved container (app/console debug:container --parameter=...) rather than trusting the YAML.getParent()) or a compiler pass to swap the service definition. There is no event dispatcher integration to hook into — a common surprise. In Laravel terms: bind your own implementation to the interface and never touch vendor code.200 with an error string in the JSON body. Watch the network tab, not the HTTP status, when something "doesn't work." Log every caught exception in your port — silently returning an error string is the single worst habit to carry over.How can I help you explore Laravel packages today?