tiloweb/base64-bundle
A small Symfony bundle that adds Base64 encoding and decoding helpers, including Twig filters, so you can transform strings directly in templates or services without writing custom code.
1. Install and confirm compatibility
composer require tiloweb/base64-bundle
The last release is from June 2020, so before anything else open the package's composer.json and check the require block for symfony/form and symfony/framework-bundle. If you're on Symfony 6/7, treat this as a "read the source and copy it" exercise rather than a drop-in dependency.
2. Register the bundle
Symfony Flex auto-registers packages typed symfony-bundle; if it wasn't added, do it manually in config/bundles.php:
return [
// ...
Tiloweb\Base64Bundle\TilowebBase64Bundle::class => ['all' => true],
];
Confirm the exact FQCN from the bundle class file — the repo (SymfonyBase64Bundle) and package name (base64-bundle) don't match cleanly, so guessing the namespace will cost you ten minutes.
3. Where to look first
src/Form/Type/ — the form type you'll actually usesrc/Form/DataTransformer/ — the UploadedFile → data:image/...;base64,... conversion, i.e. the whole value of the packageResources/config/ (or config/) — service definitions and any parametersREADME.md — for a 2-star bundle this is usually the only documentationcomposer.json — supported Symfony range, PSR-4 mapping4. First use case — an avatar field with no file storage
// src/Form/ProductType.php
use Tiloweb\Base64Bundle\Form\Type\Base64ImageType;
$builder
->add('name', TextType::class)
->add('photo', Base64ImageType::class, ['required' => false]);
The entity property is just a string:
#[ORM\Column(type: 'text', nullable: true)]
private ?string $photo = null; // "data:image/png;base64,iVBORw0KGgo..."
Because the value is already a self-contained data URI, rendering needs no asset pipeline, no storage adapter and no cleanup job:
{% if product.photo %}
<img src="{{ product.photo }}" alt="{{ product.name }}" width="64" height="64">
{% endif %}
Upload → base64 in a single field. The type wraps a file input and a transformer that reads the upload and replaces the submitted value with a data:{mime};base64,{payload} string before it reaches your model. So $form->getData()->getPhoto() returns a string, not an UploadedFile, and your controller stays trivial:
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($product);
$em->flush();
}
Editing records without clobbering the image. Submitting the form replaces the stored string, so add an explicit "keep current" branch:
$current = $product->getPhoto();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($product->getPhoto() === null) {
$product->setPhoto($current);
}
$em->flush();
}
Check the bundle's transformer first — some implementations already no-op on empty input, in which case you don't need this.
API / JSON payloads. If the front end posts a canvas toDataURL() string, the bundle adds little: validate and persist the string directly. Reach for the form type only when the input is a real multipart file upload.
EasyAdmin / Sonata. Register it as a form type and render the raw string in the list template — never wrap it in asset():
yield Field::new('photo')->setFormType(Base64ImageType::class);
Validation belongs on the property, not the type:
#[Assert\Length(max: 200_000)] // ~150 KB decoded
#[Assert\Regex('/^data:image\/(png|jpe?g|webp);base64,/')]
private ?string $photo = null;
Extracting the raw bytes downstream (thumbnailing, hashing, dedupe):
[$meta, $payload] = explode(',', $dataUri, 2);
$mime = explode(';', explode(':', $meta)[1])[0]; // image/png
$bytes = base64_decode($payload);
Good fit: avatars, logos, small icons, seeded demo data. Bad fit: anything a user might upload from a phone camera.
src/Form/ — you'll spend less time than debugging it.TEXT/LONGTEXT (Postgres: text), and check post_max_size, upload_max_filesize and MySQL's max_allowed_packet before blaming the bundle for silent empty submissions.SELECT * plans, and bloats backups. Cap dimensions and byte size client-side as well as server-side.url() or <a href> for anything reused. It's re-sent on every request and defeats HTTP caching entirely. Hash-named files win for shared assets.getimagesize() or finfo — never trust the browser-supplied type baked into the data URI.->add('photo', 'tiloweb_base64_image'). Modern Symfony wants the class name. If you hit a Twig "block not found" error, check getBlockPrefix() and whether the bundle ships its widget template under Resources/views/Form/; you may need to register it yourself.dump($form->get('photo')->getData()) in a PRE_SUBMIT listener to see the raw upload before conversion. If the entity ends up holding an UploadedFile, the transformer never ran — check service registration and autoconfigure tags in services.yaml.DataTransformerInterface via ->addModelTransformer() rather than editing the bundle.data:image/, and one asserting an oversized upload is rejected. There are none upstream.How can I help you explore Laravel packages today?