reliqarts/laravel-scavenger
Laravel Scavenger is a utility package that helps you find and clean up orphaned or stale data in your Laravel app. It scavenges leftover records and files so your database and storage stay tidy, with simple configuration and Artisan-friendly workflows.
Treat v4.0.0 as a platform upgrade, not a routine composer update. The package now requires PHP 8.3+ and supports Laravel 12 and 13 only, so the first step is confirming your app (and any CI images, Dockerfile base images, and composer.json platform block) are on 8.3 before you touch the dependency.
php -v, composer why-not php 8.3, and confirm the Laravel version constraint in composer.json resolves to 12.x/13.x. Older Laravel majors are not supported by v4.composer require <vendor>/<package>:^4.0 (or composer update <vendor>/<package>). The Goutte removal will surface as a composer conflict if anything else in your dependency tree pulls Goutte in — resolve that first, it's the fastest signal that you have code to port.App\Models\User in v4) and guzzle_settings.timeout. Read both before running anything, because both changed semantics rather than just moving.Be explicit about the daemon model. v4 changes the default to App\Models\User. That's convenient for greenfield apps and silently wrong for anyone with a tenant-scoped user model, a soft-deleted auth model, or a dedicated bot/service account table. Set the model explicitly in config even when the default matches — it documents intent and survives the next default change.
Let the hashed cast do the hashing. If your daemon model uses Laravel's hashed password cast, the package no longer hashes a second time. The pattern is: assign plaintext to the attribute, let the cast store the hash, and remove any Hash::make() you added as a workaround on 3.x. If you ever write the password via DB::table() or mass raw updates (bypassing Eloquent), you still hash manually — the cast only applies on model assignment.
Set guzzle_settings.timeout for the whole request, not the gap between bytes. This is the biggest behavioural change: the single value is now both the idle timeout and the maximum request duration. Tune it to your slowest realistic full response, not to your longest expected wait. If a daemon call legitimately takes 90 seconds end-to-end, a 30-second timeout that used to survive on trickling bytes will now hard-fail at 30. Workflows that need a long idle window plus a bounded total duration have to be redesigned (split the call, or wrap the client with your own handler stack).
Write against HttpBrowser, not Goutte. New work should use Symfony's HttpBrowser API directly — $browser->request(), $browser->submit(), and DomCrawler selectors on the response. Anything type-hinted against Goutte\Client or reaching into Client::setClient() for a custom Guzzle client needs porting; the equivalent is constructing HttpBrowser with your Guzzle client/options.
Test at the HTTP boundary. Because the package is driven by an HTTP browser plus a configurable model, the high-value tests are (a) a unit test asserting your daemon resolves the right model instance, and (b) an integration test that binds a mock Guzzle handler / HttpBrowser so the run never leaves the machine. Assert on the parsed result, not on raw HTML strings, so the Goutte→HttpBrowser migration doesn't invalidate your suite.
One daemon per identity. Where you previously ran a process per tenant or per account, keep that shape: parameterise the daemon model per process and run N supervised workers rather than trying to multiplex inside one long-lived process. Password-cast behaviour and config caching both behave more predictably that way.
App\Models\User. If you have any environment where that class exists but isn't the right identity, you get a confusing auth failure or, worse, a successful run against the wrong account. Grep your config for the daemon model setting during the upgrade.hashed cast is present. Search seeders, importers, and any password => Hash::make(...) in factories when moving to v4.timeout value means the log line "timed out after 30s" no longer tells you whether the peer went quiet or the request was just slow. Log the elapsed time and the last successful step; consider a per-call override rather than one global value if you talk to more than one endpoint.php artisan config:clear (and republish the config) before debugging anything — a missing key looks identical to a broken feature.composer show | grep -i goutte after upgrading. A transitive dependency keeping Goutte installed will mask porting mistakes until Goutte is finally dropped upstream.HttpBrowser/Guzzle client in the container for per-environment proxies, auth middleware, and retry logic rather than editing the package config; and keep custom daemon models implementing the auth contract the package expects (password attribute plus whatever it calls to identify the account) so future minors don't break you.config:cache alone won't pick up v4 settings.How can I help you explore Laravel packages today?