Prerequisites
To start with a Magento 2 module development, we want to give emphasis on two things:
1. Disable Magento cache
Firstly, to avoid manually flush the cache every time you make changes to your code, you should disable Magento cache during development.
The process is as usual: Admin → System → Cache Management → select all cache types and disable them.
2. Put Magento into a developer mode
Secondly, developer mode needs to be on to make sure that you catch all the errors Magento throws during the development.
From Magento 2 root. and through terminal, you should run the following command to begin the developer mode on:
1 2 3 |
php bin/magento deploy:mode:set developer |
Creating the module files and folders:
Download full module from our github
Module setup
In Magento 2 files are not distributed through the folders and have a modular structure. Here the modules are grouped by namespace and placed directly in the app/code folder. You can forget about code pools.
So our first step is to create the module folder and necessary files required to register a Magento module:
Create the following folders
1 2 3 4 |
app/code/Scriptlodge app/code/Scriptlodge/Helloworld |
Here, the Scriptlodge folder is the module’s namespace, and Helloworld is the module’s name.
Note: You will have to create it manually if you don’t have the code folder in your app directory.
Creating module.xml
Now in the module folder, we need to create a module.xml file in the app/code/Scriptlodge/Helloworld/etc folder with the following code:
1 2 3 4 5 6 7 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Scriptlodge_Helloworld" setup_version="1.0.0"> </module> </config> |
Creating registration.php
With the following code you need create a registration.php file in the app/code/Scriptlodge/Helloworld folder to register the module:
1 2 3 4 5 6 7 |
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Scriptlodge_Helloworld', __DIR__ ); ?> |
At this stage, you need to clear Magento cache from admin, or through terminal from Magento 2 root with the following command:
1 2 3 |
php bin/magento setup:upgrade |
Now the extension is supposed to start working. The presence of the module can be checked from Admin → Stores → Configuration → Advanced → Advanced
or you can open app/etc/config.php and check the array for the ‘Scriptlodge_Helloworld’ key, whose value should be set to 1:
Creating router:
Basically two routers are needed to create, one for frontend and other for beckend/admin part.
The following routes.xml file is created for frontName and id setting to define queries for the extension.
1. Frontend router
The app/code/Scriptlodge/Helloworld/etc/frontend folder contains the following code for front-end:
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router id="standard"> <route id="helloworld" frontName="helloworld"> <module name="Scriptlodge_Helloworld" /> </route> </router> </config> |
Look the frontend router and route is defined with an id “helloworld”.
2. Backend/admin router
Similarly, the app/code/Scriptlodge/Helloworld/etc/adminhtml folder is used for backend:
1 2 3 4 5 6 7 8 9 |
<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="scriptlodge_helloworld" frontName="scriptlodge_helloworld"> <module name="Scriptlodge_Helloworld" /> </route> </router> </config> |
Here the backend router and route is defined with an id “scriptlodge_helloworld”.
Creating a controller
The first part of the URL is generated from the frontName attribute.
Here you can see how the URLs are constructed in Magento 2:
1 2 3 |
<frontName>/<controler_folder_name>/<controller_class_name> |
So, the final URL will emerge in the following way:
helloworld/index/index
Now the Index.php controller file in the app/code/Scriptlodge/Helloworld/Controller/Index folder will have the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
namespace Scriptlodge\Helloworld\Controller\Index; use Magento\Framework\App\Action\Context; class Index extends \Magento\Framework\App\Action\Action { protected $_resultPageFactory; public function __construct(Context $context, \Magento\Framework\View\Result\PageFactory $resultPageFactory) { $this->_resultPageFactory = $resultPageFactory; parent::__construct($context); } public function execute() { $resultPage = $this->_resultPageFactory->create(); return $resultPage; } } |
Though in Magento 1 each controller can have multiple actions, in Magento 2 every action has its own class to implement the execute() method.
Creating a block:
To show something on the frontend, let’s create a block and a template for it.
Firstly, a Helloworld.php file in the app/code/Scriptlodge/Helloworld/Block folder with the following code will inherit from Magento class \Magento\Framework\View\Element\Template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
namespace Scriptlodge\Helloworld\Block; class Helloworld extends \Magento\Framework\View\Element\Template { public function __construct(\Magento\Framework\View\Element\Template\Context $context, array $data = [] ) { parent::__construct($context, $data); } public function getHelloText() { return 'Hello World!'; } } |
Creating a layout and template files:
Templates and layout files reside in view folder inside the module. So, we find three sub-folders under view:
a. adminhtml,
b. frontend,
c. base
And the above folders serve the following purposes respectively – adminhtml folder is used for admin, the frontend folder is used for frontend and the base folder is used for both, admin & frontend files.
Layout
First we will create a helloworld_index_index.xml file in the app/code/Scriptlodge/Helloworld/view/frontend/layout folder with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 |
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd" layout="1column"> <head> <css src="Scriptlodge_HelloWorld/css/hello.css"/> </head> <body> <referenceContainer name="content"> <block class="Scriptlodge\Helloworld\Block\Helloworld" name="helloworld" template="helloworld.phtml" /> </referenceContainer> </body> </page> |
A block has been added to the content container in the layout and set the template of our block to helloworld.phtml, and the styles file to use when showing on frontend. The template and styles file has the following address:
Template
A helloworld.phtml file in the app/code/Scriptlodge/Helloworld/view/frontend/templates folder will have the following code:
1 2 3 |
<h1><?php echo $this->getHelloText(); ?></h1> |
Styles, Js and Images
And a hello.css file in the Scriptlodge\HelloWorld\view\frontend\web\css\hello.css.
In the same way, the images and js folders will reside into web folder.
Create admin settings:
To create some settings for the new extension it will be needed to add adminhtml folder to Scriptlodge\HelloWorld\etc folder. It will contain configuration files for backend.
Here, a routes.xml is needed for frontName and id setting, which is used to define queries for the extension. It was created in our previous section (Creating router).
The xml markup is slightly different in Magento 2 but in the previous version, they’re located in the system.xml file.
Here comes the two most popular settings as an example: a text setting and a yes/no setting.
Looking at the config file contents you can know what it actually means:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../Magento/Config/etc/system_file.xsd"> <system> <tab id="scriptlodge" translate="label" sortOrder="500"> <label>Scriptlodge</label> </tab> <section id="scriptlodge_helloworld" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>HelloWorld</label> <tab>scriptlodge</tab> <resource>Scriptlodge_HelloWorld::scriptlodge_helloworld</resource> <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Scriptlodge HelloWorld Settings</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>Welcome Text</label> </field> </group> </section> </system> </config> |
Add a new section to the settings block using
Add the new
1 2 3 |
<section id="scriptlodge_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
Let’s check the result. Open your Magento 2 backend, go to Stores – Configuration, we can see the settings!
Creating Helper:
Now we create the helper in the following path:
1 2 3 |
Scriptlodge\HelloWorld\Helper\Data.php |
And we 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
namespace Scriptlodge\HelloWorld\Helper; class Data extends \Magento\Framework\App\Helper\AbstractHelper { protected $_scopeConfig; CONST ENABLE = 'scriptlodge_helloworld/general/enable'; CONST BLOCK_LABEL = 'scriptlodge_helloworld/general/block_label'; /* * * @var \Magento\Framework\App\Config\ScopeConfigInterfac */ 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); } } |
Let’s use these functions in the block and the template, so we need to change block function and template:
Block file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
namespace Scriptlodge\Helloworld\Block; class Helloworld extends \Magento\Framework\View\Element\Template { protected $_helper; public function __construct(\Magento\Framework\View\Element\Template\Context $context, array $data = [], \Scriptlodge\HelloWorld\Helper\Data $helper) { parent::__construct($context, $data); $this->_helper = $helper; } public function getHelloText() { return 'Hello World!'; } public function getBlockLabel() { return $this->_helper->getBlockLabel(); } protected function _toHtml() { if ($this->_helper->getEnable()) { return parent::_toHtml(); } else { return ''; } } } |
Template file:
1 2 3 4 5 |
<div class="helloworld-block" style="text-align: center;"> <h1><?php echo $this->getBlockLabel() ?></h1> </div> |
Creating Model files:
Create installation script/Schema setup scripts
As in the 1.x versions of Magento, we need to create the installation file to use our own table.
In magento 2 schema setup scripts change database schema, they create or change needed database tables. If module is installing, Setup\InstallSchema::install() is executed.
The file should be created here: Scriptlodge\HelloWorld\Setup\InstallSchema.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
namespace Scriptlodge\Helloworld\Setup; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\SchemaSetupInterface; use Magento\Framework\Setup\ModuleContextInterface; class InstallSchema implements InstallSchemaInterface { public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $installer = $setup; $installer->startSetup(); $table = $installer->getConnection() ->newTable($installer->getTable('scriptlodge_helloworld')) ->addColumn( 'id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, null, ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], 'Id' ) ->addColumn( 'name', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, null, ['default' => null, 'nullable' => false], 'Name' ) ->addColumn( 'address', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, null, ['default' => null, 'nullable' => false], 'Address' ) ->addColumn( 'city', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, null, ['default' => null, 'nullable' => false], 'City' ) ->addColumn( 'country', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, null, ['default' => null, 'nullable' => false], 'Country' ); $installer->getConnection()->createTable($table); $installer->endSetup(); } } |
We can see that we are creating a “scriptlodge_helloworld” table with one field of integer type and four fields of text type.
Current module version is saved in setup_module table, which is equivalent of core_resource in Magento 1. Magento regulates what needs setup by comparing module versions with what it sees in that table.
Create Model
As in the Magento 1.x 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 Scriptlodge\HelloWorld\Model\HelloWorld.php file and use the following initialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
namespace Scriptlodge\HelloWorld\Model; class HelloWorld extends \Magento\Framework\Model\AbstractModel { /** * Constructor * * @return void */ protected function _construct() { parent::_construct(); $this->_init('Scriptlodge\HelloWorld\Model\ResourceModel\HelloWorld'); } } |
Create a Scriptlodge\HelloWorld\Model\ResourceModel\HelloWorld.php file and use the following initialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
namespace Scriptlodge\HelloWorld\Model\ResourceModel; class HelloWorld extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { /** * Model Initialization * * @return void */ protected function _construct() { $this->_init('scriptlodge_helloworld', 'id'); } } |
Create a Scriptlodge\HelloWorld\Model\ResourceModel\HelloWorld\Collection.php file and use the following initialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
namespace Scriptlodge\HelloWorld\Model\ResourceModel\HelloWorld; class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection { /** * Define resource model * * @return void */ protected function _construct() { $this->_init('Scriptlodge\HelloWorld\Model\HelloWorld', 'Scriptlodge\HelloWorld\Model\ResourceModel\HelloWorld'); } } |
We just set the constructor for the model here. In fact, there is no great difference from the Magento1.x versions on this step.
Now we are going to check if the created model actually works. Create a simple function in the block we’re working with:
1 2 3 4 5 6 7 |
public function getCollection() { $model = $this->_objectManager->create('Scriptlodge\HelloWorld\Model\HelloWorld'); $collection = $model->getCollection(); return $collection; } |
And also update Block __construct function,
1 2 3 4 5 6 7 8 9 |
protected $_helper; protected $_objectManager; public function __construct(\Magento\Framework\View\Element\Template\Context $context, array $data = [], \Scriptlodge\HelloWorld\Helper\Data $helper, \Magento\Framework\ObjectManagerInterface $objectManager) { parent::__construct($context, $data); $this->_helper = $helper; $this->_objectManager = $objectManager; } |
Finally use these functions in the block and change the template file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<div class="helloworld-block" style="text-align: center;"> <!--<h1><?php //echo $this->getHelloText(); ?></h1>--> <h1><?php echo $this->getBlockLabel() ?></h1> <?php if (count($this->getCollection()) > 0) { ?> <div class="customer-list"> <h3 class="label">Example Customer List</h3> <ul> <?php foreach ($this->getCollection() as $item): ?> <li> <label><?php echo $item->getName() . ' -- ' . $item->getAddress() . ', ' . $item->getCity() . ', ' . $item->getCountry(); ?></label> </li> <?php endforeach; ?> </ul> </div> <?php } ?> </div> |
Finally deploy the module
1 2 3 |
php bin/magento setup:static-content:deploy en_US |