mapo-89 / laravel-qr-decoder
A cross‑platform Laravel package for decoding QR codes from PNG images using Python
Package info
github.com/mapo-89/laravel-qr-decoder
Language:JavaScript
pkg:composer/mapo-89/laravel-qr-decoder
Requires
- php: ^8.2
- illuminate/support: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.93.0
- symfony/process: ^7.4|^8.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-30 21:27:13 UTC
README
A cross‑platform Laravel package for decoding QR codes from PNG, JPEG and WebP images using Python (zxing-cpp).
This package wraps a Python decoder inside a virtual environment and exposes a clean Laravel API. It works on Windows and Linux. Laravel launches the virtual environment’s Python interpreter directly; optional wrapper scripts are available for command-line use.
Features
- ✅ Cross‑platform (Windows & Linux)
- ✅ Python isolated via
venv - ✅ No global Python dependencies
- ✅ Secure execution via Symfony Process
- ✅ Works with logos / low‑contrast QR codes
Requirements
- PHP >= 8.2
- Laravel 12 or 13
- Python >= 3.12
Version 3 requires Python 3.12 or newer and uses NumPy 2.5.4.
When upgrading from version 2, recreate any virtual environment created with
Python 3.11 using Python 3.12 or newer, then rerun php artisan qr-decoder:install.
Set QR_DECODER_PYTHON_BINARY to the newer interpreter before creating the environment.
Installation
Install the package (local / path repository)
// composer.json { "repositories": [ { "type": "path", "url": "packages/laravel-qr-decoder" } ] }
composer require mapo-89/laravel-qr-decoder
Publish config
php artisan vendor:publish --tag=qr-decoder-config
The legacy --tag=config command remains supported.
Configure Python path
Edit config/qr-decoder.php:
return [ 'python_path' => env('QR_DECODER_PYTHON_PATH', base_path('python/qr-decoder')), 'python_binary' => env('QR_DECODER_PYTHON_BINARY', PHP_OS_FAMILY === 'Windows' ? 'python' : 'python3'), 'timeout' => (int) env('QR_DECODER_TIMEOUT', 10), 'install_timeout' => (int) env('QR_DECODER_INSTALL_TIMEOUT', 300), ];
The environment variables are optional:
| Variable | Default | Purpose |
|---|---|---|
QR_DECODER_PYTHON_PATH |
<application>/python/qr-decoder |
Parent directory containing venv; an existing virtual environment directory is also accepted. |
QR_DECODER_PYTHON_BINARY |
python3 on Linux, python on Windows |
Python >= 3.12 executable used to create the environment. Use an executable path without command-line arguments. |
QR_DECODER_TIMEOUT |
10 |
Timeout in seconds for one decode operation. |
QR_DECODER_INSTALL_TIMEOUT |
300 |
Timeout in seconds for each installation step. |
After changing environment variables in a deployment using Laravel's configuration
cache, rebuild it with php artisan config:cache.
Run the installer (recommended)
php artisan qr-decoder:install
This command will:
- Create a Python virtual environment in the configured path
- Install all required Python dependencies
- Work on Windows and Linux
Usage
This package does not provide controllers or views.
It exposes a single service (QrDecoder) that can be used inside your application’s controllers, APIs, jobs or commands.
Example Use Cases
- Decode an already uploaded file on button click:
use Mapo89\QrDecoder\QrDecoder; $result = app(QrDecoder::class)->decode( storage_path('app/qr_uploaded_file.png') );
- Upload a file and decode immediately:
public function uploadAndDecode(Request $request, QrDecoder $decoder) { $request->validate([ 'qrpng' => ['required', 'file', 'mimes:png', 'max:5120'], ]); // Store temporarily $path = $request->file('qrpng')->store('qr-decoder'); $fullPath = storage_path('app/private/' . $path); try { $result = $decoder->decode($fullPath); } catch (\Mapo89\QrDecoder\QrDecoderException $e) { $result = null; // Handle error if needed } finally { if (file_exists($fullPath)) { @unlink($fullPath); } } return response()->json(['result' => $result]); }
Note: The controller should live in your application, not in this package. If you need a UI, build it in your application or in a separate package that depends on this one.
Results and errors
decode() returns the QR text exactly as decoded, including leading and trailing
spaces, tabs and newlines. An image without a QR code returns an empty string.
Images with different QR payloads are rejected, including payloads that differ
only in whitespace. Repeated copies of the same payload are accepted.
PNG transparency is composed onto a white background. Images are limited to 16,777,216 pixels and resized to at most 2048 × 2048 before detection.
Failures throw Mapo89\QrDecoder\QrDecoderException, which extends
RuntimeException. Its public reason property identifies the cause:
| Reason | Meaning |
|---|---|
unreadable_image |
The image path is missing or unreadable. |
missing_environment |
Run php artisan qr-decoder:install. |
invalid_configuration |
The configured Python path is empty or invalid. |
timeout |
Decoding exceeded the configured timeout. |
invalid_image |
The image is invalid or unsupported. |
image_too_large |
The image exceeds the pixel limit. |
ambiguous_image |
More than one distinct QR payload was detected. |
invalid_response |
Python returned an unexpected response. |
process_failed |
The process could not start or another decoder error occurred. |
Exception messages contain fixed descriptions, without decoded content or raw process output. The installer reports environment creation, dependency installation and timeout failures separately and returns a nonzero exit code.
Dependency Injection
public function decode(QrDecoder $decoder) { return $decoder->decode($path); }
Configuration
return [ 'python_path' => env('QR_DECODER_PYTHON_PATH', base_path('python/qr-decoder')), 'python_binary' => env('QR_DECODER_PYTHON_BINARY', PHP_OS_FAMILY === 'Windows' ? 'python' : 'python3'), 'timeout' => (int) env('QR_DECODER_TIMEOUT', 10), 'install_timeout' => (int) env('QR_DECODER_INSTALL_TIMEOUT', 300), ];
Directory Structure
laravel-qr-decoder/
├── src/
│ ├── QrDecoder.php
│ ├── QrDecoderFacade.php
│ └── QrDecoderServiceProvider.php
│
├── python/
│ ├── decode_qr.py
│ ├── requirements.txt
│ ├── run_qr_decoder
│ ├── run_qr_decoder.bat
│ └── venv/
│
├── config/
│ └── qr-decoder.php
│
├── composer.json
└── README.md
Testing
Install the development dependencies:
composer install
Create the Python environment so both PHP integration tests and Python tests can decode real images:
python3 -m venv python/venv python/venv/bin/python -m pip install -r python/requirements.txt python/venv/bin/python -m unittest discover -s python -p 'test_*.py' -v composer test
On Windows, use python to create the environment and
python/venv/Scripts/python.exe for the remaining commands. CI runs these tests
on Linux and Windows with Python 3.12 and 3.14. PHP tests use PHPUnit 11, 12 or 13
as supported by the selected PHP and Laravel versions. The PHP CI jobs also install
Python 3.12 and exercise Laravel → Python → QR decoding and both wrapper path
layouts. Locally, real-image PHP tests skip when no virtual environment is present;
set QR_DECODER_TEST_VENV to an existing environment directory to use a different
location. A present but broken environment fails the tests.
The service provider uses Spatie Laravel Package Tools
to register configuration and the qr-decoder:install command. Laravel discovers the
provider and facade automatically. The integration tests cover configuration merging,
both config publish tags, command registration, and the shared decoder instance.
Optional command-line wrappers
Laravel calls Python directly. For standalone use, set QR_DECODER_PYTHON_PATH
to the same parent directory or existing virtual environment directory used by
Laravel, then run:
bash python/run_qr_decoder /path/to/image.png
On Windows:
python\run_qr_decoder.bat "C:\path to\image.png"
Without the environment variable, both wrappers look for python/venv next to
the decoder script. They print UTF-8 text followed by a newline. Add --json
before the image path for structured output: {"text": "..."} on success or
{"error": "invalid_image"} on failure. An empty image result is {"text": ""}.
The exit code is zero on success and nonzero on failure. PHP uses this JSON mode
to preserve payload boundaries.
Changelog
Please see CHANGELOG for more information what has changed recently.
GitHub release notes are generated from categorized commit messages using the same
convention as core-panel-monorepo. See the commit and release guide
for the supported prefixes, release triggers, and local preview command.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email info@postler.de instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.
Package tooling
Package configuration and command registration are managed by Spatie Laravel Package Tools.
Shared release workflow
Commit types, explicit release triggers, and grouped release notes follow the shared release standard. Ordinary pushes do not publish production versions. Existing builds, artifact handling, and deployment approvals remain project-specific.
Release branch workflow
Feature branches start from main and are squash-merged into release/X.Y.Z.
Alternatively or additionally, commit directly on release/X.Y.Z; these commits
remain separate and do not require squashing. Ordinary commits do not rewrite
version metadata or CHANGELOG.md. Once the release scope is complete, run
node .release/branches.mjs prepare once to collect all changes on that branch.
If more commits follow, refresh preparation and checks together at finalization,
rather than after each commit. Then merge the prepared release into main
through a reviewed PR using a merge commit titled 🚀 Release: X.Y.Z.
Direct pushes/local merges to main and administrator bypasses are forbidden.
Publish the tested merge commit, then remove the fully integrated release branch
after successful publication and verification. Create a new branch per version.
The prepared changelog supplies the published release notes; no post-release
changelog commit is created. See .release/README.md.
Automated dependency maintenance
Dependabot proposes weekly dependency updates. Reviewed updates are retargeted to the active release branch, tested, and squash-merged through their PRs. See .release/README.md for the policy and activation requirements. Dependency PRs never authorize a release.