Compare commits

...

36 Commits
0.0.6 ... 2.0.2

Author SHA1 Message Date
0f6a3fed2b - Updating composer to allow version 2 or 3
- Update postcode in tests as the old one stopped working
2025-05-08 17:19:40 +01:00
brabli
8f52f72679 Update postcode in tests as the old one stopped working for some reason 2025-03-12 14:29:52 +00:00
brabli
555eaf4f2d Update changelog 2024-10-09 16:04:55 +01:00
brabli
d6f54eb17f Add tests and make them pass 2024-10-09 16:03:18 +01:00
e302da0fa6 Update to 2.0.0 2024-09-26 10:43:51 +01:00
9246f74117 Update changelog and readme 2024-09-26 10:41:56 +01:00
412c4eb579 Tweak how errors are thrown 2024-09-26 10:35:22 +01:00
9588938356 Add new exception 2024-09-26 10:35:04 +01:00
7cd78307f9 Remove custom messages 2024-09-26 10:34:55 +01:00
35a37a1eae Improve method names 2024-09-26 10:19:07 +01:00
9a7e6b01cb Change tests to accept an interface 2024-09-26 10:17:44 +01:00
35d5c324e2 Add method to get compiler to stop crying 2024-09-26 10:17:30 +01:00
dd2f8a76d6 Change geocoder to use geocode data 2024-09-26 09:59:10 +01:00
ab4bbd0574 Change file name 2024-09-26 09:58:43 +01:00
2aa5cb731e Add get geocode data method to interface 2024-09-26 09:51:21 +01:00
2f54e01fdd Update docblock 2024-09-26 09:44:48 +01:00
40fc3f59f8 Update changelog 2024-09-26 09:42:45 +01:00
d8ecb41ac3 Add test to return false on is geocoded if coords are 0 0 2024-09-26 09:41:54 +01:00
brabli
214fcc2bac Add changelog 2024-08-05 14:58:58 +01:00
brabli
070279e8d4 Update readme 2024-08-05 14:55:00 +01:00
brabli
10d1d3c318 Update docblocks 2024-08-05 14:50:26 +01:00
brabli
8b662ebfa3 Rename Mappable stuff to Geocode stuff, change dir structure 2024-08-05 14:25:03 +01:00
brabli
a6c8c4cc0c Throw exceptions when something goes wrong instead of returning null 2024-08-05 13:07:04 +01:00
brabli
fcda1e931c Add exceptions 2024-08-05 13:06:42 +01:00
brabli
e4222229c4 Add test 2024-08-05 12:28:25 +01:00
brabli
df96a8c1ed Update geo coordinates to use readonly properties rather than getters 2024-08-05 12:28:22 +01:00
brabli
4d936a6279 Rename LatLongModel to GeoCoordinates 2024-08-05 12:16:42 +01:00
brabli
f30c20ceda Mark classes as final 2024-08-05 12:09:10 +01:00
brabli
cb16158eb3 Add text to license file 2024-08-05 12:05:02 +01:00
brabli
58d0e93fc9 Add type to const, change const name 2024-08-05 12:01:40 +01:00
brabli
5c87bf35d5 Add types to consts 2024-08-05 12:01:33 +01:00
brabli
84ba22f287 Update php version to 8.3 2024-08-05 11:56:05 +01:00
brabli
eaa1c0a173 Update packages to symfony 7 2024-08-05 11:55:32 +01:00
brabli
af13393765 Add check to make test pass 2024-08-05 11:17:22 +01:00
brabli
7e0c231d88 Add newline 2024-08-05 11:17:09 +01:00
Brabli
57796a6fdb Return null instead of throwing 2022-10-17 15:46:19 +01:00
21 changed files with 402 additions and 237 deletions

19
CHANGELOG.md Normal file
View File

@@ -0,0 +1,19 @@
# Changelog
## [x.x.x] xxxx-xx-xx
## [2.0.2] 2025-05-08
- Updating composer to allow `doctrine/orm` version 2 or 3
- Update postcode in tests as the old one stopped working
## [2.0.1] 2024-10-09
- Fix bug that caused isGeocoded to return false if only one of the coordinates was zero
## [2.0.0] 2024-09-26
- Adjust how error messages are formatted
- Add getGeocodeData method to GeocodeInterface to allow for more abstraction in services
- Coordinates set to 0, 0 will cause isGeocoded to return false
## [1.0.0] 2024-08-05
- First major version release

View File

@@ -1,4 +1,4 @@
FROM php:8.1-alpine
FROM php:8.3-alpine
WORKDIR /code
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

View File

@@ -1 +1,7 @@
License
Copyright (c) 2024 PCM
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1 +1,12 @@
Readme
# PCM Geocode Bundle
Provides an interface/trait combo to add latitude and longitude fields to an entity.
Also included is a `Geocoder` service which accepts an instance of GeocodeInterface and attempts to return a `GeoCoordinates` object which contains the object's latitude and longitude.
# Usage
Use the `GeocodeTrait` an an entity to give it latitude and longitude fields.
If you wish to geocode an entity you must implement the `GeocodeInterface` interface, the trait will get you most of the way there but you must manually add a `getGeocodeData()` method. The simplest implementation is to return a postcode, but you can return a string of any data if it can be used to find a location.

View File

@@ -14,17 +14,17 @@
}
],
"require": {
"php": "^8.1.0",
"symfony/http-client": "^6.1",
"symfony/dependency-injection": "^6.1",
"symfony/framework-bundle": "^6.1",
"symfony/yaml": "^6.1",
"doctrine/orm": "^2.13"
"php": "^8.3.0",
"symfony/http-client": "^7.1",
"symfony/dependency-injection": "^7.1",
"symfony/framework-bundle": "^7.1",
"symfony/yaml": "^7.1",
"doctrine/orm": "^2|^3"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"symfony/phpunit-bridge": "^6.1"
"symfony/phpunit-bridge": "^7.1"
},
"autoload": {

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Entity\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;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Exception;
class ApiErrorException extends GeocodeException
{
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Exception;
class GeocodeException extends \RuntimeException
{
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Exception;
class MissingGeocodeDataException extends GeocodeException
{
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Exception;
class NoResultsFoundException extends GeocodeException
{
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Interface\Entity;
/**
* Defines latitude and longitude getters and setters
*/
interface GeocodeInterface
{
public function getLatitude(): ?float;
public function getLongitude(): ?float;
public function setLatitude(float $lat): self;
public function setLongitude(float $lon): self;
/**
* Used by {@see Pcm\GeocodeBundle\Service\Geocoder} to retrieve raw data for geocoding.
*
* @return string Data to use when geocodind an entity
*/
public function getGeocodeData(): string;
/**
* @return bool True if both latitude and longitude are set and are not (0, 0)
*/
public function isGeocoded(): bool;
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Model;
/**
* A model for accessing latitude and longitude values.
*
* @package Pcm\GeocodeBundle
*/
final class GeoCoordinates
{
public function __construct(readonly public float $latitude, readonly public float $longitude) {}
}

View File

@@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Model;
/**
* A model for accessing latitude and longitude values
*
* @package Pcm\GeocodeBundle
*/
class LatLongModel
{
public function __construct(private float $latitude, private float $longitude) {}
public function getLatitude(): float
{
return $this->latitude;
}
public function getLongitude(): float
{
return $this->longitude;
}
}

View File

@@ -10,7 +10,7 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class PcmGeocodeBundle extends AbstractBundle {
final class PcmGeocodeBundle extends AbstractBundle {
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$loader = new YamlFileLoader($builder, new FileLocator(__DIR__.'/../config/'));

View File

@@ -4,30 +4,55 @@ declare(strict_types=1);
namespace Pcm\GeocodeBundle\Service;
use Pcm\GeocodeBundle\Model\LatLongModel;
use Pcm\GeocodeBundle\Exception\ApiErrorException;
use Pcm\GeocodeBundle\Exception\MissingGeocodeDataException;
use Pcm\GeocodeBundle\Exception\NoResultsFoundException;
use Pcm\GeocodeBundle\Interface\Entity\GeocodeInterface;
use Pcm\GeocodeBundle\Model\GeoCoordinates;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
class Geocoder
/**
* Find the geo-coordinates of a postcode
*/
final class Geocoder
{
private const API_URL = "https://nominatim.openstreetmap.org/search";
private const string API_URL = "https://nominatim.openstreetmap.org/search";
public function __construct(private HttpClientInterface $client) {}
/**
* Convert a postcode into latitude and longitude
* Find and return the geo-coordinates of an entity
*
* @param string $postcode
* @return LatLongModel
* @param GeocodeInterface $entity
*
* @return GeoCoordinates
*
* @throws NoResultsFoundException when no results were found for the provided postcode
* @throws ApiErrorException when the API response contains an error
*/
public function geocodePostcode(string $postcode): LatLongModel
public function geocode(GeocodeInterface $entity): GeoCoordinates
{
$geocodeData = trim($entity->getGeocodeData());
$client = $this->createClient();
$response = $this->makeApiRequest($client, $postcode);
$data = $this->getDataFromResponse($response);
$this->throwIfDataIsEmpty($data);
return $this->createLatLongModel($data);
if ('' === $geocodeData) {
throw new MissingGeocodeDataException('No geocode data present.');
}
$response = $this->makeApiRequest($client, $geocodeData);
$data = $this->getDataFromResponse($response);
if (array_key_exists('error', $data)) {
throw new ApiErrorException($data['error']['message']);
}
if (empty($data)) {
$message = sprintf('No results found with geocode data "%s".', $geocodeData);
throw new NoResultsFoundException($message);
}
return $this->createGeoCoordinates($data);
}
private function createClient(): HttpClientInterface
@@ -37,7 +62,7 @@ class Geocoder
]);
}
private function makeApiRequest(HttpClientInterface $client, string $postcode): ResponseInterface
private function makeApiRequest(HttpClientInterface $client, string $geocodeData): ResponseInterface
{
return $client->request(
method: 'GET',
@@ -45,7 +70,7 @@ class Geocoder
options: [
'query' => [
'format' => 'json',
'postalcode' => $postcode
'q' => $geocodeData
]
]
);
@@ -56,18 +81,12 @@ class Geocoder
return $response->toArray(false);
}
private function throwIfDataIsEmpty(array $data): void
{
if (empty($data))
throw new \Exception("No data was received from API response! Were the arguments valid?");
}
private function createLatLongModel(array $data): LatLongModel
private function createGeoCoordinates(array $data): GeoCoordinates
{
$lat = $this->getLatitudeFromData($data);
$long = $this->getLongitudeFromData($data);
return new LatLongModel($lat, $long);
return new GeoCoordinates($lat, $long);
}
private function getLatitudeFromData(array $data): float
@@ -80,3 +99,4 @@ class Geocoder
return (float) $data[0]['lon'];
}
}

View File

@@ -2,18 +2,16 @@
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Entity\Trait;
namespace Pcm\GeocodeBundle\Trait\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Allows an entity to be mapped via latitude and longitude coordinates
*
* Use MappableInterface
* Implementation of {@see Pcm\GeocodeBundle\Interface\Entity\GeocodeInterface}
*
* @package Pcm\GeocodeBundle
*/
trait MappableTrait
trait GeocodeTrait
{
#[ORM\Column(type: 'decimal', precision: 10, scale: 6, nullable: true)]
private ?float $latitude = null;
@@ -45,13 +43,13 @@ trait MappableTrait
return $this;
}
/**
* Returns true if both latitude and longitude have been set
*
* @return bool
*/
public function isGeocoded(): bool
{
return null !== $this->getLatitude() && null !== $this->getLongitude();
$latIsntNull = null !== $this->getLatitude();
$longIsntNull = null !== $this->getLongitude();
$bothArentZero = !(0.0 === $this->getLatitude() && 0.0 === $this->getLongitude());
return $latIsntNull && $longIsntNull && $bothArentZero;
}
}

View File

@@ -9,7 +9,7 @@ use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
final class AppKernel extends Kernel
{
/**
* @return array
@@ -30,3 +30,4 @@ class AppKernel extends Kernel
$loader->load(__DIR__.'/config_'.$this->getEnvironment().'.yml');
}
}

View File

@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Tests;
use Pcm\GeocodeBundle\Entity\Interface\MappableInterface;
use Pcm\GeocodeBundle\Entity\Trait\MappableTrait;
use Pcm\GeocodeBundle\Model\LatLongModel;
use Pcm\GeocodeBundle\Service\Geocoder;
use Pcm\GeocodeBundle\Tests\AppKernel;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* Note we sleep 1s after requests to prevent breaking the API T&Cs
*/
class GeocodeTest extends KernelTestCase
{
// Buckingham Palace
private const POSTCODE = 'SW1A 1AA';
private Geocoder $geocoder;
protected function setUp(): void
{
$kernel = new AppKernel('test', false);
$kernel->boot();
$this->geocoder = $kernel->getContainer()->get('pcm_geocode.geocoder');
}
public function testGeocodeInstance(): void
{
$this->assertInstanceOf(Geocoder::class, $this->geocoder);
}
public function testGeocodePostcodeThrowsOnInvalidInput(): void
{
sleep(1);
$this->expectException(\Exception::class);
$this->geocoder->geocodePostcode('aaaaaaaa');
}
public function testGeocodePostcodeReturnsLatLonModel(): void
{
sleep(1);
$result = $this->geocoder->geocodePostcode(self::POSTCODE);
$this->assertInstanceOf(LatLongModel::class, $result);
}
private function getMappableEntity(): MappableInterface
{
return new class implements MappableInterface
{
use MappableTrait;
};
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Tests;
use Pcm\GeocodeBundle\Exception\MissingGeocodeDataException;
use Pcm\GeocodeBundle\Interface\Entity\GeocodeInterface;
use Pcm\GeocodeBundle\Trait\Entity\GeocodeTrait;
use Pcm\GeocodeBundle\Exception\NoResultsFoundException;
use Pcm\GeocodeBundle\Model\GeoCoordinates;
use Pcm\GeocodeBundle\Service\Geocoder;
use Pcm\GeocodeBundle\Tests\AppKernel;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* We sleep 1 second after API calls to prevent breaking the API T&Cs.
*/
final class GeocoderTest extends KernelTestCase
{
// Yes this is a real postcode
private const string POSTCODE = 'ABC 123';
private Geocoder $geocoder;
protected function setUp(): void
{
$kernel = new AppKernel('test', false);
$kernel->boot();
$this->geocoder = $kernel->getContainer()->get('pcm_geocode.geocoder');
}
public function testGeocodeThrowsOnEmptyInput(): void
{
sleep(1);
$this->expectException(MissingGeocodeDataException::class);
$entity = $this->createEntity('');
$this->expectExceptionMessageMatches("/No geocode data present./");
$this->geocoder->geocode($entity);
}
public function testGeocodeThrowsOnInvalidPostcode(): void
{
sleep(1);
$this->expectException(NoResultsFoundException::class);
$this->expectExceptionMessageMatches("/No results found with geocode data \"Invalid Postcode\"./");
$entity = $this->createEntity('Invalid Postcode');
$this->geocoder->geocode($entity);
}
public function testGeocodeReturnsGeoCoordinates(): GeoCoordinates
{
sleep(1);
$entity = $this->createEntity(self::POSTCODE);
$geoCoords = $this->geocoder->geocode($entity);
$this->assertNotNull($geoCoords);
return $geoCoords;
}
/**
* @depends testGeocodeReturnsGeoCoordinates
*/
public function testCoordinatesAreSet(GeoCoordinates $geoCoordinates): void
{
$this->assertIsFloat($geoCoordinates->latitude);
$this->assertIsFloat($geoCoordinates->longitude);
}
private function getGeocodableEntity(): GeocodeInterface
{
return new class implements GeocodeInterface
{
use GeocodeTrait;
};
}
private function createEntity(string $data): GeocodeInterface
{
$entity = new class implements GeocodeInterface {
use GeocodeTrait;
public string $data;
public function getGeocodeData(): string
{
return $this->data;
}
};
$entity->data = $data;
return $entity;
}
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Tests;
use Pcm\GeocodeBundle\Interface\Entity\GeocodeInterface;
use Pcm\GeocodeBundle\Trait\Entity\GeocodeTrait;
use PHPUnit\Framework\TestCase;
final class GeocodeTraitTest extends TestCase
{
private const float COORD = 123.456;
private GeocodeInterface $obj;
protected function setUp(): void
{
$this->obj = $this->getTraitObject();
}
public function testSetLatitude(): void
{
$this->assertInstanceOf(GeocodeInterface::class, $this->obj->setLatitude(self::COORD));
}
public function testGetLatitudeReturnsNull(): void
{
$this->assertNull($this->obj->getLatitude());
}
public function testGetLatitude(): void
{
$this->obj->setLatitude(self::COORD);
$this->assertSame(self::COORD, $this->obj->getLatitude());
}
public function testSetLongitude(): void
{
$this->assertInstanceOf(GeocodeInterface::class, $this->obj->setLongitude(self::COORD));
}
public function testGetLongitudeReturnsNull(): void
{
$this->assertNull($this->obj->getLongitude());
}
public function testGetLongitude(): void
{
$this->obj->setLongitude(self::COORD);
$this->assertSame(self::COORD, $this->obj->getLongitude());
}
public function testIsGeocodedReturnsFalse(): void
{
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsFalseIfLatIsSet(): void
{
$this->obj->setLatitude(self::COORD);
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsFalseIfLonIsSet(): void
{
$this->obj->setLongitude(self::COORD);
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsTrueIfLatAndLonAreSet(): void
{
$this->obj->setLatitude(self::COORD);
$this->obj->setLongitude(self::COORD);
$this->assertTrue($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsFalseIfLatAndLonAreBothZero(): void
{
$this->obj->setLatitude(0.000);
$this->obj->setLongitude(0.000);
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsFalseIfLatAndLonAreBothZeroInts(): void
{
$this->obj->setLatitude(0);
$this->obj->setLongitude(0);
$this->assertFalse($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsTrueIfLongitudeIsZeroAndLatIsNot(): void
{
$this->obj->setLatitude(0.1);
$this->obj->setLongitude(0);
$this->assertTrue($this->obj->isGeocoded());
}
public function testIsGeocodedReturnsTrueIfLatitudeIsZeroAndLongIsNot(): void
{
$this->obj->setLatitude(0);
$this->obj->setLongitude(0.2);
$this->assertTrue($this->obj->isGeocoded());
}
private function getTraitObject(): GeocodeInterface
{
return new class implements GeocodeInterface
{
use GeocodeTrait;
public function getGeocodeData(): string
{
return '';
}
};
}
}

View File

@@ -1,93 +0,0 @@
<?php
declare(strict_types=1);
namespace Pcm\GeocodeBundle\Tests;
use Pcm\GeocodeBundle\Entity\Interface\MappableInterface;
use Pcm\GeocodeBundle\Entity\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());
}
public function testIsGeocodeReturnsTrueIfLatAndLonAreBothZero(): void
{
$this->obj->setLatitude(0.000);
$this->obj->setLongitude(0.000);
$this->assertTrue($this->obj->isGeocoded());
}
private function getTraitObject(): MappableInterface
{
return new class implements MappableInterface
{
use MappableTrait;
};
}
}