Skip to content

Instantly share code, notes, and snippets.

@kolaente
Last active April 2, 2023 06:49
Show Gist options
  • Save kolaente/d7c78f0b7b1ebc61f48e32856447ab75 to your computer and use it in GitHub Desktop.
Save kolaente/d7c78f0b7b1ebc61f48e32856447ab75 to your computer and use it in GitHub Desktop.
Magento2 Cheat-sheet

Update Translations

Files in csv files. See here and here.

Knockout

Some translations are loaded over Knockout (Something like <span data-bind="i18n: 'Billing address'"></span>). These are retrieved from a js-translation.json which is generated based on the csv.

To generate it, run

find pub/static -name js-translation.json -exec rm {} \;
find var/view_preprocessed -name js-translation.json -exec rm {} \;
bin/magento setup:static-content:deploy  -f # Yes, even in developer mode...

If this doesn't work, try clearing caches (bin/magento cache:clean && bin/magento cache:flush) until it works. Eventually, it will.

Remove blocks conditionally

Put this in <vendor>/<module>/etc/frontend/events.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="layout_load_before">
        <observer name="remove_filter_block" instance="<vendor>\<module>\Observer\RemoveFilterBlock"/>
    </event>
</config>

In <vendor>\<module>\Observer\RemoveFilterBlock.php:

<?php

namespace <vendor>\<module>\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class RemoveFilterBlock implements ObserverInterface
{
    public function execute(Observer $observer)
    {
        /** @var \Magento\Framework\View\Layout $layout */
        $layout = $observer->getLayout();
        $layout->unsetElement('catalog.leftnav');
    }
}

Add config options to admin

In etc/adminhtml/system.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="sales" translate="label" sortOrder="10">
            <label>Sales</label>
        </tab>
        <section id="checkout" translate="label" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
            <class>separator-top</class>
            <label>Checkout</label>
            <tab>sales</tab>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Some multiselect config option</label>
                <field id="enable" translate="label" type="multiselect" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>This is the lable</label>
                    <source_model><vendor>\<PluginName>\Model\Config\Source\Option</source_model>
                    <config_path><vendor>/<PluginName>/optionName</config_path>
                </field>
            </group>
        </section>
    </system>
</config>

Hint: You can mark options with canRestore="1" to make it possible to reset them to their default values afterwards.

Hint 2: You can add a <comment>Lorem Ipsum</comment> in the <field> node which will show a helper text below the input field.

https://magento.stackexchange.com/q/239027/70510

Everything set here will be saved as <section id>/<group id>/<field id>.

The options are defined in <vendor>\<PluginName>\Model\Config\Source\Option:

namespace <vendor>\<PluginName>\Model\Config\Source;

use \Magento\Framework\Option\ArrayInterface;

class Option implements ArrayInterface
{
    public function toOptionArray()
    {
        return [
            ['value' => 1, 'label' => 'First option'],
            ['value' => 2, 'label' => 'Second option'],
            ['value' => 3, 'label' => 'Third option'],
            ['value' => 4, 'label' => 'Fourth option'],
        ];
    }
}

You can then get the configured options wherever you want like so (You'll need to inject \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig before):

$this->_scopeConfig->getValue('<vendor>/<PluginName>/optionName', Magento\Store\Model\ScopeInterface::SCOPE_STORE);

When your config form is a multiselect, the result will be a comma-seperated string.

$config = explode(',', $this->_scopeConfig->getValue('<vendor>/<PluginName>/optionName', Magento\Store\Model\ScopeInterface::SCOPE_STORE));

You can also define default values in etc/config.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <<vendor>>
            <<PluginName>>
                <optionName>default_value</optionName>
            </<PluginName>>
        </<vendor>>
    </default>
</config>

More: https://inviqa.com/blog/how-use-system-configuration-and-helpers-magento-2

Get the current product in plugin/interceptor

Injecting Priduct does not work. Instead, you need to inject \Magento\Framework\Registry and then get the current product with

$this->_registry->registry('current_product')

Make layout changes only to some kind of pages

You can create an observer to do this:

public function execute(\Magento\Framework\Event\Observer $observer)
{
    if($this->shouldHaveSomething()) {
            $layoutUpdate = $observer->getData('layout')->getUpdate();
            $layoutUpdate->addHandle('catalog_product_view_extra_xml');
        }
    }

This will add catalog_product_view_extra_xml from yourPlugin/view/frontend/catalog_product_view_extra_xml.xml to the overall layout. With this xml you can then customize the page as you normally would.

Change the page title from inside a module

Either with xml:

<referenceBlock name="page.main.title">
   <action method="setPageTitle">
      <argument translate="true" name="title" xsi:type="string">Recipe</argument>
   </action>
</referenceBlock>

Or from a controller:

public function execute() {
    $resultRedirect = $this->resultPageFactory->create();
    $resultRedirect->getConfig()->getTitle()->set(__($this->helper->getPageTitle()));
}

The controller will always be preferred over xml layout changes !

Make a class available in a phtml block

Create a block class which "proxies" the thing you want:

use Magento\Framework\View\Element\Template;

class Timezone extends \Magento\Framework\View\Element\Template
{
    /**
     * @var \Magento\Framework\Stdlib\DateTime\Timezone
     */
    private $_timezone;

    /**
     * Timezone constructor.
     *
     * @param Template\Context $context
     * @param \Magento\Framework\Stdlib\DateTime\Timezone $_timezone
     * @param array $data
     */
    public function __construct(
        Template\Context $context,
        \Magento\Framework\Stdlib\DateTime\Timezone $_timezone,
        array $data = []
    ) {
        parent::__construct($context, $data);
        $this->_timezone = $_timezone;
    }

    /**
     * @return string
     */
    public function getTimezone():string
    {
        return $this->_timezone->getConfigTimezone();
    }
}

And then set that as your block class:

<block name="footer.timezone" after="footer_imprint_privacy" template="Vendor::timezone.phtml"
      class="Vendor\Stuff\Block\Timezone">
</block>

Custom view model in templates

https://devdocs.magento.com/guides/v2.3/extension-dev-guide/view-models.html

Get another block in a template

<?php
$block = $this->getLayout()->createBlock('Magento\Cms\Block\Block')->setBlockId('block_id');
?>
<?php
if ($block):
?>
<div class="top-header">
    <?php
    echo $block->toHtml();
    ?>
</div>
<?php
endif;

DB Debugging

https://gist.github.com/matheusgontijo/b2722a6322283e84006367d2849696ab

Debug UI Components

var currentUiComponents = {};
requirejs('uiRegistry').filter(function(item){
   currentUiComponents[item.name] = item;
});
console.log(currentUiComponents);

Customize mail templates

https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/templates/template-email.html

Simple theme css adjustments

https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/css-guide/css_quick_guide_approach.html

Conditionally show a block

<block
    name="top.header.text"
    ifconfig="imi_topheader/general/enable"
  />

Create a custom template class to do stuff

class CustomClass extends Template
{
    /**
     * @return string
     */
    public function customMethod(): string
    {
        return 'Something';
    }
}

Getting data from models

There's a lot of guides suggesting addAttributeToSelect and addAttributeToFilter. However, this does not work for simple models who don't have attributes, but only fields. For these, the methods are called addFieldToSelect and addFieltToFiler respectively. Other than the different name all work the same way.

For Example:

    /** @var Collection */
    protected $modelCollectionFactory;

    public function __construct(CollectionFactory $contactsFactory)
    {
        $this->modelCollectionFactory = $contactsFactory;
    }

    private function getABunchOfData(): Collection
    {
        return $this->modelCollectionFactory->create()
            ->addFieldToSelect(['id'])
            ->addFieldToFilter(
                'created',
                ['lteq' => (new \DateTime())->modify('-3 month')->format('Y-m-d H:i:s')]
            );
    }

Queue debugging

Run

bin/magento queue:consumers:start order.placed --single-thread --max-messages=1 -vvv

to process one queued event and then use xdebug.remote_autostart=1 to debug into the consumer

To trigger a queued message again, set the status of the message in queue_message_status to 2 (= New) and remove the lock entry for the same message in queue_lock. Then run the consumer again (as above).

Email Templates Quirks

Variable names

If you put this in an email template:

{{ var order.increment_id}}

The email gets sent with an absolutely nonsense error message which does not tell you at all where the actual issue is.

However, if you send it like this:

{{var order.increment_id}}

it works without issues.

Data patches

Devdocs: https://devdocs.magento.com/guides/v2.3/extension-dev-guide/declarative-schema/data-patches.html

Remember, when adding a new attribute, you have to also declare it in db_schema.xml and db_schema_whitelist.json

Creating a new attribute and adding it to all existing product attribute sets

        $this->eavSetup->addAttribute(
            Product::ENTITY,
            self::ATTRIBUTE_CODE,
            [
                'type' => 'int',
                'backend' => '',
                'frontend' => '',
                'label' => 'Verpackungseinheit',
                'input' => 'text',
                'class' => '',
                'source' => '',
                'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
                'visible' => true,
                'required' => false,
                'user_defined' => false,
                'default' => '',
                'searchable' => false,
                'filterable' => false,
                'comparable' => false,
                'visible_on_front' => true,
                'used_in_product_listing' => true,
                'unique' => false,
                'apply_to' => '',
            ]
        );

        // Add the new attribute to all attribute sets
        $searchCriteria = $this->searchCriteriaBuilder->create();
        $attributeSets = $this->attributeSetRepository->getList($searchCriteria)->getItems();

        foreach ($attributeSets as $attributeSet) {
            $this->attributeManagement->assign(
                $attributeSet->getId(),
                $attributeSet->getDefaultGroupId(),
                self::ATTRIBUTE_CODE,
                $attributeSet->getCollection()->count() * 10
            );
        }

Disabled minification

n98-magerun2 config:store:set dev/js/merge_files 0
n98-magerun2 config:store:set dev/js/enable_js_bundling 0
n98-magerun2 config:store:set dev/js/minify_files 0
n98-magerun2 config:store:set dev/css/merge_css_files 0
n98-magerun2 config:store:set dev/css/minify_files 0
bin/magento setup:static-content:deploy

Integration Test Ressources

More Articles

Events: https://devdocs.magento.com/guides/v2.4/extension-dev-guide/events-and-observers.html

Queues: https://store.magenest.com/blog/create-a-message-queue-in-magento-2/#Sending_a_message_from_the_publisher_to_a_queue

Custom logger: https://magento.stackexchange.com/a/75954/77979

Custom CLI Commands: https://devdocs.magento.com/guides/v2.4/extension-dev-guide/cli-cmds/cli-howto.html

Plugins (Interceptors): https://devdocs.magento.com/guides/v2.4/extension-dev-guide/plugins.html

JSON Serializing:https://devdocs.magento.com/guides/v2.4/extension-dev-guide/framework/serializer.html

Widgets: https://www.toptal.com/magento/custom-widgets-in-magento-2

Adding Recaptcha to a form: https://magento.stackexchange.com/a/305471/77979

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment