diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 90f2789..f8590e2 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -2,43 +2,38 @@ name: PHP Composer on: push: - branches: ["main"] + branches: ['main'] pull_request: - branches: ["main"] + branches: ['main'] permissions: contents: read jobs: build: + name: PHP ${{ matrix.php-version }} runs-on: ubuntu-latest + strategy: - fail-fast: true + fail-fast: false matrix: - php: [8.0, 8.1, 8.2] + php-version: ['8.2', '8.3', '8.4', '8.5'] + steps: - - uses: actions/checkout@v3 + - name: Check out repository + uses: actions/checkout@v7 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: ${{ matrix.php }} - coverage: none + php-version: ${{ matrix.php-version }} + tools: composer - - name: Validate composer.json and composer.lock + - name: Validate Composer configuration run: composer validate --strict - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - name: Install dependencies - run: composer install --prefer-dist --no-progress + run: composer install --no-progress --no-interaction - name: Run test suite run: composer run-script test diff --git a/.gitignore b/.gitignore index d599431..8c302ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ -.idea/* .DS_Store -/nbproject/ +.idea/* +.phpunit.cache .phpunit.result.cache -vendor \ No newline at end of file +/nbproject/ +composer.lock +vendor diff --git a/README.md b/README.md index cf3fad6..0cc7bbb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Basic Address Normalizer + [![Build Status](https://travis-ci.org/zerodahero/address-normalization.svg?branch=master)](https://travis-ci.org/zerodahero/address-normalization) ## Purpose @@ -108,6 +109,7 @@ $address->toArray(); ``` ### Hashing + If you only need to make use of a consistent way of hashing (e.g. if you're starting with a dependable 5-part address, such as from a 3rd party service), you can build a `SimpleAddress`. ```php diff --git a/composer.json b/composer.json index 117d2a0..e37aea5 100644 --- a/composer.json +++ b/composer.json @@ -13,10 +13,10 @@ } }, "require": { - "php": ">=8.0" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^11|^12|^13" }, "scripts": { "test": "phpunit" diff --git a/phpunit.xml b/phpunit.xml index 5dff6a6..d6c74fa 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,18 +1,8 @@ - - - - ./tests - - + + + + ./tests + + diff --git a/src/Normalizer.php b/src/Normalizer.php index 85e29e4..e775efa 100644 --- a/src/Normalizer.php +++ b/src/Normalizer.php @@ -143,7 +143,29 @@ private function setupRegularExpressions() $this->zip_regexp = '(\d{5})(?:-?(\d{4})?)'; $this->corner_regexp = '(?:\band\b|\bat\b|&|\@)'; - $this->unit_regexp = '(?:(su?i?te|p\W*[om]\W*b(?:ox)?|dept|apt|apartment|ro*m|fl|unit|box)\W+|\#\W*|)([\w-]+)'; + + /* + * Unit pattern. A 'unit' is an apartment, suite, etc. + * + * There are two capture groups: the unit prefix and the unit value. + * + * The unit prefix can be: + * 1. A word from a set of acceptable words (e.g., 'Apt' or 'Apartment' + * or 'Ste') followed by whitespace or punctuation. + * 2. A pound sign (#) followed by whitespace or punctuation. + * 3. Missing (no prefix), but only if the unit value contains a number. + * + * The requirement for a number in the unit value allows us to detect + * the end of the street address and the beginning of the city when the + * user doesn't add commas between address segments or when the city is + * a multi-word name like 'Los Angeles.' + */ + $this->unit_regexp = '(?:' + . '(su?i?te|p\W*[om]\W*b(?:ox)?|dept|apt|apartment|ro*m|fl|unit|box)\W+' + . '|\#\W*' + . '|(?=[\w-]*\d)' + . ')([\w-]+)'; + $this->street_regexp = '(?:' . '(?:(' . $this->direct_regexp . ')\W+' diff --git a/tests/NormalizerTest.php b/tests/NormalizerTest.php index 7e00f02..20b3dfd 100644 --- a/tests/NormalizerTest.php +++ b/tests/NormalizerTest.php @@ -1,8 +1,17 @@ =8.2, it supports PHPUnit 11, 12, and 13. +The DataProviders syntax was changed in v12. (Using both causes no problems, as +the docblock syntax is ignored as of v12.) +*/ + namespace Tests; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use ZeroDaHero\Normalizer; use ZeroDaHero\Address; use ZeroDaHero\SimpleAddress; @@ -45,20 +54,19 @@ public function testReturnsFalseOnBadFivePartStrict() $this->assertFalse($address); } - public function normalizesAddressesDataProvider() - { + public static function normalizesAddressesDataProvider() { return [ [ - '1234 Main St. SE, Minneapolis, MN 55401', - '1234 Main Street Southeast, Minneapolis, MN 55401' + '1234 Main Street Southeast, Minneapolis, MN 55401', + '1234 Main St. SE, Minneapolis, MN 55401' ], [ - '1234 Main St. SE, Minneapolis, MN 55401', - '1234 Main St SE, Minneapolis, Minnesota 55401' + '1234 Main St SE, Minneapolis, Minnesota 55401', + '1234 Main St. SE, Minneapolis, MN 55401' ], [ - '1234 Main St. SE, Minneapolis, MN 55401', - '1234 Main St southeast, Minneapolis, Minnesota 55401' + '1234 Main St southeast, Minneapolis, Minnesota 55401', + '1234 Main St. SE, Minneapolis, MN 55401' ], ]; } @@ -67,18 +75,18 @@ public function normalizesAddressesDataProvider() * @test * @dataProvider normalizesAddressesDataProvider */ - public function testNormalizesAddresses($firstAddress, $secondAddress) + #[DataProvider('normalizesAddressesDataProvider')] + public function testNormalizesAddresses($test, $expected_result) { $normalizer = new Normalizer(); $this->assertEquals( - (string)$normalizer->parse($firstAddress), - (string)$normalizer->parse($secondAddress) + (string)$normalizer->parse($expected_result), + (string)$normalizer->parse($test) ); } - public function badAddressesDataProvider() - { + public static function badAddressesDataProvider() { return [ 'double unit no commas' => ['1234 Main St. SE Unit 101 Unit 101'], 'double unit mismatch comma' => ['1234 Main St. SE, Unit 101 Apt 101, Minneapolis, MN 55555'], @@ -91,54 +99,127 @@ public function badAddressesDataProvider() * @test * @dataProvider badAddressesDataProvider */ - public function testFailsOnBadAddresses($badAddress) + #[DataProvider('badAddressesDataProvider')] + public function testFailsOnBadAddresses($address) { $normalizer = new Normalizer(); - $this->assertFalse($normalizer->parse($badAddress)); + $this->assertFalse($normalizer->parse($address)); } - /** @test */ - public function testHandlesAddressWithoutUnitPrefix() - { - $normalizer = new Normalizer(); - - $addresses = [ - [ // Test without unit prefix + public static function addressesWithoutUnitPrefixDataProvider() { + return [ + 'Test without unit prefix' => [ 'test' => '1234 W Main Avenue 1W, Chicago, IL, 60647', 'expected_result' => '1234 W Main Ave #1W, Chicago, IL 60647' ], - [ // Regression test with unit prefix + 'Regression test with "Unit" unit prefix' => [ 'test' => '1234 W Main Avenue Unit 1W, Chicago, IL, 60647', 'expected_result' => '1234 W Main Ave Unit 1W, Chicago, IL 60647' ], - [ // Regression test with unit prefix + 'Regression test with "Apartment" unit prefix' => [ 'test' => '1234 W Main Avenue Apartment 1W, Chicago, IL, 60647', 'expected_result' => '1234 W Main Ave Apartment 1W, Chicago, IL 60647' ], - [ // Regression test with unit prefix + 'Regression test with "#" unit prefix' => [ 'test' => '1234 W Main Avenue #1W, Chicago, IL, 60647', 'expected_result' => '1234 W Main Ave #1W, Chicago, IL 60647' ], - [ // Regression test with unit prefix + 'Regression test with "Room" unit prefix' => [ 'test' => '1234 W Main Avenue Room 1, Chicago, IL, 60647', 'expected_result' => '1234 W Main Ave Room 1, Chicago, IL 60647' ], - [ // Regression test with unit prefix + 'Regression test with "Apt" unit prefix' => [ 'test' => '1234 W Main Avenue Apt 1W, Chicago, IL, 60647', 'expected_result' => '1234 W Main Ave Apt 1W, Chicago, IL 60647' ], - [ // Regression test without any unit + 'Regression test without any unit' => [ 'test' => '1234 W Main Street, Chicago, IL, 60647', 'expected_result' => '1234 W Main St, Chicago, IL 60647' ], ]; + } + + /** + * @test + * @dataProvider addressesWithoutUnitPrefixDataProvider + */ + #[DataProvider('addressesWithoutUnitPrefixDataProvider')] + public function testHandlesAddressWithoutUnitPrefix($test, $expected_result) + { + $normalizer = new Normalizer(); - foreach ($addresses as $address) { - $this->assertEquals( - $address['expected_result'], - (string)$normalizer->parse($address['test']) - ); - } + $this->assertEquals( + $expected_result, + (string)$normalizer->parse($test) + ); + } + + public static function addressesWithMultiWordCityDataProvider() { + return [ + 'Two-word city; without unit; with commas' => [ + 'test' => '123 Main Street, Los Angeles, CA 90012', + 'expected_result' => '123 Main St, Los Angeles, CA 90012', + ], + 'Three-word city; without unit; with commas' => [ + 'test' => '123 Main Street, San Luis Obispo, CA 93405', + 'expected_result' => '123 Main St, San Luis Obispo, CA 93405', + ], + 'Multi-word city; without unit; without commas' => [ + 'test' => '123 Main Street Los Angeles CA 90012', + 'expected_result' => '123 Main St, Los Angeles, CA 90012', + ], + 'Multi-word city; with unit prefix; with commas' => [ + 'test' => '123 Main Street Apt 14A, Los Angeles, CA 90012', + 'expected_result' => '123 Main St Apt 14A, Los Angeles, CA 90012', + ], + 'Multi-word city; without unit prefix; with commas' => [ + 'test' => '123 Main Street 1A, Los Angeles, CA 90012', + 'expected_result' => '123 Main St #1A, Los Angeles, CA 90012', + ], + 'Multi-word city; with unit prefix; without commas' => [ + 'test' => '123 Main Street Apt 14, Los Angeles, CA 90012', + 'expected_result' => '123 Main St Apt 14, Los Angeles, CA 90012', + ], + 'Multi-word city; without unit prefix; without commas' => [ + 'test' => '123 Main Street 1A Los Angeles CA 90012', + 'expected_result' => '123 Main St #1A, Los Angeles, CA 90012', + ], + 'Multi-word city; without unit prefix; unit is only a number; without commas' => [ + 'test' => '123 Main Street 1 Los Angeles CA 90012', + 'expected_result' => '123 Main St #1, Los Angeles, CA 90012', + ], + + // This is an edge case where we can't tell whether the 'A' is part + // of the street name or a unit. Since there's a comma, we assume + // it's part of the street. + 'Multi-word city; without unit prefix; unit is only a letter; with commas' => [ + 'test' => '123 Main Street A, Los Angeles, CA 90012', + 'expected_result' => '123 Main Street A, Los Angeles, CA 90012', + ], + + // This is an edge case where we can't tell whether the 'A' is part + // of the street or the city. Since there's no comma and we've + // found 'street,' we assume it's part of the city. + 'Multi-word city; without unit prefix; unit is only a letter; without commas' => [ + 'test' => '123 Main Street A Los Angeles CA 90012', + 'expected_result' => '123 Main St, A Los Angeles, CA 90012', + ], + ]; + } + + /** + * @test + * @dataProvider addressesWithMultiWordCityDataProvider + */ + #[DataProvider('addressesWithMultiWordCityDataProvider')] + public function testHandlesAddressWithMultiWordCity($test, $expected_result) + { + $normalizer = new Normalizer(); + + $this->assertEquals( + $expected_result, + (string)$normalizer->parse($test) + ); } }