Today we will make minor changes to the
previously developed backend launcher to get it working with the latest Magento 2 build. Additionally, we will add Composer compatibility.
Introduction
When the development of the
Magento 2 Launcher module started, Magento 2 was at 2.0.0.0-dev84. Before you know it, they change
the versioning system, push a few updates and at 0.1.0-alpha99 the Launcher module is the cause of exceptions being thrown in the Magento 2 backend. On top of that, Magento 2 its Composer compatibility has changed quite a bit. In fact, it is thusly developed that without any problem we can let Composer install a module to the system. Let’s get the module to work again and take a look at the required changes for it to be compatible with Composer. This article can be read standalone from the
initial development write-up. If you want some background information on what exactly the “Backend Launcher” is, we recommend reading at least the
introduction. If you rather skip the update process to the latest Magento 2 build and want to learn more about the Composer compatibility,
here’s a shortcut What’s wrong?
Throwing the finished product from the
previous episode into a fresh Magento 2 installation and navigating to the backend will lead to the following exception being thrown:
Fatal error: Call to a member function setActive() on a non-object in \magento2\htdocs\app\code\Magento\Backend\App\AbstractAction.php on line 155
It is possible to manually back-step through the application run and find out what’s the cause, but it is a tedious process. A faster way might be to inspect the code base and see if we can spot anything which changed in the newer builds of Magento 2.
New layout definition
Because the code base consists of one single block and some assets being added, a logical first step is to check out whether adding that block and assets is causing problems in the newest build. Opening the
view/adminhtml/layout/default.xml will quickly reveal (if you are using
a sane IDE) that the schema definition (XSD file) referenced to does not exist.
PHPStorm will let you know when something is wrong with the schema definition.
Newer Magento 2 builds either renamed, replaced or removed the
Magento/Core/etc/layout_single.xsd file referenced to, because they probably changed the XML structure in one way or another. To find out what’s new, we take a look at a module which has a similar objective – adding an interface element to the header – as the Launcher module. The module in question is
Magent_AdminNotification Upon opening its
default.xml file, the difference is quickly spotted. The root tag is no longer
layout but rather
page. The root tag and schema definition in the Launcher its
default.xml layout definition is replaced with the correct one:
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
Reloading the backend still refuses to show the Magento 2 dashboard. No direct exception, but a friendly message that something is wrong and a new error log has been generated. The error log its top-level exception is surprisingly understandable:
Invalid block type: Magento\Theme\Block\Html\Head\Script
New way to add assets
This exception is clear as crystal: there is an attempt to add a block which does not exist (anymore). In
0.1.0-alpha97 it got removed. Asset management is now done within the “page configuration API”. At the moment of writing the
documentation on page assets is outdated and we didn’t manage to find any other documentation on said API. No need to worry, we can simply dive into an existing Magento 2 module to find out how it’s done. First we remove the entire node so that the backend is at least working. Then, we check the rendered HTML and pick a JS and/or CSS file to search for. We went with
Magento_Rule its
rules.js and searched with the latter as needle through string constants. It was found in the
default.xml of
Magento_Backend its
adminhtml area:
<head>
[...]
<link src="extjs/ext-tree-checkbox.js"/>
<css src="extjs/resources/css/ext-all.css"/>
<css src="extjs/resources/css/ytheme-magento.css"/>
<link src="Magento_Rule::rules.js"/>
</head>
A bit of a coupling-issue there, but at least now we have an idea of what the new syntax is. We update our module its
default.xml accordingly:
<?xml version="1.0"?>
<page [...]>
<head>
<link src="Magenticians_Launcher::js/launcher.js"/>
<css src="Magenticians_Launcher::css/launcher.css"/>
</head>
[...]
</page>
Quick note on the new syntax
As seen in the example above, the
link tag is used to refer to a JavaScript asset and the
css tag for CSS assets. First, the naming picked is very confusing: in HTML, CSS files are included with a
link tag. Secondly, using either
link or
css for either JavaScript or CSS seems to work – Magento 2 will automatically generate the correct HTML tag. The XML logic (or lack of?) is definitely something to look deeper into. Edit: after a quick look (checked at alpha102) at the internals,
link,
script and
css are all processed with the same code. Deeper down is handled which HTML to output depending on the file type supplied.
A minor rename
The backend is still working and our module its assets are properly included. The familiar plus-sign of our module is showing in the admin panel tray and everything seems to be fine. Until we actually want the use the module. The debug console complains that the
launcher_items variable is not defined. This means that the
LauncherItems block has not been included in the rendered page. Odd, because everything seems to be fine; at least we didn’t get any application-breaking exceptions. Upon investigating the
system.log, the following message is retrieved:
Broken reference: missing declaration of the element 'before_body_end'.
One of the joys of working with an undocumented code base! Checking the
page.phtml of the Magento backend learns us that
before_body_end container is still being used:
<?php echo $this->getChildHtml('before_body_end') ?>
Further inspection reveals that in all page layouts of the Magento 2 backend
before_body_end is redefined as
before.body.end. After checking
who’s to blame, it’s probably safe to say that since 0.1.0-alpha97 all dashes in the container names are replaced with dots. Not a clue “why” but it’s nice there’s consistency.
There’s still something wrong with the JavaScript
The container name is set straight in the
default.xml layout definition and now Magento knows where the
LauncherItems block should be placed.
Magento 2 uses RequireJS
After this change, there is still a JavaScript runtime error coming through in the debug console. The affected line initializes the jQuery UI Autocomplete library on the Launcher its input field. In
2.0.0.0-dev80 Magento 2 started suporting
RequireJS. During initial development on 2.0.0.0-dev84, implementation was still at the beginning. With the latest build however, RequireJS is responsible for loading jQuery UI. This means we cannot call
.autocomplete nor
.dialog before we are certain that the UI library has been fully loaded. This is quickly anticipated upon by wrapping the jQuery
document.ready callback in a
require() callback signaling a dependency on the jQuery UI library:
jQuery(document).ready(function($) {
require(['jquery/ui'], function(ui) {
[...]
$('#magenticians-launcher-input').autocomplete({
source: launcher_items,
[...]
});
});
});
There we go, our module is up and running again!
Composer compatibility
Adding Composer compatibility to a Magento 2 module is really straight forward. Composer works with a variety of
package sources. Because Magento 2 is still in development, we chose to
use Github as our VCS repository. Later, you can always cross-publish your packages to a repository like
Packagist. Given the module is already in a Git repository, you simply create a JSON file as if it is any other project:
{
"name": "magenticians/launcher",
"version": "0.0.1",
"type": "magento2-module",
"description": "Magento 2 module which adds a launcher to the Magento 2 backend interface. Inspired by PulseStormLauncher."
}
Add author, website information et cetera as you find necessary. Now, the
require section is still up for debate. Should you include a reference to
magento/framework and include a reference to the
packages.magento.com repository? Or refer to
magento/community-edition which is on Packagist? We decided for now to leave out all requirements until the whole what-is-hosted-where issue is cleared up. It is not that often you will do a
composer install from a module its directory and expect Magento 2 to be wrapped around it. The only
extra key which you have to add – and is not something regular Composer packages have – is the
map key for letting the Magento 2 Composer installer know where which files go. To have separation of code and other project files, we have put the module its code base in a separate
src directory. Our
map looks like this:
[...]
"extra": {
"map": [
[
"src/Magenticians/Launcher",
"Magenticians/Launcher"
]
]
}
}
With this mapping all files in
src/Magenticians/Launcher will be
deployed to
app/code/Magenticians/Launcher. Done! To get the module to work within a Magento 2 installation, you only have to let Composer know where your repository is located (if it’s not cross-published on Packagist):
$ composer config repositories.magenticians_launcher vcs http://github.com/magenticians/launcher
And that it is a requirement for your current project:
$ composer require magenticians/launcher:dev_master
Final words
That’s about it! Hopefully it gives a good indication of how to stay on track with the Magento 2 code base as an outside developer. Though documentation is still sparse and more than often non-existent, with enough patience developing for the Magento 2 platform is doable, today. It’s also good to see that Magento 2 its Composer support is coming along. Though some things can still be refined, we are glad that a major thing like installing a module works without hiccups. As always, if we missed anything, made a mistake or if you have a question – please leave a comment below,
tweet,
mail or send a smoke signal. Thanks for reading and see you next time.