zareismail/maintainable
zareismail/maintainable is a Laravel package that provides maintenance-related helpers for PHP applications, making it easier to manage maintenance states and workflows with simple configuration and familiar Laravel conventions.
The 0.5.0 release is the first version with a stable authorization surface, so start there rather than with the data model.
composer require <vendor>/<package>:^0.5
php artisan vendor:publish --provider="<Vendor>\<Package>\<Package>ServiceProvider"
php artisan migrate
config/<package>.php — guard name, permission names, and the model/resource bindings.database/migrations/ — the issues table and any pivot/polymorphic columns.src/Concerns/ — the trait(s) you add to your own models.src/Policies/ and src/Resources/ — where the permission checks from 0.5.0 live.settle up that issue:
// config/<package>.php or a service provider
<Package>::morphs([
'project' => \App\Models\Project::class,
'invoice' => \App\Models\Invoice::class,
]);
Add the trait to the owning model, create an issue, then hit the settle-up endpoint as a permitted and a non-permitted user and assert the difference. If those two behave identically, your policy or permission names are not wired up.Trait on the maintainable side. Put the package trait on the model that "maintains" issues (project, team, tenant) rather than on User. That model is what 0.5.0 now walks when checking access, so keeping it the single source of the relationship avoids duplicate checks.
Morph map first, everywhere. Register aliases in a service provider (Relation::enforceMorphMap([...])) or through the package's morphs helper before any seeding or fixture creation. Aliases in tests and aliases in production must match, or polymorphic rows written by one environment won't resolve in another.
Authorization through the policy, not inline gates. 0.5.0 adds authorization to settling an issue and resolves it via the related permission — i.e. the permission attached to the parent/maintainable resource, not a standalone settle-up permission. Keep your controllers thin:
$this->authorize('settleUp', $issue); // or the package's ability name
and extend the published policy when you need tenant scoping.
Resource-level access checks. If you expose issues through your own API resources, resolve the maintainable resource explicitly (eager load the morph relation) before serializing. Authorization that depends on the parent model will trigger lazy loads otherwise.
Seeding permissions. Seed the related permissions alongside the parent resource's permissions in the same seeder so they can't drift; a role that can see a project but not settle its issues is the common misconfiguration.
Settle-up as a workflow boundary. Treat settle-up as a state transition: wrap it in a transaction, dispatch your own domain event afterwards, and keep the HTTP layer to validation plus the authorized call.
viewAny) if your app relied on that. Existing feature tests that asserted assertForbidden() on an index route will fail — that's the expected signal, not a bug.Gate::inspect() and dump the resolved parent model.admin) needs to be set explicitly, otherwise checks evaluate against the default guard's user and always fail.php artisan permission:cache-reset or the package equivalent) — otherwise the new settle-up permission won't appear until the next deploy.*_type column with aliases, not FQCNs, or your enforced morph map will reject the rows.How can I help you explore Laravel packages today?