v4.2.2 is a patch release with a single fix: an exception thrown when retrieved content is null. There is no new API surface and no migration work, so upgrading is a composer update away — but read the fix carefully, because it tells you exactly where this package is fragile in real usage.
Start here:
composer require <vendor>/<package>:^4.2
php artisan vendor:publish --provider="<Vendor>\<Package>\<PackageServiceProvider>" --tag=config
config/<package>.php — driver, endpoint, credentials, timeout, cache TTL. That file is your map to everything else.Retriever/Client/Repository contract). Code against that interface so you can swap to a fake in tests without touching call sites.null.The practical takeaway from this release: treat "content is null" as a first-class state your UI and downstream code must survive, not an error condition. Prior to v4.2.2 the package raised an exception; now it returns, which means your code is the next thing in line to fail if it doesn't handle null.
Wrap the retrieval boundary, not every call site. Put your retrieval logic behind a small application-level service so the null-handling policy lives in one place:
final class ContentResolver
{
public function __construct(private RetrieverContract $retriever) {}
public function resolve(string $id): ?Content
{
$result = $this->retriever->find($id);
return filled($result?->content) ? $result : null;
}
}
Call sites then deal with ?Content, which is a decision you make once, not a if ($x === null) scattered across ten Blade templates.
Normalise before you render. The package now hands you a result object whose content may be null. Convert that at the edge of your domain — null, empty string, and " " should all collapse into a single "no content" state so your views only branch once:
@if ($content = $resolver->resolve($id))
{!! $content->render() !!}
@else
<x-empty-state message="Content unavailable" />
@endif
Cache the miss, not just the hit. If retrieval is remote or slow, a null result is still an expensive round trip. Cache it with a shorter TTL than successful content (e.g. 5 minutes vs 1 hour) so a temporarily broken upstream doesn't hammer the API, but recovers quickly.
Queue anything that can be slow or flaky. Retrieval belongs in a job with $tries, backoff(), and a failed() handler that writes a placeholder record. Long synchronous retrieval inside a request is the single most common way these packages end up in production incidents.
Test the null path explicitly. This release exists because nobody had a test for it. Add a fixture returning null content and assert your endpoint returns 200 with an empty state — not a 500:
$this->mock(RetrieverContract::class)
->shouldReceive('find')->andReturn(new Result(content: null));
$this->get("/content/{$id}")->assertOk()->assertSee('Content unavailable');
Integration tips. Bind a fake retriever in TestCase::setUp() for feature tests. Use Http::fake() if the package ultimately speaks HTTP. Expose a health-check or artisan command that performs one retrieval on deploy so you find credential drift before users do.
null. It does not guarantee that an empty string, a missing key, or a null nested inside the payload is handled. Verify your own boundaries; don't assume "fixed" means "safe everywhere".null vs. missing vs. empty is not the same thing. If your code uses $result->content ?? 'fallback', that works for null but silently passes empty strings through to the view. Use filled() or an explicit normaliser.\Throwable and returning null will hide auth failures for months.php artisan config:clear && php artisan config:cache. Stale cached config is the number one cause of "the upgrade didn't do anything".composer.json if the package is on a fast patch cadence and content correctness matters. ^4.2.2 accepts any future 4.x patch; if you need byte-for-byte stability, use 4.2.* and upgrade deliberately.debug level behind a config flag before it's normalised. When someone reports "page is blank", you need to distinguish "upstream returned null" from "we dropped it during normalisation" — those have completely different fixes.extend() method on the manager or a Contract binding in the service provider — that's the supported seam, and it keeps you upgradeable through 4.x.?Result or a dedicated EmptyResult). If you see that, your ContentResolver wrapper is what protects you from a wider refactor.How can I help you explore Laravel packages today?