keesschepers/dhlparcelapibundle
A Symfony bundle for integrating the DHL Parcel API into PHP applications. It helps configure DHL Parcel credentials and access shipping services such as creating shipments, generating labels, and tracking parcels from within Symfony.
Decide the mode before you type composer require. This package is a Symfony 2 bundle, not a Laravel library. In a Laravel app there is no supported install path — the bundle's *Bundle class, DI extension, and XML/YAML service wiring are not consumed by Laravel's container. Treat this package as reference source code, not a dependency. Concretely: clone the repo and read it; do not add it to composer.json.
Reading order (this is the "getting started" that actually pays off):
DependencyInjection/ — the configuration tree. This tells you every credential and environment option the DHL client needs (API key/secret, test vs production base URL, account/customer IDs). That config tree is your config/dhlparcel.php spec.Resources/config/*.xml|yml — the service definitions. This shows which class wraps the HTTP client and how it's constructed. That class is the only thing worth porting.*Bundle.php and event subscribers — ignore these. They are Symfony glue with zero value in Laravel.If you are on a legacy Symfony 2.x app (the only context where this installs), the minimal steps are: register the bundle in AppKernel.php, add the dhl_parcel_api config block with sandbox credentials, inject the service into a controller via the service container, and call it directly. That's the intended happy path.
First use case: manual label creation in an internal admin tool. Not checkout, not customer-facing tracking. Generate a single label for an order from an admin screen, print it, and inspect the raw response. That gives you your first recorded sandbox fixture and confirms whether the DHL contract this bundle targets is still accepted.
Before writing any code, open the DHL Parcel Benelux developer documentation and diff it against what you found in step 3. DHL moved from older SOAP/XML interfaces toward REST/JSON; if the bundle targets the old contract, your real starting point is the official docs, and this repo is just a shape reference.
Extract-then-wrap (the primary pattern). Read the bundle's request builders and response parsers and write them down as a contract — endpoint paths, auth headers, payload fields, error codes. Then build a Laravel-native client against that contract:
// config/dhlparcel.php
return [
'base_url' => env('DHL_PARCEL_BASE_URL', 'https://sandbox...'),
'api_key' => env('DHL_PARCEL_API_KEY'),
'api_secret'=> env('DHL_PARCEL_API_SECRET'),
'account_id'=> env('DHL_PARCEL_ACCOUNT_ID'),
];
// app/Services/Shipping/DhlParcelClient.php
class DhlParcelClient
{
public function __construct(private array $config) {}
public function createLabel(LabelRequest $request): LabelResponse
{
$response = Http::withBasicAuth($this->config['api_key'], $this->config['api_secret'])
->baseUrl($this->config['base_url'])
->acceptJson()
->post('/shipments', $request->toArray());
$response->throw();
return LabelResponse::fromArray($response->json());
}
}
Bind it in a service provider, keep controllers thin, and put all DHL logic behind one class.
Interface boundary. Wrap the client behind a ShippingProvider interface with a DhlParcelAdapter. This contains DHL-specific logic to one file and makes a later switch to a maintained SDK or an aggregator a one-line binding change, not a refactor.
Sandbox-first contract tests. Before wiring to fulfilment, record real sandbox responses and assert your parsers handle them. The bundle gives you the shape; only the sandbox confirms it's current.
Asynchronous label generation. Label creation is often a synchronous step in fulfilment. Put it on a queued job with retry and backoff, and enforce idempotency so a retry doesn't produce a duplicate shipment and duplicate carrier charge.
Legacy Symfony pattern. If you're genuinely in Symfony 2.x: inject the bundle's service into controllers, keep the config tree per environment (test vs production), and do not let the API client leak into domain code — same interface boundary applies.
composer require pulls Symfony 2-era constraints (PHP 5.x/7.0-era Guzzle, PHPUnit) that conflict with any modern app. This is a dead end that eats time — don't start there.How can I help you explore Laravel packages today?