arsenaltech/nova-tab
ArsenalTech Nova Tab adds configurable tabs to Laravel Nova resource detail pages, letting you group fields into cleaner, navigable sections. Define tabs and assign fields to reduce clutter and make complex resource views easier to scan.
What changed in 0.2 and why it matters first
The single breaking change — replacing an abstract base class with a trait — is the first thing to internalise. If you were on 0.1, your integration point was class Foo extends PackageAbstractClass. On 0.2 it is class Foo { use PackageTrait; }. There is no upgrade path that preserves both, so plan the bump as a deliberate refactor rather than a composer update.
Minimal steps
composer require <vendor>/<package>:^0.2 (pin explicitly — see the versioning note below).@property / @method docblocks, since static analysis will lean on those.uses the trait is effectively the reference implementation and is usually more accurate than the README.First use case
The typical shape post-0.2:
class Order extends Model
{
use PackageTrait;
}
Because your class no longer extends a package class, it can extend whatever it needs to — Model, FormRequest, Mailable, Command, your own base class. That is the whole point of the change: the package no longer occupies your single inheritance slot. Traits in Laravel are the idiomatic answer for exactly this, which is why the maintainer made the switch.
Trait on Eloquent models
The most common landing spot. If the trait assumes model context (attributes, $this->getAttribute(), casts, relationships), confirm it declares its requirements in a docblock or an abstract-method-style contract. Traits cannot declare abstract methods enforced at the language level in the way an abstract class can — the enforcement usually moves to a runtime method_exists check or an interface you must implement. Look for either.
Trait on non-model classes
The upside of the change: the same trait can now be mixed into a Job, a Console Command, a FormRequest, or a plain service object. If your 0.1 code had a wrapper class solely to work around the base-class requirement, delete it.
Layering your own base class underneath
The standard pattern now:
abstract class BaseModel extends Model
{
// your app-wide model concerns
}
class Order extends BaseModel
{
use PackageTrait;
}
This is the migration you could not do in 0.1. If you were previously forced to choose between the package base class and your own, 0.2 removes that constraint — worth calling out to your team as a net win despite the breaking change.
Conflict resolution and aliasing
With traits, method collisions are now your problem rather than resolved by the inheritance chain. Use the insteadof / as syntax to disambiguate and to keep a method's original visibility:
use PackageTrait, HasAuditLog {
PackageTrait::boot insteadof HasAuditLog;
HasAuditLog::boot as bootAuditLog;
}
If the package trait defines a method your class already has, PHP fatal-errors on the collision. On 0.1 the subclass simply overrode the parent. Audit for method-name overlap when you roll this out — especially generic names like boot, initialize, scope*, toArray, getAttribute.
Boot logic
If the abstract class had a constructor doing setup, that work has to live somewhere in the trait. The Laravel-idiomatic version is a boot{TraitName} method picked up by Model::bootTraits(). Check whether 0.2 follows that convention; if it instead relies on an initialize{TraitName} hook or a plain constructor, your instantiation path matters.
Testing
Trait-based packages are easiest to test by mixing the trait into an anonymous or fixture class. If the package ships its own fixture, reuse it rather than inventing one.
instanceof no longer works. Any code doing $thing instanceof PackageAbstractClass is now dead. There is no trait type. The replacement is an interface — check whether 0.2 ships one and type-hint that instead. If it does not, you will need to add a marker interface of your own and apply it to every host class.
Type-hints and DI. A trait cannot be a parameter type, cannot be resolved from the container, and cannot be bound in a service provider. If 0.1 code injected or resolved the base class, that call site needs rethinking — usually you now inject whatever dependency the trait's methods actually need.
Constructor conflicts. If the trait declares __construct and your class already has one, they collide. Laravel models, controllers, and commands all define constructors, so this bites immediately. Confirm the trait has no constructor; if it does, use insteadof/as or move to a boot hook.
Property conflicts. Traits can declare properties, and redeclaring an identical property in both trait and class is a fatal error in older PHP (allowed only if compatible in more recent versions). If you have a $guarded, $table, or $fillable on your class and the trait declares the same, expect a hard failure rather than a silent merge.
parent:: is gone. Every parent::foo() call in your 0.1 host classes must become $this->foo() or an aliased call. Grep for parent:: in any class that previously extended the package.
Static analysis regressions. Larastan/PHPStan understand traits but do not infer property/method provenance as reliably as they do inheritance. Expect a spike in "undefined method" reports right after the bump. @mixin and @property docblocks on the host class are the usual workaround; @use on the trait is the other. Budget time for this — it is the most common source of "the upgrade works but the CI is red" frustration.
IDE and autocomplete. Same root cause. Methods injected via a trait show up once the IDE's index catches up; the trait's @method annotations drive the quality of the experience. If completion is poor, it is usually a missing docblock in the package, not your setup.
0.2 is pre-1.0. Under semver, 0.x minor bumps are permitted to break, so ^0.1 → ^0.2 is a legitimate breaking release and not a maintainer error. Pin to ^0.2 deliberately, and read every subsequent 0.x changelog before bumping — the class→trait migration is unlikely to be the last structural change before 1.0.
Debugging checklist when the trait "does nothing".
used in the class actually being instantiated (common with inheritance: a parent uses it, the child is fine — but a sibling branch isn't).used inside a class that also aliases the method away.bootPackageTrait vs the actual trait basename) so Laravel never calls it — trait boot methods are matched by name, so renaming the trait or aliasing the import silently disables setup.as protected alias, making it invisible to the caller.php artisan optimize:clear after the upgrade, since a stale cached class map can mask the change.Extension points. Without a base class there is no longer an obvious "override this method" seam. If the package relies on protected methods intended for overriding, those are now reachable only by defining the same method in your host class — you cannot call through to the trait's version without aliasing it first (use PackageTrait { foo as protected traitFoo; }). That aliasing trick is the replacement for parent::foo() and is worth standardising on across your codebase.
How can I help you explore Laravel packages today?