Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Media Exposer Laravel Package

knplabs/media-exposer

KnpLabs MediaExposer is a lightweight, framework-agnostic PHP library for exposing media files through a consistent interface. Pluggable exposers map media objects to public URLs, letting you serve images, video, and other assets from local storage or custom backends without tying app code to file p...

View on GitHub
Deep Wiki
Context7

Getting Started

knplabs/media-exposer is a small, framework-agnostic PHP library that answers one question: given a media identifier and a format name, what path or URL should I use? It is not a file manager, not an upload handler, and it has no Laravel integration.

Install it and pin the version, because the repository is archived and will not receive further tags:

composer require knplabs/media-exposer

Because it predates PSR-4 adoption in most Knp packages, check composer.json for the autoload block first — you may need to add a classmap or psr-0 entry under autoload in your root composer.json and run composer dump-autoload before anything resolves. There is no service provider, no facade, and no config file to publish.

Where to look first, in order:

  1. README.md — the whole public surface fits in about forty lines.
  2. src/Knp/MediaExposer/Exposer.php — the single entry point. Read expose() and note that it returns a plain string.
  3. src/Knp/MediaExposer/Resolver/ — the bundled resolvers (path, web path, URL, and a chain/aggregate). These are your templates for anything custom.
  4. Media.php and File.php — the two value objects the whole design hangs on.

The mental model is a short pipeline: a Media (id + source name) is handed to a Provider registered under that source; the provider returns a File (a filesystem path and/or a URL); a Resolver that claims supports() for that media/format turns the File into the final string.

A realistic first use case is exactly the one this library was built for: user avatars stored on disk, exposed at several named sizes.

$exposer = new \Knp\MediaExposer\Exposer($resolver);
$exposer->registerProvider('avatar', fn ($id) => new Media($id, 'avatar', new File("/uploads/avatars/{$id}.jpg")));

echo $exposer->expose(new Media($userId, 'avatar'), 'small');

If your only need is Storage::url($path), stop here — Laravel already does this and does it better.

Implementation Patterns

Bind the Exposer as a singleton in a service provider. Construct it once, register all providers, and inject Exposer where needed. Rebuilding the resolver graph per request is wasted work, and providers frequently hold repository dependencies.

$this->app->singleton(Exposer::class, function ($app) {
    $resolver = new ChainResolver([
        new WebPathResolver('/uploads', '/uploads'),
        $app->make(CdnResolver::class),
    ]);

    $exposer = new Exposer($resolver);
    $exposer->registerProvider('avatar', fn ($id) => $app->make(AvatarProvider::class)($id));

    return $exposer;
});

One provider per source, keyed by the exact source string. The source name is the join key between the Media object and the provider registry, and a typo produces a "no provider" failure rather than a silent fallback. Keep the names in a small constants class or enum rather than free-floating strings.

Use the chain resolver as your format-dispatch mechanism. Rather than one resolver with a switch ($format), register several narrow resolvers in priority order: a filesystem resolver for originals, a CDN resolver for derived sizes, and a last-resort resolver that throws a domain-specific exception. This keeps each resolver trivially testable.

Wrap it behind an application-level service. Do not let Exposer::expose() calls spread through controllers and Blade. Introduce something like MediaUrlResolver that takes your model, translates it into a Media, calls the library, and caches the result on the model or in the cache store keyed by "{$model->id}:{$format}". This gives you one place to swap the implementation later — which you almost certainly will, given the package's status.

For a custom backend (S3, a signed CDN), implement a resolver, not a fork. A resolver needs only supports(Media $media, $format) and resolve(Media $media, $format). Keep supports() cheap — a dictionary lookup on source and format, never an I/O call — because it runs for every resolver in the chain on every expose.

Testing. Resolvers are pure enough to unit test directly. For feature tests, bind a FakeResolver that returns "https://cdn.test/{$media->getId()}.jpg" for everything; this removes filesystem and network dependencies from anything that renders a view.

Migration path. Because the library returns a bare string, an adapter is easy: implement your MediaUrlResolver interface today over MediaExposer, and later over Storage::disk('s3')->url() plus an image-variant table. Nothing else in the codebase needs to change.

Gotchas and Tips

  • It is archived. Treat any adoption as taking on maintenance. There are zero dependents on Packagist, so there is no community pressure to fix bugs — anything you hit, you fix yourself.
  • PHP-version friction. The code is written against PHP 5.3-era conventions: no scalar type hints, no return types, no strict types. Under PHP 8.1+ you will see deprecation notices from implicit nullable parameters and dynamic properties in the resolvers. Suppress them at the call site or patch your fork; do not expect upstream fixes.
  • No Laravel awareness at all. No container hints, no config, no exceptions extending Symfony\Component\HttpKernel\Exception. Wrap library exceptions (RuntimeException when nothing supports a media/format pair) in your own so you control the HTTP status.
  • expose() returns a string, not an object. There is no metadata, no MIME type, no dimensions, no expiry. If you need those — and for responsive images you do — you are maintaining a parallel data structure.
  • Format handling is not automatic. A null format must be explicitly supported by one of your resolvers, or you will get an exception on the simplest call. Make the "default" resolver the last in the chain and have it handle null.
  • Resolver order is behaviour. ChainResolver takes the first that supports; inserting a new resolver at the front silently changes output for previously working formats. Cover ordering with a test.
  • File is a value object holding both a path and a URL, and the two can disagree. Be explicit about which one each resolver reads. Mixing getPath() and getUrl() in the same resolver is the most common source of // and ./ artifacts in output.
  • Path normalisation is on you. The library concatenates; it does not normalise. Trim trailing slashes in configuration, not in resolvers.
  • Debugging a "path not found" report is usually one of three things, in this order: a source-name mismatch between Media and registerProvider(), a resolver that returns false/null from supports() for an unexpected reason, or a provider that threw and was swallowed. Log inside supports() temporarily; there is no debug mode to enable.
  • Extension points are exactly two: a custom ResolverInterface and a custom provider callable. Everything else requires forking. That is a small enough surface to keep patched, which is the only thing that makes this package viable at all.
  • The honest recommendation: for greenfield Laravel work, spatie/laravel-medialibrary or plain Flysystem covers this ground with maintained code. Adopt MediaExposer only to work inside an existing codebase that already depends on it, and isolate it behind a service boundary from day one.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk