Skip to main content

Delete Issues from Tinker

A runbook for deleting issues directly from php artisan tinker when the dashboard cannot. The recipes below cover the common scopes (specific IDs, full tenant, taxonomy term, name pattern) and dispatch the file-cleanup job so the bucket does not accumulate orphans.

Destructive operation

The Issue model uses SoftDeletes, so $issue->delete() only marks rows as trashed and keeps the database record. The dispatched DeleteIssueFiles job, however, removes the underlying bucket files and the IssueFile references; once it runs the content cannot be recovered without restoring from a backup. Always run on staging first, scope queries tightly, and prefer dry-running with ->get() before swapping in ->each(...).

Prerequisites

  • Production or staging tinker access (zar tinker locally, Vapor SSH for production).
  • The target tenant_id. The fromAllTenants() macro removes the tenant scope, so the where('tenant_id', X) clause is mandatory to avoid cross-tenant deletes.
  • For the taxonomy recipe, the taxonomy slug (for example publisher) and the term names to match.

Recipes

// Delete every issue from a tenant EXCEPT a known whitelist.
\App\Domains\Content\Models\Issue::fromAllTenants()
->where('tenant_id', 106)
->whereNotIn('id', [20489, 20470, 20469, 20468, 20406])
->each(function ($issue) {
$issue->delete();
\App\Domains\Content\Jobs\IssuesProcessing\DeleteIssueFiles::dispatch($issue);
});

// Delete every issue from a tenant.
\App\Domains\Content\Models\Issue::fromAllTenants()
->where('tenant_id', 52)
->each(function ($issue) {
$issue->delete();
\App\Domains\Content\Jobs\IssuesProcessing\DeleteIssueFiles::dispatch($issue);
});

// Delete every issue tagged with one of several terms in a taxonomy.
// Note the scope is `withAnyTerms($names, $taxonomy)`, not `withAnyTags(...)`.
\App\Domains\Content\Models\Issue::fromAllTenants()
->where('tenant_id', 31)
->withAnyTerms(
['Universidad De Palermo', 'Geka', 'Fundación Universidad De Palermo', 'Nobuko', 'Editorial Brujas', 'Ediciones GeKa', 'Ediciones FADU', 'Diseño Editorial', 'CP67'],
'publisher'
)
->each(function ($issue) {
$issue->delete();
\App\Domains\Content\Jobs\IssuesProcessing\DeleteIssueFiles::dispatch($issue);
});

// Delete every issue whose name matches a pattern.
\App\Domains\Content\Models\Issue::fromAllTenants()
->where('tenant_id', 1)
->where('name', 'like', '%Summa%')
->each(function ($issue) {
$issue->delete();
\App\Domains\Content\Jobs\IssuesProcessing\DeleteIssueFiles::dispatch($issue);
});

Notes

  • DeleteIssueFiles checks that the issue is trashed before removing files. If $issue->delete() is skipped, the job logs an error and exits without touching the bucket.
  • The job runs on the lowPriority queue, so cleanup happens out-of-band; check Horizon if files appear to linger.
  • Restoring a soft-deleted issue ($issue->restore()) only brings back the database row. If DeleteIssueFiles has already run, the files are gone and the issue is unreadable.
X

Graph View