7

Problem

I need to gain access to the children blocks from the parent block alias of customer_account_navigation from the frontend layout to be used in the adminhtml system configuration.

Current Attempt

As the code is being run in the admin area (adminhtml) I assume emulation is required in order to retrieve anything from the frontend, I have used the $appState->emulateAreaCode method which has a call back and the initialised the ObjectManager as frontend.

class AccountLinks implements \Magento\Framework\Option\ArrayInterface
{
    protected $_options = [];

    /**
     * @var \Magento\Framework\ObjectManagerInterface
     */
    protected $_objectManager;

    /**
     * @var \Magento\Framework\ObjectManager\ConfigLoaderInterface
     */
    protected $_configLoader;

    public function __construct(
        \Magento\Framework\ObjectManagerInterface $objectManager,
        \Magento\Framework\ObjectManager\ConfigLoaderInterface $configLoader
    )
    {
        $this->_objectManager = $objectManager;
        $this->_configLoader = $configLoader;
    }

    /**
     * Retrieve my account menu links.
     *
     * @return array
     */
    public function toOptionArray()
    {
        if (!$this->_options) {
            /* @var $appState \Magento\Framework\App\State */
            $appState = $this->_objectManager->get('Magento\Framework\App\State');

            $appState->emulateAreaCode('frontend', [$this, 'emulateDesignCallback']);
        }

        return $this->_options;
    }

    public function emulateDesignCallback()
    {
        $this->_objectManager->configure($this->_configLoader->load('frontend'));

        /* @var $layout \Magento\Framework\View\Layout */
        $layout = $this->_objectManager->create('\Magento\Framework\View\LayoutInterface');

        $layout->getUpdate()->addHandle('default');
        $layout->getUpdate()->addHandle('customer_account_index');

        $layout->getUpdate()->load();
        $layout->generateXml();
        $layout->generateElements();

        $blocks = $layout->getChildBlocks('customer_account_navigation');

        return $this;
    }
}

Debugging

I can see 2 frontend blocks which seems to imply that using addHandle is not working correctly to generate the correct blocks.

I am also unsure if I am loading the layout after setting the handles correctly.

The _configScope is incorrectly saying it's current scope is adminhtml even with the emulation, I wonder if this is causing the problem but I can't work out how to set it

Debugging

Any ideas? Thanks.

3

I have a solution, whether it is a good one I am not sure about but it does work. Disadvantages is that you have to have the Magento/blank theme installed and uses xpath so if Magento change their layout declaration it might break, both quite unlikely scenarios.

Here is my final solution:

class AccountLinks implements \Magento\Framework\Option\ArrayInterface
{
    /**
     * @var array
     */
    protected $_options = [];

    /**
     * @var \Magento\Framework\View\Layout\ProcessorFactory
     */
    protected $_layoutProcessorFactory;

    /**
     * @var \Magento\Theme\Model\ResourceModel\Theme\CollectionFactory
     */
    protected $_themesFactory;

    /**
     * @param \Magento\Framework\View\Layout\ProcessorFactory $_layoutProcessorFactory
     * @param \Magento\Theme\Model\ResourceModel\Theme\CollectionFactory $_themesFactory
     */
    public function __construct(
        \Magento\Framework\View\Layout\ProcessorFactory $_layoutProcessorFactory,
        \Magento\Theme\Model\ResourceModel\Theme\CollectionFactory $_themesFactory
    ) {
        $this->_layoutProcessorFactory = $_layoutProcessorFactory;
        $this->_themesFactory = $_themesFactory;
    }

    /**
     * Build options array
     *
     * @return array
     */
    public function toOptionArray()
    {
        if (!$this->_options) {

            foreach ($this->getLinksFromLayout() as $link) {
                /* @var $link \Magento\Framework\View\Layout\Element */
                $name = $link->asCanonicalArray();

                $this->_options[] = [
                    'value' => $name,
                    'label' => $name
                ];
            }
        }

        return $this->_options;
    }

    /**
     * Retrieve my account menu links.
     *
     * @return \SimpleXMLElement[]
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function getLinksFromLayout()
    {
        $themeCollection = $this->_themesFactory->create();
        $theme = $themeCollection->getItemByColumnValue('code', 'Magento/blank');

        /* @var $layoutProcessor \Magento\Framework\View\Model\Layout\Merge */
        $layoutProcessor = $this->_layoutProcessorFactory->create(['theme' => $theme]);

        $layoutProcessor->addHandle('customer_account_index');
        $layoutProcessor->load();

        $layoutProcessorXML = $layoutProcessor->asSimplexml();

        $result = $layoutProcessorXML->xpath('//body/referenceBlock[@name="customer_account_navigation"]/block/@name');

        return $result;
    }

}

It doesn't use appState emulation, I would be interested in seeing an example that involves using it to access layout as I have bypassed a ton of abstraction in order to get a working solution but I tried many things and couldn't get it to register the frontend handle and return the child blocks.

2
  • Just straight up parsing the compiled layout.xml files, interesting. You can't really render the blocks this way though, right?
    – thaddeusmt
    Jun 16 '17 at 19:02
  • Hello @jzahedieh i am gettnig some of the links not getting all links... and also iwant aurguments passed to the each account link ... how i can do this need help Jan 10 '20 at 5:41
3

I bumped into this issue as well (trying to render a frontend block in the backend), with a resulting Required parameter 'theme_dir' was not passed error in my page. After diving into this, I found out that the emulateAreaCode approach should be working (and is working fine with non-theming related stuff). But with theming, the layout is already instantiated for the backend. Additionally, the configuration is also attached to the layout, meaning that the theme_dir variable I was missing was actually not needed because we are in the backend.

The solution seemed simple: Reset the configuration somehow so that the theme_dir (a numeric ID referencing the right database row, for instance with the Luma theme in it) was added to the layout.

The layout is initialized through the theme. The theme is initialized through a design. And the design has a method setDesignTheme() which could be used.

See my code sample below: $this->design is an instance of \Magento\Framework\View\DesignInterface. THe other variables are quite common and should speak for themselves.

$store = $this->storeRepository->getById($this->storeId);
$layoutFactory = $this->layoutFactory;

$themeId = $this->scopeConfig- >getValue(DesignInterface::XML_PATH_THEME_ID, 'store', $store);
$this->design->setDesignTheme($themeId);

$alertGrid = $this->appState->emulateAreaCode(
    Area::AREA_FRONTEND,
    function () use ($store, $layoutFactory) {
        $layout = $layoutFactory->create();
        $block = $layout->createBlock(MyBlock::class);
        return $block->toHtml();
    }
);

Perhaps this helps you guys as well.

1

I'm pretty sure that instead of using the emulation system you should use the area code.

I reckon you should try adding the following to your code:

$appState->setAreaCode(\Magento\Framework\App\Area::AREA_FRONTEND);

The setAreaCode method actually changes the current scope so that might help:

public function setAreaCode($code)
{
    if (isset($this->_areaCode)) {
        throw new \Magento\Framework\Exception\LocalizedException(
            new \Magento\Framework\Phrase('Area code is already set')
        );
    }
    $this->_configScope->setCurrentScope($code);
    $this->_areaCode = $code;
}
1
  • I tried this initially but because the area code is already set the Exception Area code is already set is thrown which is why I went down the emulation route, the appState is somehow a singleton I think but can't quite out figure how.
    – jzahedieh
    Jun 21 '16 at 16:03

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.