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

Data Uri Bundle Laravel Package

1tomany/data-uri-bundle

Symfony bundle wrapping 1tomany/data-uri: auto-configures a denormalizer for TemporaryFileInterface objects and adds a console command to encode files as base64 data URIs. No configuration needed.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check for a Laravel shop. This is a Symfony bundle, not a Laravel package. It ships no service provider, no facade, no config file, and Composer auto-discovery won't do anything for you. There are two legitimate ways a Laravel developer ends up here:

  1. You're touching a Symfony service (mixed estate, a legacy app, a shared monorepo, an acquired codebase). Then use the bundle as intended.
  2. You're on Laravel and only want the encoding logic. Then require the underlying library only — composer require 1tomany/data-uri — and skip the bundle entirely. The bundle adds exactly two things: a DataUriNormalizer wired into Symfony's Serializer, and the onetomany:data-uri:encode-file console command. Both are ~50 lines of glue you can reimplement idiomatically in Laravel.

Symfony path (if you're actually on Symfony):

composer require 1tomany/data-uri-bundle

With Flex the bundle registers itself in config/bundles.php. Without Flex, add:

OneToMany\DataUriBundle\DataUriBundle::class => ['all' => true],

There is no configuration. The README is explicit: the denormalizer is auto-tagged. Don't go looking for config/packages/data_uri.yaml — it doesn't exist and the bundle has no Configuration tree.

Where to look first (in this order, because the README won't tell you):

  1. vendor/1tomany/data-uri-bundle/src/ — the whole bundle is tiny; read Serializer/DataUriNormalizer.php and the service definition file (usually config/services.php or Resources/config/services.yaml).
  2. vendor/1tomany/data-uri/src/Contract/Record/DataUriInterface.php — this is the contract you'll be type-hinting against. Read the record implementations next to it; they're what you'll actually instantiate.
  3. The encode command class — it shows you the canonical, library-sanctioned way to turn a path into a URI. Treat it as the reference implementation.

First use case — smoke test before you wire anything up. The console command is the fastest way to confirm the library behaves on your files (filenames with unicode, weird MIME sniffing, symlinks):

bin/console onetomany:data-uri:encode-file public/img/logo.png
# data:image/png;base64,iVBORw0KGgo...

Take that output, paste it into a Blade template or a JSON response, and confirm the browser renders it. That's the entire value proposition in one line. Then decide whether the Serializer integration is worth wiring.

Laravel path — the 20-line equivalent. Don't add a dependency for base64_encode. If all you need is the CLI/one-off behaviour:

$mime = (new \finfo(FILEINFO_MIME_TYPE))->file($path);
$uri  = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($path)));

Reach for 1tomany/data-uri only when you want the DataUriInterface record abstraction — i.e. you're passing typed objects around rather than strings, and you want parsing/validation of incoming data URIs for free instead of a hand-rolled regex.

Implementation Patterns

Pattern 1 — Denormalize an inbound data URI into a typed object (Symfony)

The bundle's real job. In your DTO, type-hint the property against the interface; Symfony's Serializer will turn a data:image/png;base64,... string from a JSON request body into an object.

use OneToMany\DataUri\Contract\Record\DataUriInterface;
use Symfony\Component\Serializer\Attribute\SerializedName;

final class CreateAttachment
{
    #[SerializedName('file')]
    public DataUriInterface $dataUri;   // accepts "data:...;base64,..."
}

This is the pattern that makes the buy-vs-build argument: the client posts an inline image, you get a typed object, no controller-side decoding. It shines for small admin-panel thumbnails and for headless-CMS workflows where the media lives in S3 but the API needs to echo it back inline.

Pattern 2 — Normalize on the way out

As of v0.0.8 the normalizer is tagged with both serializer.normalizer and serializer.denormalizer. So the same object round-trips: DTO in → serialized string out. Older versions only did one direction; if you're pinned below 0.0.8, don't assume symmetry.

$json = $serializer->serialize($attachment, 'json');
// {"file":"data:application/pdf;base64,JVBERi0xLjQK..."}

Pattern 3 — Conditional encoding (the pattern you'll actually need in production)

Inline small things, link big ones. Do not put this branching inside the DTO — wrap it in a decorator or a dedicated service so the DTO stays a dumb value object:

final readonly class ThumbnailResolver
{
    private const MAX_INLINE_BYTES = 64 * 1024;

    public function __construct(private Encoder $encoder) {}

    public function resolve(string $path): string
    {
        if (filesize($path) > self::MAX_INLINE_BYTES) {
            return $this->cdn->url($path);   // fall back to a URL
        }

        return (string) $this->encoder->encode($path);
    }
}

Threshold in the tens of KB, not MB. See Gotchas for why.

Pattern 4 — Cache the encoded result

Base64 output is deterministic for a given file, so never encode the same bytes twice per request cycle, and ideally not twice per deploy:

$key = 'datauri:'.sha1_file($path);
$uri = Cache::rememberForever($key, fn () => (string) $encoder->encode($path));

Invalidate by content hash, not by timestamp — that makes it idempotent across servers.

Pattern 5 — Laravel-native integration points

If you're porting the idea to Laravel, these are the four seams that map 1:1 to what the bundle does:

Symfony concept Laravel equivalent
DataUriNormalizer (denormalize) FormRequest validation + a DataUri cast on the model
DataUriNormalizer (normalize) JsonResource::toArray() calling the encoder
onetomany:data-uri:encode-file php artisan make:command or a queued job
Serializer auto-tagging Explicit binding in a ServiceProvider

A custom Eloquent cast gives you the round-trip for near-free:

class DataUriCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): ?DataUriInterface
    {
        return $value === null ? null : $this->encoder->decode($value);
    }

    public function set($model, $key, $value, $attributes): ?string
    {
        return $value instanceof DataUriInterface ? (string) $value : $value;
    }
}

Pattern 6 — Console command as a build step, not a manual tool

The encode command is more useful in CI than in a terminal by hand: generate inline icons for an email template, pre-bake critical CSS assets, or seed fixtures with deterministic data URIs. Wrap it in a Composer script or a deploy step rather than asking developers to paste output into templates.

Integration tips

  • Normalizer priority. If your app already has a normalizer handling strings or file-ish objects, confirm ordering with bin/console debug:container --tag=serializer.normalizer. The bundle ships no priority config, so collisions are resolved by registration order — a classic silent-failure source.
  • Never point the denormalizer at user-controlled file paths. The interface operates on data URIs; if your implementation resolves a path internally, you've reintroduced path traversal. Keep resolution inside a whitelisted storage disk.
  • API Platform: the normalizer works, but you'll want #[ApiProperty] with an example value and an OpenAPI schema override, or your generated docs will claim the field is a plain string of unbounded length.

Gotchas and Tips

The repo is archived. This is the single most important fact and it appears nowhere in the README. Pin the version explicitly in composer.json, and accept that bug fixes are yours to make. The bundle is small enough that forking or vendoring src/ is genuinely viable — read the license, it's MIT.

Zero stars, zero dependents. There is no community to answer questions. When something breaks, the debugging path is vendor/1tomany/data-uri-bundle/src → vendor/1tomany/data-uri/src. Budget for that; don't assume a StackOverflow answer exists.

v0.1.0 pins 1tomany/data-uri to exactly 6.0.7. That's a hard lock, not a range. If your project has any constraint touching that package, expect a solver conflict, and expect to have to reason about it rather than just bumping.

The 33% size tax compounds fast. Base64 inflates payloads by a third, then JSON escaping adds more, then gzip recovers some but not all (base64 compressed ~= original compressed, so you lose most of the savings). Concrete failure modes:

  • PHP memory_limit blowups on base64_encode of large files — it needs the whole file in memory, plus the output, plus the string copy.
  • Nginx client_max_body_size and fastcgi_buffers on the response side for large inline payloads.
  • API clients with a 1 MB JSON response cap that now 502 on a 900 KB PDF.

Rule of thumb: inline icons, thumbnails, logos, small SVGs. Link everything else.

Data URIs are invisible to the browser cache. This defeats the usual "inlining reduces requests" argument for anything loaded more than once. Inline a favicon once in a layout — fine. Inline a 40 KB hero image on every page — you've traded one cacheable request for 40 KB of uncacheable HTML on every navigation.

Security, specifically:

  • Never render an untrusted data URI in an href, src, iframe, or CSS url(). data:text/html;base64,... executes in the document context in the right (wrong) circumstances. Allowlist image/* MIME prefixes at minimum.
  • Don't trust the MIME type in the URI header — it's attacker-controlled. Sniff server-side with finfo.
  • A data URI in a log line is a data leak. Make sure your exception handlers and query loggers truncate.

Error handling is sparse. "File not found" from the encode path may surface as a bare exception without the path or a useful code, per the TPM notes. Wrap calls in try/catch, log the resolved absolute path yourself, and translate to a 422 rather than a 500:

try {
    $record = $this->encoder->encode($path);
} catch (\Throwable $e) {
    throw ValidationException::withMessages(['file' => 'File could not be encoded.']);
}

No configuration tree = no extension points, but you can still decorate. The bundle exposes no options. To change behaviour, decorate rather than fork:

#[AsDecorator(decorates: OneToMany\DataUriBundle\Serializer\DataUriNormalizer::class)]
final class SafeDataUriNormalizer implements NormalizerInterface, DenormalizerInterface
{
    public function __construct(private NormalizerInterface&DenormalizerInterface $inner) {}
    // delegate, but reject non-image/* or oversized payloads
}

That's the cleanest lever: MIME allowlisting, size caps, and logging all belong in a decorator, not in your DTOs.

Debugging recipe for "the field is null." Three suspects, in order: (1) the property isn't typed against DataUriInterface so the denormalizer never fires; (2) the normalizer was overridden by a project normalizer with higher priority; (3) the DTO's property isn't settable/public and you're not using a constructor. Check (2) with debug:container --tag=serializer.normalizer, then dd($serializer->getSupportedTypes('json')).

Test the bytes, not the string. Asserting on the full base64 blob makes tests brittle and unreadable. Assert the prefix (data:image/png;base64,) and that base64_decode(substr($uri, strpos($uri, ',') + 1)) matches the fixture file's contents.

The honest recommendation for a Laravel-only team: don't adopt this. The value is the typed DataUriInterface round-trip, and you can build that in an afternoon with a cast, a JsonResource, and a FormRequest rule. Adopt it if you're on Symfony and already using the Serializer (API Platform especially) — the zero-config auto-tagging genuinely saves you from hand-wiring a normalizer. Skip it if you need streaming, transformation, or long-term maintenance guarantees; the archived status plus the pinned dependency is a real operational cost for a package whose core is base64_encode.

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.
adachsoft/changelog-linter
adachsoft/collection
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
adachsoft/php-code-style