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

Barcode Laravel Package

baks-dev/barcode

BaksDev Barcode is a PHP 8.4+ module for generating and reading barcodes, supporting Aztec, Code39/93/128, DataMatrix, EAN-8/13, ITF, PDF417, QR Code, UPC-A and UPC-E. Install via composer require baks-dev/barcode; requires ImageMagick and related extensions.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you composer require: despite the Laravel-shaped assessment that floats around, baks-dev/barcode ships no documented Laravel service provider, facade, or config file. The README is a Russian-language module doc for the BaksDev ecosystem (Symfony-oriented) that exposes three CLI executables:

  • vendor/baks-dev/barcode/Writer/Generate
  • vendor/baks-dev/barcode/Reader/Decode
  • vendor/baks-dev/barcode/Pdf/PdfCropImg

Your integration point as a Laravel developer is calling those binaries, not BarcodeGenerator::generate().

Minimal steps

  1. Check your PHP version first.

    php -v   # must be >= 8.4
    

    If you're on 8.2/8.3 (the realistic state of most Laravel 10/11 apps), stop here — this package will not install. Options are upgrading, or picking endroid/qr-code for QR-only work.

  2. Install the package.

    composer require baks-dev/barcode
    
  3. Install the system dependencies (Linux/Debian-style):

    sudo apt-get install imagick php-imagick librsvg2-dev librsvg2-bin libcairo2-dev
    sudo apt install libpoppler-glib-dev
    

    Verify Imagick is actually loaded by PHP-FPM and the CLI SAPI:

    php -m | grep imagick
    
  4. Make the three binaries executable — these are not chmodded by Composer:

    chmod +x vendor/baks-dev/barcode/Writer/Generate \
             vendor/baks-dev/barcode/Reader/Decode \
             vendor/baks-dev/barcode/Pdf/PdfCropImg
    
  5. Confirm auto-discovery didn't happen. Inspect the installed composer.json:

    cat vendor/baks-dev/barcode/composer.json | grep -A5 extra
    

    If there's no laravel.providers key, you're wrapping it yourself (see next section).

Where to look first

  • vendor/baks-dev/barcode/Writer/Generate — open it and read the argument handling. This is your only source of truth for the CLI signature, since the README doesn't document it.
  • vendor/baks-dev/barcode/Reader/Decode — same, for the decode side.
  • The test group: php bin/phpunit --group=barcode only works inside the BaksDev monorepo, not inside your app.

First use case: a barcode image endpoint

Given the package's CLI shape, the fastest day-one win is a route that renders a barcode on demand. Before writing the controller, run the binary once by hand to learn its arguments:

./vendor/baks-dev/barcode/Writer/Generate --help

Then wrap it:

// app/Services/BarcodeService.php
namespace App\Services;

use Symfony\Component\Process\Process;

class BarcodeService
{
    public function generate(string $type, string $data, string $outPath): void
    {
        $bin = base_path('vendor/baks-dev/barcode/Writer/Generate');

        $process = new Process([$bin, $type, $data, $outPath]);
        $process->setTimeout(15);
        $process->mustRun();   // throws ProcessFailedException on non-zero exit
    }
}
// app/Http/Controllers/BarcodeController.php
public function show(string $type, string $sku, BarcodeService $barcode)
{
    $path = storage_path('app/barcodes/'.md5($type.$sku).'.png');

    if (! file_exists($path)) {
        $barcode->generate($type, $sku, $path);   // e.g. 'EAN-13', '4006381333931'
    }

    return response()->file($path, [
        'Content-Type'  => 'image/png',
        'Cache-Control' => 'public, max-age=86400',
    ]);
}

Because the exact flag names aren't published, treat the first manual invocation as your integration test — capture the output and bake the real signature into the service.


Implementation Patterns

Centralise the shell-out behind one service

Don't scatter exec() calls through controllers. A single BarcodeService gives you a seam to swap in endroid/qr-code later (see Gotchas), and a place to standardise timeouts, temp dirs, and error handling.

final class BarcodeService
{
    public function __construct(private array $config) {}

    public function generateToDisk(string $type, string $data, string $diskPath): string
    {
        Storage::makeDirectory(dirname($diskPath));

        $tmp = tempnam(sys_get_temp_dir(), 'bc_').'.png';

        (new Process([base_path('vendor/baks-dev/barcode/Writer/Generate'), $type, $data, $tmp]))
            ->setTimeout(15)
            ->mustRun();

        Storage::put($diskPath, file_get_contents($tmp));
        @unlink($tmp);

        return $diskPath;
    }

    public function decode(string $absolutePath): string
    {
        $out = (new Process([base_path('vendor/baks-dev/barcode/Reader/Decode'), $absolutePath]))
            ->setTimeout(20)
            ->mustRun()
            ->getOutput();

        return trim($out);
    }
}

Cache by payload, not by request

Barcode output is deterministic for a given (type, data) pair. Cache the file path, not the request:

public function generateCached(string $type, string $data): string
{
    $path = 'barcodes/'.sha1($type.'|'.$data).'.png';

    if (! Storage::exists($path)) {
        $this->generateToDisk($type, $data, $path);
    }

    return $path;
}

This is the single biggest performance lever: rendering a PDF417 through ImageMagick costs tens to hundreds of milliseconds and real CPU, and a catalogue of 50k SKUs regenerated per request will flatten a box.

Queue bulk label runs

Bulk generation belongs in a job, not a request. A 5k-label print run will exceed PHP's max_execution_time long before it finishes:

final class GenerateLabelBatch implements ShouldQueue
{
    public function __construct(public array $skus) {}

    public function handle(BarcodeService $barcode): void
    {
        foreach ($this->skus as $sku) {
            $barcode->generateCached('EAN-13', $sku);
        }
    }
}

Give these their own queue (->onQueue('barcodes')) so a slow ImageMagick box doesn't stall your mail or webhook workers.

Streaming inline in Blade

Rather than writing to disk for one-off renders, stream from a route and let the browser cache it:

<img src="{{ route('barcode.show', ['type' => 'QRCode', 'sku' => $product->sku]) }}"
     alt="Barcode for {{ $product->sku }}">

Pair with Cache-Control and an ETag derived from the payload hash so repeat scans are free.

Decoding uploaded/scanned images

Store the upload to a temp path first — the Decode binary wants a filesystem path, not a stream:

public function decodeUpload(Request $request, BarcodeService $barcode): JsonResponse
{
    $request->validate(['image' => 'required|image|max:8192']);

    $tmp = $request->file('image')->store('tmp', 'local');
    $abs = Storage::disk('local')->path($tmp);

    try {
        return response()->json(['data' => $barcode->decode($abs)]);
    } finally {
        Storage::disk('local')->delete($tmp);
    }
}

Validate the MIME type and cap filesize before the ImageMagick process touches it — a decompression bomb will otherwise chew through RAM.

PDF workflows: generate → embed, decode → crop

The package is not a PDF generator. The realistic pattern splits into two halves:

  • Embedding: generate a PNG via Writer/Generate, then place it in your PDF with your existing tool (dompdf, Snappy/wkhtmltopdf, or FPDI). PdfCropImg is not an embedding utility.
  • Extracting: PdfCropImg input.pdf output.png renders a PDF page (or region) to an image so the Decode binary can read barcodes out of received shipping docs / invoices.

Wrap the extraction step in a job — poppler rendering is heavy and a batch of inbound PDFs will block a request thread.

Artisan command as the operational handle

Give ops something they can run without a deploy:

protected $signature = 'barcode:warm {file} {--type=EAN-13}';
// reads a CSV of SKUs, calls BarcodeService::generateCached per row

Also useful: a barcode:doctor command that checks php -m | grep imagick, verifies the three binaries are executable, and exits non-zero — runnable in CI and as a deploy smoke test.

Testing: round-trip, not pixel comparison

public function test_generate_then_decode_roundtrips(): void
{
    $path = storage_path('app/testing/rt.png');
    app(BarcodeService::class)->generate('QRCode', 'test-payload', $path);

    $this->assertFileExists($path);
    $this->assertSame('test-payload', app(BarcodeService::class)->decode($path));
}

Skip this test group when Imagick is absent ($this->markTestSkipped()), otherwise CI on a non-imagick runner fails for reasons unrelated to your code.


Gotchas and Tips

PHP 8.4 is a hard floor, and that's the adoption blocker

This is the single most consequential fact about the package. Laravel 10 supports PHP 8.1–8.3, Laravel 11 supports 8.2–8.4. If your app sits on anything below 8.4, Composer will refuse. Don't discover this after adding it to composer.json and wondering why composer update hangs on conflicts. Confirm php -v first, and check whether other packages in your lock file pin PHP below 8.4 — a mixed dependency graph can make the upgrade far more expensive than this package is worth.

chmod +x does not survive composer install

File modes are not preserved by Composer's archive extraction. Every fresh composer install on a new node (including your Docker build) loses the executable bit, and the binaries fail with Permission denied or sh: ...: cannot execute.

Bake it into your image/CI:

RUN chmod +x vendor/baks-dev/barcode/Writer/Generate \
             vendor/baks-dev/barcode/Reader/Decode \
             vendor/baks-dev/barcode/Pdf/PdfCropImg

or add a post-autoload-dump script in your composer.json. If you add it as a Composer script inside the vendor package, the next composer update wipes it.

The CLI signature is undocumented — verify it, don't trust the internet

The README documents formats and system packages, not arguments. Several published examples of this package (facades, PdfBarcode::generate(), ['width' => 300] options) are not backed by the README or any release note and may be hallucinated. Run the binaries with no arguments (or --help) and read the source of Writer/Generate before writing your service. Capture your real invocation in a test so a package update that changes the signature fails loudly rather than silently producing corrupt images.

Never build the command string by concatenation

$data is user-controlled in every realistic case (a SKU, a URL in a QR, a scanned payload). Use Symfony's Process with an argument array — it escapes each argument — rather than exec("$bin $type $data $path"). A crafted payload should not become shell syntax.

Also cap the input length: QR payload limits vary by error-correction level, and PDF417 has its own byte ceiling. Validate against the format before spawning a process, and reject anything containing non-printable bytes for 1D formats.

ImageMagick policy.xml will bite you

Default ImageMagick policies on many distros block PDF/PS decoding entirely:

policy domain="coder" rights="none" pattern="PDF"

Symptoms: PdfCropImg exits non-zero with a policy error that never reaches your logs because you swallowed stderr. When wrapping the process, always capture and log stderr (Symfony's ProcessFailedException carries it — don't catch and rethrow a bare RuntimeException). If you genuinely need PDF cropping, relax the policy in /etc/ImageMagick-6/policy.xml deliberately, and know you've just widened your attack surface for any user-supplied PDF.

Watch memory too: Imagick obeys memory/map limits in the same policy file, and a 300 DPI A4 raster will happily blow past a default 256 MB ceiling and get killed mid-job.

Not serverless, not shared-hosting friendly

The package depends on system binaries and native ImageMagick. That rules out Vercel/Cloudflare-style runtimes and most cPanel/shared hosts. Lambda/Cloud Run works but inflates cold starts (ImageMagick + poppler + exec overhead). If you're on a container platform, the binaries must be present in the runtime stage, not just the build stage.

Extremely low downstream signal — contain the blast radius

Zero stars and zero dependents on a package that shells out to three executables is a maintenance risk, not a style objection. Defend against it:

  • One interface, one implementation. BarCodeRenderer contract in your app, BaksDevRenderer implementation. Nothing else in your codebase references the vendor path.
  • A fallback. endroid/qr-code is pure-PHP, no Imagick, handles QR/easy formats and can serve as the QRCode implementation behind the same interface if the binaries break on a deploy. It won't cover PDF417/EAN — those stay on BaksDev, or you evaluate zint.
  • Pin the version. "baks-dev/barcode": "7.4.8" rather than ^7.4. Release notes for 7.4.6/7.4.7/7.4.8 are literally just "v7.4.8" — you have no changelog to diff against, so an unattended minor bump is an unattended regression.
  • Smoke test in CI. One round-trip generate→decode test per format you actually use, run on a node with Imagick installed. If it goes red after a composer update, you know instantly.

Validate check digits before generating

EAN-13, EAN-8, UPC-A, UPC-E, and ITF all carry a mod-10 check digit. Passing a bad digit to the generator produces either an error from the binary or, worse, a scannable-format image that retail scanners will reject at the till. Compute/verify the checksum in PHP first, and keep a barcode_formats config mapping format → payload regex + length so validation lives in one place:

// config/barcode.php
'EAN-13' => ['length' => 13, 'pattern' => '/^\d{13}$/', 'mod10' => true],
'QRCode' => ['max_bytes' => 2953, 'pattern' => '/^[\s\S]+$/'],

Temp file hygiene

tempnam() returns a path; the binary will overwrite it without complaint, but a failed write leaves junk in /tmp. Use try/finally around the unlink, and give the temp dir an explicit prefix (bc_) so you can rm /tmp/bc_* in a cleanup cron when a long-running worker leaves orphans behind.

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.
ippanelcom/laravel-sdk
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