luza/module-featured-product
Composer 安装命令:
composer require luza/module-featured-product
包简介
Magento 2 module for displaying a featured product with real-time inventory updates.
README 文档
README
A Magento 2 module that displays a single featured product as the first element of the homepage main content (Luma theme), with its available stock updated in real time — periodic AJAX, no full page reload.
The box shows the product's title, price, base image and salable quantity, and links to the product page when clicked. Everything is driven from the admin panel and no theme files are edited — the whole feature lives inside the module.
Table of contents
- Features
- Requirements
- Installation
- Configuration
- How it works
- Architecture & applied patterns
- Module structure
- Technical reference
- Extending
- Troubleshooting
- Author & license
Features
- Featured-product box rendered as the first element of the homepage main content,
occupying the full
content mainwidth. - Shows title, final price, base image and salable stock.
- The whole card links to the product page.
- Real-time stock: the available quantity is refreshed periodically without reloading the page (Knockout + AJAX).
- Two selection strategies: by SKU or by product, chosen in the admin.
- SKU validation on save (rejects an unknown/empty SKU).
- Self-healing config: if the featured product's SKU is renamed, the configuration is updated automatically (observer).
- Fully configurable from the admin panel, scope-aware (default / website / store view).
- No theme edits — styles and templates ship with the module.
- i18n ready (pt_BR translations included).
Requirements
- Magento Open Source / Adobe Commerce 2.4.x
- PHP 8.1 / 8.2 / 8.3
- Multi-Source Inventory (
magento/module-inventory-sales-api, enabled by default)
Installation
Install via Composer:
composer require luza/module-featured-product
Enable and upgrade:
bin/magento module:enable Luza_FeaturedProduct bin/magento setup:upgrade
In production mode, also compile DI and deploy static content:
bin/magento setup:di:compile bin/magento setup:static-content:deploy pt_BR en_US
Finally, clear the cache:
bin/magento cache:flush
Configuration
Open Stores → Configuration → General → Featured Product. There is also a shortcut under Content → Featured Product in the admin menu.
General Settings
| Field | Type | Default | Description |
|---|---|---|---|
| Enabled | Yes/No | No | Master switch. When off, the box is not rendered. |
| Selection Type | SKU / Product | — | How the featured product is chosen. Shown when Enabled. |
| Product SKU | Text | — | The SKU to feature. Validated on save (must exist). Shown when Selection Type = SKU. |
| Product | Dropdown | — | Pick the product from a list. Shown when Selection Type = Product. |
Real-Time Stock
| Field | Type | Default | Description |
|---|---|---|---|
| Enable Real-Time Update | Yes/No | No | Turns the periodic stock refresh on/off. When off, the stock is shown once (server value). |
| Update Interval (seconds) | Text (digits > 0) | 15 | How often the stock is refreshed. Shown when the update is enabled. |
All fields are scope-aware — you can configure a different featured product per website or store view.
How it works
Product selection & validation
The product shown is driven entirely by configuration and resolved through a service layer
(FeaturedProductResolverInterface):
- By SKU →
ProductRepository::get($sku). The Product SKU field is guarded by a backend model that runs on save:- empty value → "The SKU field is empty."
- unknown SKU → "No product found with SKU '…'. Please enter a valid SKU."
- By Product →
ProductRepository::getById($id)(chosen from a dropdown).
The resolver fails soft: if the feature is disabled, nothing is configured, or the product
no longer exists, it returns null and the box is simply not rendered (a warning is logged) —
the storefront never breaks.
Real-time stock
Homepage render
→ Block.getJsLayout() injects { qty, updateUrl, interval, enabled }
→ Knockout uiComponent (stock.js) starts an observable `qty` + polling
→ every N seconds: GET /featuredproduct/stock
→ thin controller → resolver + MSI stock service → { "qty": N }
→ the observable updates → Knockout re-renders ONLY the stock number
No full page reload; only the stock value changes. Stock is the salable quantity (MSI: quantity − reservations − out-of-stock threshold).
Automatic SKU sync
An observer on catalog_product_save_after watches product saves. If the product currently
configured as featured (by SKU) has its SKU renamed, the stored config value is updated to
the new SKU automatically — so the featured box never points to a missing SKU. It runs on
save_after (not before) so the config is only touched once the product save commits.
Architecture & applied patterns
- Service contracts (interfaces + DI preferences). Business logic sits behind
Api/FeaturedProductResolverInterfaceandApi/FeaturedProductStockInterface, bound to their implementations inetc/di.xml. The ViewModel (storefront) and the AJAX controller reuse the exact same services — zero duplication. - ViewModel over block/template logic.
ViewModel/FeaturedProductData(implementsArgumentInterface) exposes everything the template needs; the.phtmlstays logic-free. - Thin controller.
Controller/Stock/Indexonly resolves the product and returns{ "qty": N }— all logic is delegated to the services. - Multi-Source Inventory. Salable quantity via
GetProductSalableQtyInterface, not the deprecatedCatalogInventory\StockRegistry. - Observer to prevent broken references.
Observer/SyncFeaturedSkukeeps the configured SKU valid when a product is renamed. - Backend-model validation.
Model/Config/Backend/ProductSkuvalidates the SKU on save. - Real-time UI with jsLayout + Knockout. The stock line is a Knockout
uiComponentinitialised throughjsLayout(block argument) +Magento_Ui/js/core/app, following the core minicart pattern. - No theme edits. Styles live in
web/css/source/_module.less(auto-collected by the theme); all markup is in the module.
Module structure
Luza/FeaturedProduct/
├── Api/ # Service contracts (interfaces)
│ ├── FeaturedProductResolverInterface.php
│ └── FeaturedProductStockInterface.php
├── Block/
│ └── FeaturedProduct.php # merges dynamic data into jsLayout
├── Controller/
│ └── Stock/Index.php # thin JSON endpoint for the AJAX poll
├── Model/
│ ├── Config.php # typed, scope-aware config reader
│ ├── Config/Backend/ProductSku.php # validates the SKU on save
│ ├── Config/Source/SelectionType.php # SKU | Product options
│ ├── Config/Source/ProductList.php # product dropdown
│ ├── FeaturedProductResolver.php # resolver implementation
│ └── FeaturedProductStock.php # MSI salable-qty implementation
├── Observer/
│ └── SyncFeaturedSku.php # keeps config in sync on SKU rename
├── ViewModel/
│ └── FeaturedProductData.php # presentation data for the template
├── etc/
│ ├── acl.xml # admin ACL resource
│ ├── di.xml # interface → implementation bindings
│ ├── events.xml # catalog_product_save_after observer
│ ├── module.xml
│ ├── frontend/routes.xml # frontName "featuredproduct"
│ └── adminhtml/{menu.xml, system.xml} # admin menu + config fields
├── i18n/pt_BR.csv # translations
└── view/frontend/
├── layout/cms_index_index.xml # reference block + jsLayout argument
├── templates/featured_product.phtml # card markup + x-magento-init
└── web/
├── css/source/_module.less # styling (auto-collected by the theme)
├── js/view/stock.js # Knockout uiComponent (polling)
└── template/stock.html # Knockout template for the stock line
Technical reference
Config paths
| Path | Getter (Model\Config) |
|---|---|
luza_featured_product/general/enabled |
isEnabled() |
luza_featured_product/general/selection_type |
getSelectionType() |
luza_featured_product/general/product_sku |
getProductSku() |
luza_featured_product/general/product_id |
getProductId() |
luza_featured_product/realtime_stock/enabled |
isRealtimeStockEnabled() |
luza_featured_product/realtime_stock/update_interval |
getStockUpdateInterval() |
AJAX endpoint
GET /featuredproduct/stock → {"qty": <float>}
Returns the salable quantity of the currently configured featured product (0 when none is
resolvable). Route id luza_featuredproduct, frontName featuredproduct.
Knockout component
- Component:
Luza_FeaturedProduct/js/view/stock(extendsuiComponent). - Config (injected via
jsLayout):qty,updateUrl,interval(ms),enabled. - Template:
Luza_FeaturedProduct/stock(web/template/stock.html) — bindstext: qty. - Bootstrapped by
Magento_Ui/js/core/appfrom atext/x-magento-initblock scoped to[data-role=featured-product].
ACL
Luza_FeaturedProduct::config (child of Magento_Config::config) guards the admin section.
Extending
Because the resolution logic is behind service contracts, you can override behaviour without
touching the module — declare a preference in your own module's di.xml:
<preference for="Luza\FeaturedProduct\Api\FeaturedProductResolverInterface" type="Your\Module\Model\CustomResolver"/>
The same applies to FeaturedProductStockInterface (e.g. to change how stock is calculated).
Troubleshooting
- Box not showing — ensure Enabled is on and a valid product/SKU is configured, then
bin/magento cache:flush. - Image shows a placeholder — the base image resize may be missing; run
bin/magento catalog:images:resize. - Style/JS changes not applied (default/production mode) — regenerate static content and
clear
pub/static,var/view_preprocessed, then flush the cache. - Stock not updating — check that Enable Real-Time Update is on and that
GET /featuredproduct/stockreturns JSON.
Author & license
Created by Mateus Junior Monteiro.
Licensed under the MIT License © Mateus Junior Monteiro.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 3
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-05