Carrier Abstraction Pattern
Magento's shipping architecture uses a carrier abstraction pattern that separates rate collection logic from specific carrier implementations. This allows new carriers to be added without modifying core shipping code.
+------------------+
| RateRequest |
| (Quote\Address) |
+--------+---------+
|
v
+------------------+
| Shipping |
| (Rate Collector) |
+--------+---------+
|
+----------------+----------------+
| | |
v v v
+-------+------+ +------+-------+ +-----+--------+
| CarrierFactory| | CarrierFactory| | CarrierFactory|
+-------+------+ +------+-------+ +-----+--------+
| | |
v v v
+-------+------+ +------+-------+ +-----+--------+
| UPS Carrier | | USPS Carrier | | FedEx Carrier|
| (Online) | | (Online) | | (Online) |
+--------------+ +--------------+ +--------------+
| | |
v v v
+-------+------+ +------+-------+ +-----+--------+
| Rate\Result | | Rate\Result | | Rate\Result |
+--------------+ +--------------+ +--------------+
| | |
+----------------+----------------+
|
v
+------------------+
| CarrierResult |
| (Aggregated) |
+------------------+
Carrier Class Hierarchy
AbstractCarrierInterface (interface)
|
+-- AbstractCarrier (abstract class)
|
+-- AbstractCarrierOnline (abstract class)
|
+-- Ups\Model\Carrier (concrete)
+-- Usps\Model\Carrier (concrete)
+-- Fedex\Model\Carrier (concrete)
+-- Dhl\Model\Carrier (concrete)
|
+-- OfflineShipping\Model\Carrier\Flatrate (concrete)
+-- OfflineShipping\Model\Carrier\Tablerate (concrete)
+-- OfflineShipping\Model\Carrier\Freeshipping (concrete)
AbstractCarrier
Base class providing configuration access, handling fee calculation, country availability checks, and rate result building. Used by offline carriers (flat rate, table rate, free shipping).
AbstractCarrierOnline
Extends AbstractCarrier with API integration features: shipping labels, tracking, container types, content types, and return shipments. Used by UPS, USPS, FedEx, DHL.
Rate Collection Architecture
Shipping::collectRates()
The main orchestration method that iterates through all enabled carriers:
public function collectRates(RateRequest $request)
{
// Set origin from store config if not provided
if (!$request->getOrig()) {
$request->setCountryId($this->getConfigValue('country_id'));
$request->setRegionId($this->getConfigValue('region_id'));
$request->setPostcode($this->getConfigValue('postcode'));
}
// Get all carriers or limited set
$limitCarrier = $request->getLimitCarrier();
if (!$limitCarrier) {
$carriers = $this->scopeConfig->getValue('carriers');
foreach ($carriers as $carrierCode => $config) {
$this->collectCarrierRates($carrierCode, $request);
}
} else {
foreach ((array)$limitCarrier as $carrierCode) {
$this->collectCarrierRates($carrierCode, $request);
}
}
return $this;
}
collectCarrierRates()
Prepares and invokes a single carrier:
public function collectCarrierRates($carrierCode, $request)
{
// Create carrier via factory
$carrier = $this->prepareCarrier($carrierCode, $request);
// Check for package shipment mode
if ($carrier->getConfigData('shipment_requesttype')) {
$packages = $this->composePackagesForCarrier($carrier, $request);
if (!empty($packages)) {
$request->setPackages($packages);
}
}
// Collect rates from carrier
$result = $carrier->collectRates($request);
// Append to aggregated result
if ($result instanceof Result) {
$this->getResult()->appendResult($result, $carrier->getConfigData('showmethod'));
}
return $this;
}
Rate Result Classes
| Class | Path | Purpose |
|---|---|---|
| Result | Model/Rate/Result.php | Container for shipping methods from a single carrier |
| Method | Model/Rate/Result/Method.php | Individual shipping method with carrier, method code, price |
| Error | Quote/Model/Quote/Address/RateResult/Error.php | Error response when carrier unavailable or fails |
| CarrierResult | Model/Rate/CarrierResult.php | Aggregated result from all carriers for a request |
| PackageResult | Model/Rate/PackageResult.php | Result for multi-package shipments |
Carrier Factory
The CarrierFactory creates carrier instances
based on configuration. Each carrier is registered in its module's config.xml:
<!-- Example: module-ups/etc/config.xml -->
<config>
<default>
<carriers>
<ups>
<active>0</active>
<model>Magento\Ups\Model\Carrier</model>
<title>United Parcel Service</title>
<!-- ... additional config ... -->
</ups>
</carriers>
</default>
</config>
// CarrierFactory::create()
public function create($carrierCode, $storeId = null)
{
$className = $this->scopeConfig->getValue(
'carriers/' . $carrierCode . '/model',
ScopeInterface::SCOPE_STORE,
$storeId
);
$carrier = $this->objectManager->create($className);
$carrier->setId($carrierCode);
$carrier->setStore($storeId);
return $carrier;
}
Tracking Architecture
+-------------------+ +-------------------+ +-------------------+
| Tracking/Popup | --> | AbstractCarrier | --> | Carrier API |
| (Controller) | | ::getTracking() | | (UPS/FedEx/etc) |
+-------------------+ +-------------------+ +-------------------+
|
v
+-------------------+
| Tracking\Result |
+-------------------+
|
v
+-------------------+
| Result\Status |
| Result\Error |
+-------------------+
Tracking Result Classes
- Tracking/Result.php
- Tracking/Result/Status.php
- Tracking/Result/Error.php
- Tracking/Result/AbstractResult.php
Status Fields
- tracking - Tracking number
- carrier - Carrier code
- carrier_title - Carrier display name
- progressdetail - Array of tracking events
- deliverydate - Expected delivery
- signedby - Signature on delivery
Shipping Label Architecture
Shipping labels are generated via the Shipping\Labels
class (admin area only). This class extends the base Shipping class and adds label creation.
// adminhtml/di.xml preference
<preference for="Magento\Shipping\Model\Shipping"
type="Magento\Shipping\Model\Shipping\Labels" />
// Labels::requestToShipment()
public function requestToShipment(Shipment $orderShipment, Request $request)
{
$carrier = $this->carrierFactory->create($orderShipment->getOrder()->getShippingMethod());
// Build shipment request data
$request->setOrderShipment($orderShipment);
$request->setPackages($packages);
// Get label from carrier
$result = $carrier->requestToShipment($request);
if ($result->hasErrors()) {
throw new LocalizedException(__($result->getErrors()));
}
// Store label data on shipment
$orderShipment->setShippingLabel($result->getShippingLabelContent());
$orderShipment->setPackages($result->getPackages());
return $result;
}
Note: Only carriers extending AbstractCarrierOnline support
shipping labels. The isShippingLabelsAvailable() method must return true.
Package Composition
When a carrier has shipment_requesttype enabled, items are composed into packages
using a first-fit decreasing (FFD) bin-packing algorithm:
// Shipping::composePackagesForCarrier()
public function composePackagesForCarrier($carrier, $request)
{
$maxWeight = (double)$carrier->getConfigData('max_package_weight');
$allItems = $request->getAllItems();
// Collect weights and prices
foreach ($allItems as $item) {
// Skip bundles with dynamic shipping
// Skip free shipping items
// Handle decimal quantities
$weightItems[] = ['weight' => $itemWeight, 'price' => $item->getBasePrice()];
}
// Sort by weight descending (FFD algorithm)
usort($weightItems, fn($a, $b) => $b['weight'] <=> $a['weight']);
// Bin-pack into packages
return $this->_makePieces($weightItems, $maxWeight);
}
// First-fit decreasing bin packing
protected function _makePieces(array $items, float $maxWeight): array
{
$packages = [];
foreach ($items as $item) {
$placed = false;
foreach ($packages as &$package) {
if ($item['weight'] <= $maxWeight - $package['weight']) {
$package['weight'] += $item['weight'];
$package['price'] += $item['price'];
$placed = true;
break;
}
}
if (!$placed) {
$packages[] = ['weight' => $item['weight'], 'price' => $item['price']];
}
}
return $packages;
}
Directory Structure
module-shipping/
+-- Block/
| +-- Adminhtml/
| | +-- Create/ # Shipment creation forms
| | +-- Order/
| | | +-- Packaging/ # Package grid for labels
| | | +-- Tracking/ # Tracking management
| | +-- View/ # Shipment view pages
| +-- DataProviders/
| | +-- Tracking/ # Tracking block data providers
| +-- Tracking/ # Frontend tracking blocks
|
+-- Controller/
| +-- Adminhtml/
| | +-- Order/
| | | +-- Shipment/ # Shipment CRUD controllers
| | +-- Shipment/ # Mass actions
| +-- Tracking/ # Frontend tracking popup
|
+-- Helper/
| +-- Data.php # Tracking URL helper
|
+-- Model/
| +-- Carrier/
| | +-- AbstractCarrier.php
| | +-- AbstractCarrierOnline.php
| | +-- CarrierInterface.php
| | +-- Source/ # Config source models
| +-- Config/ # Carrier config readers
| +-- Order/ # Order tracking info
| +-- Rate/ # Rate result classes
| +-- ResourceModel/ # Carrier title resources
| +-- Shipment/ # Shipment request/item classes
| +-- Shipping/ # Labels extension
| +-- Tracking/ # Tracking result classes
| +-- CarrierFactory.php
| +-- Shipping.php # Main rate collector
|
+-- etc/
| +-- adminhtml/
| | +-- di.xml # Admin preferences
| | +-- routes.xml
| | +-- system.xml # Admin config
| +-- frontend/
| | +-- routes.xml
| +-- config.xml # Default origin settings
| +-- crontab.xml # Shipment report aggregation
| +-- di.xml # Main preferences
| +-- module.xml
|
+-- view/
+-- adminhtml/ # Admin templates/layouts
+-- frontend/ # Tracking popup templates