Skip to content

Commit 09614bb

Browse files
committed
refactor(entities): share nested coordinates
1 parent be70b0e commit 09614bb

11 files changed

Lines changed: 50 additions & 40 deletions

File tree

docs/air-pollution.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Air Pollution
22

3+
Current, forecast, and historical air pollution are included in OpenWeather's
4+
standard free and paid subscriptions.
5+
36
## Current
47

5-
The Current Air Pollution API is available on OpenWeather's standard free and
6-
paid subscriptions. See the
8+
See OpenWeather's
79
[official Air Pollution API documentation](https://openweathermap.org/api/air-pollution)
810
for the upstream endpoint contract.
911

@@ -36,6 +38,10 @@ The air quality index uses OpenWeather's native scale from 1 (good) through 5
3638
the fixed `µg/m³` unit documented by OpenWeather; weather unit configuration
3739
does not affect them.
3840

41+
OpenWeather also documents the UK, European, US, and Mainland China scales in
42+
its [Air Pollution Index levels](https://openweathermap.org/api/air-pollution-index-levels)
43+
reference.
44+
3945
```php
4046
$components = $current->components();
4147

docs/weather.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ $current = $api->weather()->current(
2323

2424
The method returns a `Current` entity. Every response property may be
2525
absent or explicitly `null`; missing or `null` condition lists become empty
26-
arrays.
26+
arrays. Contextual response coordinates are grouped under `coordinates()`.
2727

2828
```php
2929
echo $current->name();
30-
echo $current->latitude();
31-
echo $current->longitude();
30+
echo $current->coordinates()?->latitude();
31+
echo $current->coordinates()?->longitude();
3232
echo $current->temperature();
3333
echo $current->feelsLikeTemperature();
3434
echo $current->minimumTemperature();
@@ -109,8 +109,8 @@ foreach ($forecast->periods() as $period) {
109109
}
110110

111111
echo $forecast->city()?->name();
112-
echo $forecast->city()?->latitude();
113-
echo $forecast->city()?->longitude();
112+
echo $forecast->city()?->coordinates()?->latitude();
113+
echo $forecast->city()?->coordinates()?->longitude();
114114
echo $forecast->city()?->timezoneOffset();
115115
```
116116

src/Entity/AirPollution/Current.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use ProgrammatorDev\Api\Context\Context;
66
use ProgrammatorDev\Api\Contract\EntityInterface;
77
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\Concern\HasAirQuality;
8+
use ProgrammatorDev\OpenWeatherMap\Entity\Coordinates;
89
use ProgrammatorDev\OpenWeatherMap\Hydration\PayloadReader;
910

1011
final class Current implements EntityInterface
@@ -21,6 +22,8 @@ public static function fromArray(array $data, ?Context $context = null): static
2122
{
2223
$reader = PayloadReader::from($data, self::class);
2324
$coordinates = $reader->nullableArray('coord');
25+
26+
// The current endpoint wraps its single observation in a list.
2427
$observation = $reader->nullableArray('list.0') ?? [];
2528

2629
return new self(

src/Entity/AirPollution/Forecast.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use ProgrammatorDev\Api\Context\Context;
66
use ProgrammatorDev\Api\Contract\EntityInterface;
77
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\Forecast\Period;
8+
use ProgrammatorDev\OpenWeatherMap\Entity\Coordinates;
89
use ProgrammatorDev\OpenWeatherMap\Exception\HydrationException;
910
use ProgrammatorDev\OpenWeatherMap\Hydration\PayloadReader;
1011

src/Entity/AirPollution/History.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use ProgrammatorDev\Api\Context\Context;
66
use ProgrammatorDev\Api\Contract\EntityInterface;
77
use ProgrammatorDev\OpenWeatherMap\Entity\AirPollution\History\Period;
8+
use ProgrammatorDev\OpenWeatherMap\Entity\Coordinates;
89
use ProgrammatorDev\OpenWeatherMap\Exception\HydrationException;
910
use ProgrammatorDev\OpenWeatherMap\Hydration\PayloadReader;
1011

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace ProgrammatorDev\OpenWeatherMap\Entity\AirPollution;
3+
namespace ProgrammatorDev\OpenWeatherMap\Entity;
44

55
use ProgrammatorDev\Api\Context\Context;
66
use ProgrammatorDev\Api\Contract\EntityInterface;

src/Entity/Weather/Current.php

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use ProgrammatorDev\Api\Context\Context;
66
use ProgrammatorDev\Api\Contract\EntityInterface;
7+
use ProgrammatorDev\OpenWeatherMap\Entity\Coordinates;
78
use ProgrammatorDev\OpenWeatherMap\Entity\Weather\Concern\HasWeatherMeasurements;
89
use ProgrammatorDev\OpenWeatherMap\Entity\Weather\Current\Precipitation;
910
use ProgrammatorDev\OpenWeatherMap\Enum\Units;
@@ -19,8 +20,7 @@ final class Current implements EntityInterface
1920
* @param list<Condition> $conditions
2021
*/
2122
private function __construct(
22-
private readonly ?float $latitude,
23-
private readonly ?float $longitude,
23+
private readonly ?Coordinates $coordinates,
2424
private readonly array $conditions,
2525
private readonly ?float $temperature,
2626
private readonly ?float $feelsLikeTemperature,
@@ -48,6 +48,7 @@ private function __construct(
4848
public static function fromArray(array $data, ?Context $context = null): static
4949
{
5050
$reader = PayloadReader::from($data, self::class);
51+
$coordinates = $reader->nullableArray('coord');
5152
$conditions = [];
5253

5354
foreach ($reader->nullableArray('weather') ?? [] as $index => $condition) {
@@ -69,8 +70,9 @@ public static function fromArray(array $data, ?Context $context = null): static
6970
$snow = $reader->nullableArray('snow');
7071

7172
return new self(
72-
latitude: $reader->nullableFloat('coord.lat'),
73-
longitude: $reader->nullableFloat('coord.lon'),
73+
coordinates: $coordinates === null
74+
? null
75+
: Coordinates::fromArray($coordinates, $context),
7476
conditions: $conditions,
7577
temperature: $reader->nullableFloat('main.temp'),
7678
feelsLikeTemperature: $reader->nullableFloat('main.feels_like'),
@@ -96,14 +98,9 @@ public static function fromArray(array $data, ?Context $context = null): static
9698
);
9799
}
98100

99-
public function latitude(): ?float
101+
public function coordinates(): ?Coordinates
100102
{
101-
return $this->latitude;
102-
}
103-
104-
public function longitude(): ?float
105-
{
106-
return $this->longitude;
103+
return $this->coordinates;
107104
}
108105

109106
/**

src/Entity/Weather/Forecast/City.php

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
use ProgrammatorDev\Api\Context\Context;
66
use ProgrammatorDev\Api\Contract\EntityInterface;
7+
use ProgrammatorDev\OpenWeatherMap\Entity\Coordinates;
78
use ProgrammatorDev\OpenWeatherMap\Hydration\PayloadReader;
89

910
final class City implements EntityInterface
1011
{
1112
private function __construct(
1213
private readonly ?int $id,
1314
private readonly ?string $name,
14-
private readonly ?float $latitude,
15-
private readonly ?float $longitude,
15+
private readonly ?Coordinates $coordinates,
1616
private readonly ?string $countryCode,
1717
private readonly ?int $population,
1818
private readonly ?int $timezoneOffset,
@@ -23,12 +23,14 @@ private function __construct(
2323
public static function fromArray(array $data, ?Context $context = null): static
2424
{
2525
$reader = PayloadReader::from($data, self::class);
26+
$coordinates = $reader->nullableArray('coord');
2627

2728
return new self(
2829
id: $reader->nullableInt('id'),
2930
name: $reader->nullableString('name'),
30-
latitude: $reader->nullableFloat('coord.lat'),
31-
longitude: $reader->nullableFloat('coord.lon'),
31+
coordinates: $coordinates === null
32+
? null
33+
: Coordinates::fromArray($coordinates, $context),
3234
countryCode: $reader->nullableString('country'),
3335
population: $reader->nullableInt('population'),
3436
timezoneOffset: $reader->nullableInt('timezone'),
@@ -47,14 +49,9 @@ public function name(): ?string
4749
return $this->name;
4850
}
4951

50-
public function latitude(): ?float
52+
public function coordinates(): ?Coordinates
5153
{
52-
return $this->latitude;
53-
}
54-
55-
public function longitude(): ?float
56-
{
57-
return $this->longitude;
54+
return $this->coordinates;
5855
}
5956

6057
public function countryCode(): ?string

src/Resource/AirPollution.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public function history(
5757
$latitude = Assert::latitude($latitude);
5858
$longitude = Assert::longitude($longitude);
5959
Assert::chronologicalRange($start, $end);
60+
61+
// A non-future end also constrains the ordered start.
62+
// The documented minimum is left to OpenWeather because live availability differs.
6063
$end = Assert::notFuture($end, 'end date');
6164

6265
// https://openweathermap.org/api/air-pollution

tests/Unit/Entity/Weather/CurrentTest.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ public function testHydratesCapturedCurrentWeather(): void
2121
Fixture::json('weather/current/success.json'),
2222
);
2323

24-
self::assertSame(38.7223, $weather->latitude());
25-
self::assertSame(-9.1393, $weather->longitude());
24+
self::assertSame(38.7223, $weather->coordinates()?->latitude());
25+
self::assertSame(-9.1393, $weather->coordinates()?->longitude());
2626
self::assertSame(22.55, $weather->temperature());
2727
self::assertSame(Unit::CELSIUS, $weather->temperatureUnit());
2828
self::assertSame('22.55 °C', $weather->temperatureWithUnit());
@@ -124,6 +124,7 @@ public function testRetainsUnitsFromHydrationContext(): void
124124

125125
public function testToleratesMissingNullUnknownAndPartialFields(): void
126126
{
127+
self::assertNull(Current::fromArray([])->coordinates());
127128
self::assertSame([], Current::fromArray(['weather' => null])->conditions());
128129

129130
$weather = Current::fromArray([
@@ -142,8 +143,8 @@ public function testToleratesMissingNullUnknownAndPartialFields(): void
142143
'unknown' => new \stdClass(),
143144
]);
144145

145-
self::assertNull($weather->latitude());
146-
self::assertNull($weather->longitude());
146+
self::assertNull($weather->coordinates()?->latitude());
147+
self::assertNull($weather->coordinates()?->longitude());
147148
self::assertCount(1, $weather->conditions());
148149
self::assertNull($weather->conditions()[0]->icon());
149150
self::assertNull($weather->conditions()[0]->iconUrl());
@@ -185,7 +186,7 @@ public function testRejectsInvalidKnownFieldTypes(
185186
public static function invalidFields(): iterable
186187
{
187188
yield 'coordinates' => [['coord' => 'invalid'], 'coord', 'array', 'string'];
188-
yield 'latitude' => [['coord' => ['lat' => '38.7']], 'coord.lat', 'int|float', 'string'];
189+
yield 'latitude' => [['coord' => ['lat' => '38.7']], 'lat', 'int|float', 'string'];
189190
yield 'conditions' => [['weather' => 'Clouds'], 'weather', 'array', 'string'];
190191
yield 'condition member' => [['weather' => ['Clouds']], 'weather.0', 'array', 'string'];
191192
yield 'condition id' => [['weather' => [['id' => '802']]], 'id', 'int', 'string'];

0 commit comments

Comments
 (0)