Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tartarus Laravel Package

lara-zeus/tartarus

Tartarus is a Laravel package from Lara Zeus for adding access control to your application. It offers a clean, configurable setup that fits Laravel’s authorization conventions and keeps role and permission management straightforward.

View on GitHub
Deep Wiki
Context7

Getting Started

Reconcile the package identity before writing a line of code. The repository description says "simple multi tenants"; the documentation describes a Filament-native recycle bin for soft-deleted Eloquent records. These are two different products. The recycle-bin reading is the one backed by detailed docs and is consistent with the Lara Zeus family (Zeus, thunder, Hermes — Filament admin conveniences). Treat "multi tenants" as stale metadata until you have opened src/ and confirmed it yourself. If you installed this expecting a tenancy resolver — stancl/tenancy or spatie/laravel-multitenancy is what you want; this is not that.

Assuming the recycle-bin shape, the install is standard:

composer require lara-zeus/tartarus
php artisan vendor:publish --tag="tartarus-config"
php artisan vendor:publish --tag="tartarus-migrations"
php artisan migrate

Then read, in this order:

  1. config/tartarus.php — for a package this size the published config is the API surface. Read it before the README. Retention window, model registration, pruning behaviour, and any Filament panel wiring will be there.
  2. The published migration — the single fact that determines your integration strategy is whether the package keeps its own trashed-records table or just leans on Laravel's deleted_at. Check this first.
  3. The trait / base model contract — what a model must implement (beyond SoftDeletes) to appear in the bin.
  4. The Filament resource or plugin registration — this is where the actual UI comes from; verify by hitting the panel, not by reading code comments.

First use case to wire: your highest-anxiety soft-deletable model. A Post, Order, Customer, or Invoice where an accidental delete generates a support ticket. The goal: a support user opens a "Recycle Bin" page, filters by model, sees the trashed record with deleted_at, clicks Restore, and the record is back — no engineer, no SQL, no tinker session. That is the demo that sells the package internally.

Minimum viability checklist before you invest a week:

  • Install, publish, migrate on a throwaway branch.
  • Add the trait (and SoftDeletes) to exactly one model.
  • Soft-delete a record from tinker, confirm it shows up in the Filament bin.
  • Restore it — check relations and side effects come back sane.
  • Force-delete it — confirm it is gone and no orphans remain.
  • Run php artisan route:list and inspect the Filament panel — see what the package actually registered versus what you assumed.

If that loop closes in an afternoon, keep going. If it does not, you have spent an afternoon, not a sprint.

Implementation Patterns

Soft deletes are the substrate — standardise on them first. Any model you want in the bin needs SoftDeletes (or whatever trait Tartarus wraps, which should include it). This decision propagates everywhere: deleted_at columns in every relevant table, withTrashed() on any query that must see historical rows, withTrashed() on belongsTo relations that must survive a parent restore, and unique-constraint implications (see Gotchas). Audit existing models for soft-delete coverage before you promise "a recycle bin for everything" in a standup.

Roll out in two tiers. Tier 1: models where accidental deletion is a support ticket — content, contacts, transactional records. Tier 2: everything else, added incrementally as confidence grows. Do not add the trait to every model in one PR — you will inherit unique-constraint and relation-restore problems across your whole schema simultaneously, with no working baseline to compare against.

Treat restoration as a domain operation, not a column flip. restore() clears deleted_at and nothing else. If deletion had side effects — cancelled subscription, revoked invitation, file moved to cold storage, cache entry invalidated — restoration must reverse them. The durable shape:

// observer or Filament action hook - whichever the package exposes
Order::restored(function (Order $order) {
    if ($order->subscription?->trashed()) {
        $order->subscription->restore();
    }
    Activity::log('order.restored', $order, actorId: request()->user()?->id);
});

Keep restoration idempotent — support staff will click restore twice, and a scheduled job may have already resurrected the row. Log who restored what; "who put this back" is asked in every post-incident review.

Attach behaviour at the Filament action, not at the resource. Guard restores with a before callback on the action itself, so a failing precondition (duplicate email, missing parent) shows a clean notification instead of a 500:

RestoreAction::make()
    ->before(function (Order $record, RestoreAction $action) {
        if (User::withTrashed()->where('email', $record->email)->whereNull('deleted_at')->exists()) {
            Notification::make()->title('Email already in use')->danger()->send();
            $action->cancel();
        }
    })

Confirm exact action-class names against the source — hooks are the cheapest extension point and survive upgrades; subclassing the shipped resource is the next rung and costs more per version bump.

Retention is your scheduled concern, not the package's. If the config exposes a retention window or a prune command, schedule it yourself in routes/console.php (Laravel 11+) or Kernel::schedule(). Run it on a queue worker with the right DB connection. Monitor it — a pruning job that has silently stopped is indistinguishable from one that works until disk fills or a compliance request lands. Decide explicitly: does pruning force-delete, or move records somewhere? Get the answer from the source.

Give the bin a navigation badge. A count of trashed records on the Filament nav is the difference between a page nobody opens and one support checks daily. If the package exposes a scope (onlyTrashed() is already in Laravel — you may not need anything from Tartarus for this), wire the badge yourself:

public static function getNavigationBadge(): ?string
{
    return (string) static::getModel()::onlyTrashed()->count();
}

Multi-tenant scoping is on you. This is not a tenancy package — that stale description must not leak into your architecture. If your app is multi-tenant, the bin must respect tenant_id. Grep the source for withoutGlobalScopes, withoutGlobalScope, and raw DB::table( before shipping — admin tooling commonly drops global scopes to avoid surprising filtering, and if Tartarus does, a support user in tenant A could restore a record belonging to tenant B into their own view. This is the highest-risk thing in the whole integration.

Testing pattern worth adopting, one per model in the bin:

it('restores a soft-deleted order with its relations', function () {
    $order = Order::factory()->has(OrderItem::factory()->count(2))->create();
    $order->delete();

    $this->actingAs($admin)
        ->livewire(RestoreOrderAction::class, ['record' => $order->id])
        ->call('restore');

    expect($order->fresh())->not->toBeNull()
        ->and($order->fresh()->items)->toHaveCount(2);
});

Cheap, catches 90% of regressions, and survives both a package upgrade and a fork.

Gotchas and Tips

The description mismatch is a documentation-hygiene signal, not just a typo. "Simple multi tenants" versus "recycle bin" is a five-minute fix someone did not make. On a package with 11 stars and zero dependents, that tells you what to expect from edge cases, changelogs, and issue triage. Read the source before trusting it — it is small enough that reading is cheaper than debugging.

You are the QA department. Zero dependents means nobody else is finding the edge cases before you. The 2.0.1 changelog is Dependabot bumps plus a Filament compatibility fix — housekeeping, not hardening. Pin the exact version in composer.json, wrap the integration in your own tests, and keep a fork plan. MIT licence + a small source means forking is cheap — but only useful if you have the tests to tell you when a fork is needed.

Unique constraints will bite you on restore, and only you can plan for it. Classic trap: a user deletes [email protected], someone re-registers with it, restoring the original violates the unique index. The package cannot solve this. Decide the policy per model — block restore with a notification, merge, or append a suffix — and implement it as a before guard (see Patterns). This surfaces in production within a week if unplanned.

restore() on a parent does not restore children. If a child was soft-deleted in the same request that trashed the parent, the restore leaves a half-alive record. Force-deleting a parent whose children were force-deleted or orphaned is the mirror failure. Check your FK onDelete behaviour against what the package actually issues, and write an explicit restore observer per model with children.

Force-delete is the scariest button in the admin panel — treat it accordingly. Gate it with a canForceDelete() policy method, a confirmation modal with type-to-confirm, and — for anything financial or regulated — a hard rule that it is unavailable in production or requires two people. The package hands you the action; the blast radius is yours.

Queues and scheduled commands bypass request context. No middleware, no session user, auth()->user() is null or wrong. If restore/prune logic anywhere in your integration reads the current user, pass the acting user ID explicitly into the dispatched job. This is the bug that ships silently and shows up as "restored by unknown" in the audit log three months later.

Debugging order when a record does not appear in the bin:

  1. Model::onlyTrashed()->count() in tinker — confirms the data exists.
  2. Trait applied to the model.
  3. deleted_at column exists on the table.
  4. The Filament table query uses onlyTrashed() (or the package's equivalent).
  5. Global scopes (tenant, user) are not filtering the row out.

Extension points to prefer, in order: Filament action hooks / lifecycle callbacks → model observers and events → published config → subclassing the shipped resource → forking. The first two cover most day-to-day needs and survive upgrades; the last two are where upgrade costs live.

The honest day-to-day recommendation: for a Filament admin where soft-deleted records are painful to recover, this is a plausible half-day install that turns a recurring support ticket into a button click. Validate on one model, wrap it in your own tests, pin the version, give it a nav badge, and read the source. The reading is the thing that tells you whether you adopted a dependency or a design reference.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk