Skip to content

Display minimal configurable product special price cause Error : Call to undefined method ReflectionUnionType::getName() on category page

I use Magento version 2.4.5-p10

I want show on product card on category page (list and grid) the minimal price for a configurable product when this price has special price.

The objective is to show the mention : %special price% Instead of %Original price%

In file src/app/code/Custom/Catalog/view/frontend/templates/product/list.phtml at line 303 I have call to <?= /* @noEscape */ $block->getProductPrice($product); ?>


This call use src/vendor/magento/module-catalog/Block/Product/ListProduct.php

/**
     * Get product price.
     *
     * @param Product $product
     * @return string
     */
    public function getProductPrice(Product $product)
    {
        $priceRender = $this->getPriceRender();

        $price = '';
        if ($priceRender) {
            $price = $priceRender->render(
                FinalPrice::PRICE_CODE,
                $product,
                [
                    'include_container' => true,
                    'display_minimal_price' => true,
                    'zone' => Render::ZONE_ITEM_LIST,
                    'list_category_page' => true
                ]
            );
        }

        return $price;
    }

src/app/code/Custom/ConfigurableProduct/view/frontend/templates/product/price/final_price.phtml

<?php if($block->hasSpecialPrice()): ?>
    <span class="old-price">
    <?= /* @noEscape */ $block->renderAmount(
        $block->getMinimalChildPriceAMount(),
        $block->getOldPriceArguments()
    ); ?>
    </span>
<?php endif;  ?>

$block is src/app/code/Custom/ConfigurableProduct/Pricing/Render/FinalPriceBox.php

In src/app/code/Custom/ConfigurableProduct/Pricing/Render/FinalPriceBox.php, I have


<?php

/**
 * Class FinalPriceBox.
 *
 */

declare(strict_types=1);

namespace CustomConfigurableProductPricingRender;

use CustomCatalogApiPricingRenderFinalPriceBoxEcoInterface;
use CustomCatalogHelperPricingRenderEcoPriceHelper;
use CustomCustomerHelperStoreHelper;
use MagentoCatalogModelProductPricingRendererSalableResolverInterface;
use MagentoCatalogPricingPriceFinalPrice;
use MagentoCatalogPricingPriceMinimalPriceCalculatorInterface;
use MagentoCatalogPricingPriceRegularPrice;
use MagentoConfigurableProductPricingPriceConfigurableOptionsProviderInterface;
use MagentoConfigurableProductPricingRenderFinalPriceBox as MagentoFinalPriceBox;
use MagentoFrameworkExceptionNoSuchEntityException;
use MagentoFrameworkPhrase;
use MagentoFrameworkPricingAmountBase;
use MagentoFrameworkPricingPricePriceInterface;
use MagentoFrameworkPricingRenderRendererPool;
use MagentoFrameworkPricingSaleableInterface;
use MagentoFrameworkViewElementTemplateContext;
use MagentoTaxApiTaxCalculationInterface;

/**
 * Configurable product final price box.
 *
 * CustomConfigurableProductPricingRender
 */
class FinalPriceBox extends MagentoFinalPriceBox implements FinalPriceBoxEcoInterface
{
    /**
     * @var EcoPriceHelper
     */
    protected $ecoPriceHelper;

    /**
     * @var ConfigurableOptionsProviderInterface
     */
    private $configurableOptionsProvider;

    /**
     * @var MagentoTaxApiTaxCalculationInterface
     */
    private $TaxCalculationInterface;

    /**
     * @var StoreHelper
     */
    protected $storeHelper;

    /**
     * Configurable product final price box block constructor.
     *
     * @param Context                              $context
     * @param SaleableInterface                    $saleableItem
     * @param PriceInterface                       $price
     * @param RendererPool                         $rendererPool
     * @param SalableResolverInterface             $salableResolver
     * @param MinimalPriceCalculatorInterface      $minimalPriceCalculator
     * @param ConfigurableOptionsProviderInterface $configurableOptionsProvider
     * @param EcoPriceHelper                       $ecoPriceHelper
     * @param TaxCalculationInterface              $TaxCalculationInterface
     * @param StoreHelper                          $storeHelper
     * @param array                                $data
     */
    public function __construct(
        Context $context,
        SaleableInterface $saleableItem,
        PriceInterface $price,
        RendererPool $rendererPool,
        SalableResolverInterface $salableResolver,
        MinimalPriceCalculatorInterface $minimalPriceCalculator,
        ConfigurableOptionsProviderInterface $configurableOptionsProvider,
        EcoPriceHelper $ecoPriceHelper,
        TaxCalculationInterface $TaxCalculationInterface,
        storeHelper $storeHelper,
        array $data = []
    ) {
        parent::__construct(
            $context,
            $saleableItem,
            $price,
            $rendererPool,
            $salableResolver,
            $minimalPriceCalculator,
            $configurableOptionsProvider,
            $data
        );

        $this->ecoPriceHelper = $ecoPriceHelper;
        $this->configurableOptionsProvider = $configurableOptionsProvider;
        $this->TaxCalculationInterface = $TaxCalculationInterface;
        $this->storeHelper = $storeHelper;
    }

    /**
     * @inheritDoc
     */
    public function isVisibleEcoPrice(): bool
    {
        return $this->ecoPriceHelper->isVisibleEcoPrice($this->saleableItem->getId(), $this->getZone());
    }

    /**
     * @inheritDoc
     *
     * @throws NoSuchEntityException
     */
    public function getEcoText(bool $oldPriceExists = false): Phrase
    {
        if ($oldPriceExists) {
            return $this->ecoPriceHelper->getEcoTextForOldPrice($this->saleableItem->getId());
        }

        return $this->ecoPriceHelper->getEcoText($this->saleableItem->getId());
    }

    /**
     * Returns true if it is TS store.
     *
     * @return bool
     */
    public function isTs(): bool
    {
        try {
            $result = $this->storeHelper->isTs();
        } catch (NoSuchEntityException $exception) {
            return false;
        }

        return $result;
    }

    /**
     * Returns old price arguments.
     *
     * @return array
     */
    public function getOldPriceArguments(): array
    {
        $idSuffix = $this->getIdSuffix() ?: '';
        $arguments = [
            'display_label'     => __('Instead of'),
            'price_id'          => $this->getPriceId('old-price-' . $idSuffix),
            'price_type'        => 'oldPrice',
            'include_container' => true,
        ];

        if (!$this->isTs()) {
            $arguments['skip_adjustments'] = true;
        }

        return $arguments;
    }

    /**
     * Return Minimal Child Price Amount
     *
     * @return Base|bool
     */
    public function getMinimalChildPriceAMount() : Base|bool
    {
        $prices = $this->getChildPrices();
        /** filter cheapest child product */
        $cheapestChildId = array_reduce(array_keys($prices), function ($prev, $id) use ($prices) {
            return ($prev === null || $prices[$id]['regular_price'] < $prices[$prev]['regular_price']) ? $id : $prev;
        }, null);

        /** Valid only if the cheapest child (which serves as call price) is on promotion */
        $regularPrice = $prices[$cheapestChildId][RegularPrice::PRICE_CODE];
        $finalPrice = $prices[$cheapestChildId][FinalPrice::PRICE_CODE];
        if ($finalPrice < $regularPrice) {
            $amount = new Base(
                $finalPrice,
                [
                    'tax' => ($finalPrice * $this->getTaxRate())
                ]
            );

            return $amount;
        }
        return false;
    }

    /**
     * Return [subproduct_sku => [regular_price, final_price]] for the current configurable parent product
     */
    private function getChildPrices(): array
    {
        $product = $this->getSaleableItem();
        $prices = [];

        foreach ($this->configurableOptionsProvider->getProducts($product) as $subProduct) {
            $regularPrice = $subProduct->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE)->getValue();
            $finalPrice = $subProduct->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue();
            $prices[$subProduct->getSku()] =
                [
                    RegularPrice::PRICE_CODE => $regularPrice,
                    FinalPrice::PRICE_CODE => $finalPrice
                ];
        }
        return $prices;
    }

    /**
     * Return Tax Rate for the current product
     *
     * @return float
     */
    private function getTaxRate(): float
    {
        return $this->TaxCalculationInterface->getCalculatedRate(
            $this->getSaleableItem()->getTaxClassId()
        );
    }
}

In frontend https://custom.local/store_code/path/to/category/with/configurable-products.html (after setup:upgrade) I’m block by the error

Error: Call to undefined method ReflectionUnionType::getName() in /var/www/html/vendor/magento/framework/Interception/Code/Generator/Interceptor.php:216
Stack trace:
#0 /var/www/html/vendor/magento/framework/Interception/Code/Generator/Interceptor.php(105): MagentoFrameworkInterceptionCodeGeneratorInterceptor->getReturnTypeValue(Object(ReflectionMethod))
#1 /var/www/html/vendor/magento/framework/Interception/Code/Generator/Interceptor.php(76): MagentoFrameworkInterceptionCodeGeneratorInterceptor->_getMethodInfo(Object(ReflectionMethod))
#2 /var/www/html/vendor/magento/framework/Code/Generator/EntityAbstract.php(228): MagentoFrameworkInterceptionCodeGeneratorInterceptor->_getClassMethods()
#3 /var/www/html/vendor/magento/framework/Interception/Code/Generator/Interceptor.php(177): MagentoFrameworkCodeGeneratorEntityAbstract->_generateCode()
#4 /var/www/html/vendor/magento/framework/Code/Generator/EntityAbstract.php(108): MagentoFrameworkInterceptionCodeGeneratorInterceptor->_generateCode()
#5 /var/www/html/vendor/magento/framework/Code/Generator.php(130): MagentoFrameworkCodeGeneratorEntityAbstract->generate()
#6 /var/www/html/vendor/magento/framework/Code/Generator/Autoloader.php(53): MagentoFrameworkCodeGenerator->generateClass('Custom\Configur...')
#7 [internal function]: MagentoFrameworkCodeGeneratorAutoloader->load('Custom\Configur...')
#8 /var/www/html/vendor/magento/framework/Code/Reader/ClassReader.php(34): ReflectionClass->__construct('Custom\Configur...')
#9 /var/www/html/vendor/magento/framework/ObjectManager/Definition/Runtime.php(54): MagentoFrameworkCodeReaderClassReader->getConstructor('Custom\Configur...')
#10 /var/www/html/vendor/magento/framework/ObjectManager/Factory/Dynamic/Developer.php(48): MagentoFrameworkObjectManagerDefinitionRuntime->getParameters('Custom\Configur...')
#11 /var/www/html/vendor/magento/framework/ObjectManager/ObjectManager.php(56): MagentoFrameworkObjectManagerFactoryDynamicDeveloper->create('Custom\Configur...', Array)
#12 /var/www/html/vendor/magento/framework/View/Element/BlockFactory.php(44): MagentoFrameworkObjectManagerObjectManager->create('Custom\Configur...', Array)
#13 /var/www/html/vendor/magento/framework/View/Layout/Generator/Block.php(272): MagentoFrameworkViewElementBlockFactory->createBlock('Custom\Configur...', Array)
#14 /var/www/html/vendor/magento/framework/View/Layout/Generator/Block.php(252): MagentoFrameworkViewLayoutGeneratorBlock->getBlockInstance('Custom\Configur...', Array)
#15 /var/www/html/vendor/magento/framework/View/Layout.php(797): MagentoFrameworkViewLayoutGeneratorBlock->createBlock('Custom\Configur...', 'custom\configur...', Array)
#16 /var/www/html/vendor/magento/framework/View/Layout.php(780): MagentoFrameworkViewLayout->_createBlock('Custom\Configur...', 'custom\configur...', Array)
#17 /var/www/html/vendor/magento/framework/Interception/Interceptor.php(58): MagentoFrameworkViewLayout->createBlock('Custom\Configur...', 'custom\configur...', Array)
#18 /var/www/html/vendor/magento/framework/Interception/Interceptor.php(138): MagentoFrameworkViewLayoutInterceptor->___callParent('createBlock', Array)
#19 /var/www/html/vendor/magento/framework/Interception/Interceptor.php(153): MagentoFrameworkViewLayoutInterceptor->MagentoFrameworkInterception{closure}('Custom\Configur...', '', Array)
#20 /var/www/html/generated/code/Magento/Framework/View/Layout/Interceptor.php(248): MagentoFrameworkViewLayoutInterceptor->___callPlugins('createBlock', Array, Array)
#21 /var/www/html/vendor/magento/framework/Pricing/Render/RendererPool.php(78): MagentoFrameworkViewLayoutInterceptor->createBlock('Custom\Configur...', '', Array)
#22 /var/www/html/vendor/magento/framework/Pricing/Render.php(96): MagentoFrameworkPricingRenderRendererPool->createPriceRender('final_price', Object(MagentoCatalogModelProductInterceptor), Array)
#23 /var/www/html/vendor/magento/module-catalog/Block/Product/ListProduct.php(411): MagentoFrameworkPricingRender->render('final_price', Object(MagentoCatalogModelProductInterceptor), Array)
#24 /var/www/html/generated/code/Magento/Catalog/Block/Product/ListProduct/Interceptor.php(131): MagentoCatalogBlockProductListProduct->getProductPrice(Object(MagentoCatalogModelProductInterceptor))
#25 /var/www/html/app/code/Custom/Catalog/view/frontend/templates/product/list.phtml(303): MagentoCatalogBlockProductListProductInterceptor->getProductPrice(Object(MagentoCatalogModelProductInterceptor))

The error is located in https://github.com/magento/magento2/blob/ca0c22e48ad5adb5726b5a949a702e99937c04c3/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php#L216


Where I made mistake in src/app/code/Custom/ConfigurableProduct/view/frontend/templates/product/price/final_price.phtml or in src/app/code/Custom/ConfigurableProduct/Pricing/Render/FinalPriceBox.php ?