plin-code / laravel-istat-geography
Laravel package for importing and managing Italian geography data from ISTAT
Package info
github.com/plin-code/laravel-istat-geography
pkg:composer/plin-code/laravel-istat-geography
Fund package maintenance!
Requires
- php: ^8.3
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^12.0 || ^13.0
- league/csv: ^9.24
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^2.9||^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0 || ^4.0
- pestphp/pest-plugin-arch: ^3.0 || ^4.0
- pestphp/pest-plugin-laravel: ^3.0 || ^4.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
This package is not auto-updated.
Last update: 2026-03-19 14:02:06 UTC
README
A Laravel package for importing and managing Italian geographical data from ISTAT.
Features
- ๐ฎ๐น Import Italian regions, provinces, and municipalities from ISTAT
- ๐ฎ Import Italian postal codes (CAP) with support for multi-CAP municipalities
- ๐ Incremental updates: add new records, update changes, soft-delete removed ones
- ๐ Daily CSV caching to avoid unnecessary requests
- ๐ Eloquent models with hierarchical relationships
- โก Artisan commands for easy data import and synchronization
- ๐ง Fully configurable via configuration file
- ๐ UUID primary keys and soft deletes support
- ๐งช Comprehensive test suite with mocked HTTP requests
Requirements
- PHP 8.3+
- Laravel 11.0+ or 12.0+
- league/csv 9.0+
- guzzlehttp/guzzle 7.0+
Installation
composer require plin-code/laravel-istat-geography
Quick Start
- Install the package:
composer require plin-code/laravel-istat-geography
- Publish the configuration:
php artisan vendor:publish --provider="PlinCode\IstatGeography\IstatGeographyServiceProvider"
- Run migrations:
php artisan migrate
- Import the data:
php artisan geography:import
That's it! You now have all Italian geographical data in your database.
Commands
geography:import
Performs a full import of all geographical data from ISTAT. Use this for the initial data load.
php artisan geography:import
Options
| Option | Description |
|---|---|
--cap |
Also import postal codes (CAP) after ISTAT data |
--cap-only |
Import only postal codes, skip ISTAT data (requires existing municipalities) |
--cap-file=<path> |
Use a local JSON file for CAP data instead of downloading |
Examples
# Import ISTAT data only php artisan geography:import # Import ISTAT data + CAP (using local file - recommended) php artisan geography:import --cap --cap-file=cap-dataset.json # Update only CAP on existing municipalities php artisan geography:import --cap-only --cap-file=cap-dataset.json
Note: The remote GeoJSON with geometries is ~464MB. Using
--cap-filewith a preprocessed JSON file (~3MB) is recommended for better performance.
geography:download-cap
Downloads CAP GeoJSON data and saves it locally for offline import. Useful when you want to download once and import multiple times.
# Download from default URL (config/env) php artisan geography:download-cap # Download from custom URL php artisan geography:download-cap --url=https://example.com/cap.json # Specify output path php artisan geography:download-cap --output=storage/app/my-cap.json
After downloading, import with:
php artisan geography:import --cap --cap-file=storage/app/cap-dataset.json
geography:update
Incrementally synchronizes your database with the latest ISTAT data. It compares the current ISTAT CSV against your existing records and applies only the differences: new records are added, changed records are updated, and records no longer present in ISTAT are soft-deleted.
php artisan geography:update
Options
| Option | Description |
|---|---|
--dry-run |
Simulate the update without making any database changes. Shows what would be added, modified, or deleted. |
--force |
Continue execution even if non-critical errors occur (errors are logged as warnings). |
Verbosity Levels
| Flag | Output |
|---|---|
| (none) | Final summary only (e.g. 3 added, 1 modified, 0 deleted) |
-v |
Download progress, list of new/modified/suppressed records, progress bar |
-vv |
Field-level change details (e.g. name: Old Name โ New Name) |
-vvv |
Debug output with timing information for each operation |
Examples
# Preview changes without applying them php artisan geography:update --dry-run # Run with verbose output php artisan geography:update -v # Run with full debug output php artisan geography:update -vvv # Force continue on non-critical errors php artisan geography:update --force
All database operations are wrapped in a transaction. If any error occurs (and --force is not set), all changes are automatically rolled back.
Configuration
Publish the configuration file:
php artisan vendor:publish --provider="PlinCode\IstatGeography\IstatGeographyServiceProvider"
The config/istat-geography.php file allows you to customize:
- Table names: Customize the database table names
- Model classes: Use your own model classes by extending the base ones
- CSV URL: Change the ISTAT data source URL (also via
ISTAT_CSV_URLenv) - CAP GeoJSON URL: Change the CAP data source URL (also via
CAP_GEOJSON_URLenv) - Temporary file name: Customize the cache file name
Example Configuration
return [ 'tables' => [ 'regions' => 'my_regions', 'provinces' => 'my_provinces', 'municipalities' => 'my_municipalities', ], 'models' => [ 'region' => \App\Models\Region::class, 'province' => \App\Models\Province::class, 'municipality' => \App\Models\Municipality::class, ], 'import' => [ 'csv_url' => 'https://custom-url.com/data.csv', 'temp_filename' => 'my_istat_data.csv', ], ];
Models
The package provides three Eloquent models:
Region
use PlinCode\IstatGeography\Models\Geography\Region; $region = Region::where('name', 'Piemonte')->first(); $provinces = $region->provinces;
Province
use PlinCode\IstatGeography\Models\Geography\Province; $province = Province::where('code', 'TO')->first(); $municipalities = $province->municipalities; $region = $province->region;
Municipality
use PlinCode\IstatGeography\Models\Geography\Municipality; $municipality = Municipality::where('name', 'Torino')->first(); $province = $municipality->province;
ISTAT Fields
Each model exposes a static istatFields() method that returns the list of fields managed by ISTAT data. These are the fields that the geography:update command is allowed to overwrite. Any additional fields you add to your extended models will not be touched during updates.
Region::istatFields(); // ['name', 'istat_code'] Province::istatFields(); // ['name', 'code', 'istat_code', 'region_id'] Municipality::istatFields(); // ['name', 'istat_code', 'province_id', 'bel_code'] Municipality::capFields(); // ['postal_code', 'postal_codes']
Extending Models
If you want to use the package models in your main project, you can extend them:
// app/Models/Region.php namespace App\Models; use PlinCode\IstatGeography\Models\Geography\Region as BaseRegion; class Region extends BaseRegion { // Add your project-specific logic here public function customMethod() { return $this->provinces()->count(); } }
// app/Models/Province.php namespace App\Models; use PlinCode\IstatGeography\Models\Geography\Province as BaseProvince; class Province extends BaseProvince { // Add your project-specific logic here }
// app/Models/Municipality.php namespace App\Models; use PlinCode\IstatGeography\Models\Geography\Municipality as BaseMunicipality; class Municipality extends BaseMunicipality { // Add your project-specific logic here }
Remember to update the models section in the configuration file to point to your custom classes.
Database Structure
Regions
id(UUID, primary key)name(string)istat_code(string, unique)created_at,updated_at,deleted_at
Provinces
id(UUID, primary key)region_id(UUID, foreign key)name(string)code(string, unique)istat_code(string, unique)created_at,updated_at,deleted_at
Municipalities
id(UUID, primary key)province_id(UUID, foreign key)name(string)istat_code(string, unique)bel_code(string, nullable) - Cadastral/Belfiore code for CAP matchingpostal_code(string, nullable) - Primary postal code (CAP)postal_codes(string, nullable) - Range of postal codes for large municipalities (e.g., "00118-00199")created_at,updated_at,deleted_at
Relationships
RegionโhasManyโProvinceProvinceโbelongsToโRegionProvinceโhasManyโMunicipalityMunicipalityโbelongsToโProvince
Replacing Existing Command
If you already have a geography:import command in your project, you can replace it with the package's command:
// In app/Console/Kernel.php or in your existing command Artisan::command('geography:import', function () { $this->info('Starting geographical data import...'); try { $count = \PlinCode\IstatGeography\Facades\IstatGeography::import(); $this->info("Import completed successfully! Imported {$count} municipalities."); } catch (\Exception $e) { $this->error('Error during import: ' . $e->getMessage()); } })->purpose('Import regions, provinces and municipalities from ISTAT');
Testing
Run the test suite:
composer test
The package includes:
- โ Unit tests for models and relationships
- โ Feature tests for the import service
- โ Feature tests for the update command and services
- โ Mocked HTTP requests (no external dependencies)
- โ PHPStan static analysis
- โ Pest PHP testing framework
Test Coverage
- Models and their relationships
- Import service with CSV processing
- Compare service for detecting changes
- Update service for applying changes
- Artisan command functionality (import and update)
- Configuration handling
Data Sources
ISTAT Data
Geographic data (regions, provinces, municipalities) is sourced from ISTAT (Italian National Institute of Statistics), the official Italian government statistics agency.
Postal Codes (CAP)
Postal code data is sourced from Zornade Data Downloads.
A huge thanks to Zornade for their incredible work in making Italian public data freely available. Their dedication to open data helps developers build better applications for Italian users.
Contributing
- Fork the project
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
The MIT License (MIT). Please see License File for more information.
