A lightweight PHP library for validating and formatting international postal codes (ZIP/PSC).
Provides country-specific formatters, unified exception handling, and clear error codes for integration in forms, APIs, and e-commerce applications.
- β Validation and normalization of postal codes for multiple countries
- β
Country-specific formatters (
CZ,SK,DE,GB,FR,IT,β¦) - β
Unified API via
PostcodeFormatter - β Strict typing (PHP 8.1+)
- β
Consistent error handling with
PostcodeExceptionandPostcodeErrorCodeenum - β
Ready for localization (
messageKey()for translation keys) - β PSR-4 autoloading
- β PHPUnit test coverage
- β PHPStan Level 10 + Strict Rules
- β Verified with 100% PHPUnit coverage
- β Includes support for all 249 ISO 3166-1 alpha-2 country codes
composer require lemonade/component_postcodeuse Lemonade\Postcode\PostcodeFormatter;
use Lemonade\Postcode\CountryCode;
use Lemonade\Postcode\Exception\PostcodeException;
use Lemonade\Postcode\FormatterRegistry;
// initialize with default formatters
$registry = new FormatterRegistry(); // IMMUTABLE REGISTRY
$formatter = new PostcodeFormatter($registry); // READONLY REFERENCE
try {
$postcode = $formatter->format(CountryCode::CZ, '12000');
// "120 00"
} catch (PostcodeException $e) {
echo $e->getValue() . ' is invalid: ' . $e->getMessage();
}You can easily extend the library with your own country-specific formatters.
Simply implement CountryPostcodeFormatter and register it via FormatterRegistry.
Example with an anonymous class:
use Lemonade\Postcode\CountryCode;
use Lemonade\Postcode\CountryPostcodeFormatter;
use Lemonade\Postcode\Exception\InvalidPostcodeException;
use Lemonade\Postcode\FormatterRegistry;
use Lemonade\Postcode\PostcodeFormatter;
$registry = new FormatterRegistry();
// Add custom formatter for Antarctica (AQ)
$registry = $registry->register(CountryCode::AQ, new class implements CountryPostcodeFormatter {
public function format(string $postcode): string
{
if (preg_match('/^[0-9]{4}$/', $postcode) !== 1) {
throw new InvalidPostcodeException($postcode);
}
return strtoupper($postcode);
}
});
$formatter = new PostcodeFormatter($registry);
try {
echo $formatter->format(CountryCode::AQ, '1231');
// outputs "1231"
} catch (InvalidPostcodeException $e) {
echo $e->getValue() . ' is invalid: ' . $e->getMessage();
}Each country has its own Formatter class under Lemonade\Postcode\Formatter.
Examples:
- CZ_Formatter β
12000β120 00 - SK_Formatter β
81101β811 01 - PL_Formatter β
01001β01-001 - GB_Formatter β
SW1A1AAβSW1A 1AA - LT_Formatter β supports both
12345andLT12345
All errors are reported using dedicated exception classes.
This makes it easy to distinguish between invalid input and unsupported countries.
-
InvalidPostcodeException
Thrown when the postcode does not match the expected format
or contains an unsupported value.
Example:"ABCDE"forCZ. -
UnknownCountryException
Thrown when trying to format a postcode for an unsupported
or unrecognized ISO 3166-1 alpha-2 country code.
Example:"XX". -
UnsupportedCountryException
Thrown when the country code is valid, but no formatter is registered for it.Example:
"AQ"(valid ISO code, but not implemented inFormatterMapper)..
All exceptions implement the PostcodeException interface, which provides:
getValue()β returns the original input (postcodeorcountry)getCode()β stable numeric error code (PostcodeErrorCode)getMessage()β translation key for localization
All notable changes are documented in the CHANGELOG.md.
This package is fully covered by PHPUnit tests and verified with PHPStan Level 10.
Run PHPUnit tests:
composer install
vendor/bin/phpunit
vendor/bin/phpunit -c vendor/lemonade/component_postcode/phpunit.xml --bootstrap vendor/autoload.phpRun static analysis (PHPStan Level 10 + Strict Rules):
vendor/bin/phpstan analyse vendor/lemonade/component_postcode/src \
--configuration=vendor/lemonade/component_postcode/phpstan.neon.dist \
--memory-limit=1G