FeedFieldsTrait
Feeds tab: per-parameter overrides of XML feed values for this product set ({@see FeedParameterRel}).
The data hangs on the pair (feed parameter x product set), so it is edited on the set. The tab
is shared by ProductSetPresenter (mode bundle) and ProductPresenter (mode product-variant).
It is a separate sub-form, not part of editForm: tabs are siblings of {form editForm} and
nested forms are forbidden, so the main submit would never send its fields. Saving uses a
set-scoped bridge action (FeedManager::saveParameterRelsForSet()).
Every parameter is a multiplier feedMulti_<id>; only limits and buttons differ:
- non-multiple, 0-1 saved rows: one copy, no buttons.
- non-multiple, more than one saved row (anomaly):
maxCopies= row count, only "remove"; the XML gets one element withrows[0]. multiple: unlimited copies, one node per override row; with children, repetitions = parent rows and the child of repetitionitakes its owni-th row.
IMPORTANT: every saved row must be rendered. Saving is a set-scoped delete+insert of what the
form sent, so a row hidden in the UI would be silently deleted on the next save. That is why the
anomaly renders all rows and shows form.feeds.anomaly.warning.
IMPORTANT: a node with children still stores a parent row with the node tag as value. It carries
the value mode of the pair (the generator and the prefill read the mode from the parent's first
row) and counts the repetitions for the editor. The tag is never printed (resolveNodeText() is
not called for nodes with children).
IMPORTANT: parent and children are paired by position, so rows must not shift. An empty child in a filled copy is stored as an empty string (the generator treats it as "no override"); a copy empty everywhere is dropped. Children have no standalone field, otherwise one value would live in two places and the pairing would break.
Value mode: every root node gets a select feedMode_<id> (change, prepend, append, none;
default change), stored in FeedParameterRel::$valueMode and evaluated by FeedXmlGenerator.
Children never get one; a multiple node has one select in the card header for all repetitions.
IMPORTANT: a mode other than change needs a row to be stored on, otherwise "empty field = no
row" would drop none for an unfilled node. Collection creates a carrier row with an empty value
(collectFeedOverrideNodeRows()). In mode change an empty field still creates no row.
Table of Contents
Properties
- $feedOverrideModesCache : array<int, string>|null
- Memoized value modes of the edited set, `feedParameterId => mode`. Taken from the first row of the (parameter, set) pair, like the generator does.
- $feedOverrideRowsCache : array<int, array<int, string>>|null
- Memoized override rows of the edited set, `feedParameterId => list<value>` ordered by `r.id ASC` (the order of repetitions in the XML).
-
$feedOverrideStructureCache
: array<int, array{feedId: int, feedName: string, tree: list
>}>|null - Memoized tab axis: feeds with their parameter trees (including saved repetition counts).
- $feedParameterActionLabelsCache : array<int, string>|null
Methods
- onFeedOverridesFormSuccess() : void
- Saves the Feeds tab, set-scoped so it never touches the overrides of other sets ({@see \App\UI\Api\Eshop\Models\Managers\FeedManager::saveParameterRelsForSet()}).
- createComponentFeedOverridesForm() : Form
- Builds the sub-form of the Feeds tab: one multiplier `feedMulti_<id>` per parameter (a non-multiple parameter is a multiplier with `maxCopies = 1` and no buttons).
- addFeedOverrideModeSelect() : void
- Builds the value mode select: one per root node (children and repetitions of a `multiple` node do not get one).
- addFeedOverrideMultiplier() : void
- Builds the multiplier of one node; a copy is one repetition of the XML node.
- buildFeedOverrideDefaults() : array<string, mixed>
- Builds the prefill from {@see \App\UI\Admin\Eshop\Models\Managers\FeedManager::getParameterRelsForSet()} (order `r.id ASC`, the order of repetitions in the XML).
- buildFeedParameterRenderTree() : array<int, array<string, mixed>>
- Converts parameter tree nodes into the shape used by the form and the rendering.
- collectFeedOverrideDefaults() : void
- Fills defaults of one node. Children are not reached by recursion: they live in the parent copy.
- collectFeedOverrideNodeRows() : void
- Collects the rows of one node (see the trait docblock: why a node with children also gets a parent row and why an empty child is stored as an empty string).
- collectFeedOverrideRows() : array<int, array{feedParameterId: int, value: string, valueMode: string}>
- Converts form values into flat bridge rows, in the order the repetitions must appear in the XML (the bridge stores them sequentially and the generator reads them `ORDER BY r.id`).
- loadFeedOverrideModes() : array<int, string>
- Returns the value modes of the edited set, `feedParameterId => mode`. Filled together with the rows ({@see loadFeedOverrideRows()}), it is the same bridge query.
- loadFeedOverrideRows() : array<int, array<int, string>>
- Returns the override rows of the edited set across all feeds, `feedParameterId => list<value>`.
-
loadFeedOverrideStructure()
: array<int, array{feedId: int, feedName: string, tree: list
>}> - Returns the feeds with their parameter trees, using one bridge query per feed (memoized).
- loadFeedParameterActionLabels() : array<int, string>
- Returns the catalog of parameter actions, `actionId => translated name` (read-only).
Properties
$feedOverrideModesCache
Memoized value modes of the edited set, `feedParameterId => mode`. Taken from the first row of the (parameter, set) pair, like the generator does.
private
array<int, string>|null
$feedOverrideModesCache
= null
$feedOverrideRowsCache
Memoized override rows of the edited set, `feedParameterId => list<value>` ordered by `r.id ASC` (the order of repetitions in the XML).
private
array<int, array<int, string>>|null
$feedOverrideRowsCache
= null
$feedOverrideStructureCache
Memoized tab axis: feeds with their parameter trees (including saved repetition counts).
private
array<int, array{feedId: int, feedName: string, tree: list>}>|null
$feedOverrideStructureCache
= null
$feedParameterActionLabelsCache
private
array<int, string>|null
$feedParameterActionLabelsCache
= null
memoized action catalog, actionId => translated name.
Methods
onFeedOverridesFormSuccess()
Saves the Feeds tab, set-scoped so it never touches the overrides of other sets ({@see \App\UI\Api\Eshop\Models\Managers\FeedManager::saveParameterRelsForSet()}).
public
onFeedOverridesFormSuccess(Form $form) : void
It never calls redirect() on success (in mode product-variant the PRG would close the
variant modal); it only adds a notice and redraws the section, like stock operations do.
Parameters
- $form : Form
createComponentFeedOverridesForm()
Builds the sub-form of the Feeds tab: one multiplier `feedMulti_<id>` per parameter (a non-multiple parameter is a multiplier with `maxCopies = 1` and no buttons).
protected
createComponentFeedOverridesForm() : Form
IMPORTANT: defaults are filled unconditionally, with no isMethod('GET') guard like in
editForm. Form::setDefaults() is a no-op on a submitted form, so the multiplier builds its
copies from the submitted data and the prefill does not interfere. A guard would render the tab
with empty fields on any other signal (opening the price list modal, a stock operation
calling redrawVariantModal()).
Return values
FormaddFeedOverrideModeSelect()
Builds the value mode select: one per root node (children and repetitions of a `multiple` node do not get one).
private
addFeedOverrideModeSelect(Form $form, int $nodeId) : void
It sits next to the multiplier, not in its copy: the mode belongs to the whole (parameter x
set) pair, so inside copies it would exist several times and repetitions could contradict
each other. The template renders it as the second input-group element of a single-field
node, otherwise in the card header.
NOTE: aria-label is translated manually through the translator (not by key): the field
label is not rendered (it would break the [text][select] pair in an input-group, and the
header already shows the tag), so without it the select would have no accessible name.
Parameters
- $form : Form
- $nodeId : int
addFeedOverrideMultiplier()
Builds the multiplier of one node; a copy is one repetition of the XML node.
private
addFeedOverrideMultiplier(Form $form, array<string|int, mixed> $node) : void
A node without children has the field value in a copy, a node with children has
child_<childId> per child (the parent value is not shown).
minCopies = 1: an empty copy is dropped on save, so one empty row creates nothing but shows
where the value is entered. IMPORTANT: for a node without override rows the prefill therefore
gives no key at all; an empty array would tell the multiplier "0 copies" instead of letting
minCopies apply.
maxCopies: unlimited (null) for a multiple node, exactly the saved row count (normally 1)
otherwise. The hard cap guarantees that all saved data is rendered and cannot be dropped silently.
Only a multiple node gets the "add" button. "Remove" is always registered but the library
detaches it while there are only minCopies copies, so a non-multiple node shows it only in
the anomaly (more than one saved row), where it is the only way to remove the surplus.
Parameters
- $form : Form
- $node : array<string|int, mixed>
buildFeedOverrideDefaults()
Builds the prefill from {@see \App\UI\Admin\Eshop\Models\Managers\FeedManager::getParameterRelsForSet()} (order `r.id ASC`, the order of repetitions in the XML).
private
buildFeedOverrideDefaults() : array<string, mixed>
IMPORTANT: the multiplier is filled by a nested array in a single setDefaults(), never by
a manual addCopy(). The copy count is the node rowCount, child values of copy i come
from the i-th row of each child.
Return values
array<string, mixed>buildFeedParameterRenderTree()
Converts parameter tree nodes into the shape used by the form and the rendering.
private
buildFeedParameterRenderTree(array<string|int, mixed> $nodes, string $prefix, array<int, array<int, string>> $rows) : array<int, array<string, mixed>>
Node keys: id, tag (bare XML tag shown in the UI, indentation carries the hierarchy),
caption (full path through the parent, PARAM > VAL, kept by the form control for error
messages), multiple, actionLabel (source of the computed value), rowCount (repetitions
already saved), anomaly (non-multiple parameter with more than one row) and children.
Parameters
- $nodes : array<string|int, mixed>
- $prefix : string
- $rows : array<int, array<int, string>>
Return values
array<int, array<string, mixed>>collectFeedOverrideDefaults()
Fills defaults of one node. Children are not reached by recursion: they live in the parent copy.
private
collectFeedOverrideDefaults(array<string|int, mixed> $node, array<int, array<int, string>> $byParameter, array<string, mixed> &$defaults) : void
Parameters
- $node : array<string|int, mixed>
- $byParameter : array<int, array<int, string>>
- $defaults : array<string, mixed>
collectFeedOverrideNodeRows()
Collects the rows of one node (see the trait docblock: why a node with children also gets a parent row and why an empty child is stored as an empty string).
private
collectFeedOverrideNodeRows(array<string|int, mixed> $node, array<string, mixed> $values, array<int, array{feedParameterId: int, value: string, valueMode: string}> &$rows) : void
IMPORTANT: a node in a mode other than change that produced no repetition (the admin filled
nothing) gets one carrier row with an empty value; otherwise there is nowhere to store the mode
and none would be lost silently. In mode change an empty field still creates no row,
otherwise sets would fill with empty rows and rowCount would lie. The generator skips a
carrier row in prepend/append as an empty repetition
(FeedXmlGenerator::hasManualValue()) and
renders no node in none.
Children always get change: the mode belongs to the parent and the generator ignores it
for children.
Parameters
- $node : array<string|int, mixed>
- $values : array<string, mixed>
- $rows : array<int, array{feedParameterId: int, value: string, valueMode: string}>
collectFeedOverrideRows()
Converts form values into flat bridge rows, in the order the repetitions must appear in the XML (the bridge stores them sequentially and the generator reads them `ORDER BY r.id`).
private
collectFeedOverrideRows(array<string, mixed> $values) : array<int, array{feedParameterId: int, value: string, valueMode: string}>
Parameters
- $values : array<string, mixed>
Return values
array<int, array{feedParameterId: int, value: string, valueMode: string}>loadFeedOverrideModes()
Returns the value modes of the edited set, `feedParameterId => mode`. Filled together with the rows ({@see loadFeedOverrideRows()}), it is the same bridge query.
private
loadFeedOverrideModes() : array<int, string>
Return values
array<int, string>loadFeedOverrideRows()
Returns the override rows of the edited set across all feeds, `feedParameterId => list<value>`.
private
loadFeedOverrideRows() : array<int, array<int, string>>
Return values
array<int, array<int, string>>loadFeedOverrideStructure()
Returns the feeds with their parameter trees, using one bridge query per feed (memoized).
private
loadFeedOverrideStructure() : array<int, array{feedId: int, feedName: string, tree: list>}>
Form building, value collection on save and render() all read it. It is the only axis: the
form is not "one field per parameter" but a multiplier whose limits depend on multiple and on
the number of saved rows, so a separate flat parameter list would drift from the tree.
Return values
array<int, array{feedId: int, feedName: string, tree: listloadFeedParameterActionLabels()
Returns the catalog of parameter actions, `actionId => translated name` (read-only).
private
loadFeedParameterActionLabels() : array<int, string>
Used only for the "value source" caption in the parameter card header; no action is evaluated here because that would require running the generator over the set.