ColumnGate
in package
Length gate for form fields, measured by the column the field is written to.
Depending on the server sql_mode, a longer value is either silently truncated (non-strict)
or fails the whole save with a database error (strict). The hydrator does not enforce limits
and #[ORM\Column(length: ...)] is only a schema declaration, so the form is the only place
where an overflow can be caught and shown on the field.
The limit is read by reflection from the entity (ColumnLimits); a hand-copied number
drifts from the schema sooner or later.
IMPORTANT: messages are global keys with the // prefix. Admin createForm() installs a
PrefixedTranslator (see Form::useDomainTranslator()), so a relative key would be
looked up in the presenter domain and would have to be copied into every module. The prefix
bypasses the domain; messages live in admin.general.columnGate.*. Nette fills %d from the
rule argument, so the translation shows the real column limit.
NOTE: rules are skipped for an optional empty field (Nette skips them in Rules::validate()),
so the tax id pattern does not enforce filling; the caller's setRequired() does.
Usage:
ColumnGate::lengths($form, Order::class, ['sellerName', 'sellerStreet', 'sellerCity']);
ColumnGate::taxId($form->getComponent('customerRegNo'), Order::class, 'customerRegNo');
Table of Contents
Constants
- ASSET_PATH_MESSAGE : string = '//admin.general.columnGate.assetPath'
- Message for an image path that is not local; global key.
- DECIMAL_MESSAGE : string = '//admin.general.columnGate.decimal'
- Message for a number outside the column range; `%s` is the exclusive limit, filled by the caller.
- FRONT_DECIMAL_MESSAGE : string = '//front.general.columnGate.decimal'
- Front end variant of {@see DECIMAL_MESSAGE}, see {@see FRONT_MAX_LENGTH_MESSAGE}.
- FRONT_MAX_LENGTH_MESSAGE : string = '//front.general.columnGate.maxLength'
- Front end variant of the same message.
- FRONT_NEGATIVE_PRICE_MESSAGE : string = '//front.general.columnGate.negativePrice'
- Front end message for a negative price (decimal gate without `$allowNegative`); without its own message a negative value got the range one ("enter a number less than …"), which is misleading.
- FRONT_TAX_ID_MESSAGE : string = '//front.general.columnGate.taxId'
- Front end variant of {@see TAX_ID_MESSAGE}, see {@see FRONT_MAX_LENGTH_MESSAGE}.
- HEX_COLOR_MESSAGE : string = '//admin.general.columnGate.hexColor'
- Message for a value that is not a hex color; global key.
- HEX_COLOR_PATTERN : string = '#[0-9A-Fa-f]{6}'
- Hex color mask `#rrggbb`, exactly what fits the varchar(7) color columns.
- INTEGER_MESSAGE : string = '//admin.general.columnGate.integer'
- Message for a whole number outside the column (or business) range; `%s` and `%s` are the inclusive bounds.
- MAX_BYTES_MESSAGE : string = '//admin.general.columnGate.maxBytes'
- Message for a text longer than a `TEXT`-like column holds (bytes); `%d` is the byte limit.
- MAX_LENGTH_MESSAGE : string = '//admin.general.columnGate.maxLength'
- Message for a value longer than the column holds; global key, `%d` is the column limit.
- NEGATIVE_PRICE_MESSAGE : string = '//admin.general.columnGate.negativePrice'
- Admin message for a negative price (decimal gate without `$allowNegative`, passed as `$negativeMessage`); the range one alone ("must be less than …") misled for a negative value (admin walkthrough B5).
- TAX_ID_MESSAGE : string = '//admin.general.columnGate.taxId'
- Message for a value that is not a tax id shape; global key.
Methods
- assetPath() : BaseControl
- Legacy image path gate (`assetPath` of categories and regions): column length plus a local relative path only ({@see AssetPathPolicy}).
- decimal() : BaseControl
- Range gate for decimal fields, measured by the column.
- decimals() : void
- Batch variant of {@see decimal()}; see {@see lengths()} for the name matching rules.
- guard() : void
- Column gate for a whole form: every text and number control is measured by the column it writes to, found by name in the given entities.
- hexColor() : BaseControl
- Color gate: column length plus the `#rrggbb` mask.
- integer() : BaseControl
- Range gate for integer fields, measured by the column ({@see ColumnLimits::integerRange()}).
- length() : BaseControl
- Limits the control to the column width: client `maxlength` plus server rule `MAX_LENGTH`.
- lengths() : void
- Batch variant for field blocks where the control name equals the entity property name.
- taxId() : BaseControl
- Tax id gate: column length plus a loose shape mask ({@see TaxIdHelper::PATTERN}) that also lets foreign ids through.
- taxIds() : void
- Batch variant of {@see taxId()}; control names equal entity property names.
-
columnError()
: array{0: string, 1: list
}|null - columnFor() : array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}|null
- fillPlaceholders() : string
- Fills `%d` / `%s` placeholders in order (Nette does it for rule messages; a manual error has to).
- guardedControls() : array<int, BaseControl, 1: array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}}>
- mappedLimit() : array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}|null
- Limits from the `#[ORM\Column]` mapping, in the {@see ColumnSchema::describe()} shape. A `text` column without `length` is `LONGTEXT` for Doctrine, so it gets no limit here.
Constants
ASSET_PATH_MESSAGE
Message for an image path that is not local; global key.
public
string
ASSET_PATH_MESSAGE
= '//admin.general.columnGate.assetPath'
DECIMAL_MESSAGE
Message for a number outside the column range; `%s` is the exclusive limit, filled by the caller.
public
string
DECIMAL_MESSAGE
= '//admin.general.columnGate.decimal'
FRONT_DECIMAL_MESSAGE
Front end variant of {@see DECIMAL_MESSAGE}, see {@see FRONT_MAX_LENGTH_MESSAGE}.
public
string
FRONT_DECIMAL_MESSAGE
= '//front.general.columnGate.decimal'
FRONT_MAX_LENGTH_MESSAGE
Front end variant of the same message.
public
string
FRONT_MAX_LENGTH_MESSAGE
= '//front.general.columnGate.maxLength'
IMPORTANT: a // key is absolute from the translation root, which differs between front
end and admin. The admin key is not loaded on a front form, and a missing key crashes the
page, so front end callers must pass this constant.
FRONT_NEGATIVE_PRICE_MESSAGE
Front end message for a negative price (decimal gate without `$allowNegative`); without its own message a negative value got the range one ("enter a number less than …"), which is misleading.
public
string
FRONT_NEGATIVE_PRICE_MESSAGE
= '//front.general.columnGate.negativePrice'
FRONT_TAX_ID_MESSAGE
Front end variant of {@see TAX_ID_MESSAGE}, see {@see FRONT_MAX_LENGTH_MESSAGE}.
public
string
FRONT_TAX_ID_MESSAGE
= '//front.general.columnGate.taxId'
HEX_COLOR_MESSAGE
Message for a value that is not a hex color; global key.
public
string
HEX_COLOR_MESSAGE
= '//admin.general.columnGate.hexColor'
HEX_COLOR_PATTERN
Hex color mask `#rrggbb`, exactly what fits the varchar(7) color columns.
public
string
HEX_COLOR_PATTERN
= '#[0-9A-Fa-f]{6}'
INTEGER_MESSAGE
Message for a whole number outside the column (or business) range; `%s` and `%s` are the inclusive bounds.
public
string
INTEGER_MESSAGE
= '//admin.general.columnGate.integer'
MAX_BYTES_MESSAGE
Message for a text longer than a `TEXT`-like column holds (bytes); `%d` is the byte limit.
public
string
MAX_BYTES_MESSAGE
= '//admin.general.columnGate.maxBytes'
MAX_LENGTH_MESSAGE
Message for a value longer than the column holds; global key, `%d` is the column limit.
public
string
MAX_LENGTH_MESSAGE
= '//admin.general.columnGate.maxLength'
NEGATIVE_PRICE_MESSAGE
Admin message for a negative price (decimal gate without `$allowNegative`, passed as `$negativeMessage`); the range one alone ("must be less than …") misled for a negative value (admin walkthrough B5).
public
string
NEGATIVE_PRICE_MESSAGE
= '//admin.general.columnGate.negativePrice'
TAX_ID_MESSAGE
Message for a value that is not a tax id shape; global key.
public
string
TAX_ID_MESSAGE
= '//admin.general.columnGate.taxId'
Methods
assetPath()
Legacy image path gate (`assetPath` of categories and regions): column length plus a local relative path only ({@see AssetPathPolicy}).
public
static assetPath(BaseControl $control, class-string $entity, string $property) : BaseControl
Without it an external https:// address was stored and public lists put it into
<img src>. An empty optional field passes.
Parameters
- $control : BaseControl
- $entity : class-string
- $property : string
Return values
BaseControldecimal()
Range gate for decimal fields, measured by the column.
public
static decimal(BaseControl $control, class-string $entity, string $property[, bool $allowNegative = false ][, string $message = self::DECIMAL_MESSAGE ][, string|null $negativeMessage = null ]) : BaseControl
IMPORTANT: neither a length gate nor Form::FLOAT is enough. FLOAT lets exponent
notation (1e1000) through, (float) turns it into INF, number_format() into the
string inf, which a strict sql_mode rejects with a generic database error and a
non-strict one stores as 0.00. So both the shape (plain decimal
notation) and the range are checked, and the range is measured on the value after rounding
to the column scale, i.e. what is actually written; otherwise 9999.995 would pass as
below the limit and be stored as 10000.00.
An empty optional field passes; the caller's setRequired() enforces filling.
Parameters
- $control : BaseControl
- $entity : class-string
-
entity whose column receives the write
- $property : string
- $allowNegative : bool = false
-
trueonly where a negative value makes sense (stock deduction) - $message : string = self::DECIMAL_MESSAGE
- $negativeMessage : string|null = null
-
own message for a negative value (checked first); without it the range message covers negative values too
Return values
BaseControldecimals()
Batch variant of {@see decimal()}; see {@see lengths()} for the name matching rules.
public
static decimals(Container $container, class-string $entity, array<int, string> $properties[, bool $allowNegative = false ]) : void
Parameters
- $container : Container
- $entity : class-string
- $properties : array<int, string>
- $allowNegative : bool = false
guard()
Column gate for a whole form: every text and number control is measured by the column it writes to, found by name in the given entities.
public
static guard(Form $form, array<string, array<int, class-string>> $containers[, array<int, string> $except = [] ][, ColumnSchema|null $schema = null ]) : void
A field without its own maxLength/range let an oversized value through validation; the save then
failed in the database ("Data too long", "Out of range"), the admin saw a generic message and the
entity manager stayed closed for the rest of the request (admin walkthrough A1, A14). Gating field by
field missed new fields again and again, so the form declares only which entities its fields write
to and every control is gated:
varchar(n)/char(n): at most n characters;TEXT-like: at most its size in bytes;- integer types: a whole number within the column (signed/unsigned);
decimal(p,s): a number within the column after rounding to the scale.
The limits are those of the real column (ColumnSchema: the mapping says which column, the
database how big it is; mapping and schema differ in places, e.g. unsigned or tinyint). Without
the schema reader (unit tests) the #[ORM\Column] mapping is used (ColumnLimits::describe()).
The name maps to a property directly (username), or without the language suffix (name_2 → the
translation entity name). $containers maps a container path to its entities: '' is the form
itself, a multiplier path ('answers') gates every row, a key ending with * takes the form's own
controls with that name prefix ('quickPrice*': quickPriceTitle_2 → property title). Controls that already failed their own
rules, disabled ones, hidden fields and controls in $except (a value transformed before storage,
e.g. a password that is hashed) are skipped.
Runs in onValidate, so it also covers multiplier rows created from the POST; the maxlength
HTML hint is set in onRender. An empty optional field passes; setRequired() enforces filling.
Parameters
- $form : Form
- $containers : array<string, array<int, class-string>>
-
container path => entities searched in order
- $except : array<int, string> = []
-
control names never gated
- $schema : ColumnSchema|null = null
hexColor()
Color gate: column length plus the `#rrggbb` mask.
public
static hexColor(BaseControl $control, class-string $entity, string $property) : BaseControl
Color columns are varchar(7), exactly #rrggbb. Anything longer (#rrggbbaa, rgb(...),
a color name) fails the save with a generic database error (strict sql_mode) or is silently
truncated to seven characters and becomes a different color (non-strict), so the shape is
checked too, not only the length.
An empty optional field passes; the caller's setRequired() enforces filling.
Parameters
- $control : BaseControl
- $entity : class-string
- $property : string
Return values
BaseControlinteger()
Range gate for integer fields, measured by the column ({@see ColumnLimits::integerRange()}).
public
static integer(BaseControl $control, class-string $entity, string $property[, int|null $min = null ][, string $message = self::INTEGER_MESSAGE ]) : BaseControl
Form::INTEGER alone checks only the shape: 99999999999 or -1 into an unsigned int column
failed the save with a generic database error (strict sql_mode) and the entity manager stayed
closed for the rest of the request. $min narrows the lower bound where the business needs it
(1 for a quantity), it never widens the column range.
Sets the min/max HTML attributes too (a browser hint on type=number). An empty optional
field passes; the caller's setRequired() enforces filling.
Parameters
- $control : BaseControl
- $entity : class-string
- $property : string
- $min : int|null = null
- $message : string = self::INTEGER_MESSAGE
Return values
BaseControllength()
Limits the control to the column width: client `maxlength` plus server rule `MAX_LENGTH`.
public
static length(BaseControl $control, class-string $entity, string $property[, string $message = self::MAX_LENGTH_MESSAGE ]) : BaseControl
maxlength alone is not enough: it is only a browser hint, ignored by a request sent
outside the form, with JavaScript off or with edited HTML. The rule also catches pasted input.
Parameters
- $control : BaseControl
- $entity : class-string
-
entity whose column receives the write
- $property : string
- $message : string = self::MAX_LENGTH_MESSAGE
Return values
BaseControllengths()
Batch variant for field blocks where the control name equals the entity property name.
public
static lengths(Container $container, class-string $entity, array<int, string> $properties) : void
A missing control is skipped because some blocks are built conditionally (for example VAT
columns only for a VAT payer). A typo in a property name fails immediately in
ColumnLimits on purpose: a silent gate that measures nothing is worse than none.
Parameters
- $container : Container
- $entity : class-string
- $properties : array<int, string>
taxId()
Tax id gate: column length plus a loose shape mask ({@see TaxIdHelper::PATTERN}) that also lets foreign ids through.
public
static taxId(BaseControl $control, class-string $entity, string $property) : BaseControl
The checkout holds the same pair of rules (AddressFormTrait). Admin is the second path to the same columns and must not be looser, otherwise a tax value is truncated exactly where the checkout protected it.
Parameters
- $control : BaseControl
- $entity : class-string
- $property : string
Return values
BaseControltaxIds()
Batch variant of {@see taxId()}; control names equal entity property names.
public
static taxIds(Container $container, class-string $entity, array<int, string> $properties) : void
Parameters
- $container : Container
- $entity : class-string
- $properties : array<int, string>
columnError()
private
static columnError(mixed $value, array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool} $limit) : array{0: string, 1: list}|null
Parameters
- $value : mixed
- $limit : array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}
Return values
array{0: string, 1: listmessage and its placeholder values, null = fits
columnFor()
private
static columnFor(string $name, array<int, class-string> $entities, ColumnSchema|null $schema) : array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}|null
Parameters
- $name : string
- $entities : array<int, class-string>
- $schema : ColumnSchema|null
Return values
array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}|nullfillPlaceholders()
Fills `%d` / `%s` placeholders in order (Nette does it for rule messages; a manual error has to).
private
static fillPlaceholders(string $text, array<int, string> $args) : string
Parameters
- $text : string
- $args : array<int, string>
Return values
stringguardedControls()
private
static guardedControls(Form $form, array<string, array<int, class-string>> $containers, array<int, string> $except, ColumnSchema|null $schema) : array<int, BaseControl, 1: array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}}>
Parameters
- $form : Form
- $containers : array<string, array<int, class-string>>
- $except : array<int, string>
- $schema : ColumnSchema|null
Return values
array<int, BaseControl, 1: array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}}>mappedLimit()
Limits from the `#[ORM\Column]` mapping, in the {@see ColumnSchema::describe()} shape. A `text` column without `length` is `LONGTEXT` for Doctrine, so it gets no limit here.
private
static mappedLimit(class-string $entity, string $property) : array{kind: string, length: ?int, min: ?int, max: ?int, precision: ?int, scale: ?int, unsigned: bool}|null
Parameters
- $entity : class-string
- $property : string