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 Weather Laravel Package

torann/laravel-weather

Torann's Laravel Weather package brings current conditions and forecasts into Laravel through a simple facade and configurable API providers. It supports caching and multiple weather services, making it easy to add weather data to any Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. The package is low-adoption and wraps third-party APIs that change independently of it — lock the version so a composer update never silently swaps driver behaviour:

composer require torann/laravel-weather:^1.0

Laravel 5.5+ auto-discovers the service provider and Weather facade. On older versions, add Torann\Weather\WeatherServiceProvider::class to providers and 'Weather' => Torann\Weather\Facades\Weather::class to aliases in config/app.php.

Publish and read the config first. The published config file is the real documentation — it lists the supported drivers, the default key, units, and the cache block:

php artisan vendor:publish --provider="Torann\Weather\WeatherServiceProvider"

Move every key into .env immediately. A typical setup:

// config/weather.php
return [
    'default' => env('WEATHER_DRIVER', 'openweathermap'),
    'units'   => env('WEATHER_UNITS', 'metric'),
    'cache'   => [
        'enabled'  => env('WEATHER_CACHE', true),
        'lifetime' => env('WEATHER_CACHE_TTL', 15), // confirm minutes vs seconds in source
    ],
    'providers' => [
        'openweathermap' => ['api_key' => env('OPENWEATHERMAP_API_KEY')],
    ],
];

First move: probe, don't build. Before writing product code, open php artisan tinker with a live key and dump the full response shape:

Weather::getCurrent('London, UK');
Weather::getForecast('London, UK', 5);

The public API drifted across versions and the README is not authoritative — verify method names and response keys against the actual driver class in vendor/torann/laravel-weather/src/. Do the same with coordinates if you pass lat/lon.

Day-one viability check. Several wrapped providers (Yahoo, Weather Underground) are retired and OpenWeatherMap's older endpoints are deprecated in favour of One Call 3.0. Expect at most one working driver. Answer three questions before you book the sprint: which driver returns data against a live key today, what the exact response keys/units/null behaviour are, and whether composer.json PHP and illuminate/support constraints match your Laravel version. If the answer is "one driver we could call directly in ten lines," treat the package as a throwaway spike.

First vertical slice (one day): a dashboard card showing current conditions for a hardcoded city, behind your own interface, with a null-object fallback so it renders "unavailable" rather than 0°C.

Implementation Patterns

Wrap the facade behind a port

Never call Weather:: from controllers, jobs, or Blade. Treat it as a package-private API and adapt it once:

namespace App\Weather;

interface WeatherProvider
{
    public function current(Location $location): Conditions;
    public function forecast(Location $location, int $days = 5): Forecast;
}
final class PackageWeatherProvider implements WeatherProvider
{
    public function current(Location $location): Conditions
    {
        $raw = \Weather::getCurrent($location->query());
        return Conditions::fromRaw($raw, $location); // your DTO, your units
    }
}

Bind in AppServiceProvider:

$this->app->bind(WeatherProvider::class, PackageWeatherProvider::class);

Everything downstream depends on App\Weather\WeatherProvider. When a driver dies or the package stalls, you change one bind().

Normalize to a DTO on the way in

The package returns loosely-typed arrays shaped by whichever provider answered — fine as transport, terrible as a domain type. Convert once:

final class Conditions
{
    public function __construct(
        public readonly float $temperatureC,
        public readonly float $humidity,
        public readonly float $windKph,
        public readonly string $conditionCode,
        public readonly \DateTimeImmutable $observedAt,
        public readonly string $provider,
    ) {}
}

Do unit conversion in fromRaw(), not at render. That method is the only place that knows "metric vs imperial vs driver-reports-Fahrenheit."

Cache as a decorator, keyed on request shape

Don't rely on the package's internal cache toggle — it hides the key strategy. Wrap your port:

final class CachedWeatherProvider implements WeatherProvider
{
    public function __construct(
        private WeatherProvider $inner,
        private \Illuminate\Contracts\Cache\Repository $cache,
    ) {}

    public function current(Location $location): Conditions
    {
        $key = sprintf(
            'weather:current:%s:%.2f,%.2f:%s',
            config('weather.default'),
            $location->lat,
            $location->lon,
            config('weather.units'),
        );

        return $this->cache->remember(
            $key,
            now()->addMinutes(15),
            fn () => $this->inner->current($location),
        );
    }
}

Round coordinates to two decimals (~1.1 km) so you actually hit the cache. Include driver and units in the key so a config change doesn't serve yesterday's Fahrenheit. TTL is a product decision: current conditions ~10–15 min, forecast ~30–60 min, env-driven per feature.

Fallback chain without touching product code

Because providers sit behind a config key, a small chain is easy:

final class FallbackWeatherProvider implements WeatherProvider
{
    /** @param WeatherProvider[] $providers */
    public function __construct(private array $providers) {}

    public function current(Location $location): Conditions
    {
        foreach ($this->providers as $provider) {
            try {
                return $provider->current($location);
            } catch (\Throwable $e) {
                report($e);
            }
        }
        throw new WeatherUnavailable();
    }
}

Keep the chain to two providers max — every extra provider serialises latency onto cold requests.

Null object for the UI

When the chain throws, return a NullConditions (or a sibling implementing the same read interface). The rule: an empty forecast renders "Forecast unavailable," never 0°C. Silent zeroes are the failure mode stakeholders actually notice.

Test your port, not the package

Mock at the boundary:

$this->mock(WeatherProvider::class, fn ($m) => $m
    ->shouldReceive('current')
    ->andReturn(new Conditions(18.5, 62, 11, 'cloudy', now()->toImmutable(), 'fake')));

Do not reach for Http::fake() — the package uses its own HTTP client (historically Guzzle), so Laravel's HTTP fake won't intercept it. Faking at the port is more reliable and faster.

Pair that with one scheduled contract test that hits the live provider and asserts sanity (temperature between -60 and 60, non-empty condition code). This is your early warning that a provider changed its payload shape; the package won't tell you.

Warm the cache out of band

For dashboards, refresh the handful of locations you actually display on a schedule so every user request is a cache hit:

Schedule::call(fn () => $weather->current($defaultLocation))->everyFifteenMinutes();

If the location set is user-configurable, warm the top N and let the long tail go cold to bound spend.

Gotchas and Tips

Dead drivers are the headline risk. Verify every driver you configure against a live key before trusting it. Misconfigured drivers often return an empty array rather than throwing, which is worse than an error.

Expect arrays, not objects. Thin parsers over third-party payloads yield missing keys and null temperatures. Guard every access ($raw['main']['temp'] ?? null) and let nulls propagate to the null object — never into number_format() or arithmetic.

Unit drift is silent and product-visible. One driver's "metric" may still return wind in mph or pressure in hPa. Normalize in fromRaw() and write a unit test against a known fixture. This bug shows a plausible wrong number, so nothing alerts.

Timezone and date boundaries. "Tomorrow's forecast" depends on whose midnight. Pin the timezone explicitly in your Location value object, and assert index 0 is your tomorrow — daily arrays from different providers start on different days.

Coordinate precision. Round before keying the cache, but send reasonable precision to the provider. Confirm whether the driver accepts lat,lon in the same string parameter as a city name or needs separate config — a bad format returns the wrong city's weather without erroring.

config:cache will bite you. Never call env() outside the config file. Anything your adapter reads at runtime (driver, units, TTL) must come from config(). Test with php artisan config:cache locally before shipping.

Facade resolution timing. Confirm whether the package binds the provider client at boot or resolves per call. If at boot, changing weather.default at runtime (multi-tenant, per-user provider) won't take effect. Test: config(['weather.default' => 'x']) mid-request and see whether the next call changes.

No timeouts, retries, or circuit breaker — build them. The package gives you no retry policy and no explicit timeout. An unresponsive provider becomes an unresponsive page. Set a client timeout if the driver exposes one; otherwise wrap the adapter call in your own budget. A missing timeout is the most common way this package causes an incident.

Quota exhaustion looks like empty forecasts. Providers return 401/429, the parser yields nulls, the UI blanks out. Alert on error rate and on the share of responses falling through to the null object — not just on 5xx.

Cache stampede on expiry. When a popular key expires under load, every concurrent request misses simultaneously. Add Cache::lock() around the fetch, or refresh ahead on a schedule — costs five lines.

Never call it in a loop. A list of 40 locations with per-row Weather::getCurrent() is 40 sequential HTTP calls. Batch through your cache or precompute in a job.

Config drift after publish. The published config/weather.php is yours — new keys added upstream won't appear. Diff against the vendor file on every upgrade.

Custom driver is the escape hatch. The design is extensible: implement the package's provider contract and register it under providers. Read an existing driver class first — it's the best documentation in the repo.

Log the raw payload at debug level behind a config flag for the first two weeks. When numbers look wrong, you need to know whether the provider lied or your mapping did.

Fork plan, decided in advance. BSD-2-Clause, small surface, zero dependents — if upstream goes quiet (assume it will), vendor src/ into your repo behind the same port. Keep a one-page internal doc listing the exact methods and response keys you depend on; it turns a fork into a two-hour task instead of a two-day one.

Facade alias collisions. Weather is a generic alias. If another package or your own code already uses it, register it explicitly as LaravelWeather in config/app.php and reference the class directly.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager