How to create a Magento 2 extension

Yay, Magento 2 is officially out!

Great news indeed. My name is Alex, and I’m a Magento developer at Amasty. For the last few months I’ve been busy building Magento 2 extensions, and today I wanted to share an instruction on building a simple Magento 2 module.

What we’re going to build

To demonstrate how to create a Magento 2 extension, we are going to build a HelloWorld module, which is going to have some basic functionality, covering as many development aspects as possible in this case.

This Magento 2 extension is going to have a model with undefined data, which will be stored in the database. And we’ll display the values on the product page.

What’s more, we are going to create several settings to change the extension’s behavior. Nothing complicated, so let’s start!

Create configuration

The first step is to install Magento 2 and create a catalog to store the extension files. In Magento 2 files are not distributed through the folders and have a modular structure.

The files of the newly created extension will be in this catalog:

app\code\Amasty\HelloWorld/

Here Amasty is the name of the company that developed the extension, HelloWorld is the name of the Magento 2 extension we are building at the moment.

Now it’s time to actually code the extension. Catalogs purposes have slightly changed in comparison with the first versions of Magento.

Extension configuration is located in etc folder, as before.

Let’s create app\code\Amasty\ HelloWorld \etc catalog and a module.xml file inside it. This is the file where we are going to set our extension name and its version.

The file looks like this:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="Amasty_HelloWorld" setup_version="0.0.0">
    </module>
</config>

Now let’s check if Magento 2 sees the extension.

Run the following console command:

php bin/magento setup:upgrade

But there’s no result. To fix it, let’s create registration.php file in the root folder of the extension and put the following code inside:

/**
 * @author Amasty Team
 * @copyright Copyright (c) 2015 Amasty (http://www.amasty.com)
 * @package Amasty_HelloWorld
 */
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Amasty_HelloWorld',
    __DIR__
);

This is done to tell Magento 2 that it should run the extension from the current directory.

Now go and clear Magento cache, and you’ll see that our extension is working now:

How to create Magento 2 extension: enable module

Create settings

Now let’s create some settings for the new extension. To do that, add adminhtml catalog to etc catalog. It will contain configuration files for backend.

The first file we need here is routes.xml for frontName and id setting, which is used to define queries for the extension. It is done by the following piece of code:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
    <router id="admin">
        <route id="amasty_helloworld" frontName="amasty_helloworld">
            <module name="Amasty_HelloWorld" />
        </route>
    </router>
</config>

Now let’s configure the extension settings. As in the previous version, they’re located in the system.xml file, but the xml markup is slightly different in Magento 2.

I’m creating the three most popular settings as an example: a text setting, yes/no setting and a setting with custom values.

Have a look at the config file contents and what it actually means:

<?xml version="1.0"?>
<!-- /** * @author Amasty Team * @copyright Copyright (c) 2015 Amasty (http://www.amasty.com) * @package Amasty_HelloWorld */ -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../Magento/Config/etc/system_file.xsd">
    <system>
        <tab id="amasty" translate="label" sortOrder="10">
            <label>Amasty</label>
        </tab>

<section id="amasty_helloworld" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Amasty_HelloWorld</label>
            <tab>amasty</tab>
            <resource>Amasty_HelloWorld::amasty_helloworld</resource>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0">
                <label>General</label>
                <field id="enable" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable Hello World</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <comment>
                        Comment for `Enable Hello World` setting.
                    </comment>
                </field>
                <field id="block_label" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Block Label</label>
                </field>
                <field id="text_align" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Text Align</label>
                    <source_model>Amasty\HelloWorld\Model\Source\Align</source_model>
                </field>
            </group>
        </section>

    </system>
</config>

Add a new section to the settings block using <tab>. Set the unique ID and the name of the settings section inside. It is done because we have a very high possibility of using several apps from the same vendor on a single site.

Add the new <section id=”amasty_helloworld”> , again, with the unique ID, and set the necessary parameters, such as type, translation fields, order, label, the block for which the section is added, and so on. And then add the settings just inside the section. They will be divided into groups (one group in our case). The groups are defined by <group>, and the fields are set by <field>. We have already created three settings and pointed out types, labels, visibility, translation, comments and the data model.

In our case the third setting implies custom data, so we pointed out a separate data model for it. To do that, you need to create Amasty\HelloWorld\Model\Source\ Align model, where we will set the needed choice variants. The extension should be defined from \Magento\Framework\Option\ArrayInterface interface, and you need to redefine toOptionArray() and toArray() methods as well.

To check what you are doing, go to the bottom of the article and subscribe to get the code of the whole extension.

Let’s check the result. Open your Magento 2 backend, go to Stores – Configuration. Boom, we can see the settings!

Create Magento 2 extension: create module settings

Now, as we created the settings, we should set default values for the options. To do that, create a config.xml in the etc catalog and put the default values in accordance with system.xml in there.

Frontend output

Block

Now let’s try to show something on the frontend, i.e. to create a block and a template for it, and then to show this block on the product page.

Create a class of \Amasty\HelloWorld\Block\Catalog\Product\HelloWorld block, which should inherit from Magento class \Magento\Framework\View\Element\Template

We are going to use the parent constructor. Here’s how it will look like for this block:

public function __construct(
    \Magento\Framework\View\Element\Template\Context $context,
    array $data = []
) {
    parent::__construct($context, $data);
}

Template

Let’s create a simple template and put it in the following catalog:

Amasty\HelloWorld\view\frontend\templates\product\hello.phtml

You can see that in Magento 2 we have the view catalog, where we’re going to store the information, which was scattered in several theme catalogs, such as templates, layouts, scripts, styles, before.

Put this simple text inside:

<?=__(‘Hello World’);?>

As we see from the example, now you can perform translation in Magento using __() without a separate class. And the translation for this line will be pulled from Amasty\HelloWorld\i18n\en_US.csv

We have created the template, now let’s show it on the product page.

Layout

Time to create the layout! Now we are creating not a unique layout for all the pages, but a separate file for each page. As we will show the block on the product page, let’s create a layout with the following name:
Amasty\HelloWorld\view\frontend\layout\catalog_product_view.xml
Put this code inside:

<!-- /** * @author Amasty Team * @copyright Copyright (c) 2015 Amasty (http://www.amasty.com) * @package Amasty_HelloWorld */ -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Amasty_HelloWorld/css/hello.css"/>
    </head>
    <body>
        <referenceContainer name="product.info.main">
            <block name="amasty_helloworld.helloworld" class="Amasty\HelloWorld\Block\Catalog\Product\HelloWorld" before="-" template="product/hello.phtml" />
        </referenceContainer>
    </body>
</page>

As an example, we added the new block into product.info.main block in the layout code and added the styles file to use when showing on frontend. The styles file has the following address:

Amasty\HelloWorld\view\frontend\web\css\hello.css

Refresh the product page:

Create Magento 2 extension: see block on product page

Voila, the block is there!

Now let’s change the look of the block – and add the helper initialization to it.

The extension constructor looks like this:

/**
 * @var \Amasty\HelloWorld\Helper\Data
 */
protected $_helper;

public function __construct(
    \Magento\Framework\View\Element\Template\Context $context,
    array $data = [],
    \Amasty\HelloWorld\Helper\Data $helper
) {
    parent::__construct($context, $data);

    $this->_helper = $helper;
}

The helper from the created block is used as $this->_helper variable.

Helper

Create the helper in the following catalog:

Amasty\HelloWorld\Helper\Data.php

Add \Magento\Framework\App\Config\ScopeConfigInterface interface object initialization to the helper, it works to receive the data from configuration.

Now the file looks like this:

<?php /** * @author Amasty Team * @copyright Copyright (c) 2015 Amasty (http://www.amasty.com) * @package Amasty_HelloWorld */ namespace Amasty\HelloWorld\Helper; class Data extends \Magento\Framework\App\Helper\AbstractHelper { /** * @var \Magento\Framework\App\Config\ScopeConfigInterfac */ protected $_scopeConfig; CONST ENABLE = 'amasty_helloworld/general/enable'; CONST BLOCK_LABEL = 'amasty_helloworld/general/block_label'; CONST TEXT_ALIGN = 'amasty_helloworld/general/text_align'; public function __construct( \Magento\Framework\App\Helper\Context $context, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig ) { parent::__construct($context); $this->_scopeConfig = $scopeConfig;
    }

    public function getEnable(){
        return $this->_scopeConfig->getValue(self::ENABLE);
    }

    public function getBlockLabel(){
        return $this->_scopeConfig->getValue(self::BLOCK_LABEL);
    }

    public function getTextAlign(){
        return $this->_scopeConfig->getValue(self::TEXT_ALIGN);
    }
}

In this piece of code you can see that three functions for getting extension configuration from settings section were created.

Let’s use these functions in the block and change the template:

<?php /** * @author Amasty Team * @copyright Copyright (c) 2015 Amasty (http://www.amasty.com) * @package Amasty_HelloWorld */ ?>

<div class="amasty-helloworld-block" style="text-align: <?= $this->getTextAlign()?>">

<h3 class="label"><?= $this->getBlockLabel()?></h3>

    <?=__('Hello World');?>
</div>


Now the block is displayed taking the settings into account:

Create Magento 2 extension: block is displayed accroding to settings

Model creation

Create installation script

As in the 1.x versions of Magento, you need to create the installation file to use your own table. We are going to describe creating of a simple table with several fields. The file should be created here: Amasty\HelloWorld\Setup\InstallSchema.php

And this is its contents:

public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
    $installer = $setup;

    $installer->startSetup();
    $table  = $installer->getConnection()
        ->newTable($installer->getTable('amasty_helloworld'))
        ->addColumn(
            'id',
            \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
            null,
            ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
            'Id'
        )
        ->addColumn(
            'label',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            null,
            ['default' => null, 'nullable' => false],
            'Name'
        )
        ->addColumn(
            'value',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            null,
            ['default' => null, 'nullable' => false],
            'Stores'
        );
    $installer->getConnection()->createTable($table);
    $installer->endSetup();
}

You can see that we are creating an ‘amasty_helloworld’ table with one field of integer type and two fields of text type.

Model creation

As in the previous Magento version, we need to create three classes to work with the model: the model itself, the resource model and the collection.

Let’s create a Amasty\HelloWorld\Model\HelloWorld.php file and use the following initialization:

/**
 * Copyright © 2015 Amasty. All rights reserved.
 */

namespace Amasty\HelloWorld\Model\ResourceModel;

class HelloWorld extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * Model Initialization
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('amasty_helloworld', 'id');
    }
}

We just set the constructor for the model here. In fact, there is no great difference from the first Magento versions on this step. The resource model and the collection are stored in the following catalogs:

Amasty\HelloWorld\Model\ResourceModel\HelloWorld.php

Amasty\HelloWorld\Model\ResourceModel\HelloWorld\Collection.php

These files’ listings are of no particular interest, you can download the extension in the end of the article and see them if needed. Now we are going to check if the created model actually works. Create a simple function in the block we’re working with:

public function getCollection()
{
    $model = $this->_objectManager->create('Amasty\HelloWorld\Model\HelloWorld');
    $collection = $model->getCollection();

    return $collection;
}

and display it inside the template:

<?php foreach($this->getCollection() as $item):?>


<label><?= __("Title: ") . $item->getLabel()?></label> -- <label><?= __("Value: ") . $item->getValue();?></label>

<?php endforeach;?>

Now update the product page: it works!

Create Magento 2 extension: final result

Download the example Magento 2 extension for free:

We have successfully created a very simple Magento 2 extension. Remember, if you were following the steps and something is not working, first things first delete all the cache files from the var folder and then try again.
Should you have any questions about this process of Magento 2 extension development, please ask them in comments. Good luck to you!