pralhadstha/zipcoder-php 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

pralhadstha/zipcoder-php

Composer 安装命令:

composer require pralhadstha/zipcoder-php

包简介

PHP postal code to address lookup with multi-provider support and automatic fallback

README 文档

README

A PHP library to convert postal codes and zip codes into structured addresses using multiple geocoding APIs with automatic fallback.

Latest Version on Packagist PHP Version License Total Downloads PHPStan Level 9 Tests

Zipcoder turns any postal code into a normalized address (city, state, province, country, and coordinates) by querying postal code lookup APIs behind a unified interface. If one provider fails or has no data, the next one in the chain picks up automatically. No more vendor lock-in to a single zip code API.

Use cases: shipping & logistics address autofill, checkout form validation, postal code to city/state resolution, international address lookup, and geocoding from zip codes.

Key Features

  • 5 built-in postal code providers — GeoNames, Zippopotamus, Zipcodestack, Zipcodebase, and JpPostalCode
  • Automatic fallback — Chain of Responsibility pattern tries providers in order until one succeeds
  • PSR-16 caching — decorator wraps any provider to cache results and reduce API calls
  • PSR-18 HTTP — bring your own HTTP client (Guzzle, Symfony HttpClient, etc.) or use the included zero-dependency curl client
  • Normalized results — every provider returns the same Address structure regardless of the underlying API format
  • 100+ countries — covers postal codes worldwide; Japan-specific provider with English, Japanese, and Kana output
  • Zero required dependencies — only PSR interfaces; all implementations are optional
  • Extensible — implement the Provider interface to add any postal code API
  • PHP 8.2+ — readonly classes, constructor promotion, strict types throughout
  • PHPStan level 9 — fully statically analyzed

Supported Providers

Provider Countries Auth Required Free Tier Best For
GeoNames 100+ Free username 10,000/day Primary global provider
Zippopotamus ~60 None Unlimited Quick lookups, zero config
Zipcodestack 210+ API key 300/month Broadest country coverage
Zipcodebase 100+ API key 10,000/month Good middle-ground
JpPostalCode Japan None Unlimited Japan addresses in EN/JA/Kana

Requirements

  • PHP 8.2 or higher
  • ext-curl (if using the built-in CurlPsr18Client)
  • A PSR-18 HTTP client (e.g., Guzzle 7) or use the included curl client

Installation

composer require pralhadstha/zipcoder-php

For production, Guzzle is recommended as the HTTP client:

composer require pralhadstha/zipcoder-php guzzlehttp/guzzle

Framework Integration

If you are using a framework, check out our dedicated integration package:

Framework Package Stats
Laravel pralhadstha/zipcoder-laravel Latest Stable Version Total Downloads

Quick Start

Look up a US zip code and get the city and state in 4 lines:

use Pralhad\Zipcoder\Http\CurlPsr18Client;
use Pralhad\Zipcoder\Provider\Zippopotamus;
use Pralhad\Zipcoder\Query;

$http = new CurlPsr18Client();
$provider = new Zippopotamus($http, $http);

$result = $provider->lookup(Query::create('90210', 'US'));

$address = $result->first();
echo $address->city;      // "Beverly Hills"
echo $address->state;     // "California"
echo $address->stateCode; // "CA"
echo $address->latitude;  // 34.0901
echo $address->longitude; // -118.4065

With Guzzle

use GuzzleHttp\Client;
use Pralhad\Zipcoder\Provider\GeoNames;
use Pralhad\Zipcoder\Query;

$guzzle = new Client(['timeout' => 10]);
$provider = new GeoNames($guzzle, $guzzle, username: 'your_geonames_username');

$result = $provider->lookup(Query::create('100-0001', 'JP'));
echo $result->first()->city; // "Chiyoda"

With Automatic Fallback (Chain)

use Pralhad\Zipcoder\Http\CurlPsr18Client;
use Pralhad\Zipcoder\Provider\Chain;
use Pralhad\Zipcoder\Provider\GeoNames;
use Pralhad\Zipcoder\Provider\JpPostalCode;
use Pralhad\Zipcoder\Provider\Zippopotamus;
use Pralhad\Zipcoder\Query;

$http = new CurlPsr18Client();

$provider = new Chain([
    new JpPostalCode($http, $http),                       // Japan: free, best data
    new Zippopotamus($http, $http),                        // 60 countries: free, fast
    new GeoNames($http, $http, 'your_geonames_username'),  // 100+ countries: free tier
]);

// Japan postal code uses JpPostalCode provider
$result = $provider->lookup(Query::create('100-0014', 'JP'));

// For US zip code, JpPostalCode skips (not JP), Zippopotamus handles it
$result = $provider->lookup(Query::create('90210', 'US'));

Usage

Creating Queries

A Query represents a postal code lookup request. It validates input and normalizes the postal code:

use Pralhad\Zipcoder\Query;

$query = Query::create('90210', 'US');
$query->postalCode;           // "90210"
$query->countryCode;          // "US"
$query->normalizedPostalCode(); // "90210" (strips hyphens and spaces)

// Japanese postal codes with hyphens are normalized
$query = Query::create('100-0014', 'JP');
$query->normalizedPostalCode(); // "1000014"

Working with Results

Every provider returns an AddressCollection containing normalized Address objects:

$result = $provider->lookup(Query::create('10005', 'US'));

// Access the first result
$address = $result->first();
$address->postalCode;   // "10005"
$address->countryCode;  // "US"
$address->countryName;  // "United States" (if available)
$address->city;         // "New York City"
$address->state;        // "New York"
$address->stateCode;    // "NY"
$address->province;     // Province (if available)
$address->district;     // District (if available)
$address->latitude;     // 40.7063
$address->longitude;    // -74.0089
$address->provider;     // "zipcodebase" (which provider returned this)

// Iterate all results
foreach ($result as $address) {
    echo "{$address->city}, {$address->state}\n";
}

// Collection helpers
$result->count();    // Number of addresses
$result->isEmpty();  // true if no results
$result->toArray();  // Convert all addresses to arrays

Providers

GeoNames — 100+ countries, free username registration
use Pralhad\Zipcoder\Provider\GeoNames;

// Register at https://www.geonames.org/login to get a free username
$provider = new GeoNames($httpClient, $requestFactory, username: 'your_username');
$result = $provider->lookup(Query::create('100-0001', 'JP'));

Returns: postalCode, countryCode, city, state, stateCode, province, latitude, longitude

Zippopotamus — ~60 countries, no authentication needed
use Pralhad\Zipcoder\Provider\Zippopotamus;

// No API key or username needed
$provider = new Zippopotamus($httpClient, $requestFactory);
$result = $provider->lookup(Query::create('90210', 'US'));

Returns: postalCode, countryCode, countryName, city, state, stateCode, latitude, longitude

Zipcodestack — 210+ countries, broadest coverage
use Pralhad\Zipcoder\Provider\Zipcodestack;

// Get an API key at https://zipcodestack.com
$provider = new Zipcodestack($httpClient, $requestFactory, apiKey: 'your_api_key');
$result = $provider->lookup(Query::create('44600', 'NP'));

Returns: postalCode, countryCode, city, state, province, latitude, longitude

Zipcodebase — 100+ countries, 10k requests/month free
use Pralhad\Zipcoder\Provider\Zipcodebase;

// Get an API key at https://zipcodebase.com
$provider = new Zipcodebase($httpClient, $requestFactory, apiKey: 'your_api_key');
$result = $provider->lookup(Query::create('10005', 'US'));

Returns: postalCode, countryCode, city, state, stateCode, province, latitude, longitude

JpPostalCode — Japan-only, free, supports English/Japanese/Kana
use Pralhad\Zipcoder\Provider\JpPostalCode;

// English output (default)
$provider = new JpPostalCode($httpClient, $requestFactory, locale: 'en');
$result = $provider->lookup(Query::create('100-0014', 'JP'));
$result->first()->city; // "Chiyoda-ku"

// Japanese output
$provider = new JpPostalCode($httpClient, $requestFactory, locale: 'ja');
$result = $provider->lookup(Query::create('100-0014', 'JP'));
$result->first()->city; // "千代田区"

// Kana output
$provider = new JpPostalCode($httpClient, $requestFactory, locale: 'kana');
$result = $provider->lookup(Query::create('100-0014', 'JP'));
$result->first()->city; // "チヨダク"

Returns: postalCode, countryCode (JP), countryName (Japan), state, stateCode, city, district

Automatically skips non-JP queries in a chain — no API call is made.

Chain Provider (Automatic Fallback)

The Chain provider implements the Chain of Responsibility pattern. It tries each provider in order and returns the first successful result. If a provider throws an error or returns no data, it moves to the next one.

use Pralhad\Zipcoder\Provider\Chain;
use Psr\Log\LoggerInterface;

$chain = new Chain(
    providers: [
        new JpPostalCode($http, $http),
        new Zippopotamus($http, $http),
        new GeoNames($http, $http, 'username'),
        new Zipcodebase($http, $http, 'api_key'),
        new Zipcodestack($http, $http, 'api_key'),
    ],
    logger: $psrLogger, // Optional PSR-3 logger for debugging fallback behavior
);

$result = $chain->lookup(Query::create('44600', 'NP'));

How fallback works:

  1. Each provider is tried in the order given
  2. If a provider returns addresses, that result is returned immediately
  3. If a provider throws NoResult or HttpError, the chain logs a warning and continues
  4. If a provider returns an empty collection, the chain skips to the next
  5. InvalidArgument exceptions (programming errors) are not caught — they bubble up
  6. If all providers fail, a NoResult exception is thrown

Cache Provider (PSR-16)

The Cache decorator wraps any provider with PSR-16 caching to avoid redundant API calls:

use Pralhad\Zipcoder\Provider\Cache;
use Psr\SimpleCache\CacheInterface;

// Wrap any provider (or a chain) with caching
$cached = new Cache(
    provider: $chain,
    cache: $psrCache,    // Any PSR-16 cache (Laravel, Symfony, php-cache, etc.)
    ttl: 86400,          // Cache for 24 hours (default)
);

// First call: hits the API, stores result in cache
$result = $cached->lookup(Query::create('90210', 'US'));

// Second call: returns from cache, no API call
$result = $cached->lookup(Query::create('90210', 'US'));

Cache key format: zipcoder:{COUNTRY_CODE}:{NORMALIZED_CODE} (e.g., zipcoder:US:90210, zipcoder:JP:1000014)

ZipcoderLookup (Provider Registry)

ZipcoderLookup is a convenience aggregator to register and access providers by name:

use Pralhad\Zipcoder\ZipcoderLookup;

$zipcoder = new ZipcoderLookup();
$zipcoder->registerProvider($cachedChain);
$zipcoder->registerProvider(new Zippopotamus($http, $http));

// Use the first registered provider
$result = $zipcoder->lookup(Query::create('90210', 'US'));

// Use a specific provider by name
$result = $zipcoder->using('zippopotamus')->lookup(Query::create('90210', 'US'));

// List registered providers
$zipcoder->getRegisteredProviders(); // ['cache(chain)', 'zippopotamus']

Full Production Example

use Pralhad\Zipcoder\Http\CurlPsr18Client;
use Pralhad\Zipcoder\Provider\Cache;
use Pralhad\Zipcoder\Provider\Chain;
use Pralhad\Zipcoder\Provider\GeoNames;
use Pralhad\Zipcoder\Provider\JpPostalCode;
use Pralhad\Zipcoder\Provider\Zipcodebase;
use Pralhad\Zipcoder\Provider\Zipcodestack;
use Pralhad\Zipcoder\Provider\Zippopotamus;
use Pralhad\Zipcoder\Query;
use Pralhad\Zipcoder\ZipcoderLookup;

$http = new CurlPsr18Client(timeout: 10);

$chain = new Chain([
    new JpPostalCode($http, $http),
    new Zippopotamus($http, $http),
    new GeoNames($http, $http, 'your_username'),
    new Zipcodebase($http, $http, 'your_api_key'),
    new Zipcodestack($http, $http, 'your_api_key'),
]);

$provider = new Cache($chain, $yourPsr16Cache, ttl: 3600);

$zipcoder = new ZipcoderLookup();
$zipcoder->registerProvider($provider);

$result = $zipcoder->lookup(Query::create('100-0014', 'JP'));
$address = $result->first();

echo "{$address->city}, {$address->state}, {$address->countryCode}";
// "Chiyoda-ku, Tokyo, JP"

Creating a Custom Provider

Implement the Provider interface or extend AbstractHttpProvider to integrate any postal code API:

use Pralhad\Zipcoder\Contract\Provider;
use Pralhad\Zipcoder\Provider\AbstractHttpProvider;
use Pralhad\Zipcoder\Query;
use Pralhad\Zipcoder\Result\Address;
use Pralhad\Zipcoder\Result\AddressCollection;

final class MyApiProvider extends AbstractHttpProvider
{
    public function lookup(Query $query): AddressCollection
    {
        $url = "https://my-api.com/lookup?code={$query->postalCode}&country={$query->countryCode}";
        $data = $this->fetchJson($url);

        $addresses = array_map(
            fn (array $item) => new Address(
                postalCode: $query->postalCode,
                countryCode: $query->countryCode,
                city: $item['city'] ?? null,
                state: $item['region'] ?? null,
                latitude: isset($item['lat']) ? (float) $item['lat'] : null,
                longitude: isset($item['lng']) ? (float) $item['lng'] : null,
                provider: $this->getName(),
            ),
            $data['results'] ?? [],
        );

        return new AddressCollection($addresses);
    }

    public function getName(): string
    {
        return 'my-api';
    }
}

Then add it to your chain:

$chain = new Chain([
    new MyApiProvider($http, $http),
    new GeoNames($http, $http, 'username'),
]);

Error Handling

Zipcoder uses a clear exception hierarchy:

Exception When Caught by Chain?
NoResult No address found for the postal code Yes (falls back)
HttpError Network failure or HTTP error Yes (falls back)
ProviderNotRegistered Unknown provider name in ZipcoderLookup::using() No
InvalidArgument Invalid input (empty postal code, bad country code) No (programming error)
use Pralhad\Zipcoder\Exception\NoResult;
use Pralhad\Zipcoder\Exception\HttpError;
use Pralhad\Zipcoder\Exception\InvalidArgument;

try {
    $result = $provider->lookup(Query::create('99999', 'XX'));
} catch (NoResult $e) {
    // No provider could resolve this postal code
    echo "Not found: {$e->getMessage()}";
} catch (HttpError $e) {
    // All providers had network/HTTP errors
    echo "Service error: {$e->getMessage()}";
} catch (InvalidArgument $e) {
    // Bad input — fix the query
    echo "Invalid input: {$e->getMessage()}";
}

HTTP Client Options

Zipcoder accepts any PSR-18 compatible HTTP client. The first constructor argument is the ClientInterface, the second is the RequestFactoryInterface. Many clients (Guzzle 7, the included curl client) implement both.

Client Install Example
Built-in curl Included new CurlPsr18Client(timeout: 10)
Guzzle 7 composer require guzzlehttp/guzzle new \GuzzleHttp\Client(['timeout' => 10])
Symfony HttpClient composer require symfony/http-client PSR-18 adapter

Testing

# Run unit tests (mocked HTTP, no API calls)
vendor/bin/phpunit --testsuite Unit

# Run static analysis
vendor/bin/phpstan analyse

# Format code
vendor/bin/pint

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-provider)
  3. Write tests for any new functionality
  4. Ensure all tests pass (vendor/bin/phpunit)
  5. Run static analysis (vendor/bin/phpstan analyse)
  6. Format your code (vendor/bin/pint)
  7. Submit a pull request

License

Zipcoder is open-sourced software licensed under the MIT License.

Copyright (c) 2026 Pralhad Kumar Shrestha

pralhadstha/zipcoder-php 适用场景与选型建议

pralhadstha/zipcoder-php 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 25 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 04 月 05 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「geocoding」 「address」 「zipcode」 「geonames」 「lookup」 「postal-code」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 pralhadstha/zipcoder-php 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 pralhadstha/zipcoder-php 我们能提供哪些服务?
定制开发 / 二次开发

基于 pralhadstha/zipcoder-php 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 25
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 31
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-05