Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Province City Area Laravel Package

aoxiang/province-city-area

Laravel package for Chinese province, city, and area data. It supplies ready-to-use administrative divisions and helpers for cascading address selectors, making it easy to add province-city-area pickers to forms without manually maintaining region lists. Ideal for checkout, user profiles, and other...

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install — composer require aoxiang/province-city-area. The package ships a service provider (Xiang\ProvinceCityArea\ProvinceCityAreaServiceProvider) which is auto-discovered on Laravel 5.5+. On older apps, register it manually in config/app.php.

  2. Publish and load the data — this is the step that matters. The package is essentially a seeded dataset, so nothing works until you get the rows in:

    php artisan vendor:publish --provider="Xiang\ProvinceCityArea\ProvinceCityAreaServiceProvider"
    php artisan migrate
    php artisan db:seed --class=ProvinceCityAreaSeeder   # confirm class name in /database/seeds
    

    Expect a few thousand rows. Seed into an existing environment before wiring up any UI.

  3. Look first at the migration and the ProvinceCityArea model. Check the exact column names — the convention is id, pid (parent id, 0 at province level), name, and a level/type column (1 province, 2 city, 3 area). Your queries depend on those names, not on any documented API.

  4. First use case — a cascading address picker. Pull provinces once, then load children by pid on each change:

    $provinces = ProvinceCityArea::where('pid', 0)->get();
    $cities    = ProvinceCityArea::where('pid', $provinceId)->get();
    $areas     = ProvinceCityArea::where('pid', $cityId)->get();
    

Implementation Patterns

Cascading endpoints. The cleanest day-to-day shape is a single JSON route in your app that fronts the package, so your front-end never talks to the model directly:

Route::get('/areas/{pid}', fn ($pid) => ProvinceCityArea::where('pid', $pid)
    ->orderBy('id')->get(['id', 'name']));

Wire your Blade/Vue/Livewire select to that URL. Keep the controller thin — there is no business logic in this package, only lookup data.

Cache the whole tree. The dataset is read-only and small enough to cache as an array keyed by pid. Build it once with Cache::rememberForever('areas.tree', fn () => ProvinceCityArea::all()->groupBy('pid')->toArray()) and serve all three levels from memory. This removes the database from your address-dropdown hot path entirely.

Store IDs, not names. Persist province_id, city_id, area_id on your addresses/users table and resolve names via a relationship. Names are user-visible labels that the seed can change; IDs are stable.

public function province() { return $this->belongsTo(ProvinceCityArea::class, 'province_id'); }

Add a getFullAddressAttribute() accessor that concatenates province + city + area + street. Municipalities need special handling (see Gotchas).

Validate against the table. Use an exists:province_city_areas,id rule on submit so a tampered form can't store a fabricated area. For stricter checks, validate that city.pid === province_id in a withValidator callback — the package itself enforces nothing.

Seed in chunks. If you ever re-seed in tests, wrap the seeder with RefreshDatabase carefully; run --class once per test suite rather than per test, or use a cached tree fixture. Seeding thousands of rows per test case will dominate your suite runtime.

Integration tip for admin panels (Filament, Nova, Orchid): bind the three selects to each other with a dependent-select component and swap to a single text fallback (free-text city) for foreign addresses. Do not build the whole address model around this package — it is China-domestic only.

Gotchas and Tips

  • Data staleness is the real risk. Last release was May 2023, sourced from JD. Chinese administrative divisions change (district mergers, new development zones, province-level reshuffles). If your product must reflect current official divisions, treat this as a starting dataset and plan a periodic refresh, not as authoritative. Do not build compliance or shipping-rate logic on it without auditing.

  • IDs are JD's internal identifiers, not GB/T 2260 codes. If you need to interoperate with logistics, tax, or government systems that expect 6-digit administrative division codes, this package will not give them to you out of the box. Check the schema for a code column before assuming otherwise.

  • Municipalities behave oddly. Beijing, Shanghai, Tianjin, Chongqing are province-level with a city level that duplicates the province name, and some entries use placeholder names like 市辖区 or 县. Normalize before display: skip the city segment when city.name === province.name or when it matches a placeholder list.

  • pid = 0 vs null. Confirm which sentinel the seed uses for province-level rows. Mixing whereNull('pid') and where('pid', 0) will silently return empty sets; write one scope (scopeProvinces()) and use it everywhere.

  • Table name collisions. The migration creates province_city_areas unconditionally. If you already have an address table or run multiple apps off one database, publish the config first and change the table name, then update the model's $table.

  • Config quirks. Publishing with no --tag may overwrite your config on re-publish. Use vendor:publish --tag=province-city-area-config if the package registers tags; otherwise back up the file before upgrading.

  • Extension points are thin, by design. You can safely subclass ProvinceCityArea, override the table name, and add scopes/accessors in your own model — but don't fork the seeder. Wrap the package model in your own repository class so that swapping to a maintained dataset (or a self-hosted GB-code table) later is a one-file change rather than a project-wide refactor.

  • Debugging empty dropdowns. 90% of "no cities returned" reports are an unrun seeder or a stale cache. Check row counts per level (SELECT level, COUNT(*) FROM province_city_areas GROUP BY level) and flush your tree cache before touching code.

  • Low adoption means no safety net. With zero dependents and a modest star count, there is no community fixing edge cases for you. Pin the version in composer.json, read the migration before running it, and be prepared to own the data layer yourself.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk