CategoryRelsPaidSanitizationTrait
Server-side sanitization of `paid` on hydrated `categoryRels` on the external path.
IMPORTANT: categoryRels goes through the object-graph persist, so paid is a scalar that an
external client (data.categoryRels[].paid) may send freely and the hydrator writes it. A
third party could then mark its own advert as paid without a real payment (paid drives the
payment/visibility flow, see PaymentItemManager).
$meta['__external'] (set by SaveTrait::actionSave)
separates external writes from the internal admin/front path; only external writes are
overwritten (admin may set paid from the form). The hydrated $advert->getCategoryRels()
is rewritten in beforeSave (before flush) and DB values are read with raw DBAL, because
find()/findAll() would detach the merged graph via GetAllTrait::clearDoctrine().
Rules per rel:
- existing rel (advertId + categoryId in the DB): the DB
paidis kept; an external update can neither set nor reset it. - new rel:
paidis derived like on the front insert-only path AdvertRelSyncTrait::syncCategoryRels, fromCategory::getForFree(). AdvertRelSyncTrait::syncCategoryRels:Category::getForFree()(free category is displayable at once, paid one waits for payment).
Tags
Table of Contents
Methods
- sanitizedCategoryRelPaid() : bool
- Pure (stateless) sanitization derivation, extracted so it is testable without a DB.
- fetchCategoryForFreeMap() : array<int, bool>
- Raw DBAL: reads `Category::getForFree()` (column `for_free`) for the given categories.
- fetchExistingCategoryRelPaidMap() : array<int, bool>
- Raw DBAL: reads `paid` of already existing rel rows (advertId + categoryId), i.e. the pre-save DB state (`beforeSave` runs before flush). Empty on insert (advertId null) and without categories.
- sanitizeExternalCategoryRelsPaid() : void
- Entry point called from `beforeSave`. No-op on the internal path (marker missing) and when the `categoryRels` collection is empty (the external client sent no such key).
Methods
sanitizedCategoryRelPaid()
Pure (stateless) sanitization derivation, extracted so it is testable without a DB.
public
static sanitizedCategoryRelPaid(int $categoryId, array<int, bool> $existingPaidByCategoryId, array<int, bool> $forFreeByCategoryId) : bool
Parameters
- $categoryId : int
- $existingPaidByCategoryId : array<int, bool>
-
(a) DB
paidfor already existing rels - $forFreeByCategoryId : array<int, bool>
-
(b)
Category::getForFree()for new rels
Return values
boolfetchCategoryForFreeMap()
Raw DBAL: reads `Category::getForFree()` (column `for_free`) for the given categories.
private
fetchCategoryForFreeMap(array<string|int, int> $categoryIds) : array<int, bool>
Parameters
- $categoryIds : array<string|int, int>
Return values
array<int, bool> —categoryId => for_free
fetchExistingCategoryRelPaidMap()
Raw DBAL: reads `paid` of already existing rel rows (advertId + categoryId), i.e. the pre-save DB state (`beforeSave` runs before flush). Empty on insert (advertId null) and without categories.
private
fetchExistingCategoryRelPaidMap(int|null $advertId, array<string|int, int> $categoryIds) : array<int, bool>
Parameters
- $advertId : int|null
- $categoryIds : array<string|int, int>
Return values
array<int, bool> —categoryId => DB paid
sanitizeExternalCategoryRelsPaid()
Entry point called from `beforeSave`. No-op on the internal path (marker missing) and when the `categoryRels` collection is empty (the external client sent no such key).
private
sanitizeExternalCategoryRelsPaid(Advert $advert, array<string, mixed> $meta) : void
Parameters
- $advert : Advert
- $meta : array<string, mixed>