Skip to content

Attribute labels and options from documents, and attribute feed freshness - #38

Merged
Sental merged 5 commits into
mainfrom
attribute-option-labels
Oct 19, 2026
Merged

Sental merged 5 commits into
mainfrom
attribute-option-labels

Conversation

@Sental

@Sental Sental commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Option labels. ProductFrontend/Plugin/OptionsFromDocuments answers getAllOptions() and getSpecificOptions() (and so getOptionText()) of a product attribute's table source from its attribute document.
  • Store labels. ProductFrontend/Plugin/StoreLabelFromDocuments answers getStoreLabel() from the document. That covers every attribute label a page shows: the attributes table, the secondary info, the swatches, the sort order and the layered navigation.
  • Swatches. ProductFrontend/Plugin/SwatchesFromDocuments answers core's swatch helper (getSwatchesByOptionsId()) from the attribute documents: the swatches of a product page, of listing cards and of the layered navigation. The helper knows option ids only; AttributeDocuments::optionsById() finds them in the documents the request read, else in every document of the store view, read once. Core's helper keeps the swatches by option id alone for the life of a worker, across store views; the documents follow a change through the attributes feed. ProductFrontend now requires CatalogStorefrontSwatches, which writes the swatches onto the documents.
  • Reads.
    • Product page: Plugin/Detail/ProductDocument reads the documents of every attribute of the attribute set in one request.
    • Listing and search pages: FilterableAttributesFromDocuments reads every attribute document of the store view once (AttributeDocuments::all(), which byCodes() now answers from) and filters the layer in PHP.
    • Attributes with a source model of their own (options only), reads for another store and the exporter's emulated reads stay on core. A missing document or a missing label stops the page.
  • Attributes feed freshness. Core's cde_product_attributes_feed view watches only eav_attribute and catalog_eav_attribute, so a save that changed only labels, options or swatches never reached the documents. This adds triggers on:
    • eav_attribute_label and eav_attribute_option;
    • eav_attribute_option_value, and eav_attribute_option_swatch (Swatches module), through a lookup from option id to attribute.
  • New base Model/Mview/LookupSubscription for tables whose rows name no entity of the view; Snowdog's MenuLookupSubscription now extends it with the same statements.
  • Sort orders: ProductDocument 10, OptionsFromDocuments 10, StoreLabelFromDocuments 10.
  • Configurable documents. Core's option label resync queues the products that store a renamed option, and the products feed's configurableProductsByChildren resolver adds their configurables. A configurable's configurableOptions also copies the attribute's labels and its options' swatches, which core compares nowhere. ConfigurableProduct/Plugin/Eav/ResyncConfigurablesOnAttributeChange, beside core's resync on the attribute resource's save(), compares them before and after a save:
    • a changed label queues every configurable using the attribute;
    • a changed, added or removed swatch queues the products storing those options.
    • More than threshold (di.xml, default 1000) or a feed not on schedule invalidates the feed, as core does; so does a failure, which is logged. Verified live: a label change re-exported 147 configurables, a swatch change 137 products and their configurables, and a save without changes nothing.

Why

  • Speed: a product page read 17 option-label queries; from documents it reads none. Warm SQL is unchanged by the store-label part (tops 13.5, tops ?36 13.5, fitness 24.5, search 16.5, product page 15.5), and listing and search pages make one search fewer.
  • Freshness under an application server: core keeps the store labels it read (Eav\Model\ResourceModel\Entity\Attribute::$storeLabelsCache, and store_labels on the shared attribute) for the life of a worker. A changed label reached the page only after a restart; the document follows the change through the feed.
  • Correctness: without the triggers, a label, translation, sort order or swatch change never reached the documents.
  • Cold SQL: tops −6, product page −4, search −3 (every eav_attribute_option_swatch read), searches unchanged.

Verification

  • Unit tests: 654 pass.
  • Parity gates at MAX_REQUESTS=1: listing 4/4 and detail 6/6 identical.
  • Live on Mage-OS 3.6; each change arrived as a changelog partial sync:
    • an option label (Admin column and store view);
    • a new option;
    • a reorder;
    • the attribute's store label;
    • a swatch.
  • The sort order, the store label and the swatch were also changed by direct SQL, which proves each trigger on its own.
  • On long-lived workers, a store label change showed on the documents path on every request, while core kept the old label until a restart.

Notes

  • Core deletes and re-inserts every option label on each attribute save, so every Admin save of an attribute with options re-exports that one attribute.
  • Known limit, documented in LookupSubscription: core builds a shared table's trigger with the subscription model of the view that subscribes last.
  • Upgrade: setup:upgrade installs the triggers. No new document field, so no rebuild.

🤖 Generated with Claude Code

Two Additions are necessary for frankenphp's worker mode:

{
    "\\Magento\\Eav\\Model\\ResourceModel\\Entity\\Attribute": {
        "storeLabelsCache": []
    },
    "\\Magento\\Swatches\\Helper\\Data": {
        "swatchesCache": []
    }
}

These will be added to the MageOS WorkerMode package

Sental and others added 3 commits October 19, 2026 10:54
Core's table source queries the labels per attribute and value (17 on a product page); the attribute documents already carry them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core's attributes view watches eav_attribute and catalog_eav_attribute only, so such a save left the documents stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core keeps the store labels it read for a worker's life, so a changed label showed only after a restart; the document follows the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Sental Sental changed the title Option labels from attribute documents, and attribute feed freshness Attribute labels and options from documents, and attribute feed freshness Oct 19, 2026
Sental and others added 2 commits October 19, 2026 13:17
Core's resync compares option labels only; a configurable's document also copies the attribute's labels and its options' swatches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core queries the swatch table per page and keeps the answer by option id alone for a worker's life, across store views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Sental
Sental marked this pull request as ready for review October 19, 2026 13:32
@Sental
Sental merged commit 097fa33 into main Oct 19, 2026
8 checks passed
@Sental
Sental deleted the attribute-option-labels branch October 19, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant