default services conflit ?

This commit is contained in:
armansansd
2022-04-27 11:30:43 +02:00
parent 28190a5749
commit 8bb1064a3b
8132 changed files with 900138 additions and 426 deletions

View File

@@ -0,0 +1,33 @@
name: Component
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
name: PHP ${{ matrix.php-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-version: ['7.3', '7.4', '8.0', '8.1']
steps:
- uses: actions/checkout@v2
- name: Use PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: curl
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Install dependencies
run: composer update --prefer-stable --prefer-dist --no-progress
- name: Run test suite
run: composer run-script test-ci
- name: Upload Coverage report
run: |
wget https://scrutinizer-ci.com/ocular.phar
php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Exception\InvalidArgument;
class Assert
{
/**
* @param float $value
* @param string $message
*/
public static function latitude($value, string $message = '')
{
self::float($value, $message);
if ($value < -90 || $value > 90) {
throw new InvalidArgument(sprintf($message ?: 'Latitude should be between -90 and 90. Got: %s', $value));
}
}
/**
* @param float $value
* @param string $message
*/
public static function longitude($value, string $message = '')
{
self::float($value, $message);
if ($value < -180 || $value > 180) {
throw new InvalidArgument(sprintf($message ?: 'Longitude should be between -180 and 180. Got: %s', $value));
}
}
/**
* @param mixed $value
* @param string $message
*/
public static function notNull($value, string $message = '')
{
if (null === $value) {
throw new InvalidArgument(sprintf($message ?: 'Value cannot be null'));
}
}
private static function typeToString($value): string
{
return is_object($value) ? get_class($value) : gettype($value);
}
/**
* @param $value
* @param $message
*/
private static function float($value, string $message)
{
if (!is_float($value)) {
throw new InvalidArgument(sprintf($message ?: 'Expected a float. Got: %s', self::typeToString($value)));
}
}
}

View File

@@ -0,0 +1,121 @@
# Change Log
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
## 4.5.0
### Added
- Add support for PHP 8.1
### Changed
- Replace `empty()` by more strict checks
## 4.4.0
### Added
- Add support for PHP 8.0
### Removed
- Drop support for PHP 7.2
### Changed
- Upgrade PHPUnit to version 9
## 4.3.0
### Removed
- Drop support for PHP < 7.2
## 4.2.2
### Changed
- GeoJson dumper implements Dumper
- Excpetion interface extends Throwable
### Fixed
- Fix building ProviderNotRegistered exception message
## 4.2.1
### Fixed
- Bug in `AddressBuilder` where same expression is compare twice
## 4.2.0
### Added
- Add `Coordinates::toArray`
### Fixed
- Bug in `StatefulGeocoder` where different locale or bounds did not have any effect.
## 4.1.0
### Changed
- Make sure a `Country` never will be empty of data.
## 4.0.0
No changes since Beta 5.
## 4.0.0 - Beta 5
### Changed
- `GeocodeQuery::withTest` was renamed to `GeocodeQuery::withText`
## 4.0.0 - Beta 4
### Added
- Add `GeocodeQuery::withText` and `ReverseQuery::withCoordinates`.
- Create interface for GeocodeQuery and ReverseQuery
## 4.0.0 - Beta 3
### Added
- The constructor of `ProvierAggregator` will accept a callable that can decide what providers should be used for a specific query.
### Changed
- `ProvierAggregator::getProvider` is now private
- `ProvierAggregator::limit` was removed
- `ProvierAggregator::getLimit` was removed
- `ProvierAggregator::__constructor` changed the order of the parameters.
- `ProvierAggregator` is not final.
## 4.0.0 - Beta 2
### Added
- PHP7 type hints.
- `AbstractArrayDumper` and `AbstractDumper`
- `LogicException` and `OutOfBounds`
- `GeocodeQuery::__toString` and `ReverseQuery::__toString`
### Changed
- All Dumpers are now final.
- All Exceptions are now final.
- `AddressCollection` is now final.
- `ProviderAggregator` is now final.
- `StatefulGeocoder` is now final.
- `TimedGeocoder` is now final.
- `ProviderAggregator::getName()` will return "provider_aggregator"
- `TimedGeocoder::getName()` will return "timed_geocoder"
## 4.0.0 - Beta1
First release of this library.

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Exception\CollectionIsEmpty;
use Geocoder\Exception\OutOfBounds;
/**
* This is the interface that is always return from a Geocoder.
*
* @author William Durand <william.durand1@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface Collection extends \IteratorAggregate, \Countable
{
/**
* @return Location
*
* @throws CollectionIsEmpty
*/
public function first(): Location;
/**
* @return bool
*/
public function isEmpty(): bool;
/**
* @return Location[]
*/
public function slice(int $offset, int $length = null);
/**
* @return bool
*/
public function has(int $index): bool;
/**
* @return Location
*
* @throws OutOfBounds
*/
public function get(int $index): Location;
/**
* @return Location[]
*/
public function all(): array;
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author Tomas Norkūnas <norkunas.tom@gmail.com>
*/
abstract class AbstractArrayDumper
{
/**
* @param Location $location
*
* @return array
*/
protected function getArray(Location $location): array
{
$properties = array_filter($location->toArray(), function ($value) {
return !empty($value);
});
unset(
$properties['latitude'],
$properties['longitude'],
$properties['bounds']
);
if ([] === $properties) {
$properties = null;
}
$lat = 0;
$lon = 0;
if (null !== $coordinates = $location->getCoordinates()) {
$lat = $coordinates->getLatitude();
$lon = $coordinates->getLongitude();
}
$array = [
'type' => 'Feature',
'geometry' => [
'type' => 'Point',
'coordinates' => [$lon, $lat],
],
'properties' => $properties,
];
if (null !== $bounds = $location->getBounds()) {
$array['bounds'] = $bounds->toArray();
}
return $array;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
abstract class AbstractDumper
{
/**
* @param Location $address
*
* @return string
*/
protected function formatName(Location $address): string
{
$name = [];
$array = $address->toArray();
foreach (['streetNumber', 'streetName', 'postalCode', 'locality'] as $attr) {
$name[] = $array[$attr];
}
if (isset($array['adminLevels'][2])) {
$name[] = $array['adminLevels'][2]['name'];
}
if (isset($array['adminLevels'][1])) {
$name[] = $array['adminLevels'][1]['name'];
}
$name[] = $array['country'];
return implode(', ', array_filter($name));
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author William Durand <william.durand1@gmail.com>
*/
interface Dumper
{
/**
* Dumps an `Location` object as a string representation of
* the implemented format.
*
* @param Location $location
*
* @return mixed
*/
public function dump(Location $location);
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author Tomas Norkūnas <norkunas.tom@gmail.com>
*/
final class GeoArray extends AbstractArrayDumper implements Dumper
{
/**
* {@inheritdoc}
*/
public function dump(Location $location): array
{
return $this->getArray($location);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author Jan Sorgalla <jsorgalla@googlemail.com>
*/
final class GeoJson extends AbstractArrayDumper implements Dumper
{
/**
* {@inheritdoc}
*/
public function dump(Location $location): string
{
return json_encode($this->getArray($location));
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Geocoder;
use Geocoder\Location;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class Gpx extends AbstractDumper implements Dumper
{
/**
* @param Location $location
*
* @return string
*/
public function dump(Location $location): string
{
$gpx = sprintf(<<<'GPX'
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx
version="1.0"
creator="Geocoder" version="%s"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.topografix.com/GPX/1/0"
xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
GPX
, Geocoder::VERSION);
if (null !== $bounds = $location->getBounds()) {
$gpx .= sprintf(<<<'GPX'
<bounds minlat="%f" minlon="%f" maxlat="%f" maxlon="%f"/>
GPX
, $bounds->getWest(), $bounds->getSouth(), $bounds->getEast(), $bounds->getNorth());
}
$lat = null;
$lon = null;
if (null !== $coordinates = $location->getCoordinates()) {
$lat = $coordinates->getLatitude();
$lon = $coordinates->getLongitude();
}
$gpx .= sprintf(<<<'GPX'
<wpt lat="%.7f" lon="%.7f">
<name><![CDATA[%s]]></name>
<type><![CDATA[Address]]></type>
</wpt>
GPX
, $lat, $lon, $this->formatName($location));
$gpx .= <<<'GPX'
</gpx>
GPX;
return $gpx;
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author Jan Sorgalla <jsorgalla@googlemail.com>
*/
final class Kml extends AbstractDumper implements Dumper
{
/**
* {@inheritdoc}
*/
public function dump(Location $location): string
{
$name = $this->formatName($location);
$kml = <<<'KML'
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Placemark>
<name><![CDATA[%s]]></name>
<description><![CDATA[%s]]></description>
<Point>
<coordinates>%.7F,%.7F,0</coordinates>
</Point>
</Placemark>
</Document>
</kml>
KML;
$lat = null;
$lon = null;
if (null !== $coordinates = $location->getCoordinates()) {
$lat = $coordinates->getLatitude();
$lon = $coordinates->getLongitude();
}
return sprintf($kml, $name, $name, $lon, $lat);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author Jan Sorgalla <jsorgalla@googlemail.com>
*/
final class Wkb implements Dumper
{
/**
* {@inheritdoc}
*/
public function dump(Location $location): string
{
$lat = null;
$lon = null;
if (null !== $coordinates = $location->getCoordinates()) {
$lat = $coordinates->getLatitude();
$lon = $coordinates->getLongitude();
}
return pack('cLdd', 1, 1, $lon, $lat);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Dumper;
use Geocoder\Location;
/**
* @author Jan Sorgalla <jsorgalla@googlemail.com>
*/
final class Wkt implements Dumper
{
/**
* {@inheritdoc}
*/
public function dump(Location $location): string
{
$lat = null;
$lon = null;
if (null !== $coordinates = $location->getCoordinates()) {
$lat = $coordinates->getLatitude();
$lon = $coordinates->getLongitude();
}
return sprintf('POINT(%F %F)', $lon, $lat);
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* When you are trying to access an element on en empty collection.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class CollectionIsEmpty extends \LogicException implements Exception
{
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* @author William Durand <william.durand1@gmail.com>
*/
interface Exception extends \Throwable
{
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* When a required PHP extension is missing.
*
* @author Antoine Corcy <contact@sbin.dk>
*/
final class ExtensionNotLoaded extends \RuntimeException implements Exception
{
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class FunctionNotFound extends \RuntimeException implements Exception
{
/**
* @param string $functionName
* @param string $description
*/
public function __construct(string $functionName, $description = null)
{
parent::__construct(sprintf(
'The function "%s" cannot be found. %s',
$functionName,
null !== $description ? sprintf(' %s', $description) : ''
));
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* @author William Durand <william.durand1@gmail.com>
*/
class InvalidArgument extends \InvalidArgumentException implements Exception
{
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* Thrown when the Provider API declines the request because of wrong credentials.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
final class InvalidCredentials extends \RuntimeException implements Exception
{
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* When the geocoder server returns something that we cannot process.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class InvalidServerResponse extends \RuntimeException implements Exception
{
/**
* @param string $query
* @param int $code
*
* @return InvalidServerResponse
*/
public static function create(string $query, int $code = 0): self
{
return new self(sprintf('The geocoder server returned an invalid response (%d) for query "%s". We could not parse it.', $code, $query));
}
/**
* @param string $query
*
* @return InvalidServerResponse
*/
public static function emptyResponse(string $query): self
{
return new self(sprintf('The geocoder server returned an empty response for query "%s".', $query));
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class LogicException extends \LogicException implements Exception
{
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class OutOfBounds extends \OutOfBoundsException implements Exception
{
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class ProviderNotRegistered extends \RuntimeException implements Exception
{
/**
* @param string $providerName
* @param array $registeredProviders
*/
public static function create(string $providerName, array $registeredProviders = [])
{
return new self(sprintf(
'Provider "%s" is not registered, so you cannot use it. Did you forget to register it or made a typo?%s',
$providerName,
0 == count($registeredProviders) ? '' : sprintf(' Registered providers are: %s.', implode(', ', $registeredProviders))
));
}
public static function noProviderRegistered()
{
return new self('No provider registered.');
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* Thrown when you no longer may access the API because your quota has exceeded.
*
* @author Max V. Kovrigovich <mvk@tut.by>
*/
final class QuotaExceeded extends \RuntimeException implements Exception
{
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Exception;
/**
* Thrown when you are trying to use a Provider for something it does not support. Example if you trying to reverse
* geocode an IP address.
*
* @author William Durand <william.durand1@gmail.com>
*/
final class UnsupportedOperation extends InvalidArgument implements Exception
{
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Formatter;
use Geocoder\Model\AdminLevelCollection;
use Geocoder\Location;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class StringFormatter
{
const STREET_NUMBER = '%n';
const STREET_NAME = '%S';
const LOCALITY = '%L';
const POSTAL_CODE = '%z';
const SUB_LOCALITY = '%D';
const ADMIN_LEVEL = '%A';
const ADMIN_LEVEL_CODE = '%a';
const COUNTRY = '%C';
const COUNTRY_CODE = '%c';
const TIMEZONE = '%T';
/**
* Transform an `Address` instance into a string representation.
*
* @param Location $location
* @param string $format
*
* @return string
*/
public function format(Location $location, string $format): string
{
$countryName = null;
$code = null;
if (null !== $country = $location->getCountry()) {
$countryName = $country->getName();
if (null !== $code = $country->getCode()) {
$code = strtoupper($code);
}
}
$replace = [
self::STREET_NUMBER => $location->getStreetNumber(),
self::STREET_NAME => $location->getStreetName(),
self::LOCALITY => $location->getLocality(),
self::POSTAL_CODE => $location->getPostalCode(),
self::SUB_LOCALITY => $location->getSubLocality(),
self::COUNTRY => $countryName,
self::COUNTRY_CODE => $code,
self::TIMEZONE => $location->getTimezone(),
];
for ($level = 1; $level <= AdminLevelCollection::MAX_LEVEL_DEPTH; ++$level) {
$replace[self::ADMIN_LEVEL.$level] = null;
$replace[self::ADMIN_LEVEL_CODE.$level] = null;
}
foreach ($location->getAdminLevels() as $level => $adminLevel) {
$replace[self::ADMIN_LEVEL.$level] = $adminLevel->getName();
$replace[self::ADMIN_LEVEL_CODE.$level] = $adminLevel->getCode();
}
return strtr($format, $replace);
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Provider\Provider;
/**
* @author William Durand <william.durand1@gmail.com>
*/
interface Geocoder extends Provider
{
/**
* Version of this package.
*/
const MAJOR_VERSION = 4;
const VERSION = '4.0';
/**
* The default result limit.
*/
const DEFAULT_RESULT_LIMIT = 5;
/**
* Geocodes a given value.
*
* @param string $value
*
* @return Collection
*
* @throws \Geocoder\Exception\Exception
*/
public function geocode(string $value): Collection;
/**
* Reverses geocode given latitude and longitude values.
*
* @param float $latitude
* @param float $longitude
*
* @return Collection
*
* @throws \Geocoder\Exception\Exception
*/
public function reverse(float $latitude, float $longitude): Collection;
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
/**
* A trait that turns a Provider into a Geocoder.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
trait GeocoderTrait
{
abstract public function geocodeQuery(GeocodeQuery $query): Collection;
abstract public function reverseQuery(ReverseQuery $query): Collection;
/**
* {@inheritdoc}
*/
public function geocode(string $value): Collection
{
return $this->geocodeQuery(GeocodeQuery::create($value));
}
/**
* {@inheritdoc}
*/
public function reverse(float $latitude, float $longitude): Collection
{
return $this->reverseQuery(ReverseQuery::fromCoordinates($latitude, $longitude));
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2011 — William Durand <william.durand1@gmail.com>
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

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Model\AdminLevelCollection;
use Geocoder\Model\Bounds;
use Geocoder\Model\Coordinates;
use Geocoder\Model\Country;
/**
* A location is a single result from a Geocoder.
*
* @author William Durand <william.durand1@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface Location
{
/**
* Will always return the coordinates value object.
*
* @return Coordinates|null
*/
public function getCoordinates();
/**
* Returns the bounds value object.
*
* @return Bounds|null
*/
public function getBounds();
/**
* Returns the street number value.
*
* @return string|int|null
*/
public function getStreetNumber();
/**
* Returns the street name value.
*
* @return string|null
*/
public function getStreetName();
/**
* Returns the city or locality value.
*
* @return string|null
*/
public function getLocality();
/**
* Returns the postal code or zipcode value.
*
* @return string|null
*/
public function getPostalCode();
/**
* Returns the locality district, or
* sublocality, or neighborhood.
*
* @return string|null
*/
public function getSubLocality();
/**
* Returns the administrative levels.
*
* This method MUST NOT return null.
*
* @return AdminLevelCollection
*/
public function getAdminLevels(): AdminLevelCollection;
/**
* Returns the country value object.
*
* @return Country|null
*/
public function getCountry();
/**
* Returns the timezone for the Location. The timezone MUST be in the list of supported timezones.
*
* {@link http://php.net/manual/en/timezones.php}
*
* @return string|null
*/
public function getTimezone();
/**
* Returns an array with data indexed by name.
*
* @return array
*/
public function toArray(): array;
/**
* The name of the provider that created this Location.
*
* @return string
*/
public function getProvidedBy(): string;
}

View File

@@ -0,0 +1,370 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Location;
/**
* @author William Durand <william.durand1@gmail.com>
*/
class Address implements Location
{
/**
* @var Coordinates|null
*/
private $coordinates;
/**
* @var Bounds|null
*/
private $bounds;
/**
* @var string|int|null
*/
private $streetNumber;
/**
* @var string|null
*/
private $streetName;
/**
* @var string|null
*/
private $subLocality;
/**
* @var string|null
*/
private $locality;
/**
* @var string|null
*/
private $postalCode;
/**
* @var AdminLevelCollection
*/
private $adminLevels;
/**
* @var Country|null
*/
private $country;
/**
* @var string|null
*/
private $timezone;
/**
* @var string
*/
private $providedBy;
/**
* @param string $providedBy
* @param AdminLevelCollection $adminLevels
* @param Coordinates|null $coordinates
* @param Bounds|null $bounds
* @param string|null $streetNumber
* @param string|null $streetName
* @param string|null $postalCode
* @param string|null $locality
* @param string|null $subLocality
* @param Country|null $country
* @param string|null $timezone
*/
public function __construct(
string $providedBy,
AdminLevelCollection $adminLevels,
Coordinates $coordinates = null,
Bounds $bounds = null,
string $streetNumber = null,
string $streetName = null,
string $postalCode = null,
string $locality = null,
string $subLocality = null,
Country $country = null,
string $timezone = null
) {
$this->providedBy = $providedBy;
$this->adminLevels = $adminLevels;
$this->coordinates = $coordinates;
$this->bounds = $bounds;
$this->streetNumber = $streetNumber;
$this->streetName = $streetName;
$this->postalCode = $postalCode;
$this->locality = $locality;
$this->subLocality = $subLocality;
$this->country = $country;
$this->timezone = $timezone;
}
/**
* @return string
*/
public function getProvidedBy(): string
{
return $this->providedBy;
}
/**
* {@inheritdoc}
*/
public function getCoordinates()
{
return $this->coordinates;
}
/**
* {@inheritdoc}
*/
public function getBounds()
{
return $this->bounds;
}
/**
* {@inheritdoc}
*/
public function getStreetNumber()
{
return $this->streetNumber;
}
/**
* {@inheritdoc}
*/
public function getStreetName()
{
return $this->streetName;
}
/**
* {@inheritdoc}
*/
public function getLocality()
{
return $this->locality;
}
/**
* {@inheritdoc}
*/
public function getPostalCode()
{
return $this->postalCode;
}
/**
* {@inheritdoc}
*/
public function getSubLocality()
{
return $this->subLocality;
}
/**
* {@inheritdoc}
*/
public function getAdminLevels(): AdminLevelCollection
{
return $this->adminLevels;
}
/**
* {@inheritdoc}
*/
public function getCountry()
{
return $this->country;
}
/**
* {@inheritdoc}
*/
public function getTimezone()
{
return $this->timezone;
}
/**
* Create an Address with an array. Useful for testing.
*
* @param array $data
*
* @return static
*/
public static function createFromArray(array $data)
{
$defaults = [
'providedBy' => 'n/a',
'latitude' => null,
'longitude' => null,
'bounds' => [
'south' => null,
'west' => null,
'north' => null,
'east' => null,
],
'streetNumber' => null,
'streetName' => null,
'locality' => null,
'postalCode' => null,
'subLocality' => null,
'adminLevels' => [],
'country' => null,
'countryCode' => null,
'timezone' => null,
];
$data = array_merge($defaults, $data);
$adminLevels = [];
foreach ($data['adminLevels'] as $adminLevel) {
if (null === $adminLevel['level'] || 0 === $adminLevel['level']) {
continue;
}
$name = $adminLevel['name'] ?? $adminLevel['code'] ?? null;
if (null === $name || '' === $name) {
continue;
}
$adminLevels[] = new AdminLevel($adminLevel['level'], $name, $adminLevel['code'] ?? null);
}
return new static(
$data['providedBy'],
new AdminLevelCollection($adminLevels),
self::createCoordinates(
$data['latitude'],
$data['longitude']
),
self::createBounds(
$data['bounds']['south'],
$data['bounds']['west'],
$data['bounds']['north'],
$data['bounds']['east']
),
$data['streetNumber'],
$data['streetName'],
$data['postalCode'],
$data['locality'],
$data['subLocality'],
self::createCountry($data['country'], $data['countryCode']),
$data['timezone']
);
}
/**
* @param float $latitude
* @param float $longitude
*
* @return Coordinates|null
*/
private static function createCoordinates($latitude, $longitude)
{
if (null === $latitude || null === $longitude) {
return null;
}
return new Coordinates($latitude, $longitude);
}
/**
* @param string|null $name
* @param string|null $code
*
* @return Country|null
*/
private static function createCountry($name, $code)
{
if (null === $name && null === $code) {
return null;
}
return new Country($name, $code);
}
/**
* @param float $south
* @param float $west
* @param float $north
*
* @return Bounds|null
*/
private static function createBounds($south, $west, $north, $east)
{
if (null === $south || null === $west || null === $north || null === $east) {
return null;
}
return new Bounds($south, $west, $north, $east);
}
/**
* {@inheritdoc}
*/
public function toArray(): array
{
$adminLevels = [];
foreach ($this->adminLevels as $adminLevel) {
$adminLevels[$adminLevel->getLevel()] = [
'name' => $adminLevel->getName(),
'code' => $adminLevel->getCode(),
'level' => $adminLevel->getLevel(),
];
}
$lat = null;
$lon = null;
if (null !== $coordinates = $this->getCoordinates()) {
$lat = $coordinates->getLatitude();
$lon = $coordinates->getLongitude();
}
$countryName = null;
$countryCode = null;
if (null !== $country = $this->getCountry()) {
$countryName = $country->getName();
$countryCode = $country->getCode();
}
$noBounds = [
'south' => null,
'west' => null,
'north' => null,
'east' => null,
];
return [
'providedBy' => $this->providedBy,
'latitude' => $lat,
'longitude' => $lon,
'bounds' => null !== $this->bounds ? $this->bounds->toArray() : $noBounds,
'streetNumber' => $this->streetNumber,
'streetName' => $this->streetName,
'postalCode' => $this->postalCode,
'locality' => $this->locality,
'subLocality' => $this->subLocality,
'adminLevels' => $adminLevels,
'country' => $countryName,
'countryCode' => $countryCode,
'timezone' => $this->timezone,
];
}
}

View File

@@ -0,0 +1,326 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Exception\InvalidArgument;
use Geocoder\Exception\LogicException;
/**
* A class that builds a Location or any of its subclasses.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class AddressBuilder
{
/**
* @var string
*/
private $providedBy;
/**
* @var Coordinates|null
*/
private $coordinates;
/**
* @var Bounds|null
*/
private $bounds;
/**
* @var string|null
*/
private $streetNumber;
/**
* @var string|null
*/
private $streetName;
/**
* @var string|null
*/
private $locality;
/**
* @var string|null
*/
private $postalCode;
/**
* @var string|null
*/
private $subLocality;
/**
* @var array
*/
private $adminLevels = [];
/**
* @var string|null
*/
private $country;
/**
* @var string|null
*/
private $countryCode;
/**
* @var string|null
*/
private $timezone;
/**
* A storage for extra parameters.
*
* @var array
*/
private $data = [];
/**
* @param string $providedBy
*/
public function __construct(string $providedBy)
{
$this->providedBy = $providedBy;
}
/**
* @param string $class
*
* @return Address
*/
public function build(string $class = Address::class): Address
{
if (!is_a($class, Address::class, true)) {
throw new LogicException('First parameter to LocationBuilder::build must be a class name extending Geocoder\Model\Address');
}
$country = null;
if ((null !== $this->country && '' !== $this->country) || (null !== $this->countryCode && '' !== $this->countryCode)) {
$country = new Country($this->country, $this->countryCode);
}
return new $class(
$this->providedBy,
new AdminLevelCollection($this->adminLevels),
$this->coordinates,
$this->bounds,
$this->streetNumber,
$this->streetName,
$this->postalCode,
$this->locality,
$this->subLocality,
$country,
$this->timezone
);
}
/**
* @param float $south
* @param float $west
* @param float $north
* @param float $east
*
* @return AddressBuilder
*/
public function setBounds($south, $west, $north, $east): self
{
try {
$this->bounds = new Bounds($south, $west, $north, $east);
} catch (InvalidArgument $e) {
$this->bounds = null;
}
return $this;
}
/**
* @param float $latitude
* @param float $longitude
*
* @return AddressBuilder
*/
public function setCoordinates($latitude, $longitude): self
{
try {
$this->coordinates = new Coordinates($latitude, $longitude);
} catch (InvalidArgument $e) {
$this->coordinates = null;
}
return $this;
}
/**
* @param int $level
* @param string $name
* @param string|null $code
*
* @return AddressBuilder
*/
public function addAdminLevel(int $level, string $name, string $code = null): self
{
$this->adminLevels[] = new AdminLevel($level, $name, $code);
return $this;
}
/**
* @param string|null $streetNumber
*
* @return AddressBuilder
*/
public function setStreetNumber($streetNumber): self
{
$this->streetNumber = $streetNumber;
return $this;
}
/**
* @param string|null $streetName
*
* @return AddressBuilder
*/
public function setStreetName($streetName): self
{
$this->streetName = $streetName;
return $this;
}
/**
* @param string|null $locality
*
* @return AddressBuilder
*/
public function setLocality($locality): self
{
$this->locality = $locality;
return $this;
}
/**
* @param string|null $postalCode
*
* @return AddressBuilder
*/
public function setPostalCode($postalCode): self
{
$this->postalCode = $postalCode;
return $this;
}
/**
* @param string|null $subLocality
*
* @return AddressBuilder
*/
public function setSubLocality($subLocality): self
{
$this->subLocality = $subLocality;
return $this;
}
/**
* @param array $adminLevels
*
* @return AddressBuilder
*/
public function setAdminLevels($adminLevels): self
{
$this->adminLevels = $adminLevels;
return $this;
}
/**
* @param string|null $country
*
* @return AddressBuilder
*/
public function setCountry($country): self
{
$this->country = $country;
return $this;
}
/**
* @param string|null $countryCode
*
* @return AddressBuilder
*/
public function setCountryCode($countryCode): self
{
$this->countryCode = $countryCode;
return $this;
}
/**
* @param string|null $timezone
*
* @return AddressBuilder
*/
public function setTimezone($timezone): self
{
$this->timezone = $timezone;
return $this;
}
/**
* @param string $name
* @param mixed $value
*
* @return AddressBuilder
*/
public function setValue(string $name, $value): self
{
$this->data[$name] = $value;
return $this;
}
/**
* @param string $name
* @param mixed|null $default
*
* @return mixed
*/
public function getValue(string $name, $default = null)
{
if ($this->hasValue($name)) {
return $this->data[$name];
}
return $default;
}
/**
* @param string $name
*
* @return bool
*/
public function hasValue(string $name): bool
{
return array_key_exists($name, $this->data);
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Collection;
use Geocoder\Exception\CollectionIsEmpty;
use Geocoder\Exception\OutOfBounds;
use Geocoder\Location;
use Traversable;
final class AddressCollection implements Collection
{
/**
* @var Location[]
*/
private $locations;
/**
* @param Location[] $locations
*/
public function __construct(array $locations = [])
{
$this->locations = array_values($locations);
}
/**
* {@inheritdoc}
*/
public function getIterator(): Traversable
{
return new \ArrayIterator($this->all());
}
/**
* {@inheritdoc}
*/
public function count(): int
{
return count($this->locations);
}
/**
* {@inheritdoc}
*/
public function first(): Location
{
if ([] === $this->locations) {
throw new CollectionIsEmpty();
}
return reset($this->locations);
}
/**
* {@inheritdoc}
*/
public function isEmpty(): bool
{
return [] === $this->locations;
}
/**
* @return Location[]
*/
public function slice(int $offset, int $length = null)
{
return array_slice($this->locations, $offset, $length);
}
/**
* @return bool
*/
public function has(int $index): bool
{
return isset($this->locations[$index]);
}
/**
* {@inheritdoc}
*/
public function get(int $index): Location
{
if (!isset($this->locations[$index])) {
throw new OutOfBounds(sprintf('The index "%s" does not exist in this collection.', $index));
}
return $this->locations[$index];
}
/**
* {@inheritdoc}
*/
public function all(): array
{
return $this->locations;
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class AdminLevel
{
/**
* @var int
*/
private $level;
/**
* @var string
*/
private $name;
/**
* @var string|null
*/
private $code;
/**
* @param int $level
* @param string $name
* @param string|null $code
*/
public function __construct(int $level, string $name, string $code = null)
{
$this->level = $level;
$this->name = $name;
$this->code = $code;
}
/**
* Returns the administrative level.
*
* @return int Level number [1,5]
*/
public function getLevel(): int
{
return $this->level;
}
/**
* Returns the administrative level name.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Returns the administrative level short name.
*
* @return string|null
*/
public function getCode()
{
return $this->code;
}
/**
* Returns a string with the administrative level name.
*
* @return string
*/
public function __toString(): string
{
return $this->getName();
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Exception\CollectionIsEmpty;
use Geocoder\Exception\InvalidArgument;
use Geocoder\Exception\OutOfBounds;
use Traversable;
/**
* @author Giorgio Premi <giosh94mhz@gmail.com>
*/
final class AdminLevelCollection implements \IteratorAggregate, \Countable
{
const MAX_LEVEL_DEPTH = 5;
/**
* @var AdminLevel[]
*/
private $adminLevels;
/**
* @param AdminLevel[] $adminLevels
*/
public function __construct(array $adminLevels = [])
{
$this->adminLevels = [];
foreach ($adminLevels as $adminLevel) {
$level = $adminLevel->getLevel();
$this->checkLevel($level);
if ($this->has($level)) {
throw new InvalidArgument(sprintf('Administrative level %d is defined twice', $level));
}
$this->adminLevels[$level] = $adminLevel;
}
ksort($this->adminLevels, SORT_NUMERIC);
}
/**
* {@inheritdoc}
*/
public function getIterator(): Traversable
{
return new \ArrayIterator($this->all());
}
/**
* {@inheritdoc}
*/
public function count(): int
{
return count($this->adminLevels);
}
/**
* @return AdminLevel
*
* @throws CollectionIsEmpty
*/
public function first(): AdminLevel
{
if ([] === $this->adminLevels) {
throw new CollectionIsEmpty();
}
return reset($this->adminLevels);
}
/**
* @param int $offset
* @param int|null $length
*
* @return AdminLevel[]
*/
public function slice(int $offset, int $length = null): array
{
return array_slice($this->adminLevels, $offset, $length, true);
}
/**
* @return bool
*/
public function has(int $level): bool
{
return isset($this->adminLevels[$level]);
}
/**
* @return AdminLevel
*
* @throws \OutOfBoundsException
* @throws InvalidArgument
*/
public function get(int $level): AdminLevel
{
$this->checkLevel($level);
if (!isset($this->adminLevels[$level])) {
throw new InvalidArgument(sprintf('Administrative level %d is not set for this address', $level));
}
return $this->adminLevels[$level];
}
/**
* @return AdminLevel[]
*/
public function all(): array
{
return $this->adminLevels;
}
/**
* @param int $level
*
* @throws \OutOfBoundsException
*/
private function checkLevel(int $level)
{
if ($level <= 0 || $level > self::MAX_LEVEL_DEPTH) {
throw new OutOfBounds(sprintf('Administrative level should be an integer in [1,%d], %d given', self::MAX_LEVEL_DEPTH, $level));
}
}
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Assert;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class Bounds
{
/**
* @var float
*/
private $south;
/**
* @var float
*/
private $west;
/**
* @var float
*/
private $north;
/**
* @var float
*/
private $east;
/**
* @param float $south South bound, also min latitude
* @param float $west West bound, also min longitude
* @param float $north North bound, also max latitude
* @param float $east East bound, also max longitude
*/
public function __construct($south, $west, $north, $east)
{
Assert::notNull($south);
Assert::notNull($west);
Assert::notNull($north);
Assert::notNull($east);
$south = (float) $south;
$north = (float) $north;
$west = (float) $west;
$east = (float) $east;
Assert::latitude($south);
Assert::latitude($north);
Assert::longitude($west);
Assert::longitude($east);
$this->south = $south;
$this->west = $west;
$this->north = $north;
$this->east = $east;
}
/**
* Returns the south bound.
*
* @return float
*/
public function getSouth(): float
{
return $this->south;
}
/**
* Returns the west bound.
*
* @return float
*/
public function getWest(): float
{
return $this->west;
}
/**
* Returns the north bound.
*
* @return float
*/
public function getNorth(): float
{
return $this->north;
}
/**
* Returns the east bound.
*
* @return float
*/
public function getEast(): float
{
return $this->east;
}
/**
* Returns an array with bounds.
*
* @return array
*/
public function toArray(): array
{
return [
'south' => $this->getSouth(),
'west' => $this->getWest(),
'north' => $this->getNorth(),
'east' => $this->getEast(),
];
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Assert;
/**
* @author William Durand <william.durand1@gmail.com>
*/
final class Coordinates
{
/**
* @var float
*/
private $latitude;
/**
* @var float
*/
private $longitude;
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
Assert::notNull($latitude);
Assert::notNull($longitude);
$latitude = (float) $latitude;
$longitude = (float) $longitude;
Assert::latitude($latitude);
Assert::longitude($longitude);
$this->latitude = $latitude;
$this->longitude = $longitude;
}
/**
* Returns the latitude.
*
* @return float
*/
public function getLatitude(): float
{
return $this->latitude;
}
/**
* Returns the longitude.
*
* @return float
*/
public function getLongitude(): float
{
return $this->longitude;
}
/**
* Returns the coordinates as a tuple.
*
* @return array
*/
public function toArray(): array
{
return [$this->getLongitude(), $this->getLatitude()];
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Model;
use Geocoder\Exception\InvalidArgument;
/**
* A Country has either a name or a code. A Country will never be without data.
*
* @author William Durand <william.durand1@gmail.com>
*/
final class Country
{
/**
* @var string|null
*/
private $name;
/**
* @var string|null
*/
private $code;
/**
* @param string $name
* @param string $code
*/
public function __construct(string $name = null, string $code = null)
{
if (null === $name && null === $code) {
throw new InvalidArgument('A country must have either a name or a code');
}
$this->name = $name;
$this->code = $code;
}
/**
* Returns the country name.
*
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* Returns the country ISO code.
*
* @return string|null
*/
public function getCode()
{
return $this->code;
}
/**
* Returns a string with the country name.
*
* @return string
*/
public function __toString(): string
{
return $this->getName() ?: '';
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Provider;
use Geocoder\Location;
use Geocoder\Model\Address;
/**
* @author William Durand <william.durand1@gmail.com>
*/
abstract class AbstractProvider implements Provider
{
/**
* Returns the results for the 'localhost' special case.
*
* @return Location
*/
protected function getLocationForLocalhost(): Location
{
return Address::createFromArray([
'providedBy' => $this->getName(),
'locality' => 'localhost',
'country' => 'localhost',
]);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Provider;
use Geocoder\Collection;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
/**
* Providers MUST always be stateless and immutable.
*
* @author William Durand <william.durand1@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface Provider
{
/**
* @param GeocodeQuery $query
*
* @return Collection
*
* @throws \Geocoder\Exception\Exception
*/
public function geocodeQuery(GeocodeQuery $query): Collection;
/**
* @param ReverseQuery $query
*
* @return Collection
*
* @throws \Geocoder\Exception\Exception
*/
public function reverseQuery(ReverseQuery $query): Collection;
/**
* Returns the provider's name.
*
* @return string
*/
public function getName(): string;
}

View File

@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Exception\ProviderNotRegistered;
use Geocoder\Model\Coordinates;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
use Geocoder\Provider\Provider;
/**
* @author William Durand <william.durand1@gmail.com>
*/
class ProviderAggregator implements Geocoder
{
/**
* @var Provider[]
*/
private $providers = [];
/**
* @var Provider
*/
private $provider;
/**
* @var int
*/
private $limit;
/**
* A callable that decided what provider to use.
*
* @var callable
*/
private $decider;
/**
* @param callable|null $decider
* @param int $limit
*/
public function __construct(callable $decider = null, int $limit = Geocoder::DEFAULT_RESULT_LIMIT)
{
$this->limit = $limit;
$this->decider = $decider ?? __CLASS__.'::getProvider';
}
/**
* {@inheritdoc}
*/
public function geocodeQuery(GeocodeQuery $query): Collection
{
if (null === $query->getLimit()) {
$query = $query->withLimit($this->limit);
}
return call_user_func($this->decider, $query, $this->providers, $this->provider)->geocodeQuery($query);
}
/**
* {@inheritdoc}
*/
public function reverseQuery(ReverseQuery $query): Collection
{
if (null === $query->getLimit()) {
$query = $query->withLimit($this->limit);
}
return call_user_func($this->decider, $query, $this->providers, $this->provider)->reverseQuery($query);
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'provider_aggregator';
}
/**
* {@inheritdoc}
*/
public function geocode(string $value): Collection
{
return $this->geocodeQuery(GeocodeQuery::create($value)
->withLimit($this->limit));
}
/**
* {@inheritdoc}
*/
public function reverse(float $latitude, float $longitude): Collection
{
return $this->reverseQuery(ReverseQuery::create(new Coordinates($latitude, $longitude))
->withLimit($this->limit));
}
/**
* Registers a new provider to the aggregator.
*
* @param Provider $provider
*
* @return ProviderAggregator
*/
public function registerProvider(Provider $provider): self
{
$this->providers[$provider->getName()] = $provider;
return $this;
}
/**
* Registers a set of providers.
*
* @param Provider[] $providers
*
* @return ProviderAggregator
*/
public function registerProviders(array $providers = []): self
{
foreach ($providers as $provider) {
$this->registerProvider($provider);
}
return $this;
}
/**
* Sets the default provider to use.
*
* @param string $name
*
* @return ProviderAggregator
*/
public function using(string $name): self
{
if (!isset($this->providers[$name])) {
throw ProviderNotRegistered::create($name, array_keys($this->providers));
}
$this->provider = $this->providers[$name];
return $this;
}
/**
* Returns all registered providers indexed by their name.
*
* @return Provider[]
*/
public function getProviders(): array
{
return $this->providers;
}
/**
* Get a provider to use for this query.
*
* @param GeocodeQuery|ReverseQuery $query
* @param Provider[] $providers
* @param Provider $currentProvider
*
* @return Provider
*
* @throws ProviderNotRegistered
*/
private static function getProvider($query, array $providers, Provider $currentProvider = null): Provider
{
if (null !== $currentProvider) {
return $currentProvider;
}
if ([] === $providers) {
throw ProviderNotRegistered::noProviderRegistered();
}
// Take first
$key = key($providers);
return $providers[$key];
}
}

View File

@@ -0,0 +1,209 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Query;
use Geocoder\Exception\InvalidArgument;
use Geocoder\Geocoder;
use Geocoder\Model\Bounds;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class GeocodeQuery implements Query
{
/**
* The address or text that should be geocoded.
*
* @var string
*/
private $text;
/**
* @var Bounds|null
*/
private $bounds;
/**
* @var string|null
*/
private $locale;
/**
* @var int
*/
private $limit = Geocoder::DEFAULT_RESULT_LIMIT;
/**
* @var array
*/
private $data = [];
/**
* @param string $text
*/
private function __construct(string $text)
{
if ('' === $text) {
throw new InvalidArgument('Geocode query cannot be empty');
}
$this->text = $text;
}
/**
* @param string $text
*
* @return GeocodeQuery
*/
public static function create(string $text): self
{
return new self($text);
}
/**
* @param string $text
*
* @return GeocodeQuery
*/
public function withText(string $text): self
{
$new = clone $this;
$new->text = $text;
return $new;
}
/**
* @param Bounds $bounds
*
* @return GeocodeQuery
*/
public function withBounds(Bounds $bounds): self
{
$new = clone $this;
$new->bounds = $bounds;
return $new;
}
/**
* @param string $locale
*
* @return GeocodeQuery
*/
public function withLocale(string $locale): self
{
$new = clone $this;
$new->locale = $locale;
return $new;
}
/**
* @param int $limit
*
* @return GeocodeQuery
*/
public function withLimit(int $limit): self
{
$new = clone $this;
$new->limit = $limit;
return $new;
}
/**
* @param string $name
* @param mixed $value
*
* @return GeocodeQuery
*/
public function withData(string $name, $value): self
{
$new = clone $this;
$new->data[$name] = $value;
return $new;
}
/**
* @return string
*/
public function getText(): string
{
return $this->text;
}
/**
* @return Bounds|null
*/
public function getBounds()
{
return $this->bounds;
}
/**
* @return string|null
*/
public function getLocale()
{
return $this->locale;
}
/**
* @return int
*/
public function getLimit(): int
{
return $this->limit;
}
/**
* @param string $name
* @param mixed|null $default
*
* @return mixed
*/
public function getData(string $name, $default = null)
{
if (!array_key_exists($name, $this->data)) {
return $default;
}
return $this->data[$name];
}
/**
* @return array
*/
public function getAllData(): array
{
return $this->data;
}
/**
* String for logging. This is also a unique key for the query.
*
* @return string
*/
public function __toString()
{
return sprintf('GeocodeQuery: %s', json_encode([
'text' => $this->getText(),
'bounds' => $this->getBounds() ? $this->getBounds()->toArray() : 'null',
'locale' => $this->getLocale(),
'limit' => $this->getLimit(),
'data' => $this->getAllData(),
]));
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Query;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface Query
{
/**
* @param string $locale
*
* @return Query
*/
public function withLocale(string $locale);
/**
* @param int $limit
*
* @return Query
*/
public function withLimit(int $limit);
/**
* @param string $name
* @param mixed $value
*
* @return Query
*/
public function withData(string $name, $value);
/**
* @return string|null
*/
public function getLocale();
/**
* @return int
*/
public function getLimit(): int;
/**
* @param string $name
* @param mixed|null $default
*
* @return mixed
*/
public function getData(string $name, $default = null);
/**
* @return array
*/
public function getAllData(): array;
/**
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder\Query;
use Geocoder\Geocoder;
use Geocoder\Model\Coordinates;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class ReverseQuery implements Query
{
/**
* @var Coordinates
*/
private $coordinates;
/**
* @var string|null
*/
private $locale;
/**
* @var int
*/
private $limit = Geocoder::DEFAULT_RESULT_LIMIT;
/**
* @var array
*/
private $data = [];
/**
* @param Coordinates $coordinates
*/
private function __construct(Coordinates $coordinates)
{
$this->coordinates = $coordinates;
}
/**
* @param Coordinates $coordinates
*
* @return ReverseQuery
*/
public static function create(Coordinates $coordinates)
{
return new self($coordinates);
}
/**
* @param float $latitude
* @param float $longitude
*
* @return ReverseQuery
*/
public static function fromCoordinates($latitude, $longitude): self
{
return new self(new Coordinates($latitude, $longitude));
}
/**
* @param Coordinates $coordinates
*
* @return ReverseQuery
*/
public function withCoordinates(Coordinates $coordinates): self
{
$new = clone $this;
$new->coordinates = $coordinates;
return $new;
}
/**
* @param int $limit
*
* @return ReverseQuery
*/
public function withLimit(int $limit): self
{
$new = clone $this;
$new->limit = $limit;
return $new;
}
/**
* @param string $locale
*
* @return ReverseQuery
*/
public function withLocale(string $locale): self
{
$new = clone $this;
$new->locale = $locale;
return $new;
}
/**
* @param string $name
* @param mixed $value
*
* @return ReverseQuery
*/
public function withData(string $name, $value): self
{
$new = clone $this;
$new->data[$name] = $value;
return $new;
}
/**
* @return Coordinates
*/
public function getCoordinates(): Coordinates
{
return $this->coordinates;
}
/**
* @return int
*/
public function getLimit(): int
{
return $this->limit;
}
/**
* @return string
*/
public function getLocale()
{
return $this->locale;
}
/**
* @param string $name
* @param mixed|null $default
*
* @return mixed
*/
public function getData(string $name, $default = null)
{
if (!array_key_exists($name, $this->data)) {
return $default;
}
return $this->data[$name];
}
/**
* @return array
*/
public function getAllData(): array
{
return $this->data;
}
/**
* String for logging. This is also a unique key for the query.
*
* @return string
*/
public function __toString()
{
return sprintf('ReverseQuery: %s', json_encode([
'lat' => $this->getCoordinates()->getLatitude(),
'lng' => $this->getCoordinates()->getLongitude(),
'locale' => $this->getLocale(),
'limit' => $this->getLimit(),
'data' => $this->getAllData(),
]));
}
}

View File

@@ -0,0 +1,39 @@
# Common classes for the Geocoder
[![Build Status](https://travis-ci.org/geocoder-php/php-common.svg?branch=master)](http://travis-ci.org/geocoder-php/php-common)
[![Latest Stable Version](https://poser.pugx.org/willdurand/geocoder/v/stable)](https://packagist.org/packages/willdurand/geocoder)
[![Total Downloads](https://poser.pugx.org/willdurand/geocoder/downloads)](https://packagist.org/packages/willdurand/geocoder)
[![Monthly Downloads](https://poser.pugx.org/willdurand/geocoder/d/monthly.png)](https://packagist.org/packages/willdurand/geocoder)
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/geocoder-php/php-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/php-common)
[![Quality Score](https://img.shields.io/scrutinizer/g/geocoder-php/php-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/php-common)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
### Note
* This repository is **READ ONLY**
* Post issues and PRs at the main repository: https://github.com/geocoder-php/Geocoder
### History
Just some months before the release of 4.0 of `willdurand/geocoder` we changed the repository to https://github.com/geocoder-php/php-common
from https://github.com/geocoder-php/Geocoder. The new repository will only contain classes and interfaces shared between
multiple providers. The original repository is still used for issues and pull requests.
The new repository architecture allows us to use a [git subtree split](https://www.subtreesplit.com) from geocoder-php/Geocoder
to geocoder-php/php-common and to each provider.
Versions before 4.0 `willdurand/geocoder` will still work as usual, but with the new repository.
### Install
In 99% of the cases you do **not** want to install this package directly. You are more likely to install one provider.
Have a look at [the documentation](https://github.com/geocoder-php/Geocoder) to see the different providers.
```bash
composer require willdurand/geocoder
```
### Contribute
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or
report any issues you find on the [issue tracker](https://github.com/geocoder-php/Geocoder/issues).

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Model\Bounds;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
use Geocoder\Provider\Provider;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class StatefulGeocoder implements Geocoder
{
/**
* @var string|null
*/
private $locale;
/**
* @var Bounds
*/
private $bounds;
/**
* @var int
*/
private $limit;
/**
* @var Provider
*/
private $provider;
/**
* @param Provider $provider
* @param string $locale
*/
public function __construct(Provider $provider, string $locale = null)
{
$this->provider = $provider;
$this->locale = $locale;
$this->limit = Geocoder::DEFAULT_RESULT_LIMIT;
}
/**
* {@inheritdoc}
*/
public function geocode(string $value): Collection
{
$query = GeocodeQuery::create($value)
->withLimit($this->limit);
if (null !== $this->locale && '' !== $this->locale) {
$query = $query->withLocale($this->locale);
}
if (!empty($this->bounds)) {
$query = $query->withBounds($this->bounds);
}
return $this->provider->geocodeQuery($query);
}
/**
* {@inheritdoc}
*/
public function reverse(float $latitude, float $longitude): Collection
{
$query = ReverseQuery::fromCoordinates($latitude, $longitude)
->withLimit($this->limit);
if (null !== $this->locale && '' !== $this->locale) {
$query = $query->withLocale($this->locale);
}
return $this->provider->reverseQuery($query);
}
/**
* {@inheritdoc}
*/
public function geocodeQuery(GeocodeQuery $query): Collection
{
$locale = $query->getLocale();
if ((null === $locale || '' === $locale) && null !== $this->locale) {
$query = $query->withLocale($this->locale);
}
$bounds = $query->getBounds();
if (empty($bounds) && null !== $this->bounds) {
$query = $query->withBounds($this->bounds);
}
return $this->provider->geocodeQuery($query);
}
/**
* {@inheritdoc}
*/
public function reverseQuery(ReverseQuery $query): Collection
{
$locale = $query->getLocale();
if ((null === $locale || '' === $locale) && null !== $this->locale) {
$query = $query->withLocale($this->locale);
}
return $this->provider->reverseQuery($query);
}
/**
* @param string $locale
*
* @return StatefulGeocoder
*/
public function setLocale(string $locale): self
{
$this->locale = $locale;
return $this;
}
/**
* @param Bounds $bounds
*
* @return StatefulGeocoder
*/
public function setBounds(Bounds $bounds): self
{
$this->bounds = $bounds;
return $this;
}
/**
* @param int $limit
*
* @return StatefulGeocoder
*/
public function setLimit(int $limit): self
{
$this->limit = $limit;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'stateful_geocoder';
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/*
* This file is part of the Geocoder package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Geocoder;
use Geocoder\Query\GeocodeQuery;
use Geocoder\Query\ReverseQuery;
use Geocoder\Provider\Provider;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* This Geocoder allows you to profile your API/Database calls.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
final class TimedGeocoder implements Geocoder
{
use GeocoderTrait;
/**
* @var Provider
*/
private $delegate;
/**
* @var Stopwatch
*/
private $stopwatch;
public function __construct(Provider $delegate, Stopwatch $stopwatch)
{
$this->delegate = $delegate;
$this->stopwatch = $stopwatch;
}
/**
* {@inheritdoc}
*/
public function geocodeQuery(GeocodeQuery $query): Collection
{
$this->stopwatch->start('geocode', 'geocoder');
try {
$result = $this->delegate->geocodeQuery($query);
} catch (\Throwable $e) {
$this->stopwatch->stop('geocode');
throw $e;
}
$this->stopwatch->stop('geocode');
return $result;
}
/**
* {@inheritdoc}
*/
public function reverseQuery(ReverseQuery $query): Collection
{
$this->stopwatch->start('reverse', 'geocoder');
try {
$result = $this->delegate->reverseQuery($query);
} catch (\Throwable $e) {
$this->stopwatch->stop('reverse');
throw $e;
}
$this->stopwatch->stop('reverse');
return $result;
}
public function __call($method, $args)
{
return call_user_func_array([$this->delegate, $method], $args);
}
public function getName(): string
{
return 'timed_geocoder';
}
}

View File

@@ -0,0 +1,47 @@
{
"name": "willdurand/geocoder",
"type": "library",
"description": "Common files for PHP Geocoder",
"keywords": [
"geocoder",
"geocoding",
"abstraction",
"geoip"
],
"homepage": "http://geocoder-php.org",
"license": "MIT",
"authors": [
{
"name": "William Durand",
"email": "william.durand1@gmail.com"
}
],
"require": {
"php": "^7.3 || ^8.0"
},
"require-dev": {
"nyholm/nsa": "^1.1",
"phpunit/phpunit": "^9.5",
"symfony/stopwatch": "~2.5"
},
"suggest": {
"symfony/stopwatch": "If you want to use the TimedGeocoder"
},
"extra": {
"branch-alias": {
"dev-master": "4.1-dev"
}
},
"autoload": {
"psr-4": {
"Geocoder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml"
}
}