sylius/addressing
Sylius Addressing component provides models and services to manage addresses, countries, and provinces (states). Includes a flexible zones system to group regions and match addresses to zones—useful for shipping and tax calculations in PHP eCommerce apps.
Installation
composer require sylius/addressing
Add the bundle to config/bundles.php (if using Symfony) or register the service provider in config/app.php (Laravel).
Database Migrations
Run the migrations provided in vendor/sylius/addressing/migrations/ to create tables for:
countryprovince (optional, if using provinces/states)zone (for grouping countries/provinces)address (core model)First Use Case: Basic Address Storage
use Sylius\Component\Addressing\Model\AddressInterface;
use Sylius\Component\Addressing\Model\CountryInterface;
// Create and persist an address
$address = new Address();
$address->setStreet('123 Main St');
$address->setCity('New York');
$address->setPostcode('10001');
$address->setCountry($countryRepository->findOneBy(['code' => 'US']));
$addressRepository->add($address);
Where to Look First
Sylius\Component\Addressing\Model\ for Address, Country, Province, and Zone.Sylius\Component\Addressing\Repository\ for CRUD operations.Sylius\Component\Addressing\Resolver\ for zone resolution (e.g., ZoneResolverInterface).vendor/sylius/addressing/Resources/fixtures/.Sylius\Component\Addressing\Validator\Constraints\ (e.g., ValidCountry, ValidPostcode).
use Sylius\Component\Addressing\Validator\Constraints as AddressAssert;
class AddressRequest {
#[AddressAssert\ValidCountry]
public string $countryCode;
}
Sylius\Component\Addressing\Serializer\ for JSON/API responses.
$serializer = new AddressSerializer($addressRepository);
$data = $serializer->serialize($address, 'json');
$zoneResolver = $container->get('sylius.addressing.zone_resolver');
$zones = $zoneResolver->getZonesForAddress($address);
US and CA).
# config/packages/sylius_addressing.yaml
sylius_addressing:
zones:
us_shipping:
name: 'US Shipping Zone'
members: ['US', 'CA']
$country = $countryRepository->findOneBy(['code' => 'US']);
$province = new Province();
$province->setName('California');
$province->setCode('CA');
$province->setCountry($country);
$provinceRepository->add($province);
Sylius\Component\Addressing\Provider\CountryProviderInterface for dropdowns.
$countries = $countryProvider->getAll();
AppServiceProvider.
public function register() {
$this->app->bind(
\Sylius\Component\Addressing\Repository\CountryRepositoryInterface::class,
\Sylius\Component\Addressing\Doctrine\ORM\CountryRepository::class
);
}
HasFactory).
use Sylius\Component\Addressing\Model\Address as BaseAddress;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Address extends BaseAddress {
use HasFactory;
}
sylius/resource-bundle patterns for RESTful endpoints.
class AddressController extends AbstractController {
public function index(AddressRepository $repository): JsonResponse {
return $this->json($repository->findAll());
}
}
use Sylius\Component\Addressing\Form\Type\AddressType;
$form = $this->createForm(AddressType::class, $address);
Zone Resolution Performance
$cachedZones = Cache::remember("address_zones_{$address->getId()}", now()->addHours(1), fn() =>
$zoneResolver->getZonesForAddress($address)
);
Province-Only Countries
hasProvinces flag in Country model or validate dynamically.
if ($country->hasProvinces() && !$address->getProvince()) {
throw new \InvalidArgumentException('Province is required for this country.');
}
Postcode Validation
12345 vs. SW1A 1AA).ValidPostcode constraint with country-specific rules or extend the validator.
class CustomPostcodeValidator extends AbstractPostcodeValidator {
protected function validatePostcode(string $postcode, CountryInterface $country): bool {
// Custom logic for country-specific formats
}
}
Zone Membership Ambiguity
CA might belong to both US Shipping and North America zones).ZonePriorityResolver.Database Schema Conflicts
address table columns may clash with Sylius migrations.Address entity instead of overriding migrations.
class Address extends BaseAddress {
protected $customField;
}
Zone Debugging
dd($zoneResolver->getZonesForAddress($address)->map(fn($zone) => $zone->getName()));
Country/Province Fixtures
php bin/console sylius:fixtures:load -e=dev
Validation Errors
$validator = $this->validator;
$errors = $validator->validate($address);
dd($errors);
Custom Zone Resolvers
ZoneResolverInterface for business-specific logic (e.g., "Free Shipping Zone").
class CustomZoneResolver implements ZoneResolverInterface {
public function getZonesForAddress(AddressInterface $address): array {
// Custom resolution logic
}
}
Address Events
address.pre_persist or address.post_remove events for side effects.
$dispatcher->addListener(
AddressEvents::PRE_PERSIST,
fn(AddressEvent $event) => $this->logAddressCreation($event->getAddress())
);
Provider Extensions
CountryProvider or ProvinceProvider to fetch data from an external API.
class ApiCountryProvider implements CountryProviderInterface {
public function getAll(): array {
return $this->apiClient->fetchCountries();
}
}
Serializer Normalization
AddressSerializer to include custom fields.
class CustomAddressSerializer extends AddressSerializer {
public function normalize($address, $format = null, array $context = []): array {
$data = parent::normalize($address, $format, $context);
$data['custom_field'] = $address->getCustomField();
return $data;
}
}
Zone Membership Strategies
ZoneMember logic (e.g., for IP-based zones).
class IpZoneMember implements ZoneMemberInterface {
public function isMemberOfZone(AddressInterface $address
How can I help you explore Laravel packages today?