ozznest/barcode-bundle
A Laravel/PHP barcode bundle for generating and rendering common barcode formats through a simple API, making it easy to add scannable codes to invoices, labels, product catalogs, tickets, and other application workflows.
First, the reality check. ozznest/barcode-bundle is a Symfony bundle (not a Laravel package), it has zero stars/dependents, and it wraps zendframework/zend-barcode, which was abandoned when Zend Framework became Laminas in 2019. There is no Laravel service provider, no config/barcode.php, and no facade. Treat it as a thin convenience wrapper around Zend\Barcode — you are really evaluating Zend/Laminas Barcode, and in a Laravel app you will almost certainly skip the bundle and use the renderer classes directly.
Minimal steps
Install and see what resolves:
composer require ozznest/barcode-bundle
If it fails on PHP 8.1+, add a Composer alias so the abandoned package maps to the maintained fork:
"replace": { "zendframework/zend-barcode": "laminas/laminas-barcode:^2.0" }
and composer require laminas/laminas-barcode.
Verify the renderer works from php artisan tinker:
$bc = \Laminas\Barcode\Barcode::factory('code128', 'image', ['text' => 'INV-1001']);
ob_start(); $bc->draw(); $png = ob_get_clean();
file_put_contents('/tmp/test.png', $png);
Open it. If it renders, the rest is plumbing.
Where to look first in the repo: DependencyInjection/ and Resources/config/services.* — that's the only real logic. Everything else is Zend Barcode. The service id and constructor arguments there are the entire "API surface" you'd be re-implementing.
First use case: an order/invoice barcode endpoint. Wrap the renderer in a small service and a controller:
// app/Services/BarcodeService.php
namespace App\Services;
use Laminas\Barcode\Barcode;
class BarcodeService
{
public function png(string $text, string $type = 'code128', array $opts = []): string
{
$barcode = Barcode::factory($type, 'image', array_merge([
'text' => $text,
'drawText' => true,
'factor' => 2,
'barHeight' => 60,
'withQuietZone' => true,
], $opts));
ob_start();
$barcode->draw();
return (string) ob_get_clean();
}
}
// routes/web.php
Route::get('/barcode/{text}', [BarcodeController::class, 'show'])
->where('text', '[A-Za-z0-9\-]+');
// controller
public function show(string $text, BarcodeService $bc)
{
return response($bc->png($text), 200, [
'Content-Type' => 'image/png',
'Cache-Control' => 'public, max-age=31536000, immutable',
]);
}
That's the entire "package." Roughly 30 lines, which tells you a lot about its value.
Cache at the edge, not in PHP. Barcode output is deterministic for a given (type, text, options) tuple. Hash the inputs into the URL or an ETag, store the PNG once (S3/local disk), and serve it statically. Rendering is GD work plus an ob_start() round-trip — you don't want it on a hot path.
public function url(string $text): string
{
$key = 'barcodes/' . md5($text . '|code128') . '.png';
if (! Storage::exists($key)) {
Storage::put($key, $this->png($text));
}
return Storage::url($key);
}
Blade usage. Either a helper or a route-based <img> — the latter is better because it's cacheable by the browser:
<img src="{{ route('barcode.show', $order->number) }}" alt="Barcode {{ $order->number }}">
Avoid a Blade directive that calls the service inline; you lose HTTP caching and you'll render on every page load.
PDFs / dompdf. dompdf can't execute PHP mid-render, so embed a data URI:
$src = 'data:image/png;base64,' . base64_encode($bc->png($order->number));
// <img src="{{ $src }}">
Watch the PDF memory ceiling — base64 PNGs of large barcodes inflate fast.
Label / bulk printing. Push rendering into a queued job that produces a ZIP or a merged PDF (FPDI or spatie/laravel-pdf), rather than rendering N barcodes in a web request.
QR codes use the same path with Barcode::factory('qr', 'image', ...), but prefer simplesoftwareio/simple-qrcode for QR specifically — Zend's QR renderer predates the modern spec and chokes on UTF-8 (see Gotchas).
Testing. Don't snapshot bytes. Assert the invariants:
$png = app(BarcodeService::class)->png('INV-1001');
$this->assertStringStartsWith("\x89PNG", $png);
$this->assertSame('image/png', (new \finfo(FILEINFO_MIME_TYPE))->buffer($png));
[$w, $h] = getimagesizefromstring($png);
$this->assertGreaterThan(200, $w);
The bundle will not auto-register in Laravel. Ozznest\BarcodeBundle\OzznestBarcodeBundle is a Symfony\Component\HttpKernel\Bundle\Bundle. Nothing in Laravel reads Resources/config/services.yml. You are re-implementing its DI wiring by hand — which is why the "use Laminas directly" path above is the honest recommendation.
Zend is dead; Laminas is alive. zendframework/zend-barcode has been abandoned since 2019 and is not PHP 8.1+ clean without the replace alias. If you must keep the namespace, use composer replace; if you're doing this fresh, just require laminas/laminas-barcode and never touch the bundle.
draw() echoes. It does not return a string. Forgetting ob_start() dumps raw PNG bytes into an HTML response, which looks like a corrupted page and is genuinely confusing to debug. Always buffer, and use ob_get_clean() without exceptions in between.
GD with FreeType is required for text. drawText => true silently fails (or throws) if GD lacks FreeType. Check extension_loaded('gd') and gd_info()['FreeType Support']. ImageMagick can be used as the renderer backend but is far slower and needs its own setup.
Scanners are unforgiving. A Code128 rendered with default options often looks fine on screen but fails at the till: bars too thin, quiet zone too small, or drawText clipping the digits. Tune factor (bar width multiplier, 2–3 is safe) and barHeight (≥ 50px), and test on a physical scanner or a real scanning app — never by eye.
QR + UTF-8. Zend/Laminas QR encodes as ISO-8859-1. Passing accented characters, emoji, or CJK yields a barcode that scans to mojibake. Either transliterate your input or switch libraries for QR.
No config/barcode.php exists. The Symfony-side configuration lives in the DI extension's Configuration.php and is limited to service definitions — not runtime options. Design your own config file for defaults (type, factor, barHeight, disk) and pass per-call overrides.
Extension points are thin. In Symfony you'd override the service definition. In Laravel, define an interface (BarcodeGenerator) and bind your implementation — that's the seam that lets you swap to picqer/php-barcode-generator or milon/barcode later without touching callers. Given the maintenance status of this package, having that seam is the single most valuable design decision here.
How can I help you explore Laravel packages today?