Before I was a Magento Developer, I sold fair trade music from my own e-commerce website. Ten years ago, it was difficult to propose a technically new website to buy and download music from hundreds of artists; so much so that I spent all my time developing this part. There were a lot of other functionalities to develop, such as the customer relationship interface, the artist relationship interface, and much more; I said to myself that they would arrive later.
Later, my society, DiskOverMusic, began to organize concerts; what an exciting new challenge! Concert halls, technicians, lights, drinks, there were thousands of things to do for it. But how could I sell tickets to the millions of fans?.
Now, Magento exists and offers us a fantastic playground to develop everything our clients need in order to make the Internet innovative and secure. Standard development and the Magento framework allow you to develop clean, fast, and secure code to bring new functionalities to the community.
In our very first chapter, we will discuss the need for complex extensions in the Magento Marketplace. We will discover that marketable extensions fulfil a complex purpose.
Thanks to Magento, we will create TicketBlaster, a module which will enable a store owner to sell seated tickets to events at a venue.
Magento is an out of the box e-commerce platform with many features, such as catalog navigation, promotion rules, tax rules, reports, and order management, which enable the store owner to begin to sell his products. However, all this out of the box functionality does not allow him to differentiate his store from others, interface with third party web applications, and offer good quality marketing and social services to the customer, as well as—maybe the most important requirement—providing an answer to the specificities of the profession the store owner needs.
Thus, Magento's community, composed of hundreds of developers and editors, distribute a lot of free and paid complex extensions in the Magento Marketplace. The extensions cover usage in customer experience (gifts, social, and so on), site management (administration, automations, and so on), integrations (payment, shipping, gateways, and so on), marketing (ads, email marketing, SEO, and so on), tech utilities, and themes. We will explore the Marketplace in detail in Chapter 8. Optimization for Teamwork Development.
In this chapter, we will cover the following topics:
Creating an extension
Registering dependencies with Composer
Managing our work with Git source control
This book assumes you have an intermediate knowledge of Magento development and installation. You will see many examples and screenshots of the Magento I use for this book; I use a local web server running with Apache (only) on an Ubuntu desktop OS.
The Magento I use is the latest beta version at the time of writing: Magento 2 C.E. version. 1.0.0-beta. That's why my local server is reachable at the following local address: http://magento2.local.com/.
My Magento admin panel is located at http://magento2.local.com/backoff.
Obviously, this installation isn't recommended for a production environment, but can teach you where the main problems can appear with a web server that runs a Magento. And it is perfect for web development, because you are not limited by an Internet connection and you can immediately resolve problems.
Note
If you are interested in a production server installation, or if you have a development server in your organization, you can find a complete installation script at https://bitbucket.org/jeremie_blackbird/iams.
When you want to create an extension, the first step is to think about its goal and functionalities. Take this important time to define and draft a prototype of the main functionalities and how they will be managed by the admin. For this step, you can use some tools available on the web, such as Proto.io, Balsamiq, or Moqups; they allow you to create and share prototypes that look and work like your extension should, but without code.
Note
Visit http://proto.io, http://balsamiq.com, and http://moqups.com to discover these useful tools.
Another step is to look at others extensions, in order to determine whether you are writing an already existing extension, that performs the same function as yours. It doesn't matter, but if this is the case, I recommend you make a better extension than the original!
Finally, open your favourite IDE and continue to read this chapter. Here, we will begin to create TicketBlaster, a module which will enables a store owner to sell seated tickets to events at a venue.
In the following steps, we will create the files necessary to our extension:
Create the extension structure by creating the following folder structure:
app/code/Blackbird
app/code/Blackbird/TicketBlaster
app/code/Blackbird/TicketBlaster/etc
Register the extension by creating the
module.xml
file in theapp/code/Blackbird/etc
folder and add this content to it:<?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="Blackbird_TicketBlaster" setup_version="1.0.0" /> </config>
This code informs Magento that a module named TicketBlaster with the namespace
Blackbird
, located in theapp/code/Blackbird
folder can be loaded and activated.Open a terminal and change the directory to the Magento project root folder.
Enable the module by running the two following commands:
php bin/magento module:enable php bin/magento setup:upgrade
Create the
[extension_path]/registration.php
file and add the following code:<?php /** * Copyright © 2015 Magento. All rights reserved. * See COPYING.txt for license details. */ \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Blackbird_TicketBlaster', __DIR__ );
You should see mentions of your new module, as in the following screenshot:
You can already check that your module is taken into account by Magento by connecting to the admin and navigating to Stores| Configuration | Advanced | Advanced | Disable Modules Output:
A helper will (this is not a surprise) help you and the extension during development by providing functions that execute little parts of code, such as getting a configuration value, executing generic functions for the extension, or testing the time zone.
Create a Helper
class of the extension by adding the following code into the Helper
[extension_path]/Helper/Event.php
:
Note
From this point, we shall use [extension_path]
to represent the path of our extension, which is app/code/Blackbird/TicketBlaster
.
<?php namespace Blackbird\TicketBlaster\Helper; classEvent extends \Magento\Framework\App\Helper\AbstractHelper { /** * Create just a useful method for our extension * * @return bool */ public function justAUsefulMethod(){ // Real code here // ... return true; } }
Now, we will create a controller
class to handle several issues. In our case, we prepare this controller
in order to list all events at a venue. Moreover, controllers can get a request from the browser with parameters and dispatch it to the models of our extension.
Before coding our
controller
, we need to create a new XML configuration file, in order to declare the new route. Create the[extension_path]/etc/frontend/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="standard"> <route id="events" frontName="events"> <module name="Blackbird_TicketBlaster" /> </route> </router> </config>
Next, create the
[extension_path]/Controller/Index/Index.php
file and add the following code:<?php namespace Blackbird\TicketBlaster\Controller\Index; class Index extends \Magento\Framework\App\Action\Action { /** @var \Magento\Framework\View\Result\Page */ protected $resultPageFactory; /** * @param \Magento\Framework\App\Action\Context $context */ public function __construct(\Magento\Framework\App\Action\Context $context, \Magento\Framework\View\Result\PageFactory $resultPageFactory) { $this->resultPageFactory = $resultPageFactory; parent::__construct($context); } /** * Event Index, shows a list of recent events. * * @return \Magento\Framework\View\Result\PageFactory */ public function execute() { return $this->resultPageFactory->create(); } }
Upgrade your Magento instance by running the following command:
php bin/magento setup:upgrade
Verify that the URL is accessible by requesting
http://MAGENTO_URL/events/index/index/
:Note
Controllers are called by Magento when a URL is requested regarding the parameters:
events
corresponds to thefrontName
value inroutes.xml
,index
is the name of the directory placed in theController
folder, andindex
is the name of the PHP file in theIndex
directory. We will discover later in this chapter how controllers process requests.
You can create this first structure at the beginning of your project, but of course, your project needs will lead you to create some other functionalities we will cover later. For now, it's just a starter. Some extensions require only controllers, others only blocks and models. Just keep in mind this simple explanation:
Controllers handle frontend and backend requests. They can communicate their data to models if it's needed, and eventually display data to customers or administrators.
Models handle data, from controllers or from a database. They are the core of your extension, do the main job, and their cap abilities are greater.
Blocks are here to take charge of the views: every template (
.phtml
file) is handled by a block that contains every necessary method for displaying data.Helpers get useful core functions and can be overloaded to add some useful methods.
XML files, which are in the
etc
folder, declare the module itself and every core business configuration of our extension, and can eventually define some default values for the configuration.Files that are in the
Setup
folder are files that create or update database tables during the installation of the extension.
Every extension comes in a single and standalone package, which will always be located in app/code/<EditorName>
. In any case, you can't place your code in app/code/Magento
(the core folder), because it will be overwritten when Magento is upgraded.
While writing the names of controllers and actions, make sure that they are clear and recognizable, especially by you. Their name must describe what the code does.
Do not hesitate to read Magento's core code, which informs you about the structure of the modules and how files work together. Make tests by using var_dump()
to display text and variable values.
Tip
During all your Magento development work, use debug logs and the developer mode. These two functionalities will help you a lot and save you a lot of time! Read the http://magento.com/blog/technical/logging-approach-magento-2 page for explanations about logging; we will use it often in the up coming chapters.
Once the project has begun, it may be a good thing to take care of code revision and eventually collaboration with other developers.
Git is a distributed revision control system developed by Linus Torvalds in 2005, initially for Linux kernel development. It has begun to replace the Subversion (SVN) system in a lot of companies, thanks to its full-fledged repository and independence from network access and a distant server.
As soon as you start to work with other people and developers, the repository must be always available on the Internet. You have two choices to do this: the first is to create a Git repository on your own server (a private server in your organization, or a rented dedicated server), and the second is to use a service that provides a repository for you. In this recipe, we will register our code on Bitbucket.
Note
GitHub and Sourceforge provide the same services, but Bitbucket offers more free services and is fully integrated with other Atlassian services such as Hipchat or Jira. It is up to you to make your choice according to your needs and environments.
Perform the following steps for Bitbucket registration:
Sign up to Bitbucket (https://bitbucket.org) by following the instructions on the website:
In the upper-right corner of any page, click on Create, and then click Create repository:
Enter your repository name and choose whether your repository will be public or private.
Tip
Check This is a private repository if you want to hide your repository from the general public, so that only selected people can see it.
That's it for Bitbucket. Keep this window open for future instructions.
Go to your terminal and install Git on your computer by running the following command line:
sudo apt-get install git
In the following step, we will commit our code to a new repository, and then we will push the repository to Bitbucket:
Initialize an empty repository by going to your Magento source folder and running the following command:
git init
Check that your repository has been created and is empty:
git status
Create a
.gitignore
file in the root of the repository and add the following content to it:#Git ignore for extensions writing /app/code/Magento/* /dev/tests/ /lib/ [...]
Verify that the
.gitignore
file is taken into account by runninggit status
again:git status
You can see that only
.gitignore
is taken into account by Git.Note
What happened? In fact, in a Magento project, especially with Magento CE, the source files are always available online and are the same for all. That's why you can presume that each of your collaborators first, and every client next, will run Magento. This is your code, it is unique and important, and that's why your code is the only thing to keep in your repository. Note for later: if you need to add another folder in your repository, just remove the corresponding ignore line.
Run the following commands to add and commit the
.gitignore
file:git add .gitignore git commit .gitignore -m "Ignoring all resources files"
Now your repository needs to be filled with the files of our project. Repeat the operation for all the files the extension needs with the command
git
add
<folder
|
filename>
:git -f add app/code/Blackbird/
Commit your additions:
git commit -m "Adding the first extension files"
Link your repository to the Bitbucket repository:
git remote add originhttps://YOUR_USERNAME@bitbucket.org/blackbirdagency/ticket-blaster.git
Finally, send the files and commit comments to Bitbucket by pushing the repository:
git push -u origin master
You will find your files by clicking on your repository name on https://bitbucket.org/, proving that the files have been sent and are available for other users.
Note
Take note not to send useless files.
When you are creating an extension, the people who install your code will already have a Magento instance. It is very important to share only the extension files and not Magento and customizable files, which are already modified or installed by your client.
That's why we have chosen to ignore almost all files by default, and to force a git add with the -f option when the file we need to share is placed in an ignored folder.
There are others Git storage services online, such as https://github.com/ or https://sourceforge.net/, which offer different storage spaces and different public/private repo policies.
You can create your own private Git server too, which can be dedicated to your company or organization.
If your module wants to override some preference values, or just change the default work of a module, you must be able to specify that your configuration must be loaded after the one you override.
In another way, your module could use some class methods defined by other modules and extensions to do its work.
We will discover dependency registering, and we are going to use it for our extension.
Composer is a package manager for PHP that provides a standard format for managing dependencies and handling complete complex installation processes. Magento 2 decided to base all platform development on Composer because it's very powerful, open source, and can manage autoloading for third party libraries and code (such as our extension).
We will now see how to use it to manage our extension and its dependencies, which are now published in a public repository on Bitbucket:
Create the
[extension_path]/composer.json
file and add the following code:{ "name": "blackbird/ticketblaster", "description": "Ticket manager for events", "type": "magento2-module", "version": "1.0.0", "license": [ "OSL-3.0", "AFL-3.0" ], "require": { "magento/magento-composer-installer": "*", "magento/catalog":"*" }, "extra": { "map": [ [ "*", "Blackbird/TicketBlaster" ] ] }, "authors": [ { "name": "Blackbird", "homepage": "http://black.bird.eu/", "role": "Developer" } ] }
Update the main Magento 2
composer.json
file by running the following command:composer config repositories.blackbird vcs https://bitbucket.org/blackbirdagency/ticket-blaster
Install the extension by running the following command:
composer require blackbird/ticketblaster
https://packagist.org/ is the main and default Composer repository. It aggregates public PHP packages installable with Composer.
When you use the Composer binary to add a new extension, the first thing that Composer will do is read the main composer.json
file of Magento. If your required extension isn't listed or documented, it will ask https://packagist.org/ to get more information.
In the case of TicketBlaster
, the extension is published in a public Git repository and we want to share it with everyone who needs it, even if they are not familiar with Composer. The simple way for all your clients to install the extension is by runnin
Create an account on https://packagist.org.
Once logged in, submit your package:
Note
At this point, Magento 2 hasn't built its package sharing system; it will be launched in a few months.
Now you just have to run the following command to install your extension:
composer require blackbird/ticketblaster
Note
Magento 2 uses Composer to package components and product editions. This book cannot look at all the powerful functionalities of Composer, so I recommend you read the detailed explanation on http://devdocs.magento.com/guides/v2.0/extension-dev-guide/composer-integration.html, which explains how Magento and Composer work together.
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:Note
This table is really important; Magento uses it to check whether an extension is installed, and which version.
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:
We now have all the necessary bases for writing an efficient extension that can already do a lot of work, such as saving data and managing it. It only lacks the ability for users to manage this data, and that's exactly what we will provide in the following chapter.
Note
All the code in this book is freely available here:
https://bitbucket.org/blackbirdagency/ticket-blaster.
Do not hesitate to download it, update it, and maybe even send new modifications!