AdvertHardDeletePlan
in package
Plan for the hard delete of an advert (Bazaar and Comcat): the advert and all dependent data and files are deleted for good. Soft delete is a different operation (the `deleted` toggle) and does not come here.
Pure logic without DB; it decides three things that can be tested in isolation
(tests/Unit/Api/AdvertHardDeletePlanTest.php):
- What runs and in which order. The DB cascade (
on delete cascade) removes most children (rels, replies, reports, statistics, followers, payment items, watchdog e-mails and their texts). An explicit step is needed only for what the cascade does not cover:- before the advert row: the watchdog queue (
*_watchdog_email_rel,no actionin Bazaar, which made the grid delete fail on the FK); - after the row (still in the transaction): private reply conversations (a reply keeps
the thread with
set null, so the thread would be orphaned with its messages), the rating thread (rating_thread_idhas no FK, it would be orphaned with its ratings) and thecms_system_filesattachment rows; only once the cascade removed the advert link can it be told reliably whether someone else still holds them. The threads go after the advert row on purpose: an advert still managed by Doctrine references its rating thread, and a thread removed and flushed before it would make the next flush fail ("a new entity was found through the relationship"); - after commit (best-effort): files on disk, images (
releaseAssetguards a shared asset itself), the ES index and recounting adverts in categories.
- before the advert row: the watchdog queue (
- Accounting data is never deleted. Issued invoices (
cms_mod_invoicer_*) and payments (cms_system_payments+ thecms_system_payment_itemssnapshot) have no foreign key to the advert; the only link is the module itemcms_mod_*_payment_items(tariff activation for the advert), which is not an accounting document and is deleted with the advert. If a cascade from the advert ever reached an accounting table, the plan refuses (uncoveredForeignKeys()); it must never happen silently. - The schema must not surprise. uncoveredForeignKeys() walks the FK graph from
information_schemaand returns every relation the deletion could not handle, so a new table withno actionon the advert ends in a clear refusal before the first write, not in a DB exception.
Table of Contents
Constants
- ACCOUNTING_TABLE_PREFIXES : array<string|int, mixed> = ['cms_mod_invoicer_', 'cms_system_payments', 'c...
- Accounting tables: must not be deleted even by a cascade from the advert. Prefixes (`LIKE 'x%'`).
- STEP_ASSETS : string = 'assets'
- STEP_ATTACHMENT_FILES : string = 'attachmentFiles'
- STEP_ATTACHMENT_ROWS : string = 'attachmentRows'
- STEP_CATEGORY_COUNTS : string = 'categoryCounts'
- STEP_MESSAGE_THREADS : string = 'messageThreads'
- STEP_RATING_THREADS : string = 'ratingThreads'
- STEP_SEARCH_INDEX : string = 'searchIndex'
- STEP_WATCHDOG_EMAIL_RELS : string = 'watchdogEmailRels'
Properties
- $advertIds : array<string|int, mixed>
- $assetIds : array<int, int>
- $files : array<int, array{id: int, path: string, fileName: string, fileExtension: string}>
- $messageThreadIds : array<int, int>
- $ratingThreadIds : array<int, int>
Methods
- advertIds() : array<int, int>
- assetIds() : array<int, int>
- explicitlyClearedColumns() : array<int, string>
- Columns whose rows the plan deletes explicitly before the advert row, so their `no action` does not stop the deletion. Derived from the advert table prefix (`cms_mod_<module>_adverts`).
- files() : array<int, array{id: int, path: string, fileName: string, fileExtension: string}>
- fromDependents() : self
- isAccountingTable() : bool
- messageThreadIds() : array<int, int>
- ratingThreadIds() : array<int, int>
- stepsAfterAdvertRow() : array<int, string>
- Steps inside the transaction after the advert row is deleted. The threads must come after the row: an advert still managed in the unit of work references its rating thread, so a thread removed first would break the flush of the advert delete. A step without data is skipped.
- stepsAfterCommit() : array<int, string>
- Steps after commit (best-effort; a deleted advert does not come back). Files go after commit: a rollback knows nothing about the disk, and deleting a file earlier would leave a dangling DB reference on failure.
- stepsBeforeAdvertRow() : array<int, string>
- Steps inside the transaction before the advert row is deleted. The order is a contract: the watchdog queue must disappear before the cascade touches `*_watchdog_emails` (Bazaar `no action`). A step without data is skipped.
- uncoveredForeignKeys() : array<int, string>
- Returns the relations the hard delete of an advert could not handle. An empty array means the schema is covered.
- __construct() : mixed
- ids() : array<int, int>
- positiveInt() : int|null
- stringOf() : string
Constants
ACCOUNTING_TABLE_PREFIXES
Accounting tables: must not be deleted even by a cascade from the advert. Prefixes (`LIKE 'x%'`).
public
array<string|int, mixed>
ACCOUNTING_TABLE_PREFIXES
= ['cms_mod_invoicer_', 'cms_system_payments', 'cms_system_payment_items', 'cms_system_payment_transactions']
STEP_ASSETS
public
string
STEP_ASSETS
= 'assets'
STEP_ATTACHMENT_FILES
public
string
STEP_ATTACHMENT_FILES
= 'attachmentFiles'
STEP_ATTACHMENT_ROWS
public
string
STEP_ATTACHMENT_ROWS
= 'attachmentRows'
STEP_CATEGORY_COUNTS
public
string
STEP_CATEGORY_COUNTS
= 'categoryCounts'
STEP_MESSAGE_THREADS
public
string
STEP_MESSAGE_THREADS
= 'messageThreads'
STEP_RATING_THREADS
public
string
STEP_RATING_THREADS
= 'ratingThreads'
STEP_SEARCH_INDEX
public
string
STEP_SEARCH_INDEX
= 'searchIndex'
STEP_WATCHDOG_EMAIL_RELS
public
string
STEP_WATCHDOG_EMAIL_RELS
= 'watchdogEmailRels'
Properties
$advertIds read-only
private
array<string|int, mixed>
$advertIds
$assetIds read-only
private
array<int, int>
$assetIds
$files read-only
private
array<int, array{id: int, path: string, fileName: string, fileExtension: string}>
$files
$messageThreadIds read-only
private
array<int, int>
$messageThreadIds
$ratingThreadIds read-only
private
array<int, int>
$ratingThreadIds
Methods
advertIds()
public
advertIds() : array<int, int>
Return values
array<int, int>assetIds()
public
assetIds() : array<int, int>
Return values
array<int, int>explicitlyClearedColumns()
Columns whose rows the plan deletes explicitly before the advert row, so their `no action` does not stop the deletion. Derived from the advert table prefix (`cms_mod_<module>_adverts`).
public
static explicitlyClearedColumns(string $advertTable) : array<int, string>
Parameters
- $advertTable : string
Return values
array<int, string> —table.column in lowercase
files()
public
files() : array<int, array{id: int, path: string, fileName: string, fileExtension: string}>
Return values
array<int, array{id: int, path: string, fileName: string, fileExtension: string}>fromDependents()
public
static fromDependents(array<int|string, mixed> $advertIds, array{messageThreadIds?: array, ratingThreadIds?: array, assetIds?: array, files?: array} $dependents) : self
Parameters
- $advertIds : array<int|string, mixed>
- $dependents : array{messageThreadIds?: array, ratingThreadIds?: array, assetIds?: array, files?: array}
-
raw data from the repository (DBAL returns numbers as strings)
Return values
selfisAccountingTable()
public
static isAccountingTable(string $table) : bool
Parameters
- $table : string
Return values
boolmessageThreadIds()
public
messageThreadIds() : array<int, int>
Return values
array<int, int>ratingThreadIds()
public
ratingThreadIds() : array<int, int>
Return values
array<int, int>stepsAfterAdvertRow()
Steps inside the transaction after the advert row is deleted. The threads must come after the row: an advert still managed in the unit of work references its rating thread, so a thread removed first would break the flush of the advert delete. A step without data is skipped.
public
stepsAfterAdvertRow() : array<int, string>
Return values
array<int, string>stepsAfterCommit()
Steps after commit (best-effort; a deleted advert does not come back). Files go after commit: a rollback knows nothing about the disk, and deleting a file earlier would leave a dangling DB reference on failure.
public
stepsAfterCommit() : array<int, string>
Return values
array<int, string>stepsBeforeAdvertRow()
Steps inside the transaction before the advert row is deleted. The order is a contract: the watchdog queue must disappear before the cascade touches `*_watchdog_emails` (Bazaar `no action`). A step without data is skipped.
public
stepsBeforeAdvertRow() : array<int, string>
Return values
array<int, string>uncoveredForeignKeys()
Returns the relations the hard delete of an advert could not handle. An empty array means the schema is covered.
public
static uncoveredForeignKeys(string $advertTable, iterable<string|int, array{table: string, column: string, referencedTable: string, deleteRule: string}> $foreignKeys) : array<int, string>
Walks the FK graph from the advert table through cascades (what a cascade deletes must have its own children covered too):
cascade: the child is deleted, the walk continues into its children; a cascade into an accounting table is an error,set null: the child survives with an empty link, fine,no action/restrict: the DB would stop the deletion; fine only when the plan deletes those rows explicitly beforehand (explicitlyClearedColumns()).
Parameters
- $advertTable : string
-
e.g.
cms_mod_bazaar_adverts - $foreignKeys : iterable<string|int, array{table: string, column: string, referencedTable: string, deleteRule: string}>
Return values
array<int, string> —description of uncovered relations (table.column → parent (RULE))
__construct()
private
__construct(array<int, int> $advertIds, array<string|int, mixed> $messageThreadIds, array<string|int, mixed> $ratingThreadIds, array<string|int, mixed> $assetIds, array<string|int, mixed> $files) : mixed
Parameters
- $advertIds : array<int, int>
- $messageThreadIds : array<string|int, mixed>
- $ratingThreadIds : array<string|int, mixed>
- $assetIds : array<string|int, mixed>
- $files : array<string|int, mixed>
ids()
private
static ids(array<int|string, mixed> $values) : array<int, int>
Parameters
- $values : array<int|string, mixed>
Return values
array<int, int>positiveInt()
private
static positiveInt(mixed $value) : int|null
Parameters
- $value : mixed
Return values
int|nullstringOf()
private
static stringOf(mixed $value) : string
Parameters
- $value : mixed