Goal : Create a /test/index/index route that will load a product listing page with a custom collection.
Expectation : The custom collection printed in listing and the filters must be properly defined from the collection we asked.
Issue : I can get the custom collection in the listing, but the layer navigation is not updated.
Technical requirements :
magento version : 2.4.3
"smile/elasticsuite": "~2.10.13"
Controller code :
/**
* Execute action
*
* @return \Magento\Framework\View\Result\Page
*/
public function execute()
{
// Load the page with the filtered product collection
$resultPage = $this->pageFactory->create();
$resultPage->getConfig()->getTitle()->set((__('Search result')));
return $resultPage;
}
Associated layout
<?xml version="1.0"?>
<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>
<attribute name="class" value="spa-cat" />
<referenceContainer name="sidebar.main">
<block class="Magento\LayeredNavigation\Block\Navigation\Category" name="catalog.leftnav" template="Smile_ElasticsuiteCatalog::layer/view.phtml">
<block class="Magento\LayeredNavigation\Block\Navigation\State" name="catalog.navigation.state" as="state" />
<block class="Smile\ElasticsuiteCatalog\Block\Navigation\FilterRenderer" name="catalog.navigation.renderer" as="renderer">
<block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Attribute" name="catalogsearch.navigation.renderer.attribute" template="Smile_ElasticsuiteCatalog::layer/filter/attribute.phtml" after="-" />
<block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Category" name="catalogsearch.navigation.renderer.category" template="Smile_ElasticsuiteCatalog::layer/filter/default.phtml" />
<block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\Slider" name="catalog.navigation.renderer.slider" template="Smile_ElasticsuiteCatalog::layer/filter/slider.phtml" />
<block class="Smile\ElasticsuiteCatalog\Block\Navigation\Renderer\PriceSlider" name="catalog.navigation.renderer.price.slider" template="Smile_ElasticsuiteCatalog::layer/filter/slider.phtml" />
</block>
</block>
</referenceContainer>
<referenceContainer name="content">
<block class="Sa\CustomCollection\Block\Collection" name="testplate_index_index" template="Sa_CustomCollection::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\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>
</referenceContainer>
</body>
</page>
Relevant part of the product_list template
<?php
/**
* @var Sa\CustomCollection\Block\Collection $block
* @var Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\Collection $_productCollection
*/
$_productCollection = $block->getFilteredProductCollection();
<?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(); ?>
<div class="products wrapper grid products-grid">
<ol class="spa-product-list">
<?php foreach ($_productCollection as $_product) {
Collection Block that actually filter the collection (i've applied a filter on a local field, feel free to update with any other fields)
<?php
namespace Sa\CustomCollection\Block;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Block\Product\ListProduct;
use Magento\Catalog\Helper\Output as OutputHelper;
use Magento\Catalog\Model\Layer\Resolver;
use Magento\Framework\Data\Helper\PostHelper;
use Magento\Framework\Url\Helper\Data;
use Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Fulltext\CollectionFactory as ElasticCollectionFactory;
class Collection extends ListProduct
{
/**
* @var ElasticCollectionFactory
*/
protected $elasticCollectionFactory;
public function __construct(\Magento\Catalog\Block\Product\Context $context, PostHelper $postDataHelper,
Resolver $layerResolver, CategoryRepositoryInterface $categoryRepository,
ElasticCollectionFactory $elasticCollectionFactory,
Data $urlHelper, array $data = [], ?OutputHelper $outputHelper = null)
{
$this->elasticCollectionFactory = $elasticCollectionFactory;
parent::__construct($context, $postDataHelper, $layerResolver, $categoryRepository, $urlHelper, $data, $outputHelper);
}
/**
* Retrieve the filtered product collection
*
* @return \Magento\Catalog\Model\ResourceModel\Product\Collection
*/
public function getFilteredProductCollection()
{
// Get the ElasticSuite product collection
$productCollection = $this->elasticCollectionFactory->create()
->addFieldToSelect('name')
->addFieldToSelect('sku')
->addFieldToSelect('small_image')
->addFieldToSelect('price')
->addFieldToSelect('tws_piece_libelle')
->addFieldToSelect('tws_piece_marque_veh')
->addFieldToSelect('tws_piece_modele_veh')
->addFieldToSelect('tws_piece_annee_veh')
->addFieldToFilter('tws_piece_libelle', ['eq' =>'TRAPPE A CARBURANT'])
->setCurPage(1)
->setPageSize(12);
$dir = "ASC";
$attributeToSort = 'position';
foreach ($this->getRequest()->getParams() as $name => $value){
if($name != "product_list_order" && $name != "product_list_dir"){
$productCollection->addFieldToFilter($name,['eq' => $value]);
}else{
if($name == "product_list_dir"){
$dir = "DESC";
}
if($name == "product_list_order"){
$attributeToSort = $value;
}
}
}
$productCollection->addAttributeToSort($attributeToSort,$dir);
$this->getToolbarBlock()->setCollection($productCollection);
$this->getLayer()->prepareProductCollection($productCollection);
return $productCollection;
}
}
I'm now currently working with a Navigation block which I think is responsible of my issue (i'm not using it so far cause i'm failing).
src/app/code/Sa/CustomCollection/Block/Navigation.php
<?php
namespace Sa\CustomCollection\Block;
class Navigation extends \Smile\ElasticsuiteCatalog\Block\Navigation
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;
/**
* @var \Magento\Framework\Module\Manager
*/
private $moduleManager;
/**
* @var string[]
*/
private $inlineLayouts = ['1column'];
/**
* @var string|NULL
*/
private $pageLayout;
/**
* @var \Magento\Catalog\Helper\Data
*/
protected $helperData;
/**
* @var Collection
*/
protected $customCollection;
/**
* Navigation constructor.
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
* @param \Magento\Catalog\Model\Layer\FilterList $filterList
* @param \Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag
* @param \Magento\Framework\ObjectManagerInterface $objectManager
* @param \Magento\Framework\Module\Manager $moduleManager
* @param \Magento\Catalog\Helper\Data $helperData
* @param array $data
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
\Magento\Catalog\Model\Layer\Resolver $layerResolver,
\Magento\Catalog\Model\Layer\FilterList $filterList,
\Magento\Catalog\Model\Layer\AvailabilityFlagInterface $visibilityFlag,
\Magento\Framework\ObjectManagerInterface $objectManager,
\Magento\Framework\Module\Manager $moduleManager,
\Magento\Catalog\Helper\Data $helperData,
Collection $customCollection,
array $data
)
{
parent::__construct($context, $layerResolver, $filterList, $visibilityFlag, $objectManager, $moduleManager, $data);
$this->pageLayout = $context->getPageConfig()->getPageLayout() ?: $this->getLayout()->getUpdate()->getPageLayout();
$this->objectManager = $objectManager;
$this->moduleManager = $moduleManager;
$this->helperData = $helperData;
$this->customCollection = $customCollection;
$customCollectionFiltered = $customCollection->getFilteredProductCollection();
//$before = $this->_catalogLayer->getProductCollection();
//var_dump("BEFORE: ".count($before));
$this->_catalogLayer->prepareProductCollection($customCollectionFiltered);
//$after = $this->_catalogLayer->getProductCollection();
//var_dump("AFTER:" . count($after));
//$this->getLayer()->prepareProductCollection($customCollectionFiltered);
}
/**
* Check if we can show this block.
* According to @return bool
* @see \Magento\LayeredNavigationStaging\Block\Navigation::canShowBlock
* We should not show the block if staging is enabled and if we are currently previewing the results.
*
*/
public function canShowBlock()
{
$canShowBlock = parent::canShowBlock();
if ($this->moduleManager->isEnabled('Magento_Staging')) {
try {
$versionManager = $this->objectManager->get('\Magento\Staging\Model\VersionManager');
$canShowBlock = $canShowBlock && !$versionManager->isPreviewVersion();
} catch (\Exception $exception) {
;
}
}
if ($this->getLayer() instanceof \Magento\Catalog\Model\Layer\Category &&
$this->getLayer()->getCurrentCategory()->getDisplayMode() === \Magento\Catalog\Model\Category::DM_PAGE) {
$canShowBlock = false;
}
return $canShowBlock;
}
/**
* Return index of the facets that are expanded for the current page :
*
* - nth first facets (depending of config)
* - facets with at least one selected filter
*
* @return string
*/
public function getActiveFilters()
{
$activeFilters = [];
var_dump("SURCHARGE");
if (!$this->isInline()) {
$requestParams = array_keys($this->getRequest()->getParams());
$displayedFilters = $this->getDisplayedFilters();
$expandedFacets = $this->_scopeConfig->getValue(self::DEFAULT_EXPANDED_FACETS_COUNT_CONFIG_XML_PATH);
$activeFilters = [];
if ($expandedFacets > 0) {
$activeFilters = range(0, min(count($displayedFilters), $expandedFacets) - 1);
}
foreach ($displayedFilters as $index => $filter) {
if (in_array($filter->getRequestVar(), $requestParams)) {
$activeFilters[] = $index;
}
}
}
return json_encode($activeFilters);
}
/**
* Returns facet that are displayed.
*
* @return array
*/
public function getDisplayedFilters()
{
$displayedFilters = array_filter(
$this->getFilters(),
function ($filter) {
return $filter->getItemsCount() > 0;
}
);
return array_values($displayedFilters);
}
/**
* Indicates if the block is displayed inline or not.
*
* @return boolean
*/
public function isInline()
{
return in_array($this->pageLayout, $this->inlineLayouts);
}
/**
* @return string
*/
public function getCurrentCategory()
{
return is_null($this->helperData->getCategory()) ? '' : $this->helperData->getCategory()->getName();
}
}
As you can see, the products printed are good. But the counting of categories (and all other filterable attributes) is false and not updated to use the custom collection we are providing).
That being said the filtering process is working properly, cause if i go from this page result to apply a filter then the product listing is properly filtered
But as you can see even after that, the filters counting and data printed are not coherent as i should only have 24 result max in counting of each filterable attribute.
Obviously if i was passing the attribute i filtered in the collection block, the result of both printed collection and layer navigation would be fine; but this is not something i want to do as i need to filter by so many parameter that it triggers an html error due to url being too long. So it has to be with a custom collection block.
I'm also expecting not to have the attribute filtered in collection block appearing as an active filter it has to be hidden from customer side. Otherwise i will have a thousand filter printed may be. Thats not what we want.
Basically, the behaviour should be the same as a category listing. When you filter on a category...the category is not printed as an active filter but still it's there. So i'm looking to replicate that behaviour. It looks simple, but it really isn't.
If in my layout I replace with this line
<block class="Sa\CustomCollection\Block\Navigation" name="catalog.leftnav" template="Smile_ElasticsuiteCatalog::layer/view.phtml">
Then this is the error i'm getting
Error: Cannot instantiate interface Magento\Catalog\Model\Layer\FilterableAttributeListInterface in /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php:50 Stack trace: #0 /var/www/magento/vendor/magento/framework/ObjectManager/ObjectManager.php(70): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() #1 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(170): Magento\Framework\ObjectManager\ObjectManager->get() #2 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(276): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgument() #3 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(239): Magento\Framework\ObjectManager\Factory\AbstractFactory->getResolvedArgument() #4 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(34): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgumentsInRuntime() #5 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(59): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->_resolveArguments() #6 /var/www/magento/vendor/magento/framework/ObjectManager/ObjectManager.php(70): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() #7 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(170): Magento\Framework\ObjectManager\ObjectManager->get() #8 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(276): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgument() #9 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(239): Magento\Framework\ObjectManager\Factory\AbstractFactory->getResolvedArgument() #10 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(34): Magento\Framework\ObjectManager\Factory\AbstractFactory->resolveArgumentsInRuntime() #11 /var/www/magento/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(59): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->_resolveArguments() #12 /var/www/magento/vendor/magento/framework/ObjectManager/ObjectManager.php(56): Magento\Framework\ObjectManager\Factory\Dynamic\Developer->create() #13 /var/www/magento/vendor/magento/framework/View/Element/BlockFactory.php(46): Magento\Framework\ObjectManager\ObjectManager->create() #14 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(272): Magento\Framework\View\Element\BlockFactory->createBlock() #15 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(252): Magento\Framework\View\Layout\Generator\Block->getBlockInstance() #16 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(229): Magento\Framework\View\Layout\Generator\Block->createBlock() #17 /var/www/magento/vendor/magento/framework/View/Layout/Generator/Block.php(134): Magento\Framework\View\Layout\Generator\Block->generateBlock() #18 /var/www/magento/vendor/magento/framework/View/Layout/GeneratorPool.php(93): Magento\Framework\View\Layout\Generator\Block->process() #19 /var/www/magento/vendor/magento/framework/View/Layout.php(365): Magento\Framework\View\Layout\GeneratorPool->process() #20 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\View\Layout->generateElements() #21 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\View\Layout\Interceptor->___callParent() #22 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Framework\View\Layout\Interceptor->Magento\Framework\Interception{closure}() #23 /var/www/magento/generated/code/Magento/Framework/View/Layout/Interceptor.php(68): Magento\Framework\View\Layout\Interceptor->___callPlugins() #24 /var/www/magento/vendor/magento/framework/View/Layout/Builder.php(129): Magento\Framework\View\Layout\Interceptor->generateElements() #25 /var/www/magento/vendor/magento/framework/View/Page/Builder.php(65): Magento\Framework\View\Layout\Builder->generateLayoutBlocks() #26 /var/www/magento/vendor/magento/framework/View/Layout/Builder.php(65): Magento\Framework\View\Page\Builder->generateLayoutBlocks() #27 /var/www/magento/vendor/magento/framework/View/Page/Config.php(224): Magento\Framework\View\Layout\Builder->build() #28 /var/www/magento/vendor/magento/framework/View/Page/Config.php(247): Magento\Framework\View\Page\Config->build() #29 /var/www/magento/app/code/Sa/CustomCollection/Controller/Index/Index.php(58): Magento\Framework\View\Page\Config->getTitle() #30 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Sa\CustomCollection\Controller\Index\Index->execute() #31 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callParent() #32 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Sa\CustomCollection\Controller\Index\Index\Interceptor->Magento\Framework\Interception{closure}() #33 /var/www/magento/generated/code/Sa/CustomCollection/Controller/Index/Index/Interceptor.php(23): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callPlugins() #34 /var/www/magento/vendor/magento/framework/App/Action/Action.php(111): Sa\CustomCollection\Controller\Index\Index\Interceptor->execute() #35 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\App\Action\Action->dispatch() #36 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callParent() #37 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Sa\CustomCollection\Controller\Index\Index\Interceptor->Magento\Framework\Interception{closure}() #38 /var/www/magento/generated/code/Sa/CustomCollection/Controller/Index/Index/Interceptor.php(32): Sa\CustomCollection\Controller\Index\Index\Interceptor->___callPlugins() #39 /var/www/magento/vendor/magento/framework/App/FrontController.php(245): Sa\CustomCollection\Controller\Index\Index\Interceptor->dispatch() #40 /var/www/magento/vendor/magento/framework/App/FrontController.php(212): Magento\Framework\App\FrontController->getActionResponse() #41 /var/www/magento/vendor/magento/framework/App/FrontController.php(147): Magento\Framework\App\FrontController->processRequest() #42 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(58): Magento\Framework\App\FrontController->dispatch() #43 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(138): Magento\Framework\App\FrontController\Interceptor->___callParent() #44 /var/www/magento/vendor/magento/module-store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception{closure}() #45 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(135): Magento\Store\App\FrontController\Plugin\RequestPreprocessor->aroundDispatch() #46 /var/www/magento/vendor/magento/module-page-cache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception{closure}() #47 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(135): Magento\PageCache\Model\App\FrontController\BuiltinPlugin->aroundDispatch() #48 /var/www/magento/vendor/magento/framework/Interception/Interceptor.php(153): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception{closure}() #49 /var/www/magento/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\Framework\App\FrontController\Interceptor->___callPlugins() #50 /var/www/magento/vendor/magento/framework/App/Http.php(116): Magento\Framework\App\FrontController\Interceptor->dispatch() #51 /var/www/magento/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\Framework\App\Http->launch() #52 /var/www/magento/vendor/magento/framework/App/Bootstrap.php(264): Magento\Framework\App\Http\Interceptor->launch() #53 /var/www/magento/pub/index.php(29): Magento\Framework\App\Bootstrap->run() #54 {main}
Navigation is defined as follow in di.xml too
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<virtualType name="Magento\LayeredNavigation\Block\Navigation\Category" type="Sa\CustomCollection\Block\Navigation">
<arguments>
<argument name="filterList" xsi:type="object">categoryFilterList</argument>
</arguments>
</virtualType>
<virtualType name="Magento\LayeredNavigation\Block\Navigation\Search" type="Sa\CustomCollection\Block\Navigation">
<arguments>
<argument name="filterList" xsi:type="object">searchFilterList</argument>
</arguments>
</virtualType>
<virtualType name="Magento\LayeredNavigationStaging\Block\Navigation\Category" type="Sa\CustomCollection\Block\Navigation">
<arguments>
<argument name="filterList" xsi:type="object">categoryFilterList</argument>
</arguments>
</virtualType>
<virtualType name="Magento\LayeredNavigationStaging\Block\Navigation\Search" type="Sa\CustomCollection\Block\Navigation">
<arguments>
<argument name="filterList" xsi:type="object">searchFilterList</argument>
</arguments>
</virtualType>
</config>