cmfcmf/openweathermap-php-api
A lightweight, object-oriented PHP wrapper for OpenWeatherMap. Retrieve current weather, forecasts, and historical data by city name, coordinates, or city ID, with configurable units and language. Simple setup and clean response objects.
Install the package and a PSR-18 HTTP client (the library discovers one automatically, but installing it explicitly avoids surprise runtime errors):
composer require cmfcmf/openweathermap-php-api guzzlehttp/guzzle
Get an API key from OpenWeatherMap, put it in .env (never in code), and make the smallest possible call. The first useful use case is a current-conditions widget:
use Cmfcmf\OpenWeatherMap;
$owm = new OpenWeatherMap($_ENV['OWM_API_KEY']);
try {
$weather = $owm->getWeather('Berlin', 'metric', 'en');
printf(
"%s: %s (%s)\n",
$weather->city->name,
$weather->temperature->now, // __toString on the unit object
$weather->weather->description
);
} catch (\Cmfcmf\OpenWeatherMap\Exception $e) {
// invalid key, unknown city, quota exceeded, etc.
error_log($e->getMessage());
}
Where to look first:
src/OpenWeatherMap.php — the public façade; all supported endpoints are methods here.src/Query/ — one class per endpoint (CurrentWeatherQuery, ForecastQuery, etc.); this is where parsing lives if a field is missing.src/Util/ — the value objects you actually consume: Unit, Temperature, Wind, Pressure, Humidity, Sun, City, Coordinates.A minimal first call is enough to prove key, client discovery, and network egress all work before you build caching or UI around it.
Wrap the façade in one application service. Bind a single configured instance and never scatter new OpenWeatherMap(...) through controllers. In Laravel:
// AppServiceProvider::register()
$this->app->singleton(OpenWeatherMap::class, function () {
return new OpenWeatherMap(config('services.openweather.key'));
});
Then inject OpenWeatherMap into a small WeatherService that applies your defaults (units, language, coordinate convention). This gives you one place to later add caching, retries, and logging.
Use coordinates or city IDs, not city names, for anything automated. City strings are ambiguous and can resolve to an unexpected place. Keep a Coordinates value object or OWM city ID in your database:
use Cmfcmf\OpenWeatherMap\Util\Coordinates;
$weather = $owm->getWeather(new Coordinates(52.5200, 13.4050), 'metric', 'en');
Treat forecast as a 3-hourly stream, then aggregate for UI. The free forecast endpoint returns ~40 entries over five days. Daily highs/lows must be computed client-side:
$daily = [];
foreach ($owm->getForecast($coordinates, 'metric', 'en') as $point) {
$day = $point->time->format('Y-m-d');
$temp = $point->temperature->now->getValue();
$daily[$day]['min'] = min($daily[$day]['min'] ?? PHP_FLOAT_MAX, $temp);
$daily[$day]['max'] = max($daily[$day]['max'] ?? PHP_FLOAT_MIN, $temp);
}
Cache aggressively and per-unit. The package performs no caching in 2.x. Cache current weather for 5–10 minutes and forecasts for 20–30 minutes, keyed by (endpoint, location, units, lang). A PSR-16 cache works well; store either the parsed objects (they serialize cleanly) or the raw array from getRawWeather()->getRawData().
Use raw responses for fields the package does not model. getRawWeather() exposes the decoded JSON, which is the escape hatch for visibility, rain/snow volumes, timezone offsets, and any new API fields. Prefer raw access over forking the package for one missing property.
Mock the HTTP layer in tests. The constructor accepts a PSR-18 client and PSR-17 request factory; inject a stub returning fixture JSON. This keeps unit tests fast and deterministic, and lets you exercise 401/404/429 paths without hitting OWM.
Handle errors as a first-class branch. Catch Cmfcmf\OpenWeatherMap\Exception, distinguish client errors (bad key, unknown location) from transient ones (429, 5xx), and fall back to stale cache where possible. Do not let a weather widget take down a page.
Pass units and language explicitly on every call. Defaults exist, but making them explicit documents intent and prevents a mix of Kelvin, Celsius, and Fahrenheit in logs and caches. Keep the language code tied to your UI locale, not to a hard-coded 'en'.
'de' as units produces a confusing error or wrong unit.temperature->min/max on current weather are observation min/max, not the day's forecast range. Do not label them as daily highs and lows.precipitation, wind->direction, sun->rise/set (polar day/night), and rain/snow blocks may be absent or null. Guard before dereferencing.lastUpdate, forecast time, and sunrise/sunset are UTC DateTime objects. Convert at the presentation layer, not in the domain object.Coordinates is (latitude, longitude). Reversing them silently returns weather for the wrong hemisphere.php-http/discovery must be allowed in config.allow-plugins. If no client is installed, discovery throws at construction time — install Guzzle or Symfony HttpClient.$weather->weather->icon returns values like 01d; map them to your own asset URLs so you control branding and CDN behavior.OpenWeatherMap in your own service is cleaner than subclassing it; for endpoints not yet supported, call the OWM HTTP API directly with the same PSR-18 client.How can I help you explore Laravel packages today?