ambengers/kinetic
Kinetic is a Laravel package for adding state machines to Eloquent models. Define states and allowed transitions, guard changes with conditions, and hook into lifecycle events so status workflows stay explicit, consistent, and easy to maintain.
composer require ambengers/kinetic
Laravel auto-discovers the service provider. If your app disables discovery, register Ambengers\Kinetic\KineticServiceProvider::class in bootstrap/providers.php. Publish the config file if the package ships one (php artisan vendor:publish --tag=kinetic-config).
Where to look first, in order:
tests/ directory — the composer class contract (method name, arguments, return type) is usually clearer in tests than in docs.First use case — page-scoped ambient props. The canonical win is data that many pages need but no single controller owns: notification counts, feature flags, sidebar badges. Register it once in AppServiceProvider::boot() (or a dedicated provider):
use Ambengers\Kinetic\Facades\Kinetic;
public function boot(): void
{
Kinetic::compose('Dashboard', function (array $page) {
return [
'notifications' => auth()->user()?->unreadNotifications()->count() ?? 0,
];
});
}
The closure receives the Inertia page payload (component name plus existing props) and returns an array merged into the page's props before the response is serialized. The controller stays focused on the page's own data; the composer owns the cross-cutting data. If your version uses a config array rather than a facade, the mental model is identical — component name in, props out.
Class-based composers for anything non-trivial. A composer is a class with a compose method returning an array, resolved from the container — so you get constructor injection for free.
namespace App\Http\Kinetic;
class UserIndexComposer
{
public function __construct(private NotificationRepository $notifications) {}
public function compose(array $page): array
{
return [
'unread_count' => $this->notifications->unreadFor(request()->user()),
'filters' => request()->only('search', 'sort'),
];
}
}
Kinetic::compose('Users/Index', UserIndexComposer::class);
Give composers their own service provider. AppServiceProvider becomes a dumping ground fast. Create app/Providers/KineticServiceProvider.php, register it, and group compositions by domain (Users, Billing, Admin). This also makes it obvious where to add the next one.
Wildcard matching for module-wide props. If the package supports patterns like Users/* or Admin/*, use a single composer for layout-level data (breadcrumbs, sidebar nav, tenant banner) instead of duplicating it across every page in the module. Check the README for the exact glob syntax — this is the feature most likely to differ between versions.
Layer Kinetic with Inertia::share(), don't replace it. share() is truly global and runs on every response. Kinetic is page-scoped. Use share() for app name, flash messages, and auth identity; use Kinetic for data that only a subset of pages needs. Mixing the two responsibilities is the most common architectural mistake.
Defer or lazy-load expensive values. Wrap composed props in Inertia::lazy() or Inertia::defer() so the initial page load and non-partial requests don't pay for queries the page doesn't render immediately.
Conditional composition, static registration. Check roles or feature flags inside the composer and return [] when not applicable. Registering conditionally at boot makes the composer map harder to reason about and harder to test.
Testing. Use Inertia's AssertableInertia to assert composed props land where you expect:
$this->get('/users')
->assertInertia(fn (Assert $page) => $page
->component('Users/Index')
->where('unread_count', 3)
);
If composers leak between tests, reset them in setUp() (many packages expose a flush-style method, or you can re-boot the provider).
Users/Index is not users/index, and neither is Users/Index.vue. A typo produces no exception — just missing props. Keep the registration list in one file so it's scannable, and assert in a test that a known composer actually applies.$request->header('X-Inertia-Partial-Data') or by using lazy()/defer().php artisan config:cache cannot serialize closures — you'll get a LogicException or silent breakage in production. Register in a service provider and keep config to scalar values only.auth(). Call auth()->user() inside the composer closure, never at registration time. Registration runs at boot, before the request is fully resolved.Kinetic:: is unresolved, use the FQCN \Ambengers\Kinetic\Facades\Kinetic or check the package's aliases config. Auto-discovery registers the provider; it does not always register the alias.inertiajs/inertia-laravel. Kinetic sits on Inertia's response pipeline. A major Inertia upgrade can change how props are typed or merged. Pin both packages and re-run your prop assertions on every upgrade.dd() inside a composer returns HTML into a JSON response and confuses the client — use Log::info($page) instead. When props vanish, check in this order: (1) the registered component string, (2) merge precedence, (3) whether the value is lazy() and no partial reload requested it.How can I help you explore Laravel packages today?