AdvertHardDeleteTrait
Hard delete of an advert together with everything that belongs to it (Bazaar and Comcat Api `AdvertManager`).
Hooks into the lifecycle of DeleteTrait::delete(): beforeDelete() and afterDelete() run
in the delete transaction (the dependent threads go in afterDelete(), after the advert rows), file
and index cleanup runs after the commit. What is deleted, and in
which order, is decided by AdvertHardDeletePlan (it also lists the relations and the
decisions about accounting data).
The steps are explicit although the advert foreign keys are CASCADE: the cascade does not cover
(1) the watchdog queue in Bazaar (NO ACTION), (2) private reply conversations and the rating
thread (the link leads from the advert or reply to the thread, so the thread would be orphaned),
(3) rows and files of images and attachments, (4) the search index and the advert counts in categories.
A soft delete (deleted = 1) does not come here; it goes through updateColumn() and the advert
keeps its data.
IMPORTANT: both managers define completedDelete() themselves (search index), and a class method
silently overrides a trait method, so they call the post-commit cleanup explicitly:
completedAdvertHardDelete().
Tags
Table of Contents
Properties
- $pendingHardDeleteFilePaths : array<int, string>
- $pendingHardDeletePlan : AdvertHardDeletePlan|null
- Delete plan in progress, passed from `beforeDelete()` to `afterDelete()` and the post-commit step (same manager instance).
Methods
- advertHardDeleteSource() : AdvertHardDeleteSourceInterface
- Module service with the delete read contract (the inherited `$service` is typed only by the CRUD interface).
- afterDelete() : Result|null
- Runs inside the transaction, after the advert rows are deleted (the cascade already removed `*_advert_file_rels` and the replies). Deletes the reply conversations, the rating threads and the attachment rows; a failure rolls the whole delete back.
- beforeDelete() : Result|null
- completedAdvertHardDelete() : void
- Post-commit cleanup, best effort: an exception only goes to the log because a deleted advert is not brought back. Called from the manager's `completedDelete()`.
- attachmentAbsolutePath() : string|null
- Returns the absolute path of an attachment from the relative path stored in the database (`/www/modules/<module>/files/items/Y/m/d/<id>/`): the relative attachment directory prefix is replaced by the absolute one (server params, not `getSettings()`).
- deleteExistingRatingThreads() : Result
- hardDeleteWatchdogEmailRels() : Result
- releaseAttachmentRows() : Result
- Deletes the attachment rows nobody else holds and remembers their files for the post-commit cleanup.
- removeEmptyAdvertDirs() : void
- Removes the empty advert directories after the files are deleted (`…/Y/m/d/<advertId>/` and its variant subdirectories): a directory named by the id of a deleted advert and its direct subdirectories, only when empty. Then the date folders above them (`Y/m/d`) that became empty, never the module root ({@see AssetProcessingTrait::removeEmptyAssetDirs()} with the advert roots; without it an emptied `items/2026/10/22/` stayed, admin walkthrough B8).
Properties
$pendingHardDeleteFilePaths
private
array<int, string>
$pendingHardDeleteFilePaths
= []
attachment files whose row was actually deleted in the transaction
$pendingHardDeletePlan
Delete plan in progress, passed from `beforeDelete()` to `afterDelete()` and the post-commit step (same manager instance).
private
AdvertHardDeletePlan|null
$pendingHardDeletePlan
= null
Methods
advertHardDeleteSource()
Module service with the delete read contract (the inherited `$service` is typed only by the CRUD interface).
protected
abstract advertHardDeleteSource() : AdvertHardDeleteSourceInterface
Return values
AdvertHardDeleteSourceInterfaceafterDelete()
Runs inside the transaction, after the advert rows are deleted (the cascade already removed `*_advert_file_rels` and the replies). Deletes the reply conversations, the rating threads and the attachment rows; a failure rolls the whole delete back.
protected
afterDelete(array<int, object> $entities, Result $result[, array<string, mixed> $meta = [] ]) : Result|null
IMPORTANT: the threads go here, not in beforeDelete(). The adverts of the batch are loaded
(managed) before the first row goes and a Comcat advert references its rating thread; a thread
removed and flushed by its manager first would make the flush of the next advert delete fail
("a new entity was found through the relationship Advert#ratingThread"). Today only the read
UoW::clear() in the thread manager's find() hides that.
Parameters
- $entities : array<int, object>
- $result : Result
- $meta : array<string, mixed> = []
Return values
Result|nullbeforeDelete()
protected
beforeDelete(array<int, object> $entities[, array<string, mixed> $meta = [] ]) : Result|null
Parameters
- $entities : array<int, object>
-
adverts loaded by
DeleteTraitbefore the delete - $meta : array<string, mixed> = []
Return values
Result|nullcompletedAdvertHardDelete()
Post-commit cleanup, best effort: an exception only goes to the log because a deleted advert is not brought back. Called from the manager's `completedDelete()`.
protected
completedAdvertHardDelete(array<int, object> $entities, Result $result[, array<string, mixed> $meta = [] ]) : void
Parameters
- $entities : array<int, object>
- $result : Result
- $meta : array<string, mixed> = []
attachmentAbsolutePath()
Returns the absolute path of an attachment from the relative path stored in the database (`/www/modules/<module>/files/items/Y/m/d/<id>/`): the relative attachment directory prefix is replaced by the absolute one (server params, not `getSettings()`).
private
attachmentAbsolutePath(array{id: int, path: string, fileName: string, fileExtension: string} $file) : string|null
Parameters
- $file : array{id: int, path: string, fileName: string, fileExtension: string}
Return values
string|nulldeleteExistingRatingThreads()
private
deleteExistingRatingThreads(array<int, int> $ratingThreadIds) : Result
Parameters
- $ratingThreadIds : array<int, int>
Return values
ResulthardDeleteWatchdogEmailRels()
private
hardDeleteWatchdogEmailRels(array<int, int> $advertIds) : Result
Parameters
- $advertIds : array<int, int>
Return values
ResultreleaseAttachmentRows()
Deletes the attachment rows nobody else holds and remembers their files for the post-commit cleanup.
private
releaseAttachmentRows(AdvertHardDeletePlan $plan) : Result
Parameters
- $plan : AdvertHardDeletePlan
Return values
ResultremoveEmptyAdvertDirs()
Removes the empty advert directories after the files are deleted (`…/Y/m/d/<advertId>/` and its variant subdirectories): a directory named by the id of a deleted advert and its direct subdirectories, only when empty. Then the date folders above them (`Y/m/d`) that became empty, never the module root ({@see AssetProcessingTrait::removeEmptyAssetDirs()} with the advert roots; without it an emptied `items/2026/10/22/` stayed, admin walkthrough B8).
private
removeEmptyAdvertDirs(AdvertHardDeletePlan $plan, array<int, string> $attachmentDirs) : void
Parameters
- $plan : AdvertHardDeletePlan
- $attachmentDirs : array<int, string>