Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Yr Laravel Package

ekstremedia/laravel-yr

Laravel package that wraps the Yr weather service (MET Norway), letting you fetch location-based forecasts and weather data in your PHP applications. Clean, simple API with easy configuration and seamless Laravel integration for any project.

View on GitHub
Deep Wiki
Context7

Getting Started

ekstremedia/laravel-yr wraps MET Norway's public Yr/locationforecast API. Install and publish the config, then set a real contact in the User-Agent — MET rejects anonymous clients and will 403 you.

composer require ekstremedia/laravel-yr
php artisan vendor:publish --tag=yr-config

In config/yr.php the two knobs you'll touch immediately are cache_ttl and nowcast_cache_ttl, plus the User-Agent string (MET requires an identifying value with contact info). Pick your cache store here too — the package goes through Laravel's Cache facade, so Redis is worth it if you render forecasts on every page load.

First use case: a coordinates-based forecast for a page header.

use Ekstremedia\Yr\Facades\Yr;

$forecast = Yr::forecast(59.9139, 10.7522); // Oslo
// null-safe: check before touching ->timeseries

Look first at the published config, then at the facade's return shape (timeseries entries with time, data.instant.details, and symbol codes). The service provider is small; there's no deep container wiring to learn.

On v1.2.1 specifically: this is a cache-correctness release with no public API changes, so a fresh install is the same as before. What changed is how long things live in cache, which matters the moment you have a user complaint about stale weather.

Implementation Patterns

Let MET drive the TTL; treat cache_ttl as a fallback. As of v1.2.1 the cache lifetime comes from MET's Expires header (floored at 60 s), and cache_ttl / nowcast_cache_ttl only apply when MET sends no Expires. Configure them, but don't assume they bound the age of what you render. If you need a hard upper bound for a "current conditions" widget, layer your own short Cache::remember() around the package call.

Wrap calls in your own thin service. The package is a client, not a domain model. Put a WeatherService in front of it so controllers and Blade views never see MET's raw JSON shape, and so you have one place to add your own fallback cache, logging, and null handling.

class WeatherService
{
    public function forLocation(float $lat, float $lon): ?array
    {
        return Cache::remember(
            "weather:{$lat}:{$lon}",
            now()->addMinutes(10),
            fn () => Yr::forecast($lat, $lon),
        );
    }
}

Rely on conditional revalidation instead of manual refresh jobs. v1.2.1 keeps the last good body for a day and revalidates with If-Modified-Since; a 304 Not Modified reuses that body until its new Expires. This means a scheduler that "warms" the cache for popular locations is now cheap and safe — it will mostly hit 304s. That's the right pattern for a high-traffic site with a handful of fixed locations.

// app/Console/Commands/WarmForecasts.php
foreach (config('yr.locations') as $loc) {
    Yr::forecast($loc['lat'], $loc['lon']); // 304s cost you almost nothing
}

Nowcast is a different product — do not swap them. Nowcasts cover a short horizon and a limited region; they have their own TTL and their own config key. If you build a "rain in the next hour" banner, it must call nowcast; the long forecast will not answer that question.

Test with Http::fake() and assert on the caching contract, not the payload. Since v1.2.1's behaviour is all about cache lifetimes, the tests worth writing are the ones that fake a response with an Expires header, advance time, and assert the client does not refetch until expiry. Fake a 304 and assert the previous body is still returned — that's a regression that would otherwise only show up in production.

Pass explicit coordinates with bounded precision. MET wants at most four decimal places. Round before calling, both to stay compliant and to make your cache keys stable — 59.91394 and 59.91395 are different keys but the same weather.

Gotchas and Tips

Failed requests are no longer cached — plan for that. Before v1.2.1 a failure could get baked into cache for the full TTL; now it won't. Good for correctness, but it means a MET outage turns every page load into a live outbound request. Add a circuit breaker or a short-lived sentinel key in your own wrapper so you fail fast instead of hammering a struggling upstream.

A non-JSON body returns null now, not an exception. The release notes call this out as reported behaviour, presumably an HTML error page or a proxy interstitial. If you had a try/catch around the call to handle that case, it's now dead code and the null will flow straight into your view. Every call site needs a null path — a cached "last known good" value of your own, a hidden widget, or a placeholder.

Check your test suite for assertions on thrown exceptions. This is the one place "no public API changes" can still bite you: a test asserting a JsonException or similar on a malformed body will now fail, because it returns null.

Cached forecasts can be up to a day old in the degraded path. The revalidation window keeps the last good answer for a day. If a location stops being served correctly by MET, you may render a day-old forecast rather than nothing. That's usually the right trade-off for a weather widget, but it's wrong for anything safety-critical — surface a timestamp in the UI so users can judge.

The Expires-in-the-past case differs across Carbon versions. v1.2.1 fixes Laravel 10 / Carbon 2 to cache for 60 s instead of "as long as it lies behind", which was effectively unbounded negative TTL. If you're on Laravel 9/10 you want this patch specifically; if you were relying on the old behaviour (you almost certainly weren't, deliberately), re-check your hit rates.

Timezone and units are query-level, not package-level. MET returns UTC and metric; the package doesn't localise. Convert with ->setTimezone(config('app.timezone')) at render time and format symbols into your own translation layer — MET's symbol codes are a stable, finite set and map cleanly to icons.

Extension points are thin by design. There's no repository interface to swap. When you need to change transport behaviour — retries, extra headers, a different HTTP client — bind your own client around the package rather than reaching into its internals; that keeps upgrades (like this one) a non-event.

Upgrade note: v1.2.1 is a straight drop-in from v1.2.0. The only observable change is cache lifetime and the null-instead-of-throw path. Deploy it, watch your outbound request volume for a day, and confirm your null handling is in place.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox