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

Lastfm Laravel Package

dandelionmood/lastfm

A Laravel wrapper for the Last.fm API, giving you a simple, expressive client to query artists, albums, tracks, tags, and user charts without handling raw HTTP requests yourself.

View on GitHub
Deep Wiki
Context7

Getting Started

Still not a Laravel package. No service provider, no facade, no auto-discovery, no publishable config. You instantiate the client yourself and wire it in AppServiceProvider. The v0.8 release doesn't change that — what it changes is that the client is now configurable, which is the difference between "usable behind a wrapper" and "a landmine you wrap in rescue()."

  1. Check the tag before you trust the constraint. Earlier guidance here said ^1.0; the current release is v0.8, i.e. a 0.x line. That means (a) the earlier constraint was wrong and would have failed to resolve, and (b) there is no semver BC guarantee — 0.x allows breaking changes in a minor bump. Pin exactly and let Renovate/Dependabot open PRs rather than floating:

    composer require dandelionmood/lastfm:0.8.*
    

    Commit composer.lock. Re-run your PHP version check — "upgraded dependencies that were too old" is the line item most likely to have fixed the PHP 8.3+ install failure, but verify on your actual matrix (8.2/8.3/8.4) before assuming.

  2. Read the source for two things only (the rest of the class hasn't moved):

    • the constructor signature — API key, secret, session key, and now an HTTP client + request options array;
    • the exact shape of that client argument. Is it a PSR-18 ClientInterface, a Guzzle ClientInterface, or a callable? This determines your test strategy (see Gotchas) and whether you can reuse your app's configured HTTP stack.
  3. Add credentials to config/services.php and — new — put transport policy in config too, since it's now expressible:

    'lastfm' => [
        'key'     => env('LASTFM_API_KEY'),
        'secret'  => env('LASTFM_API_SECRET'),
        'timeout' => env('LASTFM_TIMEOUT', 5),
        'retries' => env('LASTFM_RETRIES', 1),
    ],
    
  4. Bind a singleton with an explicitly configured client. Don't rely on whatever the package defaults to — the whole point of v0.8 is that you can now say no:

    $this->app->singleton(\Dandelionmood\LastFm\LastFm::class, fn () => new \Dandelionmood\LastFm\LastFm(
        config('services.lastfm.key'),
        config('services.lastfm.secret'),
        config('services.lastfm.session_key'), // null for read-only
        $this->makeHttpClient(),               // see Gotchas for adapter caveat
        ['timeout' => config('services.lastfm.timeout')],
    ));
    

    Confirm argument order in src/ — the new params were likely appended, but that's a guess, not a fact.

  5. First use case: unchanged. artist.getInfo or user.getRecentTracks from tinker, dump the raw response, confirm you're getting an array (i.e. format=json is being sent) and that your timeout is actually applied by pointing the client at a blackholed host once.

Implementation Patterns

Wrap it, don't sprinkle it — the wrapper is now also where you own transport policy. Because the client accepts a transport and options, the wrapper service becomes the single place that encodes timeout, retry, throttle, and error normalization. That's a real improvement over v0.7, where those knobs didn't exist at all.

final class LastFmService
{
    public function __construct(private \Dandelionmood\LastFm\LastFm $client) {}

    public function artist(string $name): array
    {
        return Cache::remember("lastfm:artist:".md5($name), now()->addHours(6), function () use ($name) {
            $res = $this->client->artistGetInfo($name);
            if (isset($res['error'])) {
                throw new LastFmException($res['message'], $res['error']);
            }
            return $res['artist'];
        });
    }
}

Encode timeout and retry once, at construction. Build the injected client in a dedicated factory so every call site — web, queue, artisan — inherits the same ceiling. A 5s connect/read timeout with a single retry is a sane default for a metadata API; anything higher and you've reintroduced the "page render blocks on Last.fm" problem the timeout was meant to solve.

Throttle at the transport, not the call site. Last.fm's ~5 req/s is per IP, so your queue workers and web requests share one budget. With a configurable client you can now push this into a Guzzle middleware or a Redis::throttle inside the client factory — one place, all callers. Previously this had to be sprinkled.

Cache on a per-endpoint TTL tier. Artist/album/track metadata: hours to days. user.getRecentTracks: 60–120 seconds. Charts: minutes. Encode the tier per method.

Never call the API in a page render. Mirror what you need into Eloquent via a scheduled command; Scrobble::upsert() on a unique index keeps it idempotent at 5-minute cadence.

Push writes onto the queue. Scrobbling, loving/banning, tagging: signed request + user session key. retries = 3 with backoff(), now paired with a hard timeout so a hung write can't pin a worker forever.

Store session keys encrypted, one row per user ($table->text('lastfm_session_key')->nullable(), cast encrypted) — a per-user credential, not a config value.

Test with a fake transport, and keep the interface anyway. Inject a Guzzle MockHandler (or a hand-rolled client implementing whatever interface the package expects) to exercise the real wrapper methods, including the error-body branch. Keep interface LastFmGateway + a fake for higher-level tests — v0.8 makes the library less scary but doesn't make it yours.

Gotchas and Tips

Last.fm returns HTTP 200 on errors. Body is {"error": 6, "message": "Invalid parameters"}. Check isset($res['error']) before caching, and never let a negative result land in Cache::remember. A timeout-armed client makes this better, not worse — you'll now get real transport failures and soft application failures, so handle both.

Don't assume the injected client is Laravel's HTTP client. Illuminate\Http\Client\Factory is facade-shaped, not PSR-18; if the package wants PSR-18 or Guzzle, you need a thin adapter or Http::buildClient()-style bridging. Http::fake() therefore still won't intercept these calls unless you've genuinely routed through that stack. Verify in src/ before writing fake-based tests, or you'll spend an hour wondering why nothing is stubbed.

Set the timeout explicitly, every environment. The new options array only helps if populated; an empty array means the transport default (often 0/infinite on Guzzle, or default_socket_timeout if it's still file_get_contents under the hood). A short timeout in config/services.php and a queue worker with --timeout slightly larger than it.

Confirm format=json is still being sent. Last.fm defaults to XML. If you're getting strings where arrays should be, this is why.

Signatures are order-sensitive. api_sig = md5 of params sorted by key, key + value concatenated, secret appended. If write methods break, that's usually it — generate it yourself rather than debug theirs.

Watch the JSON shape-collapse bug. Single objects where you expect one-element arrays (image), and #text is the value for every image entry. Normalize once in the service layer.

autocorrect=1 silently changes your keys. The returned name won't match what you asked for. Pass autocorrect=0 when exact matching matters for joins, and key caches on the requested name.

apigen removal is dev-only. It won't change runtime behaviour, so don't let it slow down your review of the lock diff — but do scan the dependency upgrades, since "upgraded dependencies that were too old" is where an incidental major bump (Guzzle, PSR interfaces) could break your adapter.

Versioning risk has shifted, not vanished. A v0.8 release on a previously-dormant, 27-star, zero-dependents package is a good sign, not a guarantee. 0.x means the next minor can break the constructor you just wired. Mitigations: pin exactly, keep the interface seam, and treat the Http-facade rewrite (~6 read endpoints, ~10 lines of signing) as a known-cost fallback rather than an emergency. The new configurability makes staying a bit more defensible; it doesn't make the package load-bearing.

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.
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
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource