Book Image

Magento Extensions Development

By : Jérémie Bouchet
Book Image

Magento Extensions Development

By: Jérémie Bouchet

Overview of this book

Magento has been revealed as the best and the most popular open source e-commerce platform in the world, with about 250k+ online stores. Magento 2 is the most recent version of this awesome toolset: every new and modern development techniques are used to offer a real modular approach and powerful architecture. The book will support you in the writing of innovative and complex extensions. Starting from the beginning, we will cover how to set up a development environment that allows you to be really efficient in your functionality writing, including GIT registering and many other development tools. We then move on to provide a large overview of the best practices to scale your module in a high-load environment. After these foundations, you will see how to use test driven-development (TDD) and unit tests to handle your code. We then build a complex extension together, step by step, and internationally-ready. Next, you will find out how to protect the users’ data. Finally, we will take a look a publishing the extension on the new Magento Connect marketplace and how to protect your intellectual property. After you read this book, you will know everything you need to know to become an invaluable extension editor, whether it is for your customers’ needs or for your own requirements.
Table of Contents (16 chapters)
Magento Extensions Development
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Designing TicketBlaster – the backend


We have now a basic extension structure and its dependencies. We must now think about TicketBlaster's structure and functionalities.

One of the most important things when you propose an extension for the community is that it contains the capacity to be configurable; the more you let your clients configure the extension, the more they will use it and the less likely they are to ask you whether something is available or customizable. Think of people who don't know about code development, and think of all the Magento developers who install your extension and don't have enough time to modify it.

We first need to create and manage the events. These events should be able to be created by the administrator and listed in the frontend. Furthermore, the events will have some characteristics, such as a name, a venue, and a date. You can obviously add any field you want for your event, and make it even better by creating a specific list of venues. The event will contain every ticket available for it.

The tickets (the product the customer can buy) will be based on Magento virtual products. But we are going to slightly modify the way we will use these by creating a new product type. This new product type will allow us to link the product to an event.

Creating the table for the events

Perform the following steps to create the table:

  1. Create the [extension_path]/Setup/ folder and then create the InstallSchema.php file. Add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Setup;
    
    use Magento\Framework\Setup\InstallSchemaInterface;
    use Magento\Framework\Setup\ModuleContextInterface;
    use Magento\Framework\Setup\SchemaSetupInterface;
    use Magento\Framework\DB\Ddl\Table;
    
    class InstallSchema implements InstallSchemaInterface
    {
    [...]

    Note

    The source code can be found in the by-chapter branch of the Git repository, in the Chapter1 folder.

  2. Update the Magento database by running the following command:

    php bin/magento setup:upgrade
    

    Note

    If you want to manually relaunch the SQL installation, you have to delete the table added by the module and the line corresponding to TicketBlaster in the setup_module table, then execute the preceding command again.

  3. The extension and its table are installed! To verify this, open your database interface, for instance phpMyAdmin, and go to the setup_module table:

    Note

    This table is really important; Magento uses it to check whether an extension is installed, and which version.

Creating the backend view to list the events

Once the database table has been created, we need to allow the administrator to add, update, and remove events by using the backend.

The first thing we need is a menu to access to the listing of events, so let's create the menu:

  1. Create the [extension_path]/etc/adminhtml/menu.xml file and add the following code:

    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
    <add id="Blackbird_TicketBlaster::ticketblaster" title="TicketBlaster" module="Blackbird_TicketBlaster" sortOrder="50" parent="Magento_Backend::content" resource="Blackbird_TicketBlaster::ticketblaster" />
    <add id="Blackbird_TicketBlaster::ticketblaster_event" title="Events" module="Blackbird_TicketBlaster" sortOrder="0" parent="Blackbird_TicketBlaster::ticketblaster" action="ticketblaster/event" resource="Blackbird_TicketBlaster::ticketblaster_event"/>
    </menu>
    </config>

    This simple code will add the menu in the global menu of Magento, in the Content main entry:

    If you click on the menu item, you will be redirected to the dashboard, which is completely normal; we haven't created a controller to handle the request. That's what we are going to do now.

  2. Create the [extension_path]/etc/adminhtml/routes.xml file and add 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="admin">
    <route id="ticketblaster" frontName="ticketblaster">
    <module name="Blackbird_TicketBlaster" before="Magento_Backend" />
    </route>
    </router>
    </config>
  3. Create a new folder, [extension_path]/Controller/Adminhtml/Event, in which you create the Index.php file. Then add the following code:

    <?php
    namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event;
    
    use Magento\Backend\App\Action\Context;
    use Magento\Framework\View\Result\PageFactory;
    
    class Index extends \Magento\Backend\App\Action
    {
    const ADMIN_RESOURCE = 'Blackbird_TicketBlaster::ticketblaster_event';
    
        /**
         * @var PageFactory
         */
    protected $resultPageFactory;
    
        /**
         * @param Context $context
         * @param PageFactory $resultPageFactory
         */
    public function __construct(
            Context $context,
            PageFactory $resultPageFactory
        ) {
            parent::__construct($context);
            $this->resultPageFactory = $resultPageFactory;
        }
    
        /**
         * Index action
         *
         * @return \Magento\Backend\Model\View\Result\Page
         */
    public function execute()
        {
            /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
            $resultPage = $this->resultPageFactory->create();
            $resultPage->setActiveMenu('Blackbird_TicketBlaster::ticketblaster_event');
            $resultPage->addBreadcrumb(__('Events'), __('Events'));
            $resultPage->addBreadcrumb(__('Manage Events'), __('Manage Events'));
            $resultPage->getConfig()->getTitle()->prepend(__('TicketBlaster Events'));
    
    return $resultPage;
        }
    }

    Tip

    Be careful: Magento and folder names are case-sensitive.

    The default execute() method generates the page. You will get a blank page if you click on the menu item; this is normal, and it is important to ensure you that you get this blank page before continuing. As long as you are redirected or something else, it means that your controller hasn't been read.

    Note

    From Step 4 and up to Step 20, there will be nothing to see in the backend. At most, you will have some errors and exceptions. So follow the steps strictly and test by reloading only at the end of the series.

  4. Create the [extension_path]/view/adminhtml/layout folder.

  5. Create the [extension_path]/view/adminhtml/layout/ticketblaster_event_index.xml file and add the following code:

    <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="styles"/>
    <body>
    <referenceContainer name="content">
    <uiComponent name="ticketblaster_event_listing"/>
    </referenceContainer>
    </body>
    </page>

    Note

    This code will declare the grid components to load. The filename has to correspond to <frontname>_<folder_in_controller>_<actionName>.

  6. Create the [extension_path]/view/adminhtml/ui_component/ folder.

  7. Create the [extension_path]/view/adminhtml/ui_component/ticketblaster_event_listing.xml file and add the following code:

    <?xml version="1.0" encoding="UTF-8"?>
    <listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    [...]

    Note

    The source code can be found in the by-chapter branch of the Git repository, in the Chapter1 folder.

    This code generates a grid with all these functionalities:

    • dataSource: Entity source collection, which allows the loading of all the items

    • filterSearch/filters: Adds all the filters to the grid

    • massaction: Mass action declarations to manipulate items

    • paging: Pagination configuration

    • columns: Lists all the columns we want to display and their configurations, such as type, draggable, align, label, and so on

  8. Create the [extension_path]/Controller/Adminhtml/Event/AbstractMassStatus.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event;
    
    use Magento\Framework\Model\Resource\Db\Collection\AbstractCollection;
    use Magento\Framework\Controller\ResultFactory;
    
    /**
     * Class AbstractMassStatus
     */
    class AbstractMassStatus extends \Magento\Backend\App\Action
    {
    [...]

    Note

    The source code can be found in the by-chapter branch of the Git repository, in the Chapter1 folder.

    This code allows us to handle our mass actions in the status field.

  9. Create the [extension_path]/Controller/Adminhtml/Event/MassDisable.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event;
    
    use Blackbird\TicketBlaster\Controller\Adminhtml\Event\AbstractMassStatus;
    
    /**
     * Class MassDisable
     */
    class MassDisable extends AbstractMassStatus
    {
        /**
         * Field id
         */
    const ID_FIELD = 'event_id';
    
        /**
         * Resource collection
         *
         * @var string
         */
    protected $collection = 'Blackbird\TicketBlaster\Model\Resource\Event\Collection';
    
        /**
         * Event model
         *
         * @var string
         */
    protected $model = 'Blackbird\TicketBlaster\Model\Event';
    
        /**
         * Event disable status
         *
         * @var boolean
         */
    protected $status = false;
    }
  10. Create the [extension_path]/Controller/Adminhtml/Event/MassEnable.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event;
    
    use Blackbird\TicketBlaster\Controller\Adminhtml\Event\AbstractMassStatus;
    
    /**
     * Class MassEnable
     */
    class MassEnable extends AbstractMassStatus
    {
        /**
         * Field id
         */
    const ID_FIELD = 'event_id';
    
        /**
         * Resource collection
         *
         * @var string
         */
    protected $collection = 'Blackbird\TicketBlaster\Model\Resource\Event\Collection';
    
        /**
         * Event model
         *
         * @var string
         */
    protected $model = 'Blackbird\TicketBlaster\Model\Event';
    
        /**
         * Event enable status
         *
         * @var boolean
         */
    protected $status = true;
    }
  11. Create the [extension_path]/Controller/Adminhtml/Event/MassDelete.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event;
    
    
    
    [...]

    Note

    The source code can be found in the by-chapter branch of the Git repository, in the Chapter1 folder.

    This file handles our mass action to delete several items at the same time.

  12. Create the [extension_path]/Controller/Adminhtml/Event/Delete.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event;
    
    use Magento\Backend\App\Action;
    use Magento\TestFramework\ErrorLog\Logger;
    
    class Delete extends \Magento\Backend\App\Action
    {
    
        /**
         * @param Action\Context $context
         */
    public function __construct(Action\Context $context)
        {
            parent::__construct($context);
        }
    
        /**
         * {@inheritdoc}
         */
    protected function _isAllowed()
        {
    return $this->_authorization->isAllowed('Blackbird_TicketBlaster::ticketblaster_event_delete');
        }
    
        /**
         * Delete action
         *
         * @return \Magento\Framework\Controller\ResultInterface
         */
    public function execute()
        {
            /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
            $resultRedirect = $this->resultRedirectFactory->create();
            // check if we know what should be deleted
            $id = $this->getRequest()->getParam('event_id');
    if ($id) {
    try {
                    // init model and delete
                    $model = $this->_objectManager->create('Blackbird\TicketBlaster\Model\Event');
                    $model->load($id);
                    $model->delete();
                    // display success message
                    $this->messageManager->addSuccess(__('You deleted the event.'));
                    // go to grid
    return $resultRedirect->setPath('*/*/');
                } catch (\Exception $e) {
                    // display error message
                    $this->messageManager->addError($e->getMessage());
                    // go back to edit form
    return $resultRedirect->setPath('*/*/edit', ['event_id' => $id]);
                }
            }
            // display error message
            $this->messageManager->addError(__('We can\'t find a event to delete.'));
            // go to grid
    return $resultRedirect->setPath('*/*/');
        }
    }

    Note

    This code handles the deletion of one piece of content at a time. It will also be used in the edit page.

  13. Create the [extension_path]/Model/Event.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Model;
    
    use Blackbird\TicketBlaster\Api\Data\EventInterface;
    use Magento\Framework\Object\IdentityInterface;
    
    class Event extends \Magento\Framework\Model\AbstractModel implements EventInterface, IdentityInterface
    {
    
    [...]

    Note

    The source code can be found in the by-chapter branch of the Git repository, in the Chapter1 folder.

    This code declares our model for handling our events.

  14. Create the [extension_path]/Model/Resource/Event.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Model\Resource;
    
    class Event extends \Magento\Framework\Model\Resource\Db\AbstractDb
    {
        [...]

    Note

    The source code can be found in the by-chapter branch of the Git repository, in the Chapter1 folder.

    This class declares our link between the model and the database.

  15. Create the [extension_path]/Model/Resource/Event/Collection.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Model\Resource\Event;
    
    class Collection extends \Magento\Framework\Model\Resource\Db\Collection\AbstractCollection
    {
        /**
         * Define resource model
         *
         * @return void
         */
    protected function _construct()
        {
            $this->_init('Blackbird\TicketBlaster\Model\Event', 'Blackbird\TicketBlaster\Model\Resource\Event');
        }
    
    }

    Note

    This code declares our collection of content of the Event type.

  16. Create the [extension_path]/Model/Event/Source/IsActive.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Model\Event\Source;
    
    class IsActive implements \Magento\Framework\Data\OptionSourceInterface {
    
        /**
         * @var \Blackbird\TicketBlaster\Model\Event
         */
    protected $_event;
    
        /**
         * Constructor
         *
         * @param \Blackbird\TicketBlaster\Model\Event $event
         */
    public function __construct(\Blackbird\TicketBlaster\Model\Event $event) {
            $this->_event = $event;
        }
    
        /**
         * Get options
         *
         * @return array
         */
    public function toOptionArray() {
            $options[] = ['label' => '', 'value' => ''];
            $availableOptions = $this->_event->getAvailableStatuses();
    foreach ($availableOptions as $key => $value) {
                $options[] = [
                    'label' => $value,
                    'value' => $key,
                ];
            }
    return $options;
        }
    
    }

    Note

    This code lists the available statuses to be used in the grid.

  17. Create the [extension_path]/Api/Data/EventInterface.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Api\Data;
    
    interface EventInterface
    {
    const EVENT_ID       = 'event_id';
    const URL_KEY       = 'url_key';
    const TITLE         = 'title';
    const VENUE         = 'venue';
    const EVENT_TIME       = 'event_time';
    const CREATION_TIME = 'creation_time';
    const UPDATE_TIME   = 'update_time';
    const IS_ACTIVE     = 'is_active';
    
    [...]

    Note

    The source code can be found in the by-chapter branch of the GIT repository, in the Chapter1 folder.

    This code lists all the methods available for an API to use in Magento. This part will be studied later in this book.

  18. Create the [extension_path]/etc/acl.xml file and add the following code:

    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation=" urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
    <resources>
    <resource id="Magento_Backend::admin">
    <resource id="Magento_Backend::content">
    <resource id="Blackbird_TicketBlaster::ticketblaster" title="TicketBlaster" sortOrder="10" >
    <resource id="Blackbird_TicketBlaster::ticketblaster_event" title="Events" sortOrder="40">
    <resource id="Blackbird_TicketBlaster::ticketblaster_event_save" title="Save" sortOrder="10" />
    <resource id="Blackbird_TicketBlaster::ticketblaster_event_delete" title="Delete" sortOrder="20" />
    </resource>
    </resource>
    </resource>
    </resource>
    </resources>
    </acl>
    </config>

    Note

    This code lists all the permissions that can be used to restrict access for specific users in the admin panel.

  19. Create the [extension_path]/etc/di.xml file and add the following code:

    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation=" urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Blackbird\TicketBlaster\Api\Data\EventInterface" type="Blackbird\TicketBlaster\Model\Event" />
    <virtualType name="EventGridFilterPool" type="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool">
    <arguments>
    <argument name="appliers" xsi:type="array">
    <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item>
    <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item>
    </argument>
    </arguments>
    </virtualType>
    <virtualType name="EventGridDataProvider" type="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider">
    <arguments>
    <argument name="collection" xsi:type="object" shared="false">Blackbird\TicketBlaster\Model\Resource\Event\Collection</argument>
    <argument name="filterPool" xsi:type="object" shared="false">EventGridFilterPool</argument>
    </arguments>
    </virtualType>
    </config>

    Note

    This XML code declares some virtual types that provide filtered data to the grid.

  20. Create the [extension_path]/Ui/Component/Listing/Column/EventActions.php file and add the following code:

    <?php
    
    namespace Blackbird\TicketBlaster\Ui\Component\Listing\Column;
    
    use Magento\Framework\View\Element\UiComponent\ContextInterface;
    use Magento\Framework\View\Element\UiComponentFactory;
    use Magento\Ui\Component\Listing\Columns\Column;
    use Magento\Framework\UrlInterface;
    
    class EventActions extends Column
    {
        /** Url path */
    const TICKETBLASTER_URL_PATH_EDIT = 'ticketblaster/event/edit';
    const TICKETBLASTER_URL_PATH_DELETE = 'ticketblaster/event/delete';
    
        /** @var UrlInterface */
    protected $urlBuilder;
    
        /**
         * @var string
         */
    private $editUrl;
    
        /**
         * @param ContextInterface $context
         * @param UiComponentFactory $uiComponentFactory
         * @param UrlInterface $urlBuilder
         * @param array $components
         * @param array $data
         * @param string $editUrl
         */
    public function __construct(
            ContextInterface $context,
            UiComponentFactory $uiComponentFactory,
            UrlInterface $urlBuilder,
    array $components = [],
    array $data = [],
            $editUrl = self::TICKETBLASTER_URL_PATH_EDIT
        ) {
            $this->urlBuilder = $urlBuilder;
            $this->editUrl = $editUrl;
            parent::__construct($context, $uiComponentFactory, $components, $data);
        }
    
        /**
         * Prepare Data Source
         *
         * @param array $dataSource
         * @return void
         */
    public function prepareDataSource(array & $dataSource)
        {
    if (isset($dataSource['data']['items'])) {
    foreach ($dataSource['data']['items'] as & $item) {
                    $name = $this->getData('name');
    if (isset($item['event_id'])) {
                        $item[$name]['edit'] = [
                            'href' => $this->urlBuilder->getUrl($this->editUrl, ['event_id' => $item['event_id']]),
                            'label' => __('Edit')
                        ];
                        $item[$name]['delete'] = [
                            'href' => $this->urlBuilder->getUrl(self::TICKETBLASTER_URL_PATH_DELETE, ['event_id' => $item['event_id']]),
                            'label' => __('Delete'),
                            'confirm' => [
                                'title' => __('Delete "${ $.$data.title }"'),
                                'message' => __('Are you sure you wan\'t to delete a "${ $.$data.title }" record?')
                            ]
                        ];
                    }
                }
            }
        }
    }

    Note

    This class handles the action column of our grid by adding two links: delete and edit.

  21. Update Magento by running the following command:

    php bin/magento setup:upgrade
    

    Note

    This command, which we use a lot, is very important during Magento development: it clears the cache, upgrades the DB data and schema, generates interceptors and factories, and more!

    Hurrah! We can reload our page to see the grid: