ImportRules
in package
Rules shared by the validator (dry-run) and the executor (real write): the single place for column limits, nullable/not-null field lists and scalar interpretation. If each side kept its own copy, the report would promise something different from what the executor does.
Pure static helpers without I/O; DB matching is done by ImportMatcher, per-item verdicts by ImportValidator.
Table of Contents
Constants
- CODE_MAX_LENGTH : int = 20
- DEFAULT_UNIT_CODE : string = 'ks'
- Default unit of a new set when the file has no `<unit>`.
- EAN_MAX_LENGTH : int = 45
- FROM_AMOUNT_DEFAULT : string = '0'
- Default tier when the file omits `fromAmount` (the base tier of the DB).
- FROM_AMOUNT_MIN : int = 0
- Lowest quantity tier of the price series. Data contract: the base tier ("from 1 piece") is `from_amount = 0` (legacy, admin price list, {@see \App\UI\Api\Eshop\Models\Services\PriceResolver}), and the export writes it that way, so the import accepts zero (export to import round trip).
- INT_UNSIGNED_MAX : string = '4294967295'
- Upper limit of int unsigned columns (`product_set_prices.from_amount`, `product_set_prices.points`).
- MAX_IMAGES_PER_VARIANT : int = 20
- Maximum number of images per variant.
- NAME_MAX_LENGTH : int = 255
- OPTION_PARAMETER_TYPES : array<string|int, mixed> = [\App\Core\Base\Shared\Shared\Models\Entities\P...
- Parameter types whose value is matched to an option: the `option` attribute or the element content as a name. Shared by validator and writer so that the rules are not duplicated.
- PARAMETER_KIND_OPTION : string = 'option'
- PARAMETER_KIND_RANGE : string = 'range'
- PARAMETER_KIND_TEXT : string = 'text'
- PRICE_MAX_EXCLUSIVE : string = '100000000000'
- Upper limit of a money value. `product_set_prices.price` is decimal(15,4), so 11 digits before the decimal point; above that the DB would fail the write or silently clip the value (same reason as {@see WEIGHT_MAX_EXCLUSIVE}). Compared with bcmath on a string, not a float: `(float) '1e1000'` is `INF` and any comparison with it lies.
- PRICE_SCALE : int = 4
- Precision of the money columns of the price series (`product_set_prices.price`/`original_price` decimal(15,4)) and of the Store stock columns (decimal(10,3)). `PRICE_WORK_SCALE` is the working precision of the VAT calculation so that nothing is rounded twice.
- PRICE_WORK_SCALE : int = 8
- PRODUCER_MAX_LENGTH : int = 45
- PRODUCT_NOT_NULL_FIELDS : array<string|int, mixed> = ['activated']
- RANGE_PARAMETER_TYPES : array<string|int, mixed> = [\App\Core\Base\Shared\Shared\Models\Entities\P...
- STOCK_MAX_EXCLUSIVE : string = '10000000'
- Same limit for the stock axis. Store columns are decimal(10,3), so 7 digits before the decimal point; it applies to the absolute value because `mode="add"` may subtract.
- STOCK_SCALE : int = 3
- VARIANT_NOT_NULL_FIELDS : array<string|int, mixed> = ['code', 'unit', 'weight', 'warranty', 'freeDel...
- Not-null variant columns; an empty tag is a hard error. `description` and `shortDescription` are deliberately absent: they are not null, but an empty string is a legitimate value for them (clearing a description makes sense).
- VARIANT_NULLABLE_FIELDS : array<string|int, mixed> = ['ean', 'isbn', 'publishTime', 'unpublishTime',...
- Variant fields that can be cleared by an empty tag (nullable columns and nullable FKs).
- WARRANTY_MAX : int = 255
- WEIGHT_MAX_EXCLUSIVE : string = '10000000'
- Number ranges. A value out of range would fail the write with a generic database error (strict `sql_mode`) or be silently clipped to the column limit (non-strict); the range is therefore a hard item error: - `weight` decimal(10,3): 0 <= x < 10^7, - `warranty` tinyint(3) unsigned: 0-255 months.
- WEIGHT_SCALE : int = 3
- EXPAND_MAX_LENGTH : int = 512
- Length cap of an expanded exponential notation. `1e1000000000` would expand to a gigabyte string; above the cap the value is not expanded and stays in exponential form, which {@see isPlainDecimal()} rejects as invalid input.
Methods
- classifyParameterKind() : string
- The single place that decides which data shape a parameter of a given type expects: the validator uses it for the type check (hard error on mismatch), the writer to build the `ProductSetParameterRel` value. `$hasOptions` decides when `$type` alone is not enough (the entity can have options without being listed in {@see OPTION_PARAMETER_TYPES}, e.g.
- isBoolean() : bool
- isDateTime() : bool
- isDecimal() : bool
- Returns whether the value has a shape the writer can calculate with. A non-numeric value is a hard error; without this test `(float)` of "abc" would become a silent zero.
- isFromAmountValid() : bool
- Quantity tier `fromAmount`: an integer >= 0 (int unsigned column; the base tier "from 1 piece" is `0` in the DB). Anything else is a hard item error, otherwise the price would be written to a different axis than the file meant. The export writes `fromAmount="0"`, so zero must be accepted for the export to import round trip.
- isNegativeAmount() : bool
- Exact sign of a numeric value, shared by the price and stock axes. `(float)` and `bccomp()` with a scale are not enough: both lose non-zero digits below the scale, so `-0.00009` would come out as zero and the writer would round it half-up to a negative value.
- isPlainDecimal() : bool
- Plain decimal notation: the form that bcmath and a decimal column both accept. Stricter than `is_numeric()`: no exponents, no hex, no spaces.
- isPointsValid() : bool
- `points` is an integer >= 0 (int unsigned column).
- isPriceAmountValid() : bool
- Returns whether `<amount>`/`<originalAmount>`/... is a number >= 0; a negative price is nonsense.
- isStockAmountInRange() : bool
- Returns whether a quantity fits decimal(10,3) after rounding. It is measured on the value the writer actually stores ({@see toStockDecimal()}), not on the raw input, otherwise the same gap as with prices remains between the gate and the rounding. The range applies to the absolute value; the sign is handled by {@see isNegativeAmount()} according to the mode.
- isStockDecimalInRange() : bool
- Same limit on a value that is already in the column shape. The writer needs it separately: with `mode="add"` the DB gets `bcadd(current stock, delta)`, not the input, so the input gate guarantees nothing (`9000000 + 5000000` passed validation and was silently clipped).
- isWarrantyInRange() : bool
- `warranty` is an integer 0-{@see WARRANTY_MAX} (tinyint unsigned).
- isWeightInRange() : bool
- `weight` is a number in the decimal(10,3) range: 0 <= x < 10^7.
- normalizeDecimal() : string
- Decimal point canonical, comma tolerated. Also expands exponential notation (`1e2`, `1.5E+3`) to a plain decimal. Spreadsheets emit it into exports and `is_numeric()` accepts it, but bcmath does not: `bcdiv('1e2', ...)` ends with `ValueError: ... is not well-formed` inside {@see priceWithoutVat()}/{@see toStockDecimal()}.
- normalizeFromAmount() : int
- Tier axis for writing and comparing. Both `0` and `1` mean "from 1 piece" (the resolver takes the highest `from_amount <= quantity`) and the DB keeps the base tier as `0`, so `fromAmount="1"` must target the same axis; otherwise a parallel axis `1` would appear next to the base tier and silently override it on the front end. Call only with a value that passed {@see isFromAmountValid()}.
- parseBoolean() : bool|null
- `1`/`0` canonical, `true`/`false` tolerated; anything else yields `null` (the caller has already validated).
- parseDateTime() : DateTime|null
- Parses `Y-m-d H:i:s` or `Y-m-d`. Returns a `DateTime` in the application zone (Europe/Prague): `publish_time`/`unpublish_time` are stored through Doctrine `datetime` exactly as the admin form (`ProductSetEditComponent`) writes them, so after an import the admin sees exactly the value from the file.
- priceWithoutVat() : string
- Price without VAT from a price with VAT and a rate in percent (`base = withVat / (1 + p/100)`), rounded half-up to {@see PRICE_SCALE} decimals, the column precision.
- resolveAction() : string
- A per-item `action` overrides the product card `action`, which overrides the global job `mode`.
- toPriceDecimal() : numeric-string
- Converts a money value to the shape of the `price`/`original_price` decimal(15,4) columns. It has the same shape as {@see \App\UI\Api\Eshop\Models\Managers\ProductSetManager::savePriceSnapshot()} writes (`number_format(..., 4)`), so the check "same price means no new snapshot" compares like with like.
- toStockDecimal() : numeric-string
- Converts a stock quantity to the shape of the Store decimal(10,3) columns (`stock_levels.quantity`, `stock_movements.quantity`, `stock_items.min_quantity`).
- toWeightDecimal() : numeric-string
- Converts a weight to the shape of `product_sets.weight` decimal(10,3).
- expandScientific() : string
- Expands exponential notation to a plain decimal, exactly and without floats, by shifting the decimal point in the digit string: `1e2` to `100`, `1.5e-3` to `0.0015`.
- isNegativeDecimal() : bool
- Exact test of a negative decimal string, without bcmath scale (which would drop non-zero digits below the scale) and without floats. Negative means a minus sign plus at least one non-zero digit; `-0`/`-0.0000` is zero.
- isUnsignedIntInRange() : bool
- Integer in the range of an int unsigned column. Compared as a string (bcmath), not with `(int)`: on 64 bits `99999999999999999999999` does not fit an int and the cast yields a value that passes the gate.
- roundHalfUp() : numeric-string
- bcmath half-up rounding (`bcadd`/`bcdiv` truncate on their own), same approach as `OrderCalculator::round3()`.
Constants
CODE_MAX_LENGTH
public
int
CODE_MAX_LENGTH
= 20
DEFAULT_UNIT_CODE
Default unit of a new set when the file has no `<unit>`.
public
string
DEFAULT_UNIT_CODE
= 'ks'
EAN_MAX_LENGTH
public
int
EAN_MAX_LENGTH
= 45
FROM_AMOUNT_DEFAULT
Default tier when the file omits `fromAmount` (the base tier of the DB).
public
string
FROM_AMOUNT_DEFAULT
= '0'
FROM_AMOUNT_MIN
Lowest quantity tier of the price series. Data contract: the base tier ("from 1 piece") is `from_amount = 0` (legacy, admin price list, {@see \App\UI\Api\Eshop\Models\Services\PriceResolver}), and the export writes it that way, so the import accepts zero (export to import round trip).
public
int
FROM_AMOUNT_MIN
= 0
INT_UNSIGNED_MAX
Upper limit of int unsigned columns (`product_set_prices.from_amount`, `product_set_prices.points`).
public
string
INT_UNSIGNED_MAX
= '4294967295'
IMPORTANT: without it 4294967296 passed; a strict sql_mode fails the write, a non-strict
one silently clips it to 4294967295. For from_amount that is worse than losing a number: it is the axis of the
price series, so two quantity tiers collapse into one after clipping and idempotence breaks
(ImportProductWriter::loadCurrentPrices() reads the clipped value while the key uses
the unclipped one, so a match never happens and every run creates a new point).
MAX_IMAGES_PER_VARIANT
Maximum number of images per variant.
public
int
MAX_IMAGES_PER_VARIANT
= 20
NAME_MAX_LENGTH
public
int
NAME_MAX_LENGTH
= 255
OPTION_PARAMETER_TYPES
Parameter types whose value is matched to an option: the `option` attribute or the element content as a name. Shared by validator and writer so that the rules are not duplicated.
public
array<string|int, mixed>
OPTION_PARAMETER_TYPES
= [\App\Core\Base\Shared\Shared\Models\Entities\ParameterType::SELECT, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::MULTISELECT, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::RADIO, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::CHECKBOXLIST, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::SELECT_CURRENCY, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::SELECT_REGION]
PARAMETER_KIND_OPTION
public
string
PARAMETER_KIND_OPTION
= 'option'
PARAMETER_KIND_RANGE
public
string
PARAMETER_KIND_RANGE
= 'range'
PARAMETER_KIND_TEXT
public
string
PARAMETER_KIND_TEXT
= 'text'
PRICE_MAX_EXCLUSIVE
Upper limit of a money value. `product_set_prices.price` is decimal(15,4), so 11 digits before the decimal point; above that the DB would fail the write or silently clip the value (same reason as {@see WEIGHT_MAX_EXCLUSIVE}). Compared with bcmath on a string, not a float: `(float) '1e1000'` is `INF` and any comparison with it lies.
public
string
PRICE_MAX_EXCLUSIVE
= '100000000000'
PRICE_SCALE
Precision of the money columns of the price series (`product_set_prices.price`/`original_price` decimal(15,4)) and of the Store stock columns (decimal(10,3)). `PRICE_WORK_SCALE` is the working precision of the VAT calculation so that nothing is rounded twice.
public
int
PRICE_SCALE
= 4
PRICE_WORK_SCALE
public
int
PRICE_WORK_SCALE
= 8
PRODUCER_MAX_LENGTH
public
int
PRODUCER_MAX_LENGTH
= 45
PRODUCT_NOT_NULL_FIELDS
public
array<string|int, mixed>
PRODUCT_NOT_NULL_FIELDS
= ['activated']
not-null product card fields
RANGE_PARAMETER_TYPES
public
array<string|int, mixed>
RANGE_PARAMETER_TYPES
= [\App\Core\Base\Shared\Shared\Models\Entities\ParameterType::RANGE, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::RANGE_SLIDER, \App\Core\Base\Shared\Shared\Models\Entities\ParameterType::LIMITRANGE_SLIDER]
parameter types with a value + value2 range
STOCK_MAX_EXCLUSIVE
Same limit for the stock axis. Store columns are decimal(10,3), so 7 digits before the decimal point; it applies to the absolute value because `mode="add"` may subtract.
public
string
STOCK_MAX_EXCLUSIVE
= '10000000'
IMPORTANT: without this gate 1e1000 passed as a number (is_numeric() accepts an unexpanded
exponent) and the writer turned it into 0.000, so mode="set" silently zeroed the stock
instead of rejecting the item.
STOCK_SCALE
public
int
STOCK_SCALE
= 3
VARIANT_NOT_NULL_FIELDS
Not-null variant columns; an empty tag is a hard error. `description` and `shortDescription` are deliberately absent: they are not null, but an empty string is a legitimate value for them (clearing a description makes sense).
public
array<string|int, mixed>
VARIANT_NOT_NULL_FIELDS
= ['code', 'unit', 'weight', 'warranty', 'freeDelivery', 'freePayment', 'activated']
VARIANT_NULLABLE_FIELDS
Variant fields that can be cleared by an empty tag (nullable columns and nullable FKs).
public
array<string|int, mixed>
VARIANT_NULLABLE_FIELDS
= ['ean', 'isbn', 'publishTime', 'unpublishTime', 'vat', 'state']
WARRANTY_MAX
public
int
WARRANTY_MAX
= 255
WEIGHT_MAX_EXCLUSIVE
Number ranges. A value out of range would fail the write with a generic database error (strict `sql_mode`) or be silently clipped to the column limit (non-strict); the range is therefore a hard item error: - `weight` decimal(10,3): 0 <= x < 10^7, - `warranty` tinyint(3) unsigned: 0-255 months.
public
string
WEIGHT_MAX_EXCLUSIVE
= '10000000'
IMPORTANT: the limit is a string compared with bcmath, never a float. (float) '1e1000' is
INF and number_format() turned it into the text inf; 9999999.9995 passed a float gate
and was rounded to 10000000.000, one digit more than the column holds (same reason for
PRICE_MAX_EXCLUSIVE and STOCK_MAX_EXCLUSIVE).
WEIGHT_SCALE
public
int
WEIGHT_SCALE
= 3
EXPAND_MAX_LENGTH
Length cap of an expanded exponential notation. `1e1000000000` would expand to a gigabyte string; above the cap the value is not expanded and stays in exponential form, which {@see isPlainDecimal()} rejects as invalid input.
private
int
EXPAND_MAX_LENGTH
= 512
Methods
classifyParameterKind()
The single place that decides which data shape a parameter of a given type expects: the validator uses it for the type check (hard error on mismatch), the writer to build the `ProductSetParameterRel` value. `$hasOptions` decides when `$type` alone is not enough (the entity can have options without being listed in {@see OPTION_PARAMETER_TYPES}, e.g.
public
static classifyParameterKind(int|null $type, bool $hasOptions) : string
type === null on old data).
Parameters
- $type : int|null
- $hasOptions : bool
Return values
stringisBoolean()
public
static isBoolean(string $value) : bool
Parameters
- $value : string
Return values
boolisDateTime()
public
static isDateTime(string $value) : bool
Parameters
- $value : string
Return values
boolisDecimal()
Returns whether the value has a shape the writer can calculate with. A non-numeric value is a hard error; without this test `(float)` of "abc" would become a silent zero.
public
static isDecimal(string $value) : bool
IMPORTANT: is_numeric() is the wrong test here, as for prices: the unexpanded exponent
1e1000 is a valid number for it, but toStockDecimal() turns it into a safe 0.000. The gate
therefore measures the same shape the conversion handles: isPlainDecimal() on the
expanded value.
Parameters
- $value : string
Return values
boolisFromAmountValid()
Quantity tier `fromAmount`: an integer >= 0 (int unsigned column; the base tier "from 1 piece" is `0` in the DB). Anything else is a hard item error, otherwise the price would be written to a different axis than the file meant. The export writes `fromAmount="0"`, so zero must be accepted for the export to import round trip.
public
static isFromAmountValid(string $value) : bool
Parameters
- $value : string
Return values
boolisNegativeAmount()
Exact sign of a numeric value, shared by the price and stock axes. `(float)` and `bccomp()` with a scale are not enough: both lose non-zero digits below the scale, so `-0.00009` would come out as zero and the writer would round it half-up to a negative value.
public
static isNegativeAmount(string $value) : bool
Parameters
- $value : string
Return values
boolisPlainDecimal()
Plain decimal notation: the form that bcmath and a decimal column both accept. Stricter than `is_numeric()`: no exponents, no hex, no spaces.
public
static isPlainDecimal(string $normalized) : bool
The assert narrows the type for callers: whatever passes is also a numeric-string, so the
bcmath calls below get the type they require.
Parameters
- $normalized : string
Tags
Return values
boolisPointsValid()
`points` is an integer >= 0 (int unsigned column).
public
static isPointsValid(string $value) : bool
IMPORTANT: the upper limit INT_UNSIGNED_MAX is required: 99999999999999999999999 passed
and the writer turned it into nonsense with an (int) cast, which the DB then silently clipped.
Parameters
- $value : string
Return values
boolisPriceAmountValid()
Returns whether `<amount>`/`<originalAmount>`/... is a number >= 0; a negative price is nonsense.
public
static isPriceAmountValid(string $value) : bool
Both the shape and the range are checked. The validation accepts exactly what the writer can
calculate and a decimal(15,4) column can hold: an expandable exponent passes as a plain number,
an unexpandable or overflowing one is a hard item error (same policy as for weight, see
WEIGHT_MAX_EXCLUSIVE). Without it 1e2 passed and the writer crashed on bcdiv(), and
1e1000 produced the string inf for a decimal column.
Parameters
- $value : string
Return values
boolisStockAmountInRange()
Returns whether a quantity fits decimal(10,3) after rounding. It is measured on the value the writer actually stores ({@see toStockDecimal()}), not on the raw input, otherwise the same gap as with prices remains between the gate and the rounding. The range applies to the absolute value; the sign is handled by {@see isNegativeAmount()} according to the mode.
public
static isStockAmountInRange(string $value) : bool
Parameters
- $value : string
Return values
boolisStockDecimalInRange()
Same limit on a value that is already in the column shape. The writer needs it separately: with `mode="add"` the DB gets `bcadd(current stock, delta)`, not the input, so the input gate guarantees nothing (`9000000 + 5000000` passed validation and was silently clipped).
public
static isStockDecimalInRange(numeric-string $stored) : bool
Parameters
- $stored : numeric-string
-
value after toStockDecimal() or a bcmath sum
Return values
boolisWarrantyInRange()
`warranty` is an integer 0-{@see WARRANTY_MAX} (tinyint unsigned).
public
static isWarrantyInRange(string $value) : bool
Parameters
- $value : string
Return values
boolisWeightInRange()
`weight` is a number in the decimal(10,3) range: 0 <= x < 10^7.
public
static isWeightInRange(string $value) : bool
The same three gates as for prices and stock, with bcmath instead of float: the shape
(isDecimal(), not is_numeric(), which accepts the unexpanded exponent 1e1000), the
sign exactly (isNegativeAmount(); (float) loses digits below the scale) and the range
measured on the rounded value, i.e. on what actually goes into the column.
Parameters
- $value : string
Return values
boolnormalizeDecimal()
Decimal point canonical, comma tolerated. Also expands exponential notation (`1e2`, `1.5E+3`) to a plain decimal. Spreadsheets emit it into exports and `is_numeric()` accepts it, but bcmath does not: `bcdiv('1e2', ...)` ends with `ValueError: ... is not well-formed` inside {@see priceWithoutVat()}/{@see toStockDecimal()}.
public
static normalizeDecimal(string $value) : string
IMPORTANT: the expansion works on the string (shifting the decimal point), never via
(float): for large exponents a float is INF and number_format() then prints inf.
A value that cannot be expanded (non-numeric, or exponent above EXPAND_MAX_LENGTH) is
returned unchanged; is_numeric() and isPlainDecimal() filter it out themselves.
Parameters
- $value : string
Return values
stringnormalizeFromAmount()
Tier axis for writing and comparing. Both `0` and `1` mean "from 1 piece" (the resolver takes the highest `from_amount <= quantity`) and the DB keeps the base tier as `0`, so `fromAmount="1"` must target the same axis; otherwise a parallel axis `1` would appear next to the base tier and silently override it on the front end. Call only with a value that passed {@see isFromAmountValid()}.
public
static normalizeFromAmount(string $value) : int
Parameters
- $value : string
Return values
intparseBoolean()
`1`/`0` canonical, `true`/`false` tolerated; anything else yields `null` (the caller has already validated).
public
static parseBoolean(string $value) : bool|null
Parameters
- $value : string
Return values
bool|nullparseDateTime()
Parses `Y-m-d H:i:s` or `Y-m-d`. Returns a `DateTime` in the application zone (Europe/Prague): `publish_time`/`unpublish_time` are stored through Doctrine `datetime` exactly as the admin form (`ProductSetEditComponent`) writes them, so after an import the admin sees exactly the value from the file.
public
static parseDateTime(string $value) : DateTime|null
Parameters
- $value : string
Return values
DateTime|nullpriceWithoutVat()
Price without VAT from a price with VAT and a rate in percent (`base = withVat / (1 + p/100)`), rounded half-up to {@see PRICE_SCALE} decimals, the column precision.
public
static priceWithoutVat(string $withVat, string|null $vatPercents) : string
Own bcmath calculation instead of OrderCalculator::withoutVat() on purpose: the order
calculator works with 3 decimals (document rows are decimal(10,3)) while the price series has
4, so sharing it would cut one decimal off the price on every import.
Parameters
- $withVat : string
- $vatPercents : string|null
Return values
stringresolveAction()
A per-item `action` overrides the product card `action`, which overrides the global job `mode`.
public
static resolveAction(ImportVariantDto $variant, ImportProductDto $product, ImportSettingsDto $settings) : string
Parameters
- $variant : ImportVariantDto
- $product : ImportProductDto
- $settings : ImportSettingsDto
Return values
stringtoPriceDecimal()
Converts a money value to the shape of the `price`/`original_price` decimal(15,4) columns. It has the same shape as {@see \App\UI\Api\Eshop\Models\Managers\ProductSetManager::savePriceSnapshot()} writes (`number_format(..., 4)`), so the check "same price means no new snapshot" compares like with like.
public
static toPriceDecimal(string $value) : numeric-string
Parameters
- $value : string
Return values
numeric-stringtoStockDecimal()
Converts a stock quantity to the shape of the Store decimal(10,3) columns (`stock_levels.quantity`, `stock_movements.quantity`, `stock_items.min_quantity`).
public
static toStockDecimal(string $value) : numeric-string
Parameters
- $value : string
Return values
numeric-stringtoWeightDecimal()
Converts a weight to the shape of `product_sets.weight` decimal(10,3).
public
static toWeightDecimal(string $value) : numeric-string
IMPORTANT: the writer must use this conversion, not its own number_format((float) ..., 3): a
float overflows to INF for large values and the string inf was written into the column.
Parameters
- $value : string
Return values
numeric-stringexpandScientific()
Expands exponential notation to a plain decimal, exactly and without floats, by shifting the decimal point in the digit string: `1e2` to `100`, `1.5e-3` to `0.0015`.
private
static expandScientific(string $value) : string
Parameters
- $value : string
Return values
stringisNegativeDecimal()
Exact test of a negative decimal string, without bcmath scale (which would drop non-zero digits below the scale) and without floats. Negative means a minus sign plus at least one non-zero digit; `-0`/`-0.0000` is zero.
private
static isNegativeDecimal(string $normalized) : bool
Parameters
- $normalized : string
-
value after normalizeDecimal(); a non-numeric shape counts as non-negative
Return values
boolisUnsignedIntInRange()
Integer in the range of an int unsigned column. Compared as a string (bcmath), not with `(int)`: on 64 bits `99999999999999999999999` does not fit an int and the cast yields a value that passes the gate.
private
static isUnsignedIntInRange(string $value, int $min) : bool
Parameters
- $value : string
- $min : int
Return values
boolroundHalfUp()
bcmath half-up rounding (`bcadd`/`bcdiv` truncate on their own), same approach as `OrderCalculator::round3()`.
private
static roundHalfUp(numeric-string $value, int $scale) : numeric-string
Parameters
- $value : numeric-string
- $scale : int