I've created a module that modifies list.phtml
to eventually place some data under the price in product category listing. I'm using Magento 2.4.1.
Module can be downloaded here: https://www.dropbox.com/s/g24ut8cwp11esle/payment-plan.zip?dl=0
The module loads, but when it does I am getting the following error when trying to view category pages:
1 exception(s):
Exception #0 (ReflectionException): Class Magento\Catalog\ViewModel\Product\OptionsData does not exist
Exception #0 (ReflectionException): Class Magento\Catalog\ViewModel\Product\OptionsData does not exist
<pre>#1 Magento\Framework\Code\Reader\ClassReader->getConstructor() called at [vendor/magento/framework/ObjectManager/Definition/Runtime.php:54]
#2 Magento\Framework\ObjectManager\Definition\Runtime->getParameters() called at [vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:48]
#3 Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() called at [vendor/magento/framework/ObjectManager/ObjectManager.php:70]
#4 Magento\Framework\ObjectManager\ObjectManager->get() called at [vendor/magento/framework/View/Layout/Argument/Interpreter/DataObject.php:61]
#5 Magento\Framework\View\Layout\Argument\Interpreter\DataObject->evaluate() called at [vendor/magento/framework/Data/Argument/Interpreter/Composite.php:61]
#6 Magento\Framework\Data\Argument\Interpreter\Composite->evaluate() called at [vendor/magento/framework/View/Layout/Argument/Interpreter/Decorator/Updater.php:47]
#7 Magento\Framework\View\Layout\Argument\Interpreter\Decorator\Updater->evaluate() called at [vendor/magento/framework/View/Layout/Generator/Block.php:320]
#8 Magento\Framework\View\Layout\Generator\Block->evaluateArguments() called at [vendor/magento/framework/View/Layout/Generator/Block.php:229]
#9 Magento\Framework\View\Layout\Generator\Block->generateBlock() called at [vendor/magento/framework/View/Layout/Generator/Block.php:134]
#10 Magento\Framework\View\Layout\Generator\Block->process() called at [vendor/magento/framework/View/Layout/GeneratorPool.php:93]
#11 Magento\Framework\View\Layout\GeneratorPool->process() called at [vendor/magento/framework/View/Layout.php:365]
#12 Magento\Framework\View\Layout->generateElements() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#13 Magento\Framework\View\Layout\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#14 Magento\Framework\View\Layout\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#15 Magento\Framework\View\Layout\Interceptor->___callPlugins() called at [generated/code/Magento/Framework/View/Layout/Interceptor.php:68]
#16 Magento\Framework\View\Layout\Interceptor->generateElements() called at [vendor/magento/framework/View/Layout/Builder.php:129]
#17 Magento\Framework\View\Layout\Builder->generateLayoutBlocks() called at [vendor/magento/framework/View/Page/Builder.php:65]
#18 Magento\Framework\View\Page\Builder->generateLayoutBlocks() called at [vendor/magento/framework/View/Layout/Builder.php:65]
#19 Magento\Framework\View\Layout\Builder->build() called at [vendor/magento/framework/View/Page/Config.php:224]
#20 Magento\Framework\View\Page\Config->build() called at [vendor/magento/framework/View/Page/Config.php:587]
#21 Magento\Framework\View\Page\Config->getElementAttribute() called at [vendor/magento/framework/View/Page/Config.php:545]
#22 Magento\Framework\View\Page\Config->addBodyClass() called at [vendor/magento/module-catalog/Controller/Category/View.php:249]
#23 Magento\Catalog\Controller\Category\View->execute() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#24 Magento\Catalog\Controller\Category\View\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#25 Magento\Catalog\Controller\Category\View\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/App/Action/Plugin/ActionFlagNoDispatchPlugin.php:51]
#26 Magento\Framework\App\Action\Plugin\ActionFlagNoDispatchPlugin->aroundExecute() called at [vendor/magento/framework/Interception/Interceptor.php:135]
#27 Magento\Catalog\Controller\Category\View\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#28 Magento\Catalog\Controller\Category\View\Interceptor->___callPlugins() called at [generated/code/Magento/Catalog/Controller/Category/View/Interceptor.php:23]
#29 Magento\Catalog\Controller\Category\View\Interceptor->execute() called at [vendor/magento/framework/App/Action/Action.php:111]
#30 Magento\Framework\App\Action\Action->dispatch() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#31 Magento\Catalog\Controller\Category\View\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#32 Magento\Catalog\Controller\Category\View\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#33 Magento\Catalog\Controller\Category\View\Interceptor->___callPlugins() called at [generated/code/Magento/Catalog/Controller/Category/View/Interceptor.php:32]
#34 Magento\Catalog\Controller\Category\View\Interceptor->dispatch() called at [vendor/magento/framework/App/FrontController.php:186]
#35 Magento\Framework\App\FrontController->processRequest() called at [vendor/magento/framework/App/FrontController.php:118]
#36 Magento\Framework\App\FrontController->dispatch() called at [vendor/magento/framework/Interception/Interceptor.php:58]
#37 Magento\Framework\App\FrontController\Interceptor->___callParent() called at [vendor/magento/framework/Interception/Interceptor.php:138]
#38 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/module-store/App/FrontController/Plugin/RequestPreprocessor.php:99]
#39 Magento\Store\App\FrontController\Plugin\RequestPreprocessor->aroundDispatch() called at [vendor/magento/framework/Interception/Interceptor.php:135]
#40 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/module-page-cache/Model/App/FrontController/BuiltinPlugin.php:75]
#41 Magento\PageCache\Model\App\FrontController\BuiltinPlugin->aroundDispatch() called at [vendor/magento/framework/Interception/Interceptor.php:135]
#42 Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}() called at [vendor/magento/framework/Interception/Interceptor.php:153]
#43 Magento\Framework\App\FrontController\Interceptor->___callPlugins() called at [generated/code/Magento/Framework/App/FrontController/Interceptor.php:23]
#44 Magento\Framework\App\FrontController\Interceptor->dispatch() called at [vendor/magento/framework/App/Http.php:116]
#45 Magento\Framework\App\Http->launch() called at [generated/code/Magento/Framework/App/Http/Interceptor.php:23]
#46 Magento\Framework\App\Http\Interceptor->launch() called at [vendor/magento/framework/App/Bootstrap.php:263]
#47 Magento\Framework\App\Bootstrap->run() called at [pub/index.php:29]
</pre>
app/code/jr2/payment-plan/etc/module.xml
:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="JR2_PaymentPlan"/>
</config>
app/code/jr2/payment-plan/view/frontend/layout/catalog_category_view.xml
(this data is copied as is from Magento/Catalog/view/frontend/layout/catalog_category_view.xml
:
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="columns.top">
<container name="category.view.container" htmlTag="div" htmlClass="category-view" after="-">
<block class="Magento\Catalog\Block\Category\View" name="category.image" template="Magento_Catalog::category/image.phtml">
<arguments>
<argument name="image" xsi:type="object">Magento\Catalog\ViewModel\Category\Image</argument>
<argument name="output" xsi:type="object">Magento\Catalog\ViewModel\Category\Output</argument>
</arguments>
</block>
<block class="Magento\Catalog\Block\Category\View" name="category.description" template="Magento_Catalog::category/description.phtml"/>
<block class="Magento\Catalog\Block\Category\View" name="category.cms" template="Magento_Catalog::category/cms.phtml"/>
</container>
</referenceContainer>
<referenceContainer name="content">
<block class="Magento\Catalog\Block\Category\View" name="category.products" template="Magento_Catalog::category/products.phtml">
<block class="Magento\Catalog\Block\Product\ListProduct" name="category.products.list" as="product_list" template="Magento_Catalog::product/list.phtml">
<container name="category.product.list.additional" as="additional" />
<block class="Magento\Framework\View\Element\RendererList" name="category.product.type.details.renderers" as="details.renderers">
<block class="Magento\Framework\View\Element\Template" name="category.product.type.details.renderers.default" as="default"/>
</block>
<block class="Magento\Catalog\Block\Product\ProductList\Item\Container" name="category.product.addto" as="addto">
<block class="Magento\Catalog\Block\Product\ProductList\Item\AddTo\Compare"
name="category.product.addto.compare" as="compare"
template="Magento_Catalog::product/list/addto/compare.phtml"/>
</block>
<block class="Magento\Catalog\Block\Product\ProductList\Toolbar" name="product_list_toolbar" template="Magento_Catalog::product/list/toolbar.phtml">
<block class="Magento\Theme\Block\Html\Pager" name="product_list_toolbar_pager"/>
</block>
<action method="setToolbarBlockName">
<argument name="name" xsi:type="string">product_list_toolbar</argument>
</action>
</block>
</block>
<block class="Magento\Cookie\Block\RequireCookie" name="require-cookie" template="Magento_Cookie::require_cookie.phtml">
<arguments>
<argument name="triggers" xsi:type="array">
<item name="compareProductLink" xsi:type="string">.action.tocompare</item>
</argument>
</arguments>
</block>
</referenceContainer>
<referenceBlock name="page.main.title">
<arguments>
<argument name="id" xsi:type="string">page-title-heading</argument>
<argument name="add_base_attribute_aria" xsi:type="string">page-title-heading toolbar-amount</argument>
</arguments>
<block class="Magento\Catalog\Block\Category\Rss\Link" name="rss.link" template="Magento_Catalog::category/rss.phtml"/>
</referenceBlock>
<referenceBlock name="category.products.list">
<arguments>
<argument name="viewModel" xsi:type="object">Magento\Catalog\ViewModel\Product\OptionsData</argument>
</arguments>
</referenceBlock>
</body>
</page>
And finally app/code/jr2/payment-plan/view/templates/product/list.phtml
(again, this is copied as is from Magento/Catalog/view/frontend/templates/product/list.phtml
:
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
use Magento\Framework\App\Action\Action;
?>
<?php
/**
* Product list template
*
* @var $block \Magento\Catalog\Block\Product\ListProduct
* @var \Magento\Framework\Escaper $escaper
* @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer
*/
?>
<?php
$_productCollection = $block->getLoadedProductCollection();
/** @var \Magento\Catalog\Helper\Output $_helper */
$_helper = $block->getData('outputHelper');
?>
<?php if (!$_productCollection->count()): ?>
<div class="message info empty">
<div><?= $escaper->escapeHtml(__('We can\'t find products matching the selection.')) ?></div>
</div>
<?php else: ?>
<?= $block->getToolbarHtml() ?>
<?= $block->getAdditionalHtml() ?>
<?php
if ($block->getMode() === 'grid') {
$viewMode = 'grid';
$imageDisplayArea = 'category_page_grid';
$showDescription = false;
$templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW;
} else {
$viewMode = 'list';
$imageDisplayArea = 'category_page_list';
$showDescription = true;
$templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW;
}
/**
* Position for actions regarding image size changing in vde if needed
*/
$pos = $block->getPositioned();
?>
<div class="products wrapper <?= /* @noEscape */ $viewMode ?> products-<?= /* @noEscape */ $viewMode ?>">
<ol class="products list items product-items">
<?php /** @var $_product \Magento\Catalog\Model\Product */ ?>
<?php foreach ($_productCollection as $_product): ?>
<li class="item product product-item">
<div class="product-item-info"
id="product-item-info_<?= /* @noEscape */ $_product->getId() ?>"
data-container="product-<?= /* @noEscape */ $viewMode ?>">
<?php
$productImage = $block->getImage($_product, $imageDisplayArea);
if ($pos != null) {
$position = 'left:' . $productImage->getWidth() . 'px;'
. 'top:' . $productImage->getHeight() . 'px;';
}
?>
<?php // Product Image ?>
<a href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>"
class="product photo product-item-photo"
tabindex="-1">
<?= $productImage->toHtml() ?>
</a>
<div class="product details product-item-details">
<?php $_productNameStripped = $block->stripTags($_product->getName(), null, true); ?>
<strong class="product name product-item-name">
<a class="product-item-link"
href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>">
<?=/* @noEscape */ $_helper->productAttribute($_product, $_product->getName(), 'name')?>
</a>
</strong>
<?= $block->getReviewsSummaryHtml($_product, $templateType) ?>
<?= /* @noEscape */ $block->getProductPrice($_product) ?>
<?= $block->getProductDetailsHtml($_product) ?>
<div class="product-item-inner">
<div class="product actions product-item-actions">
<div class="actions-primary">
<?php if ($_product->isSaleable()):?>
<?php $postParams = $block->getAddToCartPostParams($_product); ?>
<form data-role="tocart-form"
data-product-sku="<?= $escaper->escapeHtml($_product->getSku()) ?>"
action="<?= $escaper->escapeUrl($postParams['action']) ?>"
method="post">
<?php $optionsData = $block->getData('viewModel')->getOptionsData($_product); ?>
<?php foreach ($optionsData as $optionItem): ?>
<input type="hidden"
name="<?= $escaper->escapeHtml($optionItem['name']) ?>"
value="<?= $escaper->escapeHtml($optionItem['value']) ?>">
<?php endforeach; ?>
<input type="hidden"
name="product"
value="<?= /* @noEscape */ $postParams['data']['product'] ?>">
<input type="hidden"
name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>"
value="<?=
/* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED]
?>">
<?= $block->getBlockHtml('formkey') ?>
<button type="submit"
title="<?= $escaper->escapeHtmlAttr(__('Add to Cart')) ?>"
class="action tocart primary"
disabled>
<span><?= $escaper->escapeHtml(__('Add to Cart')) ?></span>
</button>
</form>
<?php else:?>
<?php if ($_product->isAvailable()):?>
<div class="stock available">
<span><?= $escaper->escapeHtml(__('In stock')) ?></span></div>
<?php else:?>
<div class="stock unavailable">
<span><?= $escaper->escapeHtml(__('Out of stock')) ?></span></div>
<?php endif; ?>
<?php endif; ?>
</div>
<?= strpos($pos, $viewMode . '-primary') ?
/* @noEscape */ $secureRenderer->renderStyleAsTag(
$position,
'product-item-info_' . $_product->getId() . ' div.actions-primary'
) : '' ?>
<div data-role="add-to-links" class="actions-secondary">
<?php if ($addToBlock = $block->getChildBlock('addto')): ?>
<?= $addToBlock->setProduct($_product)->getChildHtml() ?>
<?php endif; ?>
</div>
<?= strpos($pos, $viewMode . '-secondary') ?
/* @noEscape */ $secureRenderer->renderStyleAsTag(
$position,
'product-item-info_' . $_product->getId() . ' div.actions-secondary'
) : '' ?>
</div>
<?php if ($showDescription): ?>
<div class="product description product-item-description">
<?= /* @noEscape */ $_helper->productAttribute(
$_product,
$_product->getShortDescription(),
'short_description'
) ?>
<a href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>"
title="<?= /* @noEscape */ $_productNameStripped ?>"
class="action more"><?= $escaper->escapeHtml(__('Learn More')) ?></a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?= strpos($pos, $viewMode . '-actions') ?
/* @noEscape */ $secureRenderer->renderStyleAsTag(
$position,
'product-item-info_' . $_product->getId() . ' div.product-item-actions'
) : '' ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<?= $block->getChildBlock('toolbar')->setIsBottom(true)->toHtml() ?>
<script type="text/x-magento-init">
{
"[data-role=tocart-form], .form.map.checkout": {
"catalogAddToCart": {
"product_sku": "<?= $escaper->escapeJs($_product->getSku()) ?>"
}
}
}
</script>
<?php endif; ?>
I've tried:
- Overriding
list.html
in a them without overridingcatalog_category_view.xml
and just overriding thelist.phtml
. This causes the same problem.
Can anybody help me figure out what is causing this?
Edit: Following the suggestion in the comment with a link to another question I replace the contents of catalog_category_view.xml
with the following:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="category.products.list" template="JR2_PaymentPlan::product/list.phtml"/>
</body>
</page>
This is what I see on the frontend:
list.html
is. It's a core in Magento module:Magento/Catalog/view/frontend/templates/product/list.phtml
. I've also edited the question to show what I see when I add<referenceBlock name="category.products.list" template="JR2_PaymentPlan::product/list.phtml"/>
.