First commit

This commit is contained in:
Brabli
2022-07-19 10:21:40 +01:00
commit 25c87ef115
12 changed files with 379 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
.vscode
/vendor
composer.lock
.phpunit.result.cache

1
LICENSE Normal file
View File

@@ -0,0 +1 @@
License

1
README.md Normal file
View File

@@ -0,0 +1 @@
Readme

38
composer.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "pcm/geocode-bundle",
"description": "Geocode postcodes and entities",
"type": "symfony-bundle",
"license": "MIT",
"authors": [
{
"name": "Bradley Goode",
"email": "bg@pcmsystems.co.uk"
},
{
"name": "Matt Feeney",
"email": "mf@pcmsystems.co.uk"
}
],
"require": {
"php": "^8.1.0",
"symfony/http-client": "^6.1",
"symfony/dependency-injection": "^6.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"symfony/phpunit-bridge": "^6.1"
},
"autoload": {
"psr-4": {
"Pcm\\GeocodeBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Pcm\\GeocodeBundle\\Tests\\": "tests/"
}
}
}

27
phpunit.xml.dist Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="./vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
<ini name="intl.default_locale" value="en" />
<ini name="intl.error_level" value="0" />
<ini name="memory_limit" value="-1" />
</php>
<testsuites>
<testsuite name="Test suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Entity;
class GeocodeData
{
private float $latitude;
private float $longitude;
public function setLatitude(float $latitude): void
{
$this->latitude = $latitude;
}
public function setLongitude(float $longitude): void
{
$this->longitude = $longitude;
}
public function getLatitude(): float
{
return $this->getLatitude();
}
public function getLongitude(): float
{
return $this->getLongitude();
}
}

66
src/Geocoder.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle;
use Exception;
use Pcm\GeocodeBundle\Entity\GeocodeData;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
class Geocoder
{
private const API_URL = "https://nominatim.openstreetmap.org/search";
/**
* @param string $postcode
* @return GeocodeData
*/
public function geocodePostcode(string $postcode): GeocodeData
{
$client = $this->getHttpClient();
$response = $this->makeApiRequest($client, $postcode);
$data = $response->toArray();
$this->throwIfNoResponseData($data);
return $this->createGeocodeDataObject($data);
}
private function getHttpClient(): HttpClientInterface
{
return HttpClient::create([
'headers' => ["User-Agent" => "Geocode"]
]);
}
private function makeApiRequest(HttpClientInterface $client, string $postcode): ResponseInterface
{
return $client->request(
method: 'GET',
url: self::API_URL,
options: [
'query' => [
'format' => 'json',
'postalcode' => $postcode
]
]
);
}
private function throwIfNoResponseData(array $data): void
{
if (empty($data))
throw new Exception("No data was received from API response! Were the arguments valid?");
}
private function createGeocodeDataObject(array $data): GeocodeData
{
$geocodeData = new GeocodeData();
$geocodeData->setLatitude((float) $data[0]['lat']);
$geocodeData->setLongitude((float) $data[0]['lon']);
return $geocodeData;
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Interface;
interface MappableInterface
{
public function getLatitude(): ?float;
public function getLongitude(): ?float;
public function setLatitude(float $lat): self;
public function setLongitude(float $lon): self;
public function isGeocoded(): bool;
}

9
src/PcmGeocodeBundle.php Normal file
View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class PcmGeocodeBundle extends AbstractBundle {}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Trait;
trait MappableTrait
{
private ?float $latitude = null;
private ?float $longitude = null;
public function getLatitude(): ?float
{
return $this->latitude;
}
public function getLongitude(): ?float
{
return $this->longitude;
}
public function setLatitude(float $lat): self
{
$this->latitude = $lat;
return $this;
}
public function setLongitude(float $lon): self
{
$this->longitude = $lon;
return $this;
}
public function isGeocoded(): bool
{
return $this->getLatitude() && $this->getLongitude();
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace app\tests\Service;
use Pcm\GeocodeBundle\Entity\GeocodeData;
use Pcm\GeocodeBundle\Geocoder;
use Pcm\GeocodeBundle\Interface\MappableInterface;
use Pcm\GeocodeBundle\Trait\MappableTrait;
use PHPUnit\Framework\TestCase;
class GeocodeTest extends TestCase
{
// Buckingham Palace
private const POSTCODE = 'SW1A 1AA';
/**
* @var \Pcm\GeocodeBundle\Geocoder
*/
private Geocoder $geocoder;
protected function setUp(): void
{
$this->geocoder = new Geocoder();
}
public function testGeocodeInstance(): void
{
$this->assertInstanceOf(\Pcm\GeocodeBundle\Geocoder::class, $this->geocoder);
}
public function testGeocodePostcodeThrowsOnInvalidInput(): void
{
sleep(1);
$this->expectException(\Exception::class);
$this->geocoder->geocodePostcode('aaaaaaaa');
}
public function testGeocodePostcodeReturnsGeocodeObject(): void
{
sleep(1);
$result = $this->geocoder->geocodePostcode(self::POSTCODE);
$this->assertInstanceOf(GeocodeData::class, $result);
}
private function getMappableEntity(): MappableInterface
{
return new class implements MappableInterface
{
use MappableTrait;
};
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace app\tests\Trait;
use Pcm\GeocodeBundle\Interface\MappableInterface;
use Pcm\GeocodeBundle\Trait\MappableTrait;
use PHPUnit\Framework\TestCase;
class MappableTraitTest extends TestCase
{
private const FLOAT = 123.456;
private MappableInterface $obj;
protected function setUp(): void
{
$this->obj = $this->getTraitObject();
}
public function testSetLatitude(): void
{
$this->assertInstanceOf(MappableInterface::class, $this->obj->setLatitude(self::FLOAT));
}
public function testGetLatitudeReturnsNull(): void
{
$this->assertNull($this->obj->getLatitude());
}
public function testGetLatitude(): void
{
$this->obj->setLatitude(self::FLOAT);
$this->assertSame(self::FLOAT, $this->obj->getLatitude());
}
public function testSetLongitude(): void
{
$this->assertInstanceOf(MappableInterface::class, $this->obj->setLongitude(self::FLOAT));
}
public function testGetLongitudeReturnsNull(): void
{
$this->assertNull($this->obj->getLongitude());
}
public function testGetLongitude(): void
{
$this->obj->setLongitude(self::FLOAT);
$this->assertSame(self::FLOAT, $this->obj->getLongitude());
}
public function testIsGeocodedReturnsFalse(): void
{
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsFalseIfLatIsSet(): void
{
$this->obj->setLatitude(self::FLOAT);
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsFalseIfLonIsSet(): void
{
$this->obj->setLongitude(self::FLOAT);
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsTrueIfLatAndLonAreSet(): void
{
$this->obj->setLatitude(self::FLOAT);
$this->obj->setLongitude(self::FLOAT);
$this->assertTrue($this->obj->isGeocoded());
}
private function getTraitObject(): MappableInterface
{
return new class implements MappableInterface
{
use MappableTrait;
};
}
}