markup/barcode-bundle
A Symfony bundle for generating barcodes in PHP applications. It provides configurable rendering of common barcode formats, making it easy to add scannable codes to invoices, labels, and product pages.
1. Install
composer require markup/barcode-bundle
This pulls in zendframework/zend-barcode (Zend Framework 2 component) as a dependency, so you also need the GD extension compiled in (php -m | grep gd).
2. Register the bundle (Symfony 2/3 style — the package predates Flex):
// app/AppKernel.php
$bundles = [
// ...
new Markup\BarcodeBundle\MarkupBarcodeBundle(),
];
On Symfony 4+ put it in config/bundles.php instead and be prepared to fix service visibility (see Gotchas).
3. Where to look first
The README is thin, so read the source in this order:
vendor/markup/barcode-bundle/
├── MarkupBarcodeBundle.php
├── DependencyInjection/Configuration.php # what's configurable
├── DependencyInjection/MarkupBarcodeExtension.php
├── Resources/config/services.xml # real service IDs
└── Twig/ # helper signature, if present
Then confirm the actual container wiring rather than trusting the docs:
bin/console debug:container --show-private | grep -i barcode
bin/console debug:container markup_barcode.generator # or whatever ID you found
4. First use case — a printable SKU label
Generate a Code 128 barcode for an order/SKU, cache it as a PNG, and print it on a label:
use Zend\Barcode\Barcode;
$barcode = Barcode::factory(
'code128', // barcode object
'image', // renderer
[
'text' => 'SKU-10482',
'barHeight' => 40,
'drawText' => false,
],
[
'imageType' => 'png',
]
);
ob_start();
$barcode->render();
$png = ob_get_clean();
Even if the bundle's Twig helper doesn't fit your needs, that snippet is the entire value proposition — the bundle is essentially a service wrapper and Twig function around this factory.
The cleanest integration is a dedicated route so the browser can cache the image and your HTML stays small:
/**
* @Route("/barcode/{type}/{text}", requirements={"type"="code128|ean13|code25"})
*/
public function barcodeAction($type, $text)
{
$barcode = Barcode::factory($type, 'image', [
'text' => $text,
], [
'imageType' => 'png',
]);
ob_start();
$barcode->render();
$png = ob_get_clean();
$response = new Response($png, 200, ['Content-Type' => 'image/png']);
$response->setPublic();
$response->setMaxAge(86400);
$response->setEtag(md5($png));
return $response;
}
<img src="{{ path('app_barcode', {type: 'code128', text: order.sku}) }}" alt="Barcode {{ order.sku }}">
Note the requirements on the route: it is your whitelist and also prevents the arbitrary text segment from ever being interpreted as a renderer name.
Useful for e-mails, PDFs, and labels inside a single HTML document:
class BarcodeHelper
{
public function dataUri(string $type, string $text, array $options = []): string
{
$barcode = Barcode::factory($type, 'image', ['text' => $text] + $options, [
'imageType' => 'png',
]);
ob_start();
$barcode->render();
return 'data:image/png;base64,' . base64_encode(ob_get_clean());
}
}
Register it in services.yml with a public alias, then:
<img src="{{ barcode_data_uri('code128', order.number) }}" alt="{{ order.number }}">
Keep it out of the Twig function if the bundle only exposes its own helper — a small decorator service is easier to test and lets you swap Zend for Laminas later.
Barcodes are deterministic for a given (type, text, options) tuple. For anything that appears on a page more than once — packing slips, warehouse labels, invoices — precompute on save:
$file = sprintf('%s/barcodes/%s.png', $this->getParameter('kernel.project_dir') . '/public', sha1($sku));
if (!file_exists($file)) {
// render with Barcode::factory(...) => $png
file_put_contents($file, $png);
}
Then {{ asset('barcodes/' ~ sku|sha1 ~ '.png') }}. This turns a per-request GD call into a static file and lets your CDN/Nginx take over.
If debug:container shows a generator service (markup_barcode.generator or similar), inject that:
# services.yml
App\Service\LabelPrinter:
arguments: ['@markup_barcode.generator']
Advantage: you inherit whatever default renderer options the bundle configures, and unit tests can mock the service. If it is not public on Symfony 4+, either request it in a compiler pass or (simpler) just build your own thin factory around Barcode::factory() and treat the bundle as reference code.
Check Configuration.php for the tree the bundle accepts; typical keys mirror Zend's renderer options:
markup_barcode:
renderer: image
options:
imageType: png
width: 2
height: 40
drawText: true
font: /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
Setting font is usually necessary — Zend's bundled fonts are not always present, and without a resolvable font the text line silently disappears or throws.
Zend's Barcode::factory() resolves code128, code25, code39, ean13, ean8, upca, upce, itf14, royalmail, etc. There is no QR code — if a spec asks for QR, this package is the wrong tool (reach for endroid/qr-code and keep both). Map your domain enum to Zend names in one place so call sites never pass raw strings.
Symfony 4/5/6 compatibility is not guaranteed. The bundle was written against Symfony 2 conventions: XML service definitions, $container->get()-style access, public-by-default services. On modern Symfony you will likely need to copy the service definitions into your own services.yml with public: true, or bypass the bundle entirely and call Zend\Barcode\Barcode yourself. Given the last release (2020) and near-zero dependents, treat it as a starting point, not a maintained dependency.
Zend Framework is abandoned. zendframework/zend-barcode was superseded by laminas/laminas-barcode, which is API-compatible but namespaced Laminas\Barcode. If Composer refuses to install on PHP 8, that is why. Options:
composer require laminas/laminas-barcode and change Zend\Barcode\... to Laminas\Barcode\... in your code (the bundle's own classes may still reference Zend — in that case fork or vendor the bundle).composer why-not markup/barcode-bundle to see the exact conflicting constraint before fighting it.GD is mandatory, not optional. The image renderer requires GD with PNG support. Deployments frequently ship PHP without it; add ext-gd: * to your composer.json and to your Docker image. For headless/CLI generation make sure gd is enabled in the CLI SAPI too, not just FPM.
Output buffering is the way to capture the image. Zend's renderer writes directly to output. Always wrap with ob_start() / ob_get_clean(), and be careful that any error or notice emitted inside the buffer corrupts the PNG. Wrap the render in a try/finally that discards the buffer on failure.
EAN/UPC inputs are not what you think. For ean13, pass the 12-digit payload; the checksum is computed. Passing 13 digits (checksum included) usually throws. Same for ean8 (7 digits) and upca (11 digits). Validate at the boundary and surface a 400 rather than a 500.
Never let user input choose the renderer. Barcode::factory($userType, ...) will attempt class resolution; a whitelist (in_array($type, [...], true)) on the route/controller is cheap insurance and avoids leaking which classes exist.
Twig escaping of base64 data URIs. data:image/png;base64,... contains + and / but Twig's default escaping leaves them alone; do not run it through raw unnecessarily, and never url_encode it. The real risk is whitespace — do not wrap the output in multi-line template tags, or the browser gets a broken URI.
Fonts and text rendering. If drawText => true and the label prints but the human-readable line is missing, the font path is wrong. Prefer an absolute path to a .ttf you control; check that the web user can read it. Also note fontSize in Zend applies to the text height in points, not pixels.
Debugging failures. Zend throws Zend\Barcode\Renderer\Exception\ExceptionInterface and Zend\Barcode\Object\Exception\ExceptionInterface. Catch and log those explicitly; a bare catch-all turns configuration mistakes into blank images. Zend\Barcode\Barcode::canRender('image') is a quick startup assertion you can run in a health-check or debug command to fail fast when GD is missing.
Caching semantics. Because output is deterministic, set Cache-Control, ETag, and Last-Modified on the image route — otherwise every page view re-runs GD. Add a Vary-free, query-string-driven URL (/barcode/code128/SKU-1.png) so proxies cache cleanly.
Extension points. If the bundle doesn't expose what you need, don't patch vendor code:
BarcodeRenderer of your own around Barcode::factory()).pdf if you need vector output for print.Test the bytes, not the pixels. A fast assertion is assertStringStartsWith("\x89PNG\r\n\x1a\n", $png) plus a length check; snapshot-comparing PNGs across GD versions is flaky.
Maintenance reality check. Two stars, zero dependents, a 2020 release, and a dependency on an abandoned framework component. Use it as a thin convenience layer, keep the actual generation logic behind your own interface, and you can drop the bundle in a day when it becomes a liability.
How can I help you explore Laravel packages today?