Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 316 additions & 34 deletions src/Provider/Here/Here.php

Large diffs are not rendered by default.

20 changes: 11 additions & 9 deletions src/Provider/Here/Model/HereAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,27 @@ final class HereAddress extends Address
/**
* @var string|null
*/
private $locationId;
private ?string $locationId = null;

/**
* @var string|null
*/
private $locationType;
private ?string $locationType = null;

/**
* @var string|null
*/
private $locationName;
private ?string $locationName = null;

/**
* @var array<string, mixed>|null
*/
private $additionalData;
private ?array $additionalData = [];

/**
* @var array<string, mixed>|null
*/
private $shape;
private ?array $shape = [];

/**
* @return string|null
Expand Down Expand Up @@ -107,8 +107,10 @@ public function withAdditionalData(?array $additionalData = null): self
{
$new = clone $this;

foreach ($additionalData as $data) {
$new = $new->addAdditionalData($data['key'], $data['value']);
if (null !== $additionalData) {
foreach ($additionalData as $data) {
$new = $new->addAdditionalData($data['key'], $data['value']);
}
}

return $new;
Expand Down Expand Up @@ -136,7 +138,7 @@ public function getAdditionalDataValue(string $name, mixed $default = null): mix

public function hasAdditionalDataValue(string $name): bool
{
return array_key_exists($name, $this->additionalData);
return null !== $this->additionalData && array_key_exists($name, $this->additionalData);
}

/**
Expand Down Expand Up @@ -174,6 +176,6 @@ public function getShapeValue(string $name, mixed $default = null): mixed

public function hasShapeValue(string $name): bool
{
return array_key_exists($name, $this->shape);
return null !== $this->shape && array_key_exists($name, $this->shape);
}
}
89 changes: 79 additions & 10 deletions src/Provider/Here/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,111 @@
This is the Here provider from the PHP Geocoder. This is a **READ ONLY** repository. See the
[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation.

You can find the [documentation for the provider here](https://developer.here.com/documentation/geocoder/dev_guide/topics/resources.html).
## API Versions

This provider supports two HERE API generations:

| | Geocoding & Search API | Legacy Geocoder REST API |
|---|---|---|
| This provider calls it | **"v8"** (new default) | **"v7"** (deprecated) |
| HERE's own name | "Geocoding & Search API v7" / GS7 | "Geocoder REST API" / 6.2 |
| URL path | `/v1/` | `/6.2/` |
| Authentication | API Key only | API Key or App ID + App Code |
| Deprecated by HERE | — | **December 31, 2023** |
| Shut down by HERE | — | **July 2025** |
| Removed from this provider | — | **Next major release** |

> **Note on version naming:** HERE's legacy API uses URL path `/6.2/` and is called the "Geocoder REST
> API". HERE confusingly named its replacement the "Geocoding & Search API **v7**" (also known as GS7).
> To avoid this collision, this provider uses the shorthand **"v8"** for the new Geocoding & Search API
> and **"v7"** for the legacy 6.2 API.

**Timeline:**
- **December 31, 2023** — HERE deprecated the legacy Geocoder REST API (v7).
- **July 2025** — HERE shut down the v7 endpoints. Live requests to `geocoder.ls.hereapi.com` will fail.
- **Next major release of this provider** — The v7 compatibility code will be removed: `createV7UsingApiKey()`, `new Here($client, $appId, $appCode)`, all `GEOCODE_ENDPOINT_URL_*` / `REVERSE_ENDPOINT_URL_*` v7 constants, and `parseV7Response()`.

Migrate to v8 as soon as possible.
See the [HERE migration guide](https://www.here.com/docs/bundle/geocoding-and-search-api-migration-guide/page/migration-geocoder/README.html) for details.

### Install

```bash
composer require geocoder-php/here-provider
```

## Using
## Using v8 (Recommended)

New applications on the Here platform use the `api_key` authentication method.
New and existing applications should use `createUsingApiKey()`, which targets the v8 Geocoding & Search API:

```php
$httpClient = new \Http\Discovery\Psr18Client();

// You must provide an API key
// Provide your HERE API Key
$provider = \Geocoder\Provider\Here\Here::createUsingApiKey($httpClient, 'your-api-key');

$result = $geocoder->geocodeQuery(GeocodeQuery::create('Buckingham Palace, London'));
$result = $geocoder->geocodeQuery(GeocodeQuery::create('10 Downing St, London, UK'));
```

### v8 Query Parameters

The v8 API supports the following extra parameters via `GeocodeQuery::withData()`:

| Parameter | Description |
|-----------|-------------|
| `at` | Reference position for result sorting, e.g. `"52.5,13.4"` |
| `in` | Geographic area filter, e.g. `"countryCode:DEU"` |
| `types` | Filter result types, e.g. `"houseNumber,street"` |
| `country` | ISO 3166-1 alpha-3 country code filter (mapped to `qq` param) |
| `state` | State/region filter (mapped to `qq` param) |
| `county` | County filter (mapped to `qq` param) |
| `city` | City filter (mapped to `qq` param) |

### v8 Response Fields

In addition to standard Geocoder fields, `HereAddress` provides:

- `getLocationId()` — unique HERE location ID
- `getLocationType()` — result type (`houseNumber`, `street`, `locality`, `administrativeArea`, etc.)
- `getLocationName()` — formatted title of the result
- `getAdditionalDataValue($name)` — access extra fields such as `Label`, `CountryName`, `StateName`, `CountyName`, `CountyCode`, `StateCode`, `District`, `Subdistrict`, `HouseNumberType`, etc.

## Using v7 (Deprecated — Shut down July 2025)

> **Warning:** The HERE Geocoder REST API v7 was deprecated December 31, 2023 and shut down in
> July 2025. Requests will fail. Migrate to v8 using `createUsingApiKey()` above.
> See the [HERE retirement announcement](https://www.here.com/learn/blog/additional-important-guidance-on-here-location-services-end-of-life) for details.

If you have existing code that uses the legacy API Key authentication:

```php
$httpClient = new \Http\Discovery\Psr18Client();

// @deprecated — Migrate to createUsingApiKey() for the v8 API
$provider = \Geocoder\Provider\Here\Here::createV7UsingApiKey($httpClient, 'your-legacy-api-key');
```

If you're using the legacy `app_code` authentication method, use the constructor on the provider like so:
If you're using the legacy `app_id` + `app_code` authentication:

```php
$httpClient = new \Http\Discovery\Psr18Client();

// You must provide both the app_id and app_code
// @deprecated — Migrate to createUsingApiKey() for the v8 API
$provider = new \Geocoder\Provider\Here\Here($httpClient, 'app-id', 'app-code');

$result = $geocoder->geocodeQuery(GeocodeQuery::create('Buckingham Palace, London'));
```

## Migrating from v7 to v8

1. Replace `new Here($client, $appId, $appCode)` or `createV7UsingApiKey(...)` with `createUsingApiKey($client, $apiKey)`.
2. The response structure changes: `additionalData` values like `CountryName`, `StateName`, `CountyName` are still available via `getAdditionalDataValue()`, but are now sourced from v8 address fields.
3. The `shape` data (v7 `IncludeShapeLevel` parameter) is not available in v8. Remove `withData('IncludeShapeLevel', ...)` from your queries.
4. Replace v7-specific `withData()` keys (`Country2`, `IncludeRoutingInformation`, `IncludeChildPOIs`, etc.) with v8 equivalents where available.

See the [official migration guide](https://www.here.com/docs/bundle/geocoding-and-search-api-migration-guide/page/migration-geocoder/README.html) for a full parameter mapping.

### Language parameter

Define the preferred language of address elements in the result. Without a preferred language, the Here geocoder will return results in an official country language or in a regional primary language so that local people will understand. Language code must be provided according to RFC 4647 standard.
Define the preferred language of address elements in the result. Without a preferred language, the HERE geocoder will return results in an official country language or in a regional primary language. Language code must be provided according to RFC 4647 standard.

### Contribute

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:12:"{"items":[]}";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:550:"{"items":[{"title":"10 Downing St, London, SW1A 2AA, United Kingdom","id":"here:af:streetsection:some-id","resultType":"houseNumber","houseNumberType":"PA","address":{"label":"10 Downing St, London, SW1A 2AA, United Kingdom","countryCode":"GBR","countryName":"United Kingdom","state":"England","county":"Greater London","city":"London","district":"Westminster","street":"Downing St","postalCode":"SW1A 2AA","houseNumber":"10"},"position":{"lat":51.50322,"lng":-0.12768},"mapView":{"west":-0.12955,"south":51.50232,"east":-0.12581,"north":51.50412}}]}";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:12:"{"items":[]}";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s:533:"{"items":[{"title":"Pennsylvania Ave NW, Washington, DC 20502, United States","id":"here:af:streetsection:whitehouse-id","resultType":"street","address":{"label":"Pennsylvania Ave NW, Washington, DC 20502, United States","countryCode":"USA","countryName":"United States","state":"District of Columbia","county":"District of Columbia","city":"Washington","street":"Pennsylvania Ave NW","postalCode":"20502"},"position":{"lat":38.89812,"lng":-77.03653},"mapView":{"west":-77.04201,"south":38.89625,"east":-77.02961,"north":38.90241}}]}";
37 changes: 29 additions & 8 deletions src/Provider/Here/Tests/HereTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function testGeocodeWithRealAddress(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$results = $provider->geocodeQuery(GeocodeQuery::create('10 avenue Gambetta, Paris, France')->withLocale('fr-FR'));

Expand Down Expand Up @@ -75,7 +75,7 @@ public function testGeocodeWithDefaultAdditionalData(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$results = $provider->geocodeQuery(GeocodeQuery::create('Sant Roc, Santa Coloma de Cervelló, Espanya')->withLocale('ca'));

Expand Down Expand Up @@ -117,7 +117,7 @@ public function testGeocodeWithAdditionalData(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$results = $provider->geocodeQuery(GeocodeQuery::create('Sant Roc, Santa Coloma de Cervelló, Espanya')
->withData('Country2', 'true')
Expand Down Expand Up @@ -167,7 +167,7 @@ public function testGeocodeWithExtraFilterCountry(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$queryBarcelonaFromSpain = GeocodeQuery::create('Barcelona')->withData('country', 'ES')->withLocale('ca');
$queryBarcelonaFromVenezuela = GeocodeQuery::create('Barcelona')->withData('country', 'VE')->withLocale('ca');
Expand Down Expand Up @@ -202,7 +202,7 @@ public function testGeocodeWithExtraFilterCity(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$queryStreetCity1 = GeocodeQuery::create('Carrer de Barcelona')->withData('city', 'Sant Vicenç dels Horts')->withLocale('ca')->withLimit(1);
$queryStreetCity2 = GeocodeQuery::create('Carrer de Barcelona')->withData('city', 'Girona')->withLocale('ca')->withLimit(1);
Expand Down Expand Up @@ -240,7 +240,7 @@ public function testGeocodeWithExtraFilterCounty(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$queryCityRegion1 = GeocodeQuery::create('Cabanes')->withData('county', 'Girona')->withLocale('ca')->withLimit(1);
$queryCityRegion2 = GeocodeQuery::create('Cabanes')->withData('county', 'Castelló')->withLocale('ca')->withLimit(1);
Expand Down Expand Up @@ -274,7 +274,7 @@ public function testReverseWithRealCoordinates(): void
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

$provider = Here::createUsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);
$provider = Here::createV7UsingApiKey($this->getHttpClient($_SERVER['HERE_API_KEY']), $_SERVER['HERE_API_KEY']);

$results = $provider->reverseQuery(ReverseQuery::fromCoordinates(48.8632156, 2.3887722));

Expand Down Expand Up @@ -358,12 +358,33 @@ public function testGeocodeWithRealIPv6(): void
$provider->geocodeQuery(GeocodeQuery::create('::ffff:88.188.221.14'));
}

public function testDefaultVersionIsV8(): void
{
$provider = Here::createUsingApiKey($this->getMockedHttpClient(), 'some-api-key');
$this->assertEquals(Here::GEOCODE_ENDPOINT_URL, $provider->getBaseUrl(GeocodeQuery::create('Paris')));
$this->assertEquals(Here::REVERSE_ENDPOINT_URL, $provider->getBaseUrl(ReverseQuery::fromCoordinates(0, 0)));
}

public function testV7ExplicitSelectionViaConstructor(): void
{
$provider = new Here($this->getMockedHttpClient(), 'appId', 'appCode');
$this->assertEquals(Here::GEOCODE_ENDPOINT_URL_APP_CODE, $provider->getBaseUrl(GeocodeQuery::create('Paris')));
$this->assertEquals(Here::REVERSE_ENDPOINT_URL_APP_CODE, $provider->getBaseUrl(ReverseQuery::fromCoordinates(0, 0)));
}

public function testCreateV7UsingApiKeyFactory(): void
{
$provider = Here::createV7UsingApiKey($this->getMockedHttpClient(), 'some-api-key');
$this->assertEquals(Here::GEOCODE_ENDPOINT_URL_API_KEY, $provider->getBaseUrl(GeocodeQuery::create('Paris')));
$this->assertEquals(Here::REVERSE_ENDPOINT_URL_API_KEY, $provider->getBaseUrl(ReverseQuery::fromCoordinates(0, 0)));
}

public function getProvider(): Here
{
if (!isset($_SERVER['HERE_API_KEY'])) {
$this->markTestSkipped('You need to configure the HERE_API_KEY value in phpunit.xml');
}

return Here::createUsingApiKey($this->getHttpClient(), $_SERVER['HERE_API_KEY']);
return Here::createV7UsingApiKey($this->getHttpClient(), $_SERVER['HERE_API_KEY']);
}
}
Loading