robclancy/presenter
RobClancy Presenter is a Laravel package for decorating models and other objects with presentation logic, keeping views clean and models focused. Define reusable presenter classes to format output, handle HTML, and separate display concerns from domain code.
Verify the PHP floor before anything else. 3.0.0 requires PHP 8.1, so the first step is confirming your runtime and your composer.json allow it:
php -v
composer why-not php "8.1.*" # surfaces packages pinning you below the new floor
Check config.platform.php in composer.json — a hardcoded "php": "8.0" there will block the upgrade even if the server runs 8.1. Same for CI images and Docker bases.
Where to look first:
composer.json of the package — the php constraint and the illuminate/* version ranges tell you which Laravel versions 3.0.0 still supports. A PHP 8.1 floor usually signals the package has moved to Laravel 9/10-era internals.CHANGELOG.md / UPGRADING.md — confirm whether the ArrayAccess deprecation fix changed any public signatures (see Gotchas).First use case: install, publish any config (php artisan vendor:publish --provider="...\ServiceProvider"), then exercise the ArrayAccess surface directly, because that is what this release touched:
$thing = app(SomePackageClass::class);
$thing['key'] = 'value';
isset($thing['key']); // must agree with offsetExists
$thing['key'] ?? null; // must not emit a deprecation
unset($thing['key']);
Run this with error_reporting(E_ALL) locally — the whole point of the release is that these calls are now clean on 8.1.
Treat the upgrade as a platform bump, not a feature adoption. 3.0.0 adds no new behaviour; it moves a floor and silences deprecations. The work is in dependency alignment, not in rewriting call sites.
CI matrix first, code second. Add 8.1 explicitly so the fix is actually proven:
php: ['8.1', '8.2', '8.3']
Run composer update --prefer-lowest on 8.1 as a separate job — that combination is where implicit ArrayAccess signature mismatches surface.
ArrayAccess objects through Laravel plumbing. If the package exposes an ArrayAccess-backed object, the common integration points are data_get()/Arr::get() fallbacks, Blade {{ $thing['key'] }}, and collect($thing). Laravel's data_get uses Arr::accessible() which checks instanceof ArrayAccess, so these work — but only if offsetExists/offsetGet are consistent. Add a small feature test asserting the object survives a json_encode/collect round-trip; that catches inconsistent offsets fast.
Container + facade usage. Resolve from the container rather than new, so config and any shared state apply:
$this->app->make(PackageClass::class);
// or via its facade if one is documented
Static analysis as the migration gate. Annotate generics on the ArrayAccess implementer in your own extending classes:
/** @implements \ArrayAccess<string, mixed> */
Then run PHPStan at your existing level — the 8.1 mixed return on offsetGet mismatches are exactly the class of bug this release fixes, and PHPStan will find any subclass in your code that still declares the old signature.
Extension points. Subclasses and decorators of the package's ArrayAccess type are the highest-risk integration. Grep your codebase for extends on the package namespace before bumping:
grep -rn "extends .*PackageNamespace" app/ tests/
The offsetGet return type is the real gotcha. PHP 8.1 deprecates ArrayAccess::offsetGet() without a mixed return type (and the same for ReturnTypeWillChange on the other three). The package fixed its implementations, but any class in your app extending it must be updated too — otherwise you swap one deprecation for another.
Deprecations are logged, not thrown — until tests. Laravel's exception handler logs E_DEPRECATED rather than throwing, so this upgrade can look clean in the app and still fail in CI. PHPUnit's convertDeprecationsToExceptions (default on in PHPUnit 10+) will fail on them. That is a feature: it is your verification that the fix works. If you run an older PHPUnit, enable it explicitly.
$obj['k'] ?? null calls two methods. It invokes offsetExists then offsetGet. If the package's offsetExists returns true for keys whose offsetGet returns null, ?? silently behaves differently than array_key_exists did. Test both idioms.
Debugging deprecations that survive the bump:
php -d error_reporting=E_ALL -d display_errors=1 artisan tinker
# or capture the trace:
php -d error_reporting=E_ALL vendor/bin/phpunit --display-deprecations
--display-deprecations on PHPUnit 10 is the fastest way to attribute a notice to a specific test.
Composer platform pin. The single most common failure: composer update refuses to install 3.0.0 because config.platform.php says 8.0. Fix the platform config first, then update, or you will misdiagnose it as a version conflict.
Laravel version coupling. Dropping PHP 8.0 generally means the package has also dropped Laravel versions that only ran below 8.1. Check the package's illuminate/support constraint against your app's Laravel version before bumping — a 3.0.0 install on Laravel 8 is likely no longer supported even if Composer allows it.
Lockfile hygiene. Bump the package explicitly (composer require vendor/package:^3.0) rather than a blanket composer update, so unrelated transitive changes don't get blamed on this release during debugging.
Extension point to revisit: if the deprecation fix involved an #[\ReturnTypeWillChange] attribute rather than a real mixed return type, inherited signatures in your code may still need the same attribute. Check the actual diff on the implementing class rather than assuming a native mixed return.
How can I help you explore Laravel packages today?