1

I've created a new custom module to store manufacturers, which links to products via a custom attribute on products.

There are 2 places I want to use the manufacturer details. One is on the product page; the other is on a separate manufacturer page.

My question is how to structure the blocks in terms of inheritance. Both blocks use some of the same methods (getManufacturer(), getManufacturerName(), etc.), so being a good programmer and not wanting to repeat myself I thought of creating some kind of base class which these could both inherit from.

However, the block on the product page uses the product view block's getProduct() method, which is very useful and again I wouldn't want to repeat this code.

Another option would be to have the product page block inherit from product view, and the manufacturer page block(s) inherit from this afore-mentioned block - but this doesn't seem particularly logical to me.

Obviously I can't extend from both blocks. What is the best way to structure this so that I'm not repeating code unnecessarily and structuring things in a logical way?

0

I had this problem myself, so I thought I'll share some code with you.

You'll soon find you'll need that getProduct() logic more often than you think. So I suggest you extract that bit of logic into a separate helper so other blocks can use it.

For this, I've created a helper to extract/resolve the Product object from a block. This way, I can pass whichever block in there. The helper below works in the following cases:

a) a Product object is injected in the block with $block->setProduct($product); (for custom or dynamic blocks)
b) a Product ID is injected in the block: $block->setProductId(); (less often, but useful);
c) the global registry contains a Product object in either the product or current_product keys (the usual scenario of the product page).

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Framework\App\Helper\Context as AppHelperContext;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Registry;
use Magento\Framework\View\Element\AbstractBlock;

class CurrentProduct extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * @var Registry
     */
    private $registry;

    /**
     * @var ProductRepositoryInterface
     */
    private $productRepository;

    /**
     * @param AppHelperContext $context
     * @param Registry $registry
     * @param ProductRepositoryInterface $productRepository
     */
    public function __construct(
        AppHelperContext $context,
        Registry $registry,
        ProductRepositoryInterface $productRepository
    ) {
        parent::__construct($context);
        $this->registry = $registry;
        $this->productRepository = $productRepository;
    }

    /**
     * @param AbstractBlock $block
     * @return Product|null
     */
    public function getCurrentProductFromBlock(AbstractBlock $block)
    {
        // do not call $block->getProduct() directly, as some blocks
        // may have that method calling THIS one, and an infinite-loop
        // will soon follow
        if (!$block->getData('product') instanceof Product) {
            return $this->tryResolvingCurrentProduct($block);
        }

        return $block->getData('product');
    }

    /**
     * @param AbstractBlock $block
     * @return Product|null
     */
    private function tryResolvingCurrentProduct(AbstractBlock $block)
    {
        $product = null;

        if ($this->registry->registry('product') instanceof Product) {
            $product = $this->registry->registry('product');
        } elseif ($this->registry->registry('current_product') instanceof Product) {
            $product = $this->registry->registry('current_product');
        } elseif ($block->getProductId() > 0) {
            try {
                $product = $this->productRepository->getById($block->getProductId());
            } catch (NoSuchEntityException $e) {
                $this->_logger->warning($e);
            }
        }

        return $product;
    }
}

So now, let's consider your Manufacturer Details block on the Product page:

// consider $this->currentProductHelper as being injected with the above "CurrentProduct" object

// Manufacturer.php
private function getProduct()
{
    return $this->currentProductHelper->getCurrentProductFromBlock($this);
}

You can use the above logic in several ways:

a) directly injecting this block in the Product page via layout XML; it should work, as our helper searches the registry;
b) dynamically creating the block and injecting the Product: $block->getLayout()->createBlock(\Some\Block\ManufacturerCustom::class, ['product' => $someProduct]). This would still work because our helper looks initially with getData('product');
c) $block->getLayout()->createBlock(\Some\Block\ManufacturerCustom::class, ['product_id' => $someProductId]).

I've only created this helper because I don't want to use inheritance - but delegate, and I most certainly don't want to inherit from Product\View: it's huge! I just want the current product.


How do I not repeat code while not being able to extend multiple blocks?

tl;dr You extract the common code into a helper/trait/class/something and make your classes delegate to that. In M2 world, that usually means creating a helper class.

PS: Avoid inheritance if you can, btw. Rely on collaborator objects/helpers and delegate.

| improve this answer | |
  • Would something like this $block->getParentBlock()->getProduct() make sense in your resolving method, provided our block is a child of a block that has the getProduct method? – Kristof at Fooman Jun 6 '16 at 21:52
  • That's a pretty tight coupling in my book, so I usually avoid that, but yeah, it can be done. – nevvermind Jun 7 '16 at 7:48
  • Thanks, that's done the trick. Just out of curiosity, why would you go for helpers over inheritance? – piemanji Jun 7 '16 at 15:59
  • @piemanji - some links for you: 1 and 2. Lots of articles on the "composition over inheritance" subject, if you'll look them up. Along with some other advantages, composition is not blocked by "multiple extends", which actually gave away your problem, in my opinion. – nevvermind Jun 8 '16 at 7:28
0
  • An idea create an abstract class which will extends by two block class
  • and write code of getManufacturer(), getManufacturerName() at Abstract class.
  • Then Extends class this class for this two class

As good example is Magento\Catalog\Block\Product\AbstractProduct;

This abstract class is called when new product collection Magento\Catalog\Block\Product\NewProduct; is class and

also when default ListProduct.(Magento\Catalog\Block\Product\ListProduct;)

| improve this answer | |
  • Thanks for your answer Amit. However this would not allow me to use the getProduct() method from the product view block. I think it would be silly to rewrite that code for getting the current product? – piemanji Jun 6 '16 at 10:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.