liverbool/media
Liverbool Media is a Laravel package for managing media files attached to Eloquent models. Upload, store, and organize images and other files on any filesystem disk, define custom conversions, and retrieve URLs or collections through a clean, fluent API.
This package is a 2016 fork of sylius/media ("tempolary for sylius/media" per its own description), last released 2016-05-24. Treat it as a legacy component to reference or fork, not a dependency you add to a new project. Start by reading the upstream Sylius\Component\Media code, since that is what you are actually getting.
Minimal steps:
composer require liverbool/media in a throwaway project (PHP 5.6/7.0-era constraints — expect conflicts on Laravel 9+). Do not add it to an existing app first.vendor/liverbool/media/src and confirm the namespace. Forks of this kind often keep Sylius\Component\Media verbatim, or rename to Liverbool\Media while keeping class names. Every snippet below assumes the original names; verify each one.Model\Media / Model\Image — plain PHP objects (path, name, content, createdAt, updatedAt, plus width/height on Image).Model\File — a value object wrapping a path, mime type, and size.Uploader\ImageUploader — takes an ImageInterface, a File, and writes it through Flysystem.PathResolver\* and UriResolver\* — turn a File into a storage path and a public URL.storage/app/public/media, and resolve a public URL.$file = new File($uploadedFile->getRealPath());
$file->setMimeType($uploadedFile->getClientMimeType());
$image = new Image();
$image->setFile($file);
$uploader = new ImageUploader($pathResolver, $uriResolver, $filesystem);
$uploader->upload($image);
// persist $image->getPath() on your own Eloquent model
The package is a component, not a bundle and not a service provider. There is no auto-registration — you wire the pieces yourself.
Use the uploader as a service, not the models as entities. The cleanest Laravel integration is to ignore Media/Image persistence entirely and use ImageUploader + PathResolver as a small upload service, then keep your own Eloquent Media model with path, mime, size, width, height. This avoids pulling Doctrine into a Laravel app and keeps migrations under your control.
Custom path resolver for dates or tenancy. The default resolver produces flat paths. Subclass it to shard by date or owner:
class ShardedPathResolver implements PathResolverInterface
{
public function resolve(FileInterface $file): string
{
return sprintf('media/%s/%s.%s',
date('Y/m'),
bin2hex(random_bytes(8)),
$file->getExtension()
);
}
}
Custom URI resolver for CDN or signed routes. The default resolver concatenates a base URL and the path. If you serve through S3/CloudFront, replace it with one that returns Storage::disk('s3')->url($path) or a temporary URL. Keeping this behind the interface is the main reason to use the package at all.
Upload pipeline in a queued job. ImageUploader::upload() is synchronous I/O. For large images, move the File construction and upload into a job, pass the temporary path in the payload, and delete the temp file after the job commits. This also lets you retry on transient Flysystem failures.
Replacing an existing file. Delete the old path from the filesystem after the new upload succeeds and after the DB update commits, not before. Keep the old path in a column (or a previous_path field) until you have confirmed the new row is live.
Validation lives in your app, not the package. There is no mime, size, or dimension checking anywhere in Media/Image. Use a Laravel FormRequest with mimes, max, and dimensions rules; pass only validated files into the uploader.
Testing. The uploader talks to a Flysystem FilesystemInterface, so swap in League\Flysystem\Memory\MemoryAdapter (or vfsStream) in unit tests. Do not attempt to assert against storage/app in tests — you will fight both Flysystem's root and Laravel's Storage root, which are unrelated.
If you need images resized, pair the stored path with intervention/image or league/glide in your own service. The package stores originals only; named image filters in upstream Sylius come from sylius/imagine, which is not part of this repo.
It is abandoned (2016, zero stars, zero dependents). No security fixes, no PHP 8 support, likely no support for Flysystem 3 or Symfony 5+. If you must use it, fork it into your own namespace, audit Uploader and the resolvers, and pin an exact commit. For new work, reimplement the ~200 lines of resolver/uploader logic — it is genuinely small — or use spatie/laravel-medialibrary or intervention/image directly.
Namespace ambiguity is the first trap. Check whether classes are Sylius\Component\Media\... or renamed. Autoloading Sylius\... classes from a liverbool/media vendor dir works, but Composer will still report the package as liverbool/media, which confuses IDE navigation and makes it easy to believe you have a different dependency than you do.
File is a lazy stat object. getSize() and getMimeType() may hit the filesystem or be null depending on the constructor path used. If you construct new File($path) from a path that no longer exists, uploads fail with unhelpful Flysystem exceptions. Always build from UploadedFile::getRealPath() before the request ends.
Symfony UploadedFile requirement. Laravel's Illuminate\Http\UploadedFile extends Symfony's, so it satisfies the type hints — but only if the upload passed isValid(). A UPLOAD_ERR_NO_FILE will produce a File pointing at a nonexistent temp path.
Double-prefix path bug. Flysystem's local adapter has its own root, and Laravel's filesystems.disks.public has another. Configuring both to storage/app/public yields files at storage/app/public/media/... while the URI resolver emits /storage/media/.... Set Flysystem's root once, and keep the resolver base URL completely separate.
Filename collisions and traversal. The default name generator is weak; if you subclass a path resolver, sanitize extensions and never interpolate user-supplied names. Prefer bin2hex(random_bytes(8)) over uniqid().
Deletion order. ImageUploader has no remove() in most forks. Deleting the entity before deleting the file creates orphans that are impossible to garbage-collect later; deleting the file first means a failed DB write leaves a broken row. Log the path on failure so a sweeper can reconcile.
No width/height on plain Media. If you plan to apply named image filters, store dimensions yourself; Image carries the fields but nothing populates them automatically on upload.
Extension points are narrow but sufficient. The interfaces to implement are PathResolverInterface, UriResolverInterface, and (if needed) a custom UploaderInterface. Everything else — persistence, validation, resizing — is deliberately out of scope and should stay in your application layer.
How can I help you explore Laravel packages today?