├── .github └── workflows │ └── phpunit.yml ├── .gitignore ├── LICENSE ├── composer.json ├── phpunit.php ├── phpunit.xml ├── readme.md ├── src ├── CountryState.php ├── CountryStateFacade.php ├── CountryStateServiceProvider.php ├── Exceptions │ ├── CountryNotFoundException.php │ └── StateNotFoundException.php └── config │ └── countrystate.php └── tests ├── CountryStateTest.php └── laravel └── CountryStateLaravelTest.php /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit 2 | 3 | on: 4 | pull_request: 5 | branches: [ master, develop ] 6 | push: 7 | branches: [ master, develop ] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | php: [8.2, 8.3, 8.4] 16 | 17 | name: PHP ${{ matrix.php }} tests 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Setup PHP 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: ${{ matrix.php }} 25 | coverage: none 26 | 27 | - name: Validate composer.json and composer.lock 28 | run: composer validate 29 | 30 | - name: Install dependencies 31 | run: composer install --prefer-dist --no-progress --no-suggest 32 | 33 | - name: Run test suite 34 | run: vendor/bin/phpunit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .phpunit.result.cache 5 | /.phpunit.cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Doug Sisk 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dougsisk/laravel-country-state", 3 | "description": "Country & state helper for Laravel.", 4 | "keywords": [ 5 | "countries", 6 | "country", 7 | "laravel", 8 | "state", 9 | "states" 10 | ], 11 | "homepage": "https://github.com/DougSisk/laravel-country-state", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Doug Sisk", 16 | "homepage": "https://dougsisk.com" 17 | } 18 | ], 19 | "support": { 20 | "issues": "https://github.com/DougSisk/laravel-country-state/issues" 21 | }, 22 | "require": { 23 | "php": "^8.2", 24 | "illuminate/support": "^11.0|^12.0", 25 | "rinvex/countries": "^9.0" 26 | }, 27 | "require-dev": { 28 | "orchestra/testbench": "^9.0|^10.0", 29 | "phpunit/phpunit": "^10.0|^11.0|^12.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "DougSisk\\CountryState\\": "src/" 34 | } 35 | }, 36 | "extra": { 37 | "laravel": { 38 | "providers": [ 39 | "DougSisk\\CountryState\\CountryStateServiceProvider" 40 | ] 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /phpunit.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests/CountryStateTest.php 6 | 7 | 8 | tests/laravel/CountryStateLaravelTest.php 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Laravel Country & State Helper 2 | ============================== 3 | [![Latest Stable Version](https://poser.pugx.org/dougsisk/laravel-country-state/version)](https://packagist.org/packages/dougsisk/laravel-country-state) 4 | [![Total Downloads](https://poser.pugx.org/dougsisk/laravel-country-state/downloads)](https://packagist.org/packages/dougsisk/laravel-country-state) 5 | [![License](https://poser.pugx.org/dougsisk/laravel-country-state/license)](https://packagist.org/packages/dougsisk/laravel-country-state) 6 | 7 | A helper to list countries & states in English in **Laravel 11.0+**. 8 | 9 | What's Changed in 5.0 10 | ----------------- 11 | 12 | * **PHP 8.2+ required** 13 | 14 | _I'm aware the underlying country/state data package I utilize has not been actively maintained. I've looked into other packages, but have yet to find one that can easily be swapped in. Please feel free to submit a PR if you find one you think is a suitable replacement._ 15 | 16 | Installation 17 | ------------ 18 | 19 | Require this package with composer: 20 | 21 | ``` 22 | composer require dougsisk/laravel-country-state 23 | ``` 24 | 25 | This package will automatically be discovered by Laravel, if enabled. If you don't have auto package discovery on, you'll need to add the following service provider to your config/app.php: 26 | 27 | ``` 28 | DougSisk\CountryState\CountryStateServiceProvider::class, 29 | ``` 30 | 31 | Copy the package config to your local config with the publish command: 32 | 33 | ``` 34 | php artisan vendor:publish --provider="DougSisk\CountryState\CountryStateServiceProvider" --tag="config" 35 | ``` 36 | 37 | Configuration 38 | ------------- 39 | 40 | By default, the helper will preload states for the US. You can change this via the `preloadCountryStates` config option: 41 | 42 | ``` 43 | 'preloadCountryStates' => ['CA', 'MX', 'US'] 44 | ``` 45 | 46 | If you don't want every country to be returned, you can define countries using the `limitCountries` config option: 47 | 48 | ``` 49 | 'limitCountries' => ['CA', 'MX', 'US'] 50 | ``` 51 | 52 | Usage 53 | ----- 54 | 55 | You may now use the `CountryState` facade to access countries and states. 56 | 57 | **Remember to import the namespace to access the facade in your files:** 58 | 59 | ``` 60 | use CountryState; 61 | ``` 62 | 63 | To get an array of countries: 64 | 65 | ``` 66 | $countries = CountryState::getCountries(); 67 | ``` 68 | 69 | The array keys will be the countries' 2 letter ISO code and the values will be the countries' English name. You may also set the 3 letter ISO key as the argument to receive translations of the countries' names (limited support). 70 | 71 | 72 | To get an array of a country's states, simply pass the country's 2 letter ISO code: 73 | 74 | ``` 75 | $states = CountryState::getStates('US'); 76 | ``` 77 | 78 | The array keys will be the states' 2 letter ISO code and the values will be the states' English name. 79 | 80 | License 81 | ------- 82 | 83 | This library is available under the [MIT license](LICENSE). 84 | -------------------------------------------------------------------------------- /src/CountryState.php: -------------------------------------------------------------------------------- 1 | loadCountry($code); 27 | 28 | $this->countries[$country->getIsoAlpha2()] = $country; 29 | } 30 | } else { 31 | $countries = CountryLoader::countries(true, true); 32 | 33 | foreach ($countries as $country) { 34 | $this->countries[$country->getIsoAlpha2()] = $country; 35 | } 36 | } 37 | 38 | $this->setLanguage($language); 39 | 40 | if ($preloadCountryStates) { 41 | foreach ($preloadCountryStates as $country) { 42 | $this->addCountryStates($country); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Get a list of countries. If class has been constructed to limit countries, only those countries will be returned. 49 | * The array returned will be countries' names in the class' set language. 50 | * If a different language is desired, pass the three character ISO 639-3 code of the desired language 51 | */ 52 | public function getCountries(?string $language = null): array 53 | { 54 | if ($language) { 55 | $this->setLanguage($language); 56 | } 57 | 58 | return $this->countriesTranslated; 59 | } 60 | 61 | /** 62 | * Get the information of a country by passing its two character code 63 | */ 64 | public function getCountry(string $lookFor): Country 65 | { 66 | return $this->loadCountry($lookFor); 67 | } 68 | 69 | /** 70 | * Get the name of a country by passing its two character code 71 | */ 72 | public function getCountryName(string $lookFor): string 73 | { 74 | return $this->getCountry($lookFor)->getName(); 75 | } 76 | 77 | /** 78 | * Get a list of states for a given country. 79 | * The country's two character ISO code 80 | */ 81 | public function getStates(string $country): array 82 | { 83 | return $this->findCountryStates($country); 84 | } 85 | 86 | /** 87 | * Get the name of a state by passing its two character code 88 | * Specifying a two character ISO country code will limit the search to a specific country 89 | */ 90 | public function getStateName(string $lookFor, ?string $country = null): string 91 | { 92 | if ($country) { 93 | if (! isset($this->states[$country])) { 94 | $this->findCountryStates($country); 95 | } 96 | 97 | if (isset($this->states[$country][$lookFor])) { 98 | return $this->states[$country][$lookFor]; 99 | } 100 | 101 | throw new Exceptions\StateNotFoundException; 102 | } 103 | 104 | foreach ($this->countries as $countryCode => $countryName) { 105 | $this->findCountryStates($countryCode); 106 | 107 | if (isset($this->states[$countryCode][$lookFor])) { 108 | return $this->states[$countryCode][$lookFor]; 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Pass a country code to search a single country or an array of codes to search several countries in the order given 115 | * If $countries is null all countries will be searched, which can be slow. 116 | */ 117 | public function getStateCode(string $lookFor, null|string|array $countries = null): ?string 118 | { 119 | $lookFor = mb_strtoupper($lookFor); 120 | 121 | if (is_null($countries)) { 122 | $countries = array_keys($this->countries); 123 | } elseif (is_string($countries)) { 124 | $countries = [$countries]; 125 | } 126 | 127 | foreach ($countries as $countryCode) { 128 | $states = array_map('mb_strtoupper', $this->findCountryStates($countryCode)); 129 | 130 | if ($code = array_search($lookFor, $states)) { 131 | return $code; 132 | } 133 | } 134 | 135 | return null; 136 | } 137 | 138 | /** 139 | * Change the default translation language using the three character ISO 639-3 code of the desired language. 140 | * Country name translations will be reloaded. 141 | */ 142 | public function setLanguage(string $language): self 143 | { 144 | $this->language = $language; 145 | 146 | foreach ($this->countries as $country) { 147 | $this->countriesTranslated[$country->getIsoAlpha2()] = $country->getTranslation($this->language)['common']; 148 | } 149 | 150 | return $this; 151 | } 152 | 153 | protected function loadCountry($code): Country 154 | { 155 | try { 156 | return CountryLoader::country(mb_strtolower($code)); 157 | } catch (Exception $e) { 158 | throw new Exceptions\CountryNotFoundException; 159 | } 160 | } 161 | 162 | protected function findCountryStates($country): array 163 | { 164 | if (! array_key_exists($country, $this->states)) { 165 | $this->addCountryStates($country); 166 | } 167 | 168 | return $this->states[$country]; 169 | } 170 | 171 | protected function addCountryStates($country): void 172 | { 173 | if (! $country instanceof Country) { 174 | $country = $this->loadCountry($country); 175 | } 176 | 177 | $countryCode = $country->getIsoAlpha2(); 178 | 179 | $this->states[$countryCode] = []; 180 | $states = $country->getDivisions(); 181 | 182 | if (! is_null($states)) { 183 | foreach ($states as $code => $division) { 184 | $code = preg_replace('/([A-Z]{2}-)/', '', $code); 185 | $this->states[$countryCode][$code] = $division['name']; 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/CountryStateFacade.php: -------------------------------------------------------------------------------- 1 | app->singleton('CountryState', function () { 18 | return new CountryState(config('countrystate.limitCountries'), config('countrystate.preloadCountryStates'), config('countrystate.language')); 19 | }); 20 | $this->mergeConfigFrom(__DIR__ . '/config/countrystate.php', 'countrystate'); 21 | } 22 | 23 | /** 24 | * Publish the plugin configuration. 25 | */ 26 | public function boot() 27 | { 28 | $this->publishes([ 29 | __DIR__ . '/config/countrystate.php' => config_path('countrystate.php'), 30 | ], 'config'); 31 | 32 | AliasLoader::getInstance()->alias('CountryState', 'DougSisk\CountryState\CountryStateFacade'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Exceptions/CountryNotFoundException.php: -------------------------------------------------------------------------------- 1 | 'eng', 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Limit countries to load automatically 22 | |-------------------------------------------------------------------------- 23 | | 24 | | By default, all countries will be loaded. 25 | | This is quite slow, so you can define an array 26 | | of country codes you want to load automatically. 27 | | Example: ['US', 'CA'] 28 | | 29 | */ 30 | 'limitCountries' => [], 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Load states automatically for countries 35 | |-------------------------------------------------------------------------- 36 | | 37 | | You can define an array of countries who's 38 | | states will automatically be loaded. 39 | | 40 | */ 41 | 'preloadCountryStates' => ['US'], 42 | 43 | ]; 44 | -------------------------------------------------------------------------------- /tests/CountryStateTest.php: -------------------------------------------------------------------------------- 1 | getCountries(); 11 | 12 | $this->assertMatchesRegularExpression('/([A-Z]{2})/', key($countries)); 13 | $this->assertEquals('United States', $countries['US']); 14 | } 15 | 16 | public function testGetCountry() 17 | { 18 | $this->assertInstanceOf('Rinvex\Country\Country', (new CountryState())->getCountry('ca')); 19 | } 20 | 21 | public function testGetCountryName() 22 | { 23 | $this->assertEquals('Canada', (new CountryState())->getCountryName('ca')); 24 | } 25 | 26 | public function testGetCountryStates() 27 | { 28 | $states = (new CountryState())->getStates('US'); 29 | 30 | $this->assertMatchesRegularExpression('/([A-Z]{2})/', key($states)); 31 | $this->assertEquals('Hawaii', $states['HI']); 32 | } 33 | 34 | public function testGetCountryStatesForCountriesWithoutStates() 35 | { 36 | $states = (new CountryState())->getStates('AW'); 37 | 38 | $this->assertEmpty($states); 39 | } 40 | 41 | public function testGetStateCode() 42 | { 43 | $stateCode = (new CountryState())->getStateCode('Hawaii', 'US'); 44 | 45 | $this->assertEquals('HI', $stateCode); 46 | } 47 | 48 | public function testGetStateName() 49 | { 50 | $stateName = (new CountryState())->getStateName('HI', 'US'); 51 | 52 | $this->assertEquals('Hawaii', $stateName); 53 | } 54 | 55 | public function testCountryNotFound() 56 | { 57 | $this->expectException(\DougSisk\CountryState\Exceptions\CountryNotFoundException::class); 58 | 59 | (new CountryState())->getStates('USA'); 60 | } 61 | 62 | public function testStateNotFound() 63 | { 64 | $this->expectException(\DougSisk\CountryState\Exceptions\StateNotFoundException::class); 65 | 66 | (new CountryState())->getStateName('AY', 'US'); 67 | } 68 | 69 | public function testGetTranslatedCountries() 70 | { 71 | $translatedCountries = (new CountryState())->getCountries('spa'); 72 | 73 | $this->assertEquals('Estados Unidos', $translatedCountries['US']); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/laravel/CountryStateLaravelTest.php: -------------------------------------------------------------------------------- 1 | set('countrystate.limitCountries', ['CA']); 17 | $app['config']->set('countrystate.preloadCountryStates', ['CA']); 18 | } 19 | 20 | public function testGetCountries() 21 | { 22 | $countries = CountryState::getCountries(); 23 | 24 | $this->assertMatchesRegularExpression("/([A-Z]{2})/", key($countries)); 25 | $this->assertEquals('Canada', $countries['CA']); 26 | } 27 | 28 | public function testGetCountryStates() 29 | { 30 | $states = CountryState::getStates('CA'); 31 | 32 | $this->assertMatchesRegularExpression("/([A-Z]{2})/", key($states)); 33 | $this->assertEquals('Manitoba', $states['MB']); 34 | } 35 | 36 | public function testGetStateCode() 37 | { 38 | $stateCode = CountryState::getStateCode('Manitoba', 'CA'); 39 | 40 | $this->assertEquals('MB', $stateCode); 41 | } 42 | 43 | public function testGetStateName() 44 | { 45 | $stateName = CountryState::getStateName('MB', 'CA'); 46 | 47 | $this->assertEquals('Manitoba', $stateName); 48 | } 49 | 50 | public function testCountryNotFound() 51 | { 52 | $this->expectException(CountryNotFoundException::class); 53 | 54 | CountryState::getStates('CAN'); 55 | } 56 | 57 | public function testStateNotFound() 58 | { 59 | $this->expectException(StateNotFoundException::class); 60 | 61 | CountryState::getStateName('AY', 'CA'); 62 | } 63 | 64 | public function testGetTranslatedCountries() 65 | { 66 | $translatedCountries = CountryState::getCountries('spa'); 67 | 68 | $this->assertEquals('Canadá', $translatedCountries['CA']); 69 | } 70 | } 71 | --------------------------------------------------------------------------------