Build a Complete Module

By | December 9th 2018 02:05:24 AM | viewed 92 times

Magento 2 is a Model View ViewModel (MVVM) system. While being closely related to its sibling Model View Controller (MVC), an MVVM architecture provides a more robust separation between the Model and the View layers. Below is an explanation of each of the layers of a MVVM system:

  • The Model holds the business logic of the application, and depends on an associated class—the ResourceModel—for database access. Models rely on service contracts to expose their functionality to the other layers of the application.
  • The View is the structure and layout of what a user sees on a screen - the actual HTML. This is achieved in the PHTML files distributed with modules. PHTML files are associated to each ViewModel in the Layout XML files, which would be referred to as binders in the MVVM dialect. The layout files might also assign JavaScript files to be used in the final page.
  • The ViewModel interacts with the Model layer, exposing only the necessary information to the View layer. In Magento 2, this is handled by the module’s Block classes. Note that this was usually part of the Controller role of an MVC system. On MVVM, the controller is only responsible for handling the user flow, meaning that it receives requests and either tells the system to render a view or to redirect the user to another route.

A Magento 2 module can in turn define external dependencies by using Composer, PHP’s dependency manager. Magento 2 core modules depend on the Zend Framework, Symfony as well as other third-party libraries.It is also interesting to notice that, in practice, all of Magento 2’s inner workings live inside a module. for example, Magento_Checkout responsible for the checkout process, and Magento_Catalog responsible for the handling of products and categories.

Below is the structure of Magento_Cms, a Magento 2 core module responsible for handling the creation of pages and static blocks.

Each folder of a Module holds one part of the architecture, as follows:

  • Api: Service contracts, defining service interfaces and data interfaces
  • Block: The ViewModels of our MVVM architecture
  • Controller: Controllers, responsible for handling the user’s flow while interacting with the system
  • etc: Configuration XML files—The module defines itself and its parts (routes, models, blocks, observers, and cron jobs) within this folder. The etc files can also be used by non-core modules to override the functionality of core modules.
  • Helper: Helper classes that hold code used in more than one application layer. For example, in the Cms module, helper classes are responsible for preparing HTML for presentation to the browser.
  • i18n: Holds internationalization CSV files, used for translation
  • Model: For Models and ResourceModels
  • Observer: Holds Observers, or Models which are “observing” system events. Usually, when such an event is fired, the observer instantiates a Model to handle the necessary business logic for such an event.
  • Setup: Migration classes, responsible for schema and data creation
  • Test: Unit tests
  • Ui: UI elements such as grids and forms used in the admin application
  • view: Layout (XML) files and template (PHTML) files for the front-end and admin application

Creating Our First Magento 2 Module

Registering Our Module

Now we will register our module with Magento. Go ahead and create a file registration.php in the location : app/code/UKM/Post/ with the following code where UKM is the namespace and Post is the Module name

	<?php

	\Magento\Framework\Component\ComponentRegistrar::register(
	    \Magento\Framework\Component\ComponentRegistrar::MODULE,
	    'UKM_Post',
	    __DIR__
	);

  

Create a file composer.json in the location : app/code/UKM/Post/

Create a file module.xml in the location : app/code/UKM/Post/etc/ with the following code

	<?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="UKM_Post" setup_version="0.1.1">
		        <sequence>
		            <module name="Magento_Directory" />  
		            <module name="Magento_Config" />
		        </sequence>
		    </module>
		</config>
  

Now we run the following command to upgrade database info

    projectname>php bin/magento setup:upgrade
	projectname>php bin/magento cache:flush
  

Now we check module status by the command

    projectname>php bin/magento module:status
  

If the module is shown in the module list and is disable, then run the following command

    projectname>php bin/magento module:enable UKM_Post
  

To create table in the database:

Create a file InstallSchema.php in the location: app/code/UKM/Post/Setup/ with the following code

   <?php

namespace UKM\Post\Setup;

use \Magento\Framework\Setup\InstallSchemaInterface;
use \Magento\Framework\Setup\ModuleContextInterface;
use \Magento\Framework\Setup\SchemaSetupInterface;
use \Magento\Framework\DB\Ddl\Table;

/**
 * Class InstallSchema
 *
 * @package UKM\Post\Setup
 */
class InstallSchema implements InstallSchemaInterface
{
    /**
     * Install Blog Posts table
     *
     * @param SchemaSetupInterface $setup
     * @param ModuleContextInterface $context
     */
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $tableName = $setup->getTable('ukm_post');

        if ($setup->getConnection()->isTableExists($tableName) != true) {
            $table = $setup->getConnection()
                ->newTable($tableName)
                ->addColumn(
                    'post_id',
                    Table::TYPE_INTEGER,
                    null,
                    [
                        'identity' => true,
                        'unsigned' => true,
                        'nullable' => false,
                        'primary' => true
                    ],
                    'ID'
                )
                ->addColumn(
                    'title',
                    Table::TYPE_TEXT,
                    null,
                    ['nullable' => false],
                    'Title'
                )
                ->addColumn(
                    'content',
                    Table::TYPE_TEXT,
                    null,
                    ['nullable' => false],
                    'Content'
                )
                ->addColumn(
                    'created_at',
                    Table::TYPE_TIMESTAMP,
                    null,
                    ['nullable' => false, 'default' => Table::TIMESTAMP_INIT],
                    'Created At'
                )
                ->setComment('Toptal Blog - Posts');
            $setup->getConnection()->createTable($table);
        }

        $setup->endSetup();
    }
}

   

Create a file UpgradeData.php in the location: app/code/UKM/Post/Setup/ with the following code to add some demo data in the table

   
 <?php
namespace UKM\Post\Setup;

use \Magento\Framework\Setup\UpgradeDataInterface;
use \Magento\Framework\Setup\ModuleContextInterface;
use \Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * Class UpgradeData
 *
 * @package UKM\Post\Setup
 */
class UpgradeData implements UpgradeDataInterface
{

    /**
     * Creates sample blog posts
     *
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     * @return void
     */
    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        if ($context->getVersion()
            && version_compare($context->getVersion(), '0.1.1') < 0
        ) {
            $tableName = $setup->getTable('ukm_post');

            $data = [
                [
                    'title' => 'Post 1 Title',
                    'content' => 'Content of the first post.',
                ],
                [
                    'title' => 'Post 2 Title',
                    'content' => 'Content of the second post.',
                ],
            ];

            $setup
                ->getConnection()
                ->insertMultiple($tableName, $data);
        }

        $setup->endSetup();
    }
}

   

Run the following command to check table created sussessfully

    projectname>php bin/magento setup:upgrade
	projectname>php bin/magento cache:flush
   

Create Model to face data:

To get single record we will create a file Post.php in the location: app/code/UKM/Post/Model/ResourceModel/ with the following code:

     <?php

		namespace UKM\Post\Model\ResourceModel;

		use \Magento\Framework\Model\ResourceModel\Db\AbstractDb;

		class Post extends AbstractDb
		{
			/**
			 * Post Abstract Resource Constructor
			 * @return void
			 */
			protected function _construct()
			{
				$this->_init('ukm_post', 'post_id');
			}
		}

  

To get all records we will create a file Collection.php in the location: app/code/UKM/Post/Model/ResourceModel/Post/ with the following code:

     <?php

		namespace UKM\Post\Model\ResourceModel\Post;

		use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;

		class Collection extends AbstractCollection
		{
		    /**
		     * Remittance File Collection Constructor
		     * @return void
		     */
		    protected function _construct()
		    {
		        $this->_init('UKM\Post\Model\Post', 'UKM\Post\Model\ResourceModel\Post');
		    }
		}

  

Now we create a file PostInterface.php of an interface to get table field in the location: app/code/UKM/Post/Api/Data/, and it should hold the table’s fields names, along with the methods for accessing them:

  
<?php

namespace UKM\Post\Api\Data;

interface PostInterface
{
    /**#@+
     * Constants for keys of data array. Identical to the name of the getter in snake case
     */
    const POST_ID               = 'post_id';
    const TITLE                 = 'title';
    const CONTENT               = 'content';
    const CREATED_AT            = 'created_at';
    /**#@-*/


    /**
     * Get Title
     *
     * @return string|null
     */
    public function getTitle();

    /**
     * Get Content
     *
     * @return string|null
     */
    public function getContent();

    /**
     * Get Created At
     *
     * @return string|null
     */
    public function getCreatedAt();

    /**
     * Get ID
     *
     * @return int|null
     */
    public function getId();

    /**
     * Set Title
     *
     * @param string $title
     * @return $this
     */
    public function setTitle($title);

    /**
     * Set Content
     *
     * @param string $content
     * @return $this
     */
    public function setContent($content);

    /**
     * Set Crated At
     *
     * @param int $createdAt
     * @return $this
     */
    public function setCreatedAt($createdAt);

    /**
     * Set ID
     *
     * @param int $id
     * @return $this
     */
    public function setId($id);
}


  

Now create a file Post.php to implementation the data interface in the location: app/code/UKM/Post/Model/Post.php with the following codes

  
  <?php

namespace UKM\Post\Model;

use \Magento\Framework\Model\AbstractModel;
use \Magento\Framework\DataObject\IdentityInterface;
use \Toptal\Blog\Api\Data\PostInterface;

/**
 * Class File
 * @package UKM\Post\Model
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Post extends AbstractModel implements PostInterface, IdentityInterface
{
    /**
     * Cache tag
     */
    const CACHE_TAG = 'ukm_post';

    /**
     * Post Initialization
     * @return void
     */
    protected function _construct()
    {
        $this->_init('UKM\Post\Model\ResourceModel\Post');
    }


    /**
     * Get Title
     *
     * @return string|null
     */
    public function getTitle()
    {
        return $this->getData(self::TITLE);
    }

    /**
     * Get Content
     *
     * @return string|null
     */
    public function getContent()
    {
        return $this->getData(self::CONTENT);
    }

    /**
     * Get Created At
     *
     * @return string|null
     */
    public function getCreatedAt()
    {
        return $this->getData(self::CREATED_AT);
    }

    /**
     * Get ID
     *
     * @return int|null
     */
    public function getId()
    {
        return $this->getData(self::POST_ID);
    }

    /**
     * Return identities
     * @return string[]
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * Set Title
     *
     * @param string $title
     * @return $this
     */
    public function setTitle($title)
    {
        return $this->setData(self::TITLE, $title);
    }

    /**
     * Set Content
     *
     * @param string $content
     * @return $this
     */
    public function setContent($content)
    {
        return $this->setData(self::CONTENT, $content);
    }

    /**
     * Set Created At
     *
     * @param string $createdAt
     * @return $this
     */
    public function setCreatedAt($createdAt)
    {
        return $this->setData(self::CREATED_AT, $createdAt);
    }

    /**
     * Set ID
     *
     * @param int $id
     * @return $this
     */
    public function setId($id)
    {
        return $this->setData(self::POST_ID, $id);
    }
}


  

Define route to browse url:

Create a file routes.xml in the location: app/code/UKM/Post/etc/frontend/ with the following code

  <?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="post" frontName="post">
            <module name="UKM_Post"/>
        </route>
    </router>
</config>

  

Define Controller to process reguest and response:

Create a file Index.php in the location: app/code/UKM/Post/Controller/Index/ with the following code

  <?php

namespace UKM\Post\Controller\Index;

use \Magento\Framework\App\Action\Action;
use \Magento\Framework\View\Result\PageFactory;
use \Magento\Framework\View\Result\Page;
use \Magento\Framework\App\Action\Context;
use \Magento\Framework\Exception\LocalizedException;

class Index extends Action
{

    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param Context $context
     * @param PageFactory $resultPageFactory
     *
     * @codeCoverageIgnore
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory
    ) {
        parent::__construct(
            $context
        );
        $this->resultPageFactory = $resultPageFactory;
    }

    /**
     * Prints the blog from informed order id
     * @return Page
     * @throws LocalizedException
     */
    public function execute()
    {
        $resultPage = $this->resultPageFactory->create();
        return $resultPage;
    }
}


  

Define View:

Create a file post_index_index.xml in the location: app/code/UKM/Post/view/frontend/layout with the following code to define layout in which container the post will appear

  <?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="UKM\Post\Block\Posts" name="posts.list" template="UKM_Post::post/list.phtml" />
        </referenceContainer>
    </body>
</page>

  

Define Block:

Create a file Posts.php in the location: app/code/UKM/Post/Block/ with the following code

  <?php

namespace UKM\Post\Block;

use \Magento\Framework\View\Element\Template;
use \Magento\Framework\View\Element\Template\Context;
use \UKM\Post\Model\ResourceModel\Post\Collection as PostCollection;
use \UKM\Post\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory;
use \UKM\Post\Model\Post;

class Posts extends Template
{
    /**
     * CollectionFactory
     * @var null|CollectionFactory
     */
    protected $_postCollectionFactory = null;

    /**
     * Constructor
     *
     * @param Context $context
     * @param PostCollectionFactory $postCollectionFactory
     * @param array $data
     */
    public function __construct(
        Context $context,
        PostCollectionFactory $postCollectionFactory,
        array $data = []
    ) {
        $this->_postCollectionFactory = $postCollectionFactory;
        parent::__construct($context, $data);
    }

    /**
     * @return Post[]
     */
    public function getPosts()
    {
        /** @var PostCollection $postCollection */
        $postCollection = $this->_postCollectionFactory->create();
        $postCollection->addFieldToSelect('*')->load();
        return $postCollection->getItems();
    }

    /**
     * For a given post, returns its url
     * @param Post $post
     * @return string
     */
    public function getPostUrl(
        Post $post
    ) {
        return $this->getBaseUrl().'/post/post/view/id/' . $post->getId();
    }

}

  

Define Template:

Create a file list.phtml in the location: app/code/UKM/Post/view/frontend/templates/post/ with the following code

     <?php /** @var UKM\Post\Block\Posts $block */ ? >
		 <h1 >Toptal Posts </h1 >
		 <?php foreach($block->getPosts() as $post): ? >
		     <?php /** @var UKM\Post\Model\Post */ ? >
		     <h2 > <a href=" <?php echo $block->getPostUrl($post);? >" > <?php echo $post->getTitle(); ? > </a > </h2 >
		     <p > <?php echo $post->getContent(); ? > </p >
		 <?php endforeach; ? >

   

Run the following command or manually flush cache from admiin panel

projectname>php bin/magento setup:upgrade
projectname>php bin/magento cache:flush

browse the following url:http://localhost/projectname/post/index/index

To View Individual Posts we create another file on controller,block,template,layout respectively

Create a file View.php in the location: app/code/UKM/Post/Controller/Post/ with the following code


<?php

namespace UKM\Post\Controller\Post;

use \Magento\Framework\App\Action\Action;
use \Magento\Framework\View\Result\PageFactory;
use \Magento\Framework\View\Result\Page;
use \Magento\Framework\App\Action\Context;
use \Magento\Framework\Exception\LocalizedException;
use \Magento\Framework\Registry;

class View extends Action
{
    const REGISTRY_KEY_POST_ID = 'ukm_post_post_id';

    /**
     * Core registry
     * @var Registry
     */
    protected $_coreRegistry;

    /**
     * @var PageFactory
     */
    protected $_resultPageFactory;

    /**
     * @param Context $context
     * @param Registry $coreRegistry
     * @param PageFactory $resultPageFactory
     *
     * @codeCoverageIgnore
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        PageFactory $resultPageFactory
    ) {
        parent::__construct(
            $context
        );
        $this->_coreRegistry = $coreRegistry;
        $this->_resultPageFactory = $resultPageFactory;
    }

    /**
     * Saves the blog id to the register and renders the page
     * @return Page
     * @throws LocalizedException
     */
    public function execute()
    {
        $this->_coreRegistry->register(self::REGISTRY_KEY_POST_ID, (int) $this->_request->getParam('id'));
        $resultPage = $this->_resultPageFactory->create();
        return $resultPage;
    }
}

Create a file View.php in the location: app/code/UKM/Post/Block/ with the following code

<?php

namespace UKM\Post\Block;

use \Magento\Framework\Exception\LocalizedException;
use \Magento\Framework\View\Element\Template;
use \Magento\Framework\View\Element\Template\Context;
use \Magento\Framework\Registry;
use \UKM\Post\Model\Post;
use \UKM\Post\Model\PostFactory;
use \UKM\Post\Controller\Post\View as ViewAction;

class View extends Template
{
    /**
     * Core registry
     * @var Registry
     */
    protected $_coreRegistry;

    /**
     * Post
     * @var null|Post
     */
    protected $_post = null;

    /**
     * PostFactory
     * @var null|PostFactory
     */
    protected $_postFactory = null;

    /**
     * Constructor
     * @param Context $context
     * @param Registry $coreRegistry
     * @param PostFactory $postCollectionFactory
     * @param array $data
     */
    public function __construct(
        Context $context,
        Registry $coreRegistry,
        PostFactory $postFactory,
        array $data = []
    ) {
        $this->_postFactory = $postFactory;
        $this->_coreRegistry = $coreRegistry;
        parent::__construct($context, $data);
    }

    /**
     * Lazy loads the requested post
     * @return Post
     * @throws LocalizedException
     */
    public function getPost()
    {
        if ($this->_post === null) {
            /** @var Post $post */
            $post = $this->_postFactory->create();
            $post->load($this->_getPostId());

            if (!$post->getId()) {
                throw new LocalizedException(__('Post not found'));
            }

            $this->_post = $post;
        }
        return $this->_post;
    }

    /**
     * Retrieves the post id from the registry
     * @return int
     */
    protected function _getPostId()
    {
        return (int) $this->_coreRegistry->registry(
            ViewAction::REGISTRY_KEY_POST_ID
        );
    }
}

Create a file view.phtml in the location: app/code/UKM/Post/view/frontend/templates/post/ with the following code

<?php /** @var Toptal\Blog\Block\View $block */ ?>
<h1><?php echo $block->getPost()->getTitle(); ?></h1>
<p><?php echo $block->getPost()->getContent(); ?></p>

Create a file post_post_view.xml in the location: app/code/UKM/Post/view/frontend/layout/ with the following code


<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="UKM\Post\Block\View" name="post.view" template="UKM_Post::post/view.phtml" />
        </referenceContainer>
    </body>
</page>


Run the following command or manually flush cache from admiin panel

projectname>php bin/magento setup:upgrade
projectname>php bin/magento cache:flush

browse the following url:http://localhost/projectname/post/post/view/id/1

bONEandALL