omobude/dhl-symfony-bundle
Composer 安装命令:
composer require omobude/dhl-symfony-bundle
包简介
Modern Symfony 7+ bundle for DHL API integration. Create shipment labels and download DHL shipment labels with ease.
README 文档
README
Modern Symfony 7+ bundle for DHL API integration. Create shipment labels, view and download DHL shipments label with ease using OAuth authentication.
Features
- 🚀 Create DHL shipments
- 📦 Download shipping labels (PDF)
- 🔐 OAuth 2.0 authentication with automatic token management
- ⚡ Token caching for optimal performance
- 🧪 Sandbox mode for testing
- 📝 Comprehensive logging support
- 🎯 Type-safe models with PHP 8.2+
- 🇬🇧 Northern Ireland clearance declaration support (Windsor Framework / UKIMS)
- 🔄 Modern Symfony 7 integration
Requirements
- PHP 8.2 or higher
- Symfony 7.0 or higher
- Symfony Cache component
- DHL Developer Account (Sign up here)
Table of Contents
- Installation
- Getting DHL Credentials
- Usage
- Configuration Reference
- Switching to Production
- Troubleshooting
- Contributing
- License
Installation
Step 1: Install the Bundle
composer require omobude/dhl-symfony-bundle
Step 2: Register the Bundle (If Not Auto-Registered)
If Symfony Flex doesn't automatically register the bundle, manually add it to config/bundles.php:
<?php return [ // ... other bundles Omobude\DhlBundle\OmobudeDhlBundle::class => ['all' => true], ];
Note: With Symfony Flex, this step is usually automatic. Only add this manually if you encounter the error: "There is no extension able to load the configuration for 'omobude_dhl'".
Step 3: Configure Environment Variables
Add your DHL credentials to your .env file:
###> omobude/dhl-symfony-bundle ### DHL_CLIENT_ID=your_dhl_client_id_here DHL_CLIENT_SECRET=your_dhl_client_secret_here DHL_CLIENT_SANDBOX=true ###< omobude/dhl-symfony-bundle ###
Step 4: Create Bundle Configuration
Create the file config/packages/omobude_dhl.yaml:
omobude_dhl: client_id: '%env(DHL_CLIENT_ID)%' client_secret: '%env(DHL_CLIENT_SECRET)%' sandbox: '%env(bool:DHL_CLIENT_SANDBOX)%'
Step 5: Clear Cache
php bin/console cache:clear
Getting DHL Credentials
For Sandbox (Testing)
- Go to DHL Developer Portal
- Sign up for a free account
- Create a new application
- Navigate to your application settings
- Copy your Client ID and Client Secret
- Use these credentials in your
.envfile
For Production
- Contact DHL to request production API access
- Complete any required business verification
- Receive your production Client ID and Client Secret
- Update your production environment variables
- Set
sandbox: falsein your configuration
Usage
Creating a Shipment
<?php declare(strict_types=1); namespace App\Controller; use Omobude\DhlBundle\Exception\DhlApiException; use Omobude\DhlBundle\Exception\DhlAuthenticationException; use Omobude\DhlBundle\Exception\DhlDownloadLabelException; use Omobude\DhlBundle\Model\ConsigneeAddress; use Omobude\DhlBundle\Model\PickupData; use Omobude\DhlBundle\Model\SenderAddress; use Omobude\DhlBundle\Model\ShipmentData; use Omobude\DhlBundle\Model\ShipmentDetails; use Omobude\DhlBundle\Service\DhlApiClient; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class ShippingController extends AbstractController { #[Route('/create-shipment', name: 'create_shipment')] public function createShipment(DhlApiClient $dhlClient): Response { try { $pickupData = new PickupData( date: new \DateTimeImmutable('now', new \DateTimeZone("Europe/London")), accountAddress: true ); $senderAddress = new SenderAddress( companyName: 'XXXXXXXXXX LIMITED', address1: 'UNIT 5C, XXXXXXX DRIVE', city: 'SHEFFIELD', postalCode: 'XXXXX', country: 'GB', name: 'DISPATCH MANAGER', phone: '07443822832', email: 'customersupport@xxxxxxxxxx.com', address2: 'XXXXXXXXXX HOUSE', address3: 'SHEFFIELD' ); $consigneeAddress = new ConsigneeAddress( name: 'JOHN DOE', address1: '123 CUSTOMER STREET', city: 'LONDON', postalCode: 'XXXXXX', country: 'GB', phone: '07123456789', email: 'customer@example.com', recipientType: 'residential', addressType: 'doorstep', address2: 'APARTMENT 4B' ); $shipmentDetails = new ShipmentDetails( customerRef1: 'TN-' . date('YmdHis'), customerRef2: substr(md5(uniqid()), 0, 8), orderedProduct: '1', // 1 = Next day, 48 = 48 hours totalPieces: 1, totalWeight: 5.5 ); $shipmentData = new ShipmentData( pickupAccount: 'XXXXXXX', dropoffType: 'PICKUP', consigneeAddress: $consigneeAddress, pickupData: $pickupData, senderAddress: $senderAddress, shipmentDetails: $shipmentDetails, ); // Create shipment with PDF label $result = $dhlClient->createShipment($shipmentData); return $this->json([ 'success' => true, 'shipment_id' => $result->getShipmentId(), 'message' => 'Shipment created successfully', ]); } catch (DhlApiException|DhlAuthenticationException $ex) { return $this->json([ 'success' => false, 'error' => $ex->getMessage(), 'code' => $ex->getCode(), ], $ex->getCode()); } } }
Northern Ireland Shipments (Clearance Declaration)
Shipments delivered to Northern Ireland (postcodes starting with BT) require an additional clearance declaration under the Windsor Framework / UK Internal Market Scheme (UKIMS). The bundle automatically detects Northern Ireland postcodes and will throw a BadRequestHttpException if a clearance declaration is missing.
When is a Clearance Declaration Required?
| Destination | Postcode Prefix | Clearance Declaration |
|---|---|---|
| Great Britain (England, Scotland, Wales) | Most UK postcodes | ❌ Not required |
| Northern Ireland | BT |
✅ Required |
Building a Clearance Declaration
A clearance declaration is composed of two model classes:
ClearanceDeclaration— top-level customs information for the shipmentClearanceItem— line-item details for each product in the shipment
<?php declare(strict_types=1); namespace App\Controller; use Omobude\DhlBundle\Exception\DhlApiException; use Omobude\DhlBundle\Exception\DhlAuthenticationException; use Omobude\DhlBundle\Model\ClearanceDeclaration; use Omobude\DhlBundle\Model\ClearanceItem; use Omobude\DhlBundle\Model\ConsigneeAddress; use Omobude\DhlBundle\Model\PickupData; use Omobude\DhlBundle\Model\SenderAddress; use Omobude\DhlBundle\Model\ShipmentData; use Omobude\DhlBundle\Model\ShipmentDetails; use Omobude\DhlBundle\Service\DhlApiClient; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; class ShippingController extends AbstractController { #[Route('/create-ni-shipment', name: 'create_ni_shipment')] public function createNorthernIrelandShipment(DhlApiClient $dhlClient): Response { try { $pickupData = new PickupData( date: new \DateTimeImmutable('now', new \DateTimeZone('Europe/London')), accountAddress: true ); $senderAddress = new SenderAddress( companyName: 'XXXXXXXXXX LIMITED', address1: 'UNIT 5C, XXXXXXX DRIVE', city: 'SHEFFIELD', postalCode: 'S9 1XX', country: 'GB', name: 'DISPATCH MANAGER', phone: '07443822832', email: 'customersupport@xxxxxxxxxx.com', address2: 'XXXXXXXXXX HOUSE', address3: 'SHEFFIELD' ); // Northern Ireland consignee — postcode starts with "BT" $consigneeAddress = new ConsigneeAddress( name: 'JANE DOE', address1: '45 BELFAST ROAD', city: 'BELFAST', postalCode: 'BT1 5GS', country: 'GB', phone: '07123456789', email: 'customer@example.com', recipientType: 'residential', addressType: 'doorstep', address2: 'FLAT 2' ); $shipmentDetails = new ShipmentDetails( customerRef1: 'NI-' . date('YmdHis'), customerRef2: substr(md5(uniqid()), 0, 8), orderedProduct: '1', totalPieces: 2, totalWeight: 3.2 ); // Build each line item being shipped $items = [ new ClearanceItem( descriptionOfGoods: 'Cotton T-Shirt', unitQuantity: 1, commodityCode: '6109100010' ), new ClearanceItem( descriptionOfGoods: 'Wool Scarf', unitQuantity: 1, commodityCode: '6117100000' ), ]; // Build the clearance declaration $clearanceDeclaration = new ClearanceDeclaration( shipmentMovementType: 'B2C', // B2C or B2B totalValue: 49.99, // Total goods value (excl. shipping) numberOfItems: 2, // Total number of items items: $items, sendersEORINumber: 'GB123456789000', // Replace with your actual EORI sendersUKIMSNumber: 'XIUKIM12345678901234567890' // Replace with your actual UKIMS number ); $shipmentData = new ShipmentData( pickupAccount: 'XXXXXXX', dropoffType: 'PICKUP', consigneeAddress: $consigneeAddress, pickupData: $pickupData, senderAddress: $senderAddress, shipmentDetails: $shipmentDetails, clearanceDeclaration: $clearanceDeclaration, // <-- required for NI ); $result = $dhlClient->createShipment($shipmentData); return $this->json([ 'success' => true, 'shipment_id' => $result->getShipmentId(), 'message' => 'Northern Ireland shipment created successfully', ]); } catch (BadRequestHttpException $ex) { // Thrown when clearance declaration is missing for an NI postcode return $this->json([ 'success' => false, 'error' => 'Clearance declaration required', 'message' => $ex->getMessage(), ], Response::HTTP_BAD_REQUEST); } catch (DhlApiException | DhlAuthenticationException $ex) { return $this->json([ 'success' => false, 'error' => $ex->getMessage(), 'code' => $ex->getCode(), ], $ex->getCode() ?: Response::HTTP_BAD_REQUEST); } } }
ClearanceDeclaration Fields
| Field | Type | Required | Description |
|---|---|---|---|
shipmentMovementType |
string | Yes | Movement type — B2C (business to consumer) or B2B (business to business) |
totalValue |
float | Yes | Total declared value of goods (excluding shipping costs) |
numberOfItems |
int | Yes | Total number of items across all line items |
items |
ClearanceItem[] |
Yes | Array of ClearanceItem objects, one per line item |
sendersEORINumber |
string | No | Sender's EORI (Economic Operators Registration and Identification) number |
sendersUKIMSNumber |
string | No | Sender's UKIMS (UK Internal Market Scheme) authorisation number |
Note on EORI / UKIMS: Although marked optional in the model, most B2C movements into Northern Ireland under the Windsor Framework will require a valid UKIMS number to qualify for the green-lane "not at risk" treatment. Check your DHL contract and current HMRC guidance before omitting these fields.
ClearanceItem Fields
| Field | Type | Required | Description |
|---|---|---|---|
descriptionOfGoods |
string | Yes | Plain-English description of the goods (e.g. "Cotton T-Shirt") |
unitQuantity |
int | Yes | Quantity of this item being shipped |
commodityCode |
string | No | HS / commodity tariff code (e.g. "6109100010"). Strongly recommended for customs clearance |
Validation Behaviour
The bundle enforces clearance declaration rules at the point of calling toArray() on ShipmentData (which happens automatically inside DhlApiClient::createShipment()):
- ✅ NI postcode + clearance declaration present → shipment proceeds
- ❌ NI postcode + no clearance declaration → throws
BadRequestHttpException - ✅ GB postcode + no clearance declaration → shipment proceeds
- ✅ GB postcode + clearance declaration present → shipment proceeds (declaration is included)
Always catch BadRequestHttpException separately if you want to surface clear validation errors to your users before the request hits the DHL API.
Downloading a Label
/** * Download shipping label as a file (PDF). * Returns a BinaryFileResponse that automatically downloads the file. */ #[Route('/label/{shipmentId}', name: 'get_label')] public function getLabel(string $shipmentId, DhlApiClient $dhlClient): Response { try { return $dhlClient->getLabel($shipmentId); } catch (DhlAuthenticationException $ex) { return $this->json([ 'success' => false, 'error' => 'Authentication failed', 'message' => $ex->getMessage(), ], Response::HTTP_UNAUTHORIZED); } catch (DhlDownloadLabelException $ex) { return $this->json([ 'success' => false, 'error' => 'Failed to process label', 'message' => $ex->getMessage(), ], Response::HTTP_INTERNAL_SERVER_ERROR); } catch (DhlApiException $ex) { return $this->json([ 'success' => false, 'error' => 'DHL API error', 'message' => $ex->getMessage(), ], $ex->getCode() ?: Response::HTTP_BAD_REQUEST); } }
/** * Inline display of label (opens in browser). */ #[Route('/label/{shipmentId}/view', name: 'view_label', methods: ['GET'])] public function viewLabel(string $shipmentId, DhlApiClient $dhlClient): BinaryFileResponse|Response { try { $response = $dhlClient->getLabel($shipmentId); // Change disposition to inline so it opens in browser $response->headers->set( 'Content-Disposition', sprintf('inline; filename="label-%s.pdf"', $shipmentId) ); return $response; } catch (DhlAuthenticationException | DhlDownloadLabelException | DhlApiException $e) { return $this->json([ 'success' => false, 'error' => $e->getMessage(), ], $e->getCode() ?: Response::HTTP_BAD_REQUEST); } }
Checking Sandbox Mode
if ($dhlClient->isSandbox()) { // Running in sandbox mode echo "Testing mode - no real shipments created"; } else { // Running in production mode echo "Production mode - real shipments will be created"; }
Configuration Reference
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
client_id |
string | Yes | - | Your DHL OAuth Client ID |
client_secret |
string | Yes | - | Your DHL OAuth Client Secret |
sandbox |
boolean | Yes | Enable sandbox/testing mode |
DHL Product Codes
Common product codes for orderedProduct:
| Code | Service | Delivery Time |
|---|---|---|
1 |
DHL Parcel | Next day |
48 |
DHL Parcel Neighbour | 48 hours |
Recipient Types
Valid values for recipientType:
residential- Home deliverybusiness- Business address
Address Types
Valid values for addressType:
doorstep- Standard deliveryneighbour- Deliver to neighbour if recipient not available
Shipment Movement Types
Valid values for ClearanceDeclaration::$shipmentMovementType:
B2C- Business to consumerB2B- Business to business
Switching to Production
Step 1: Update Environment Variables
Update your production .env file:
###> omobude/dhl-symfony-bundle ### DHL_CLIENT_ID=your_production_client_id DHL_CLIENT_SECRET=your_production_client_secret DHL_CLIENT_SANDBOX=false ###< omobude/dhl-symfony-bundle ###
Step 3: Clear Production Cache
php bin/console cache:clear --env=prod
Step 4: Test in Production
Always test with a single shipment first to ensure everything works correctly.
Troubleshooting
Bundle Not Registered
Problem: "There is no extension able to load the configuration for 'omobude_dhl'"
Solution:
- Ensure the bundle is registered in
config/bundles.php:
Omobude\DhlBundle\OmobudeDhlBundle::class => ['all' => true],
- Clear the cache:
php bin/console cache:clear - Verify installation:
composer show omobude/dhl-symfony-bundle
Authentication Errors
Problem: "Authentication failed" or "Invalid credentials"
Solution:
- Verify your Client ID and Client Secret are correct
- Ensure you're using sandbox credentials with
sandbox: true - Check that credentials are properly set in
.env - Try clearing the token cache:
php bin/console cache:clear
Configuration Errors
Problem: "The child config 'client_id' under 'omobude_dhl' must be configured"
Solution:
- Ensure
config/packages/omobude_dhl.yamlexists - Verify the configuration syntax is correct
- Check that environment variables are defined in
.env - Run
php bin/console debug:config omobude_dhlto verify
Northern Ireland Clearance Errors
Problem: BadRequestHttpException: Clearance declaration is required for Northern Ireland deliveries (postcode: BT1 5GS).
Solution:
- The destination postcode starts with
BT(Northern Ireland), so aClearanceDeclarationmust be passed toShipmentData - See the Northern Ireland Shipments section for a full example
- Make sure your
ClearanceDeclarationincludes a validshipmentMovementType,totalValue,numberOfItems, and at least oneClearanceItem - For most B2C movements, also include a valid UKIMS number via
sendersUKIMSNumber
Token Caching Issues
Problem: "Cached token expired" or authentication errors after some time
Solution:
- The bundle automatically refreshes tokens
- Clear cache if issues persist:
php bin/console cache:clear - Check cache directory permissions:
var/cache/should be writable - Verify
symfony/cacheis installed
API Errors
Problem: DHL API returns error codes
Solution:
- Check DHL API Documentation for error codes
- Enable logging to see detailed error messages
- Verify all required fields are provided
- Ensure addresses are in the correct format
Debugging
Enable detailed logging:
# config/packages/monolog.yaml monolog: channels: ['dhl'] handlers: dhl: type: stream path: '%kernel.logs_dir%/dhl.log' level: debug channels: ['dhl']
Check logs:
tail -f var/log/dhl.log
Verify bundle configuration:
php bin/console debug:config omobude_dhl php bin/console debug:container DhlApiClient
Environment-Specific Configuration
Development
# .env.local DHL_CLIENT_ID=sandbox_dev_client_id DHL_CLIENT_SECRET=sandbox_dev_client_secret DHL_CLIENT_SANDBOX=true
Staging
# .env.staging DHL_CLIENT_ID=sandbox_staging_client_id DHL_CLIENT_SECRET=sandbox_staging_client_secret DHL_CLIENT_SANDBOX=true
Production
# .env.production DHL_CLIENT_ID=production_client_id DHL_CLIENT_SECRET=production_client_secret DHL_CLIENT_SANDBOX=false
Security Best Practices
- Never commit credentials - Add
.envto.gitignore - Use environment variables - Store credentials in environment, not code
- Separate environments - Use different credentials for dev/staging/prod
- Monitor access logs - Check DHL dashboard for unusual activity
- Use HTTPS only - The bundle uses HTTPS by default
- Limit permissions - Only grant necessary access to DHL accounts
Recommended .gitignore
.env .env.local .env.*.local .env.production .env.prod
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes
- Write tests for your changes
- Ensure all tests pass:
./vendor/bin/phpunit - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
Coding Standards
- Follow PSR-12 coding standards
- Use PHP 8.2+ features (typed properties, readonly, etc.)
- Write PHPDoc comments for all public methods
- Add tests for new features
License
This bundle is released under the MIT License. See the LICENSE file for details.
Author
Omobude Kelly
- GitHub: @komobude2021
- Email: k.omobude2019@gmail.com
Support
Need help? Here are your options:
- 📖 Read the Documentation
- 🐛 Report Issues
- 💬 GitHub Discussions
- 📧 Email Support
- 📚 DHL API Documentation
Acknowledgments
- Built for the Symfony community
- Powered by DHL API
- Inspired by modern Symfony best practices
Made with ❤️ for the Symfony community
If this bundle helped you, please consider giving it a ⭐ on GitHub!
omobude/dhl-symfony-bundle 适用场景与选型建议
omobude/dhl-symfony-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 533 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 02 月 07 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「symfony」 「api」 「oauth」 「bundle」 「shipping」 「tracking」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 omobude/dhl-symfony-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 omobude/dhl-symfony-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 omobude/dhl-symfony-bundle 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
WeChat OAuth SDK
Ory-Hydra OAuth 2.0 Client Provider for The PHP League OAuth2-Client
Library for ORCID web services
The bundle for easy using json-rpc api on your project
A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.
A PSR-7 compatible library for making CRUD API endpoints
统计信息
- 总下载量: 533
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 29
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-07