CoolCMS API

ResponseColumnGuardTrait

Guards advert responses against values that do not fit their columns.

The form guard (…\Front\{Bazaar,Comcat}\Components\ResponseFormComponent\Traits\FormTrait) is only one input, not the write path. Every response save (front form through ApiBridge, admin edit through the Admin to Api bridge, external :Api:*:Response:save) ends in ResponseManager::save(), that is in SaveTrait, which calls beforeSave(). The external path never meets the form: the payload goes through the hydrator straight into the entity and DecimalType::convertToDatabaseValue() is a passthrough.

The database does not guard the columns usefully either. With a strict sql_mode an overflow fails the save with a generic database error; with a non-strict one it throws nothing and silently stores nonsense in advert_responses:

  • "abc" becomes 0.000 (an offer for zero),
  • "1e1000" becomes 9999999.999 (the column maximum),
  • "-5" is stored as a negative offer,
  • "12,5" becomes 12.000 (the comma truncates),
  • a phone longer than 20 characters is truncated (an unreachable contact),
  • an e-mail longer than 255 characters is truncated (the reply goes to a wrong address),
  • a subject longer than 255 characters is truncated (Comcat collects it, Bazaar leaves it null). The stored value is then shown to the advertiser and sent in the e-mail (ResponseManager::afterSave()).

The guard therefore sits in beforeSave() of both ResponseManagers, the only place every response write passes through (insert and update). A rejection is VALIDATION_FAILED (like DecimalQuantityGuardTrait), so SaveTrait rolls back the whole operation and no half-saved response remains.

NOTE: limits are never copied here; they are read by reflection from #[ORM\Column] of the entity that is really saved (ColumnLimits), so the guard follows schema changes.

IMPORTANT: after the check the price is overwritten with the value the guard measured (dot form rounded to the column scale). Otherwise there is a gap between guard and write: "12,5" passes the guard as 12.5, but the raw string would reach the database and become 12.000.

NOTE: an empty value passes, because price and phone are optional (price is a nullable column). Requiredness belongs to the form / MISSING_REQUIRED_FIELD, not to this guard.

Table of Contents

Methods

guardResponseColumnLimits()  : Result|null
Rejects a response whose value does not fit its typed column.
responseColumnFailure()  : Result
Builds the uniform rejection; `errorCode` is a machine-readable reason.

Methods

guardResponseColumnLimits()

Rejects a response whose value does not fit its typed column.

private guardResponseColumnLimits(Response|Response $response) : Result|null

Returns null when everything is valid (the price is then normalized to the form that gets written).

The entity is typed by the shared ancestors of both modules: the same beforeSave() also handles \App\UI\Admin\{Bazaar,Comcat}\Models\Entities\Response (the Admin to Api bridge does not rewrite \Admin\ to \Api\), so a narrower type would make an admin edit fail with a TypeError.

Parameters
$response : Response|Response
Return values
Result|null

responseColumnFailure()

Builds the uniform rejection; `errorCode` is a machine-readable reason.

private responseColumnFailure(string $message, string $errorCode) : Result
Parameters
$message : string
$errorCode : string
Return values
Result
On this page

Search results