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.
Perform the following steps to create the table:
Create the
[extension_path]/Setup/
folder and then create theInstallSchema.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 { [...]
Update the Magento database by running the following command:
php bin/magento setup:upgrade
The extension and its table are installed! To verify this, open your database interface, for instance
phpMyAdmin
, and go to thesetup_module
table:
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:
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.
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>
Create a new folder,
[extension_path]/Controller/Adminhtml/Event
, in which you create theIndex.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; } }
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.Create the
[extension_path]/view/adminhtml/layout
folder.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>
Create the
[extension_path]/view/adminhtml/ui_component/
folder.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 theChapter1
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
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 theChapter1
folder.This code allows us to handle our mass actions in the status field.
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; }
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; }
Create the
[extension_path]/Controller/Adminhtml/Event/MassDelete.php
file and add the following code:<?php namespace Blackbird\TicketBlaster\Controller\Adminhtml\Event; [...]
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('*/*/'); } }
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 { [...]
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 { [...]
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'); } }
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; } }
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'; [...]
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>
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>
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?') ] ]; } } } } }
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: