diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..73faf98 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,83 @@ +name: Tests +on: push + +jobs: + build-84: + name: Static analysis, unit tests, and linting on PHP 8.4 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup PHP 8.4 + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + tools: composer + + - name: Cache Composer dependencies + uses: actions/cache@v5 + with: + path: ./vendor + key: composer-${{ runner.os }}-8.4 + + - name: Install dependencies + run: | + composer install + + - name: Run PHPStan + run: | + ./vendor/bin/phpstan + + - name: PHP Code Sniffer + run: | + ./vendor/bin/phpcs + + - name: Run PHPUnit + run: | + ./vendor/bin/phpunit + + build-85: + name: Static analysis, unit tests, and linting on PHP 8.5 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup PHP 8.5 + uses: shivammathur/setup-php@v2 + with: + php-version: '8.5' + tools: composer + coverage: pcov + + - name: Cache Composer dependencies + uses: actions/cache@v5 + with: + path: ./vendor + key: composer-${{ runner.os }}-8.5 + + - name: Install dependencies + run: | + composer install + + - name: Run PHPStan + run: | + ./vendor/bin/phpstan + + - name: PHP Code Sniffer + run: | + ./vendor/bin/phpcs + + - name: Run PHPUnit + run: | + ./vendor/bin/phpunit --coverage-clover ./coverage.xml + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + verbose: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9e270ad..0f0f75e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.idea composer.phar composer.lock -vendor/ \ No newline at end of file +vendor/ +.phpunit.cache +.phpunit.result.cache \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index deb51d8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: php - -php: - - 5.5 - - 5.6 - - 7.0 - - hhvm - -matrix: - allow_failures: - - php: 7.0 - -sudo: false - -install: travis_retry composer install --no-interaction --prefer-source - -script: vendor/bin/phpunit \ No newline at end of file diff --git a/README.md b/README.md index a3877bf..b9b96ec 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Distance -[![Build Status](https://travis-ci.org/ldebrouwer/distance.svg)](https://travis-ci.org/ldebrouwer/distance) +[![Tests](https://github.com/ldebrouwer/distance/actions/workflows/tests.yml/badge.svg)](https://github.com/ldebrouwer/distance/actions/workflows/tests.yml) [![Latest Stable Version](https://poser.pugx.org/ldebrouwer/distance/v/stable)](https://packagist.org/packages/ldebrouwer/distance) [![Total Downloads](https://poser.pugx.org/ldebrouwer/distance/downloads)](https://packagist.org/packages/ldebrouwer/distance) [![Latest Unstable Version](https://poser.pugx.org/ldebrouwer/distance/v/unstable)](https://packagist.org/packages/ldebrouwer/distance) @@ -16,15 +16,20 @@ When using [Composer](https://getcomposer.org) you can always load in the latest ```bash { "require": { - "ldebrouwer/distance": "~0.2" + "ldebrouwer/distance": "^1.0" } } ``` Check it out [on Packagist](https://packagist.org/packages/ldebrouwer/distance). -## Documentation +## Usage -This is still on the to-do list. The code is pretty well documented though and should autocomplete in most IDEs. +```php +$distance = new Distance() + ->setFormula(Formula::HAVERSINE) + ->setUnit(Unit::KILOMETRES) + ->between(37.331741, -122.030333, 37.422546, -122.084250); +``` ## Still to come - Expanded unit support. diff --git a/composer.json b/composer.json index 02b010b..13af2b5 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,20 @@ { "name": "Luc De Brouwer", "email": "info@lucdebrouwer.nl", - "homepage": "http://www.lucdebrouwer.nl", - "role": "Developer" + "homepage": "http://www.lucdebrouwer.nl" } ], "require": { - "php": ">=5.5" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "4.6.*" + "phpunit/phpunit": "^13.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "roave/security-advisories": "dev-latest", + "phpstan/phpstan-strict-rules": "^2.0", + "squizlabs/php_codesniffer": "^4.0", + "slevomat/coding-standard": "^8.27" }, "extra": { "branch-alias": { @@ -33,6 +38,18 @@ } }, "autoload": { - "psr-4": {"LucDeBrouwer\\Distance\\": "src/"} + "psr-4": { + "ldebrouwer\\Distance\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\ldebrouwer\\Distance\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } -} \ No newline at end of file +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..d2e5a58 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,132 @@ + + + The coding standard for ldebrouwer/distance. + + + + src/ + tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..fa81b49 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +parameters: + level: max + paths: + - src + - tests + reportUnmatchedIgnoredErrors: true + +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan/conf/bleedingEdge.neon diff --git a/phpunit.xml b/phpunit.xml index 6df70d2..a3722f2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,18 +1,23 @@ - + cacheDirectory=".phpunit.cache" + executionOrder="depends,defects" + beStrictAboutOutputDuringTests="true" + displayDetailsOnPhpunitDeprecations="true" + failOnPhpunitDeprecation="true" + failOnRisky="true" + failOnWarning="true"> - - ./tests/ + + tests - \ No newline at end of file + + + + src + + + diff --git a/src/Distance.php b/src/Distance.php index d0eb5c2..0212b4c 100644 --- a/src/Distance.php +++ b/src/Distance.php @@ -1,87 +1,48 @@ - '100', // centimeters - 'in' => '39.3700787', // inches - 'ft' => '3.2808399', // feet - 'm' => '1', // meters - 'km' => '0.001', // kilometers - 'mi' => '0.000621371192', // miles - ]; + private Unit $unit = Unit::KILOMETRES; /** - * Method that returns the distance between two GPS locations in the preferred unit according to the set formula. - * - * @param float $latitudeA The latitude for point A. - * @param float $longitudeA The longitude for point A. - * @param float $latitudeB The latitude for point B. - * @param float $longitudeB The longitude for point B. - * - * @uses betweenVincenty - * @uses betweenHaversine - * @throws Exception - * @return float + * Returns the distance between two GPS locations in the preferred unit according to the set formula. */ - public function between($latitudeA, $longitudeA, $latitudeB, $longitudeB) + public function between(float $latitudeA, float $longitudeA, float $latitudeB, float $longitudeB): float { - if (!floatval($latitudeA) || !floatval($longitudeA) || !floatval($latitudeB) || !floatval($longitudeB)) { - throw new Exception('One or more of the parsed variables are not valid floats.'); - } - - $distanceInMeters = call_user_func_array([$this, 'between' . ucfirst($this->getFormula())], - [$latitudeA, $longitudeA, $latitudeB, $longitudeB]); + $distanceInMeters = match ($this->formula) { + Formula::VINCENTY => $this->betweenVincenty($latitudeA, $longitudeA, $latitudeB, $longitudeB), + Formula::HAVERSINE => $this->betweenHaversine($latitudeA, $longitudeA, $latitudeB, $longitudeB), + }; return $this->convert($distanceInMeters); } /** - * Method that returns the distance between two GPS locations in meters according to the Vincenty formula. - * - * @param float $latitudeA The latitude for point A. - * @param float $longitudeA The longitude for point A. - * @param float $latitudeB The latitude for point B. - * @param float $longitudeB The longitude for point B. - * - * @return float + * Returns the distance between two GPS locations in meters according to the Vincenty formula. */ - private function betweenVincenty($latitudeA, $longitudeA, $latitudeB, $longitudeB) + private function betweenVincenty(float $latitudeA, float $longitudeA, float $latitudeB, float $longitudeB): float { $latitudeA = deg2rad($latitudeA); $longitudeA = deg2rad($longitudeA); $latitudeB = deg2rad($latitudeB); $longitudeB = deg2rad($longitudeB); $longDelta = $longitudeB - $longitudeA; - $a = pow(cos($latitudeB) * sin($longDelta), - 2) + pow(cos($latitudeA) * sin($latitudeB) - sin($latitudeA) * cos($latitudeB) * cos($longDelta), 2); + $a = ((cos($latitudeB) * sin($longDelta)) ** 2) + ((cos($latitudeA) * sin($latitudeB) - sin($latitudeA) * cos($latitudeB) * cos($longDelta)) ** 2); $b = sin($latitudeA) * sin($latitudeB) + cos($latitudeA) * cos($latitudeB) * cos($longDelta); $angle = atan2(sqrt($a), $b); @@ -89,16 +50,9 @@ private function betweenVincenty($latitudeA, $longitudeA, $latitudeB, $longitude } /** - * Method that returns the distance between two GPS locations in meters according to the Haversine formula. - * - * @param float $latitudeA The latitude for point A. - * @param float $longitudeA The longitude for point A. - * @param float $latitudeB The latitude for point B. - * @param float $longitudeB The longitude for point B. - * - * @return float + * Returns the distance between two GPS locations in meters according to the Haversine formula. */ - private function betweenHaversine($latitudeA, $longitudeA, $latitudeB, $longitudeB) + private function betweenHaversine(float $latitudeA, float $longitudeA, float $latitudeB, float $longitudeB): float { $latitudeA = deg2rad($latitudeA); $longitudeA = deg2rad($longitudeA); @@ -106,80 +60,33 @@ private function betweenHaversine($latitudeA, $longitudeA, $latitudeB, $longitud $longitudeB = deg2rad($longitudeB); $latDelta = $latitudeB - $latitudeA; $longDelta = $longitudeB - $longitudeA; - $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) + cos($latitudeA) * cos($latitudeB) * pow(sin($longDelta / 2), 2))); + $angle = 2 * asin(sqrt((sin($latDelta / 2) ** 2) + cos($latitudeA) * cos($latitudeB) * (sin($longDelta / 2) ** 2))); return floor($angle * 6371000); } - /** - * @param $distance - * @return mixed - */ - private function convert($distance) - { - return $distance * $this->getConversion()[$this->getUnit()]; - } - - /** - * Returns the currently set formula used for distance calculation. - * - * @return string - */ - public function getFormula() + private function convert(float $distance): float { - return $this->formula; + return $distance * $this->unit->multiplierFromMetres(); } /** * Sets the formula to be used for distance calculation. - * - * @param string $formula - * - * @throws Exception */ - public function setFormula($formula) + public function setFormula(Formula $formula): self { - if (!in_array($formula, ['vincenty', 'haversine'])) { - throw new Exception('You have tried to set an invalid distance formula.'); - } - $this->formula = $formula; - } - /** - * Returns the currently set unit used when returning the distance between two coordinates. - * - * @return string - */ - public function getUnit() - { - return $this->unit; + return $this; } /** * Sets the unit to be used when returning the distance between two coordinates. - * - * @param string $unit - * - * @throws Exception */ - public function setUnit($unit) + public function setUnit(Unit $unit): self { - if (!in_array($unit, array_keys($this->getConversion()))) { - throw new Exception('You have tried to set an invalid distance unit.'); - } - $this->unit = $unit; - } - /** - * Gets the conversion table between different units. - * - * @return array - */ - public function getConversion() - { - return $this->conversion; + return $this; } - -} \ No newline at end of file +} diff --git a/src/Formula.php b/src/Formula.php new file mode 100644 index 0000000..156b079 --- /dev/null +++ b/src/Formula.php @@ -0,0 +1,11 @@ + 100, + self::INCHES => 39.3700787, + self::FEET => 3.2808399, + self::METRES => 1, + self::KILOMETRES => 0.001, + self::MILES => 0.000621371192, + }; + } +} diff --git a/tests/DistanceTest.php b/tests/DistanceTest.php index e05abdd..f01e3ef 100644 --- a/tests/DistanceTest.php +++ b/tests/DistanceTest.php @@ -1,132 +1,50 @@ setUnit('mi'); - - $this->assertEquals('mi', $distance->getUnit()); - } - - /** - * Test the throwing of an exception in case we try to set an invalid unit. - * - * @expectedException Exception - */ - public function testInvalidUnit() - { - $distance = new Distance(); - - $distance->setUnit('mm'); - } - - /** - * Test the setting and getting of the distance formula. - */ - public function testFormula() - { - $distance = new Distance(); - $distance->setFormula('vincenty'); - - $this->assertEquals('vincenty', $distance->getFormula()); - } - - /** - * Test the throwing of an exception in case we try to set an invalid formula. - * - * @expectedException Exception - */ - public function testInvalidFormula() - { - $distance = new Distance(); - - $distance->setFormula('Leonardo'); - } - - /** - * Test the throwing of an exception in case an invalid parameter is being passed. - * - * @expectedException Exception - */ - public function testInvalidParams() - { - $distance = new Distance(); +namespace Tests\ldebrouwer\Distance; - $distance->between('1', 1, 'not a float', 'invalid'); - } +use ldebrouwer\Distance\Distance; +use ldebrouwer\Distance\Formula; +use ldebrouwer\Distance\Unit; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\TestCase; - /** - * Test the retrieval of the distance between the Apple and Google campuses using the Vincenty formula. - */ - public function testDistanceBetweenAppleAndGoogleUsingVincenty() +class DistanceTest extends TestCase +{ + private const float LATITUDE_A = 37.331741; + private const float LONGITUDE_A = -122.030333; + private const float LATITUDE_B = 37.422546; + private const float LONGITUDE_B = -122.084250; + + #[Test] + #[TestWith([Unit::METRES, 11164.0])] + #[TestWith([Unit::KILOMETRES, 11.164])] + #[TestWith([Unit::MILES, 6.936987987488])] + #[TestWith([Unit::CENTIMETRES, 1116400.0])] + #[TestWith([Unit::FEET, 36627.2966436])] + #[TestWith([Unit::INCHES, 439527.5586068])] + public function canConvertDistanceUsingVincentyFormula(Unit $unit, float $expected): void { $distance = new Distance(); - $distance->setUnit('m'); - $distance->setFormula('vincenty'); - - $this->assertEquals(11164, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('km'); - - $this->assertEquals(11.164, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('mi'); - - $this->assertEquals(6.936987987488, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('cm'); - - $this->assertEquals(1116400, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('ft'); - - $this->assertEquals(36627.2966436, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); + $distance->setFormula(Formula::VINCENTY); - $distance->setUnit('in'); - - $this->assertEquals(439527.5586068, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); + self::assertSame($expected, $distance->setUnit($unit)->between(self::LATITUDE_A, self::LONGITUDE_A, self::LATITUDE_B, self::LONGITUDE_B)); } - /** - * Test the retrieval of the distance between the Apple and Google campuses using the Haversine formula. - */ - public function testDistanceBetweenAppleAndGoogleUsingHaversine() + #[Test] + #[TestWith([Unit::METRES, 11164.0])] + #[TestWith([Unit::KILOMETRES, 11.164])] + #[TestWith([Unit::MILES, 6.936987987488])] + #[TestWith([Unit::CENTIMETRES, 1116400.0])] + #[TestWith([Unit::FEET, 36627.2966436])] + #[TestWith([Unit::INCHES, 439527.5586068])] + public function canConvertDistanceUsingHaversineFormula(Unit $unit, float $expected): void { $distance = new Distance(); - $distance->setUnit('m'); - $distance->setFormula('haversine'); - - $this->assertEquals(11164, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('km'); - - $this->assertEquals(11.164, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('mi'); - - $this->assertEquals(6.936987987488, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('cm'); - - $this->assertEquals(1116400, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('ft'); - - $this->assertEquals(36627.2966436, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); - - $distance->setUnit('in'); + $distance->setFormula(Formula::HAVERSINE); - $this->assertEquals(439527.5586068, $distance->between(37.331741, -122.030333, 37.422546, -122.084250)); + self::assertSame($expected, $distance->setUnit($unit)->between(self::LATITUDE_A, self::LONGITUDE_A, self::LATITUDE_B, self::LONGITUDE_B)); } -} \ No newline at end of file +}