waleedhu / laravel-hl7
Hospital HL7v2 feeds as native Laravel events: MLLP listener, typed events, honest ACKs, test fakes.
Requires
- php: ^8.2
- aranyasen/hl7: ^3.1
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/events: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
README
Hospital HL7v2 feeds as native Laravel events.
Hospitals broadcast everything that happens — admissions, discharges, lab results, appointments — as HL7v2 messages over MLLP. This package gives that stream the Laravel treatment: run one artisan command, listen for typed events, and let the acknowledgement the hospital engine receives reflect what your application actually did.
composer require waleedhu/laravel-hl7
Requires PHP 8.2+ and Laravel 11, 12 or 13.
Listening
php artisan hl7:listen # binds hl7.host:hl7.port (default 0.0.0.0:2575)
php artisan hl7:listen --port=6000
Point the hospital integration engine (Mirth, Rhapsody, Cloverleaf, ...) at that address and handle messages the way you handle any Laravel event:
use WaleedHu\Hl7\Events\PatientAdmitted; Event::listen(function (PatientAdmitted $event) { Patient::updateOrCreate( ['mrn' => $event->patientId()], ['name' => $event->patientName(), 'ward' => $event->assignedLocation()], ); });
Events
| MSH-9 | Event | Readers |
|---|---|---|
ADT^A01 |
PatientAdmitted |
patientId(), patientName(), assignedLocation() |
ADT^A02 |
PatientTransferred |
same as above |
ADT^A03 |
PatientDischarged |
same as above |
ADT^A08 |
PatientUpdated |
same as above |
ORU^R01 |
LabResultReceived |
patient readers + observations() |
SIU^S12 |
AppointmentScheduled |
patient readers |
| anything else | Hl7MessageReceived |
— |
No message is ever dropped: unmapped types dispatch the fallback event. Every event exposes message() (the parsed Aranyasen\HL7\Message), raw() and type(), so nothing is ever out of reach. Readers return null rather than throwing when a segment or field is absent.
observations() returns one entry per OBX segment:
[
['id' => 'HGB', 'label' => 'Hemoglobin', 'value' => '10.1', 'units' => 'g/dL', 'range' => '13.5-17.5', 'flag' => 'L'],
]
Map your own types (or override the defaults) in config/hl7.php:
php artisan vendor:publish --tag=hl7-config
'events' => [ 'ADT^A01' => \App\Events\Admission::class, // override 'MDM^T02' => \App\Events\DocumentNotified::class, // extend ],
Custom events extend WaleedHu\Hl7\Events\Hl7Event and receive the parsed message.
Acknowledgements are honest
The ACK returned to the engine is tied to what your application did with the message:
| Outcome | MSA-1 | Engine behaviour |
|---|---|---|
| Event dispatched, all listeners succeeded | AA |
done |
| A listener threw | AR |
engine re-sends later |
| Message could not be parsed | AE |
flagged as error |
The listener never dies on a bad message, and MSA-2 always echoes the inbound control ID so engines can correlate.
Testing your integration
No sockets needed — Hl7::receive() pushes a raw message through the exact pipeline the listener uses:
use WaleedHu\Hl7\Facades\Hl7; use WaleedHu\Hl7\Testing\Samples; Hl7::fake(); Event::fake(); Hl7::receive(Samples::adtA01(patientId: '12345', patientName: 'DOE^JANE')); Hl7::assertReceived(PatientAdmitted::class, fn ($e) => $e->patientId() === '12345'); Hl7::assertAcked('AA'); Event::assertDispatched(PatientAdmitted::class);
Samples ships realistic, spec-shaped messages for every supported type — because getting real test messages out of a hospital is the hard part.
Out of scope (for now)
Outbound sending and message building, message persistence and retries, HL7v3 and FHIR. Parsing is delegated to the excellent aranyasen/hl7.
License
MIT.