CoolCMS API

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
requires

AssetProcessingTrait — releaseAsset(), deleteAssetFiles(), assetDir()

advertHardDeleteSource() — module service (AdvertHardDeleteSourceInterface)

$this->watchdogEmailRelManager, $this->messageThreadManager, $this->ratingThreadManager, $this->fileManager, $this->categoryManager

getAdvertSettingModule(): int, getAdvertModuleName(): string

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

afterDelete()

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|null

beforeDelete()

protected beforeDelete(array<int, object> $entities[, array<string, mixed> $meta = [] ]) : Result|null
Parameters
$entities : array<int, object>

adverts loaded by DeleteTrait before the delete

$meta : array<string, mixed> = []
Return values
Result|null

completedAdvertHardDelete()

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|null

deleteExistingRatingThreads()

private deleteExistingRatingThreads(array<int, int> $ratingThreadIds) : Result
Parameters
$ratingThreadIds : array<int, int>
Return values
Result

hardDeleteWatchdogEmailRels()

private hardDeleteWatchdogEmailRels(array<int, int> $advertIds) : Result
Parameters
$advertIds : array<int, int>
Return values
Result

removeEmptyAdvertDirs()

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>
On this page

Search results