tuncaybahadir/quar
Qr Code Generator for Laravel (PHP 8.2+, Laravel 10–13) built on BaconQrCode and simple-qrcode. Use the Quar facade to generate QR codes, set size, and force versions 1–40; output renders directly in Blade templates.
Architecture fit. tuncaybahadir/quar is a thin, opinionated fluent builder over simplesoftwareio/simple-qrcode, which in turn wraps bacon/bacon-qr-code. It is Laravel-only (service provider + Quar facade) and exposes a chained configuration API (size, eye, style, color, gradient, merge, withText, version) terminating in generate($payload, $path = null). For a Laravel monolith that needs branded QR assets in Blade views, transactional emails, invoices, or PDF generators, the ergonomics are genuinely better than raw BaconQrCode. There is no queue, storage abstraction, HTTP surface, or persistence layer — it is a pure rendering library, so it slots in as a leaf dependency with no schema or infrastructure implications. That is the extent of the good news: it is a convenience layer, not a platform capability, and the value it adds over simple-qrcode is mostly styling sugar (gradients, eye coloring, captions, logo merging).
Integration feasibility. Low effort, single composer require, no publishable config or migrations documented. The realistic blockers are dependency-level, not code-level:
quar → simple-qrcode → bacon-qr-code) means two transitive constraint sets you do not control. If the application already uses simple-qrcode, endroid/qr-code, or BaconQrCode directly, expect version pinning friction.setFont('/path/to/font.ttf') and merge($path, ...) imply filesystem and font dependencies with no documented defaults or licensing.{{ $qr }} in Blade) is suspect: generate() without a path returns rendered output, and Blade's escaped echo will corrupt binary PNG data. Plan for {!! !!} with an SVG, a base64 data URI, or a persisted file path instead.Technical risk. High for the maturity profile, moderate for the code itself.
composer.json constraints, not aspirational README badges — over-broad constraints (* or ^13.0 on unreleased majors) are a common source of future install failures.merge() logo or color() from request A bleeding into request B). No documentation addresses reset semantics.margin(1) or higher "due to a bug in the BaconQrCode package." That is an upstream coupling and a silent-failure class you would own operationally.Key questions.
composer.json constraints for laravel/framework, php, simple-qrcode, and bacon/bacon-qr-code, and do they conflict with our lockfile?generate(), and is it safe under Octane / Horizon workers? (Test with a logo-merge call followed by a plain call.)generate() return a Laravel response, a raw string, or a binary blob — and what is the sanctioned Blade rendering path?margin >= 1 logo bug patched upstream or only documented as a caller-side workaround, and what breaks silently if it regresses?withText(), and what are their licenses?size(500) + version(40) + merge() + gradient() — is per-request generation viable, or must it be cached?Stack fit. Clean only if the target is exclusively Laravel 10–12 on PHP 8.2–8.4 with GD available. It is not viable for framework-agnostic services, non-Laravel workers, or a shared internal library consumed by Symfony/Slim components. Where branding fidelity is cosmetic rather than contractual, simple-qrcode or endroid/qr-code used directly is a lower-risk equivalent with far more adoption; where styling (gradients, custom eyes, captions, embedded logos) is a real product requirement, quar is the shortest path.
Migration path. Treat it as an optional rendering dependency behind an internal seam:
QrRenderer::render(QrSpec): string) and one QuarRenderer adapter.use tbQuar\Facades\Quar outside the adapter class.quar is already in use, the swap is mechanical: the fluent chain maps closely onto simple-qrcode's API (the primary upstream), so a fallback adapter is a one-file change."tuncaybahadir/quar": "1.8.*") and commit the lockfile; do not float the constraint.Compatibility. Confirm GD vs Imagick availability in every runtime image (web, queue, CI, local Docker). Confirm storage/app/public permissions and the storage:link prerequisite if you follow the README's file-persistence pattern — and replace its md5(random_int(...).date(...)) filenames with a deterministic, collision-safe scheme. Verify Blade rendering explicitly rather than trusting the README snippet.
Sequencing. (a) Spike: install in a branch, exercise every README feature used by the product, capture rendered output, and stress the Octane/queue state-leak question. (b) Adapter + tests with golden images. (c) Roll out to a single low-risk surface (invoice footer, internal admin asset). (d) Expand to customer-facing surfaces only after a cache layer exists. (e) Schedule a quarterly re-evaluation against endroid/qr-code and simple-qrcode — the switching cost is intentionally kept near zero.
Maintenance. Effectively self-maintenance. One author, no dependents, no visible CI or test suite, and a changelog whose most recent entry is a README edit. Budget for reading the source when behavior surprises you, for tracking two upstream projects, and for owning the fork if the maintainer stops. Add composer audit and Dependabot/Renovate to CI, and treat any new release as unreviewed until you have diffed it.
Support. No vendor, no SLA, no security process documented. Internal support falls to whoever integrated it. Escalation path is a GitHub issue on a repo with 80 stars — treat response time as unbounded. Document the margin(1) logo workaround in your own runbook so it is not rediscovered in production.
Scaling. Rendering is CPU- and memory-bound (raster compositing at size(300–500), gradients, logo alpha blending). Do not generate per request at volume: generate at write time, persist to object storage, and serve the URL; key the artifact by a hash of the full spec so styling changes invalidate correctly. Cache hit rates will be near-total for invoice/asset use cases, which makes this a non-issue if designed correctly and a p95 latency problem if not. Watch memory limits on small containers for size >= 500 with merge().
Failure modes.
WriterException when content exceeds a forced version() — a content-length-dependent 500 unless caught and mapped to a validation error.storage/.Ramp-up. Very low for a Laravel developer — the README is example-dense and the chain reads naturally. The cost is not learning the API but learning its edges: reset semantics, escaped-output pitfalls, the logo/margin constraint, and the styling-value caps (0.9 for square/dot/round, 0.5 for star/vertigo). Expect half a day to productive use and roughly a day of hardening to production-safe, dominated by caching and state-isolation work rather than by the library itself.
How can I help you explore Laravel packages today?