hakanispirli / laravel-turkey-geo-database
Laravel package for Turkish geographic data (cities, districts, neighborhoods) based on PTT database. Includes 81 cities, 900+ districts, and 50,000+ neighborhoods with postal codes.
Package info
github.com/hakanispirli/laravel-turkey-geo-database
pkg:composer/hakanispirli/laravel-turkey-geo-database
Requires
- php: ^8.2
- illuminate/database: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
README
Complete Turkish geographic data package for Laravel applications. Get all Turkish cities, districts, and neighborhoods with postal codes in your database with just a few commands.
Installation
Install the package via Composer:
composer require hakanispirli/laravel-turkey-geo-database
Setup Options
Choose the installation method that fits your needs:
Option 1: Standard Installation (Most Common)
Use this if you want the default database structure without modifications.
# 1. Publish migrations (creates timestamped migration files) php artisan vendor:publish --tag=turkey-geo-migrations # 2. Publish data files php artisan vendor:publish --tag=turkey-geo-data # 3. Run migrations php artisan migrate # 4. Seed the database php artisan db:seed --class="Webmarka\TurkeyGeo\Database\Seeders\TurkeyGeoSeeder"
That's it! Your database now contains all Turkish geographic data.
Option 2: Custom Installation
Use this if you need to modify the database structure (add columns, change types, etc.).
# 1. Publish migrations php artisan vendor:publish --tag=turkey-geo-migrations # 2. Customize the migration files in database/migrations/ # Add your custom columns, indexes, or modifications # 3. Publish data files php artisan vendor:publish --tag=turkey-geo-data # 4. Run migrations php artisan migrate # 5. Seed the database php artisan db:seed --class="Webmarka\TurkeyGeo\Database\Seeders\TurkeyGeoSeeder"
💡 Pro Tip: Migration files are published with current timestamps, so they'll run after your existing migrations without conflicts.
⏱️ Seeding Time: Approximately 30-60 seconds to insert all data with progress tracking.
Basic Usage
Get All Cities
use Webmarka\TurkeyGeo\Models\City; $cities = City::all();
Get Districts for a City
$ankara = City::where('name', 'ANKARA')->first(); $districts = $ankara->districts;
Get Neighborhoods for a District
use Webmarka\TurkeyGeo\Models\District; $district = District::find(1); $neighborhoods = $district->neighborhoods;
Search by Postal Code
use Webmarka\TurkeyGeo\Models\Neighborhood; $neighborhoods = Neighborhood::where('postal_code', '06100')->get();
Common Use Cases
1. City Dropdown for Forms
// Controller public function create() { $cities = City::orderBy('name')->get(); return view('address.create', compact('cities')); }
<!-- Blade View --> <select name="city_id"> <option value="">Select City</option> @foreach($cities as $city) <option value="{{ $city->id }}">{{ $city->name }}</option> @endforeach </select>
2. Dynamic District Loading (AJAX)
// Route Route::get('/api/districts/{cityId}', function ($cityId) { return District::where('city_id', $cityId) ->orderBy('name') ->get(['id', 'name']); });
// JavaScript $('#city_id').change(function() { let cityId = $(this).val(); $.get(`/api/districts/${cityId}`, function(districts) { $('#district_id').html('<option value="">Select District</option>'); districts.forEach(district => { $('#district_id').append(`<option value="${district.id}">${district.name}</option>`); }); }); });
3. Get Full Address Details
$neighborhood = Neighborhood::with('district.city')->find($id); echo $neighborhood->name; // Neighborhood name echo $neighborhood->district->name; // District name echo $neighborhood->district->city->name; // City name echo $neighborhood->postal_code; // Postal code
Database Structure
cities
id- City ID (1-81)name- City name
districts
id- District IDcity_id- Belongs to cityname- District name
neighborhoods
id- Neighborhood IDdistrict_id- Belongs to districtname- Neighborhood namearea- Area/region informationpostal_code- PTT postal code
Configuration (Optional)
You can customize table names, seeding batch size, and other options:
php artisan vendor:publish --tag=turkey-geo-config
Edit config/turkey-geo.php to customize:
- Table names
- Seeding batch size
- Progress display options
Advanced Features
Eager Loading Relationships
// Load city with all its districts $city = City::with('districts')->find(7); // Load district with neighborhoods $district = District::with('neighborhoods')->find(1);
Validation Example
use Illuminate\Validation\Rule; public function rules() { return [ 'city_id' => 'required|exists:cities,id', 'district_id' => [ 'required', Rule::exists('districts', 'id')->where('city_id', $this->city_id), ], 'neighborhood_id' => [ 'required', Rule::exists('neighborhoods', 'id')->where('district_id', $this->district_id), ], ]; }
Caching for Performance
use Illuminate\Support\Facades\Cache; $cities = Cache::remember('turkish-cities', 3600, function () { return City::orderBy('name')->get(); });
Troubleshooting
Seeder Class Not Found
Run composer autoload dump:
composer dump-autoload
Data Files Not Found Error
Make sure you published the data files:
php artisan vendor:publish --tag=turkey-geo-data
Verify files exist in database/data/turkey-geo/ directory.
Memory Issues During Seeding
Reduce batch size in config file:
// config/turkey-geo.php 'seeding' => [ 'batch_size' => 500, // Default is 1000 ],
Requirements
- PHP ^8.2
- Laravel ^11.0 or ^12.0
What's Included
- 81 Turkish Cities (İller)
- 900+ Districts (İlçeler)
- 50,000+ Neighborhoods (Mahalleler)
- PTT Postal Codes for all neighborhoods
- Optimized Performance with indexed database columns
- Eloquent Models with pre-configured relationships
- Progress Tracking during data seeding
Contributing
Contributions are welcome! Please submit a Pull Request.
Security
If you discover any security issues, please email destek@webmarka.com.
Credits
- Hakan İspirli
- Webmarka
- PTT (Turkish Post) for official postal code data
License
The MIT License (MIT). See License File for more information.
Made with ❤️ by Webmarka