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 tinkerlocally, Vapor SSH for production). - The target
tenant_id. ThefromAllTenants()macro removes the tenant scope, so thewhere('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
DeleteIssueFileschecks 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
lowPriorityqueue, 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. IfDeleteIssueFileshas already run, the files are gone and the issue is unreadable.