webit/gls-bundle
A Symfony bundle that integrates the GLS courier API into your application, letting you create shipments, generate parcel labels, and track delivery status directly from your existing Symfony services and configuration.
Install into a Laravel app. This is a Symfony bundle, not a Laravel package — there is no service provider and no auto-discovery, so Composer will simply drop the library into vendor/webit/gls-bundle and nothing else happens.
composer require webit/gls-bundle
Look at the bundle internals first. Open vendor/webit/gls-bundle/ and read, in this order:
DependencyInjection/Configuration.php — the real config keys, which are Symfony-style (webservice: { username, password, ... }) and not Laravel-friendly.Resources/config/services.xml (or services.yml) — gives you the service IDs and constructor arguments you'll need to hand-wire in Laravel.Resources/config/ for any WSDL paths — the GLS API is SOAP-based (ADE for labels, Track & Trace for status), and the WSDL file is usually referenced from service definitions.composer.json for the Symfony/PSR-7 versions it pins. If it requires old Symfony DI components, be ready to add those explicitly.First use case: Track & Trace. It's the lowest-risk entry point — read-only, one call, no label files. Manually construct the tracker service in a tinker session with your GLS credentials to confirm connectivity before wiring anything into HTTP routes.
Build a thin Laravel wrapper. Because there's no service provider, your first real task is a small GlsServiceProvider that reads config/gls.php, builds the underlying clients with the same arguments the bundle's XML uses, and binds them as singletons. Everything else in your app should depend on that binding, never on the bundle classes directly.
Config + provider bridge. Mirror the bundle's configuration keys in a flat config/gls.php and map them into the constructor arguments manually:
// config/gls.php
return [
'username' => env('GLS_USERNAME'),
'password' => env('GLS_PASSWORD'),
'api_url' => env('GLS_API_URL', 'https://api.mygls.hu/...'),
'wsdl' => storage_path('gls/ade.wsdl'),
];
// app/Providers/GlsServiceProvider.php
public function register(): void
{
$this->app->singleton(TrackTraceClient::class, fn ($app) => new TrackTraceClient(
config('gls.username'),
config('gls.password'),
config('gls.api_url'),
));
}
Facade for ergonomics. A Gls facade over the container bindings keeps controllers readable (Gls::track($parcelNumber)), and gives you one place to swap the implementation later.
Domain value objects around the SDK. GLS payloads are verbose arrays/objects with Hungarian/English field naming and lots of optional keys. Map your own Shipment/Parcel models to the SDK's request objects in a dedicated GlsPayloadMapper class. This is the single biggest quality lever — it isolates the unmaintained dependency to one file.
Label generation as a queued job. Creating an ADE parcel involves a SOAP call plus a PDF label download (usually from a returned URL or a base64 field). Push it onto a queue:
class CreateGlsLabel implements ShouldQueue
{
public function handle(GlsLabelService $gls): void
{
$label = $gls->create($this->shipment); // throws on failure
$this->shipment->attachLabel($label);
}
}
Retry with a low $tries (2–3) — GLS parcel-number and printer-resolution mismatches don't fix themselves on retry, so log the raw SOAP fault.
Status sync as a scheduled command. Poll Track & Trace for open shipments on a schedule (->dailyAt('07:00')) and translate the returned status codes into your own enum. GLS status codes are numeric and undocumented in the bundle; keep a translation map in config/gls_statuses.php.
Test seam. Since the bundle's classes are hard to mock (final/static-heavy SOAP), bind an interface (GlsClientContract) and provide a FakeGlsClient for tests. Never let feature tests hit the real SOAP endpoint.
Symfony DI leakage. Service definitions reference Symfony service IDs and may rely on %parameter% placeholders and service_container injection. You cannot app()->make(SomeGlsClass::class) and expect autowiring to work — you must pass every constructor argument yourself by reading the XML.
No auto-discovery, no publishable config. You're re-creating Configuration.php by hand in config/gls.php. Keep the key names identical to the bundle's to make future upgrades easier to diff.
Version pins. This package is old (Symfony 2 era). Expect it to require symfony/dependency-injection and symfony/config at versions that conflict with modern Laravel. Prefer composer require --ignore-platform-reqs only as a last resort; if the conflict is hard, install the bundle's HTTP/SOAP logic classes without the DI layer by autoloading them directly.
WSDL file path. The GLS WSDL is often shipped inside the bundle or expected at a configured path. If you get "failed to load WSDL", the config path resolved relative to Symfony's cache dir — use an absolute path from Laravel.
SOAP faults don't throw typed exceptions you can catch cleanly. SoapFault messages from GLS are often terse ("Invalid parcel number"). Wrap every call in a service that catches \SoapFault, logs $fault->getMessage() plus the raw request payload, and rethrows your own GlsException.
Debugging. Enable SOAP trace and dump __getLastRequest() / __getLastResponse() — either in the client options or via a monolog handler around the client. Log full XML to storage/logs/gls.log with rotation; it's the only way to debug field-mismatch failures.
Caching and rate limits. Bind clients as singletons (the SOAP client object is expensive to build and keeps an HTTP connection). GLS also throttles Track & Trace — batch parcel numbers where the API supports it rather than looping one call per parcel.
Print/format quirks. ADE label calls usually take printer type and label size. Default them in your mapper; the SDK's defaults often don't match what your warehouse printer expects, producing unreadable 4×5 labels.
Extension points. If you need custom logging or transport, look for the bundle's logger/transport interfaces in Resources/config/services.xml — they're typically tagged services you can replace with your own class implementing the same interface. That's the safest way to extend without forking.
Maintenance risk. One star, zero dependents, Symfony 2 lineage — treat it as vendored code. Wrap it fully behind your own GlsClientContract, pin the exact version in composer.json, and consider copying the handful of request-builder classes into app/Services/Gls/ so you're not blocked when the package inevitably stops installing.
How can I help you explore Laravel packages today?