webit/gls-ade
Laravel package for GLS ADE parcel shipping integration. Create shipments, generate labels, manage pickup requests, and retrieve tracking data through a clean PHP API with configurable credentials and straightforward service classes.
This is a standalone PHP library (not a Laravel package — no service provider, no config file, no facades). The first step is recognizing that you're wrapping it yourself.
composer require webit/gls-ade
Add credentials to config/services.php (or a dedicated config/gls.php):
'gls' => [
'username' => env('GLS_API_USERNAME'),
'password' => env('GLS_API_PASSWORD'),
'sandbox' => env('GLS_SANDBOX', true),
],
Bind a thin wrapper in AppServiceProvider:
$this->app->singleton(GlsClient::class, fn () => new GlsClient(
config('services.gls.username'),
config('services.gls.password'),
config('services.gls.sandbox'),
));
Where to look first: the Webit\GlsAde\Model\* namespace. The Parcel, Pickup, Address, Service, ParcelStatus, and ParcelIdent classes are the value objects you actually build against. The Webit\GlsAde\Api\* interfaces are the operations you call.
First use case — the 20% that covers 80% of the package:
Parcel object (consignee address, weight, service codes, reference).addParcel() / addParcels().getPrintedLabels() to fetch base64-encoded PDF and save it.The GLS ADE protocol is SOAP-based under the hood; the package wraps the WSDL with its own API classes. Don't try to "just curl" it — use the provided Api interfaces.
Session-first, everything-else-second. GLS ADE requires authentication to obtain a session token before any parcel call. Every operation on the API is effectively session-scoped. In practice:
$session = $this->gls->session()->authenticate(); // returns Session
// pass $session or let the client hold it internally, depending on version
Cache the session in your cache store (short TTL) if you're making many calls in a batch job — GLS rate-limits repeated auth calls and login failures have lockout behaviour.
Build a domain adapter. Do not scatter new Parcel(...) through controllers. Have a ShipmentService or LabelService that takes your Order model and produces a Parcel. This is the extension point — your order → parcel mapping is where all the business logic (weight defaults, insurance threshold, service selection by destination) lives.
public function toParcel(Order $order): Parcel
{
return (new Parcel())
->setReference($order->number)
->setConsignee($this->toConsignee($order->shippingAddress))
->setWeight($order->weight_grams / 1000)
->setServices([Service::SERVICE_COD, ...]);
}
Labels: decode and store, don't stream directly.
$pdf = base64_decode($response->getContent());
Storage::disk('labels')->put("{$order->number}.pdf", $pdf);
Store the PDF in object storage and link it back to the order — regenerating labels later is possible but the API is slow and unreliable, so persist on first generation.
Status tracking via queued job. getParcelStatuses($ident) accepts an array of parcels; use a scheduled command every 30–60 minutes that polls open shipments, maps GLS ParcelStatus codes to your own enum, and dispatches notifications on transitions. Never poll synchronously in a user request — you'll block for seconds per call.
Pickups are a separate booking object. A Pickup is not a parcel — it's a truck-pickup request with a date, window, and list of parcels. Usually booked once per day in a cron at ~15:00 for the next day's collection. The addPickup() call takes a DateTime and a Service collection; get the date format wrong and it fails silently with a cryptic code.
Testing seam. Since the library talks SOAP, don't hit sandbox in PHPUnit. Wrap the Api interface in your own interface (GlsGateway) and mock that. The package's API interfaces are clean enough that you can implement a fake without much trouble.
Country and service codes are string enums, not constants you can use. You'll write raw strings like 'PL', 'D' (service flags), 'COD'. Centralize them in your own GlsConstant class — the library's value objects don't validate them, so a typo only surfaces as a rejected parcel at runtime.
Weight and dimensions are in kilograms and centimeters. If your order table stores grams, you will ship 100× too heavy parcels the first time. Put the division inside your toParcel() mapper and never at the call site.
The base64 PDF is not always a PDF. On error, GLS sometimes returns a base64 blob that decodes to a .txt or empty string rather than throwing. Validate with substr($pdf, 0, 4) === '%PDF' before storing.
Error handling is code-based, not exception-based. Most failures come back as a result object with an error code; the library may or may not throw depending on version. Check the response object explicitly — do not rely on try/catch alone. Log the raw code; GLS's own error catalogue is not distributed with the package.
Reference number is your idempotency key. GLS uses the parcel reference to deduplicate in some flows (particularly addParcelsToPickup). Don't regenerate references on retry, or you'll create duplicate parcels in the GLS system. Always use $order->number or a stable UUID.
Date/time is Polish local time (Europe/Warsaw). Pickup dates get parsed server-side; if you send UTC you can be a day off around midnight. Use Carbon::now('Europe/Warsaw') for pickup date construction.
Sandbox is not a full mirror. Not every service code is accepted in test mode, and printed labels may be watermarked stubs. Don't gate your integration tests on "sandbox accepted it" — validate the request object shape in unit tests instead.
Extension points. The ApiFactory/client constructors often accept an injected HTTP/SOAP client. If you need request logging or retry, supply your own client or decorate the API interface. There's no LoggerAwareInterface — you build observability yourself.
Version pinning matters. 4 stars, 0 dependents, low score: this is a small library maintained by a single author. Pin the exact version in composer.json ("webit/gls-ade": "x.y.z"), commit composer.lock, and vendor the label PDF logic in your own tests. Do not ^ it — a breaking change in a minor release is plausible and there's no upgrade guide.
Documentation is in Polish. The GLS ADE specification (which the package mirrors) is Polish; expect to read the WSDL and the Model source to understand field semantics. Budget half a day for the first parcel, not an hour.
How can I help you explore Laravel packages today?