Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento
front end book you'll ever need. Get your copy
today!

Today we’re going to run through a basic “Hello World” example in Magento 2. We’ll setup a new URL, and show you how to create a phtml template for that URL. Along the way we’re going to be talking about Design Patterns™, design patterns, and how Magento got here. However, at the heart of this article are a set of simple, repeatable, steps to follow that will help you get started with module development in Magento 2.

One big surprise for Magento 1 developers is that Magento 2 is not a Model, View, Controller system. While Magento 2’s architects haven’t staked out a particular label for their system, it’s closer to a Model, View, ViewModel (MVVM) system.

In Magento 2, when you request a URL

http://magento.example.com/hello_mvvm/hello/world

The system will route the request to a controller object’s execute method, similar to the way an MVC system will route a URL to a controller object’s action method. However, unlike a traditional MVC system, this controller object is only responsible for

Deciding which page layout to use

Handling saving data from POST requests

Either

Telling the system to render the HTTP response

Or redirecting users to the next/previous page

You’ll notice there’s no mention of setting variables in views. That’s because it’s the responsibility of each view to fetch its own information from the model layer, request object, or any other external system. Magento breaks up an HTML page into sections called containers. Each container holds a nested tree of objects called blocks. Each block object has a phtml template file which specifies the HTML a block object renders.

In MVVM parlance, a Magento block object is the View Model. The block object will do any programmatic reading of the CRUD models, request object, external systems, etc. The View is the phtml template file. It only interacts with the ViewModel (the block object).

As a module developer, if you want to create a new URL in Magento, you need to

Configure a module to tell Magento which controller it should use for a URL

Configure a module to tell Magento which Block objects should be added to the system

If that’s all a little overwhelming, don’t worry. The rest of this introductory article will walk you through each step that’s necessary in setting up a Magento 2 “Hello World” module. After you run through it a few times, you’ll start to get a hang of the new terminology, and what code needs to go where.

A Note on “Cache Clearing”

Magento 2, like most modern frameworks, uses a number of different cache files to speed up slow running operations. These caches are meant to make a production system run faster — but this is often at the expense of Magento not picking up changes to configuration or source files.

It’s often necessary to clear your cache when creating a new feature on a Magento 2 system. You can do this via the bin/magento CLI application (cache:clean), the GUI (System -> Cache Management), or by deleting the cache files (in var/cache/ — unless you’re running an alternative cache storage engine).

In addition to these cache files, Magento 2 also generates a number of boilerplate classes for you on-the-fly. These files are generated in the var/generate folder. When you change certain configurations or code files, it’s often necessary to regenerate these generated code files. There are not, at this time, any features (CLI or GUI) for doing this. The only way to clear the generated code folders is to manually delete the files in var/generation.

We’ll try to make a note whenever you need to clear your cache and generated code files — but if you’re running into something you think should be working, but isn’t working, it’s always worth clearing your cache and generated code files, and then trying to reload the page/re-run the command. A quick

Magento 2 Hello World Module

Setup a new page at the URL http://magento.example.com/hello_mvvm/hello/world that displays the message Hello World inside the default Magento 2 frontend/cart theme

From a high level, the steps we’ll need to take to accomplish this are.

Create a Magento module to hold our code

Configure this module with a route for a URL

Create a class for our controller object

Create a full action name layout handle XML file

Use the full action name layout handle XML file to add a new block to the content container

Create a template for our block

Some of the terminology above we’ve already talked about. Some of it we haven’t. Some of it may seem familiar from Magento 1, but enough has changed that you may want to blank your mind on how things worked in Magento 1. While there’s a lot of places in Magento 2 where your Magento 1 knowledge will be invaluable, assuming something works the same as it did in Magento 1 may not lead to the outcome you’re looking for.

Creating a Magento 2 Module

In Magento 2, a module allows programmers to add new code to the system in a structured way. When you add your code to Magento via a module, the system code knows where to look for it. Modules also define rules for using PHP namespaces to avoid conflicts with other developer’s code. Modules are first class citizens in Magento 2 — the core team itself uses modules to implement all the frontend-cart and backend-admin features.

Modules live in the app/code folder. Every module in Magento 2 has unique name that’s made up of two parts. The first part is a word that describes the company, individual, or group that built the extension. This is sometimes called the “vendor” namespace. The second part of a module’s name is a word that describes what the module does.

For example, one module that ships with Magento 2 is named Magento_Contact. The first part, Magento, describes the group that built the extension (the Magento core team). The second part, Contact, describes what the extension does (adds a contact form to Magento).

For our tutorial we’re going to create a module named Pulsestorm_HelloWorldMVVM. To create this module, create the following file, with the following content, in your Magento system

All modules are located in the app/code folder, in a folder structure based on the module’s full name (Pulsestorm_HelloWorldMVVM – Pulsestorm/HelloWorldMVVM).

A Magento 2 module will contain many XML configuration files, and configuration files live in etc. This module.xml file is the main module configuration file. This file is what Magento’s core code looks for when scanning the system for modules.

The <module/> node tells Magento we want to add a module to the system. The name attribute tells Magento what that module’s name is, and the setup_version attribute tells Magento which version of our module this is. This version node is important to Magento’s setup-resource/migration system, but we won’t be covering that today.

With the above in place, there’s one last step we need to take, and that’s adding our module to the global module list at app/etc/config.php. If you look at this file

You’ll see a PHP include file with a long list of modules. This file is here so the core code doesn’t need to do a literal directory scan of app/code/* on every page request. Let’s add our module to the end of the array

Merchant Beta Changes Update: Quick interruption here from editorial. The Magento core team made some significant changes to Magento’s module architecture after the merchant beta, and if you’re using a system built on the official Magento 2.0 release, then there’s one more step you’ll need to take. In addition to the files mentioned above, every module also needs a registration.php file that looks like this

These files are identical for all modules, with the exception of the Pulsestorm_HelloWorldMVVM text. This should be your module’s full name (combining the package name of Pulsestorm and the vendor name of HelloWorldMVVM).

Why is this here here? It’s part of how Magento identifies which modules are installed into a system. This replaces the old Magento 1 app/etc/module files, and takes the ultimate source of truth for module presence away from app/etc/config.php.

Installation Troubleshooting

There’s a few different ways to check if a module’s installed correctly. One is to use the CLI application’s module:status command

Although unrelated to enabling modules, this section does contain a list of every module in the system.

One final note on module creation. After installing a new module into the system, you may see the following error

Please upgrade your database: Run "bin/magento setup:upgrade"
from the Magento root directory.
The following modules are outdated:
//...

Without getting too deep into the reasons for it, this is a warning from Magento’s setup resource migration system. It’s telling you that the configured version of the module does not match the last version ran by the setup resource migration scripts.

Don’t worry if that didn’t make sense — just run the following command from the terminal

$ php bin/magento setup:upgrade

and the error will go away.

Adding a Controller Action

With a module created, that’s one item crossed off our list

Create a Magento module to hold our code

Configure this module with a route for a URL

Create a class for our controller object

Create a full action name layout handle XML file

Use the full action name layout handle XML file to add a new block to the content container

Create a template for our block

Next, we’re going to tackle configuring a route for a URL, and creating a class for our controller object. Before we do that, there’s a quick step we’ll want to take that will make our lives much easier.

One of the neat things about Magento 2 is many features that only existed in Enterprise Edition have been brought to Community Edition. This includes the full page caching feature. While this is a great feature for production systems, it’s a bit of a pain in the behind to have turned on when you’re setting up a new URL. You can turn this feature off by logging into the admin and going to

System -> Cache Management

and clicking the checkbox next to Page Cache, choosing “Disable” from the dropdown menu, and then clicking the Submit button.

If you choose to leave full page caching on, you’ll need to delete the full page cache between every request for the rest of this tutorial. These cache files are in the

var/page_cache

folder. This is a separate cache system from Magento’s normal cache and generated code folders.

With full page caching off, we’re ready to proceed.

Routing in Magento 2

We’re going to make Magento 2 respond to the following URL

http://magento.example.com/index.php/hello_mvvm/hello/world

The index.php portion is optional — if you have mod_rewrite (or your web server’s equivalent) enabled, this URL is the same as

http://magento.example.com/hello_mvvm/hello/world

In Magento 2, each individual module can claim a “front name”. The “front name” is the first segment of the URL — in our case that’s hello_mvvm. When a module “claims” a front name, that is its way of saying

Hello Magento systems code — if you see any URLs that start with /hello_mvvm, I have controllers for them

To have our module “claim” the hello_mvvm front name, add the following configuration file.

This new routes.xml file is where we add the configuration that tells Magento our module wants to claim a front name. Notice that this file lives in a sub-folder named frontend. The Magento 2 system allows developers to create multiple “application areas”. Areas control things like what sessions load, what access control rules get checked, and which configuration files get loaded. The frontend area is the cart application. If that didn’t make complete sense yet, don’t worry, just put your file in the specified location and you’ll be all set.

As for this file’s contents, the top level (after the root node) <router/> node contains all our routes.

<router id="standard">
<!-- ... -->
</router>

The id="standard" attribute is a little confusing. If you’re setting up a URL for the frontend area, you’ll want to use the standard router. If you’re setting up a URL for the admin console application, you’ll want to use id="admin" instead. The reasons for this are mostly historical, and we’ll (hopefully) cover them in a future article.

Each individual <route/> node tells Magento we want to claim a particular front name. The value of the frontName attribute is the actual text of the URL’s first segment we want to claim. i.e. frontName="hello_mvvm" tells Magento we want to claim URLs that look like

http://magento.example.com/hello_mvvm/*

The id attribute of the route node uniquely identifies this node across all Magento 2 modules installed in the system. This id is also sometimes called the route’s name. By convention, and 99.9% of the time, this id value should match the frontName. If you don’t know why that’s true — you don’t need to know why that’s true. Just make them the same and you’ll be happier for it.

Within the route node you’ll find

<module name="Pulsestorm_HelloWorldMVVM" />

This name attribute node should be the name of our module. With the above configuration in place, we’re ready to move on to creating our controller file.

Creating a Controller File

Magento 2 uses a traditional PHP “transform the URL into a controller class name” approach to controller naming. Let’s take another look at our URL

http://magento.example.com/hello_mvvm/hello/world

To come up with a controller class name, Magento 2 will look at the second and third URL segments (hello, and world above, respectively). That is

The class name starts with Pulsestorm\HelloWorldMVVM since we configured Pulsestorm_HelloWorldMVVM to claim the hello_mvvm front name.

Then we append Controller, because we’re defining a controller (Pulsestorm\HelloWorldMVVM\Controller)

Then Magento appends the second URL segment (hello) with an upper casing (Pulsestorm\HelloWorldMVVM\Controller\Hello)

Then Magento appends the third URL segment (world) with an upper casing (Pulsestorm\HelloWorldMVVM\Controller\Hello\World)

That gives us a final controller name of Pulsestorm\HelloWorldMVVM\Controller\Hello\World. Let’s create the class file now!

The base class Magento\Framework\App\Action\Action is the standard base class for frontend controllers, (the name “action” is derived from the historical name “action controller”).

In Magento 2, each controller has one, and only one, entry point. That’s the execute method. This is a step Magento 2’s architects took to help avoid conflicts with a large development teams all editing the same controller file for different features.

With the above in place, try loading the following URL in your Magento system (after clearing your generated code and configuration folders, of course)

http://magento.example.com/hello_mvvm/hello/world

If you’ve followed the steps above correctly, you should see the following output.

You Did It
string 'Pulsestorm\HelloWorldMVVM\Controller\Hello\World::execute' (length=57)

Congratulations — you just created your first Magento 2 controller!

Passing off to the View

Earlier we stated a controller’s job was

Deciding which page layout to use

Handling saving data from POST requests

Either

Telling the system to render the HTTP response

Or redirecting users to the next/previous page

Today we’re only interested in the Telling the system to render the HTTP response responsibility. In Magento 2, if you want a controller to render an HTML page, you need to have the controller’s execute method return a “page” object. This is a three step process

We’ll get to what we did in this file in a second, but try (after clearing your cache and clearing the contents of var/generation) reloading your page. You may be confused, as all you’ll see is the method name from our var_dump statement

The problem here isn’t incorrect code — it’s that we haven’t instructed the layout system what it needs to do on the http://magento.example.com/hello_mvvm/hello/world page. Because of that, Magento’s rendered the outer HTML skeleton, but no content. We’ll get to how we instruct the layout system what it needs to do, but only after we explain what we’ve done above.

The first two changes to the controller file are

use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\App\Action\Context;

These lines aren’t strictly needed, but they will allow us to use the short class name PageFactory and Context for these two classes below. If you’re not familiar with PHP namespace, our short primer is a great place to start.

This is Magento 2’s automatic constructor dependency injection in action — if you’re not familiar with it you may want to work your way through our object manager series. The short version is, Magento has some magic code that will automatically create objects for you if you type hint them in a constructor. So, this is how we create our PageFactory object, and assign it to our new pageFactory property.

Even if you’re familiar with automatic constructor dependency injection, the $context variable above may confuse you. This is here because it’s also in the parent object’s (Magento\Framework\App\Action\Action) constructor, and we need to call parent::__construct to make sure any work in the parent constructor still happens.

Here we use the PageFactory object to create a page object, and then return that page object.

Creating the View

If we come back to our to do list

Create a Magento module to hold our code

Configure this module with a route for a URL

Create a class for our controller object

Create a full action name layout handle XML file

Use the full action name layout handle XML file to add a new block to the content container

Create a template for our block

We’ll see there’s only three steps left. Let’s get to it!

For end-user-programmers (us!) Magento’s Page Layout system is controllable via an XML based domain specific language. In less engineering speak, this means we can create XML files with a list of instructions for the layout system. Covering this system in full is beyond the scope of this (or even a new single) article, but if you have questions the comments and Stack Exchange are a great place to ask.

We’re going to blitz through this to get a working example in place, and then cover what we did afterwards.

With the above in place, clear your cache and generated code folders, reload the page, and you should see our Hello World title, surrounded by the Magento design.

What Just Happened

The next few sections cover everything we just did above — this is probably the most confusing part of the article so far. We’re introducing new terminology and redefining old Magento 1 terminology. You don’t need to fully comprehend these sections to proceed, but they’re a great jumping off point for deeper dives into Magento’s core systems code.

Even if the next few sections are a little over your head, you’ll definitely want to skip ahead to the View/View Model section near the end.

This is something called a full action name layout handle XML file. As we mentioned, developers communicate with Magento’s Page Layout system via instructions placed in an XML file. This is that XML file. A “layout handle” is sort of like an event or notifier for the Page Layout System. Certain handles “fire” on each page, and these handles tell Magento which Layout Handle XML Files it should load.

Every controller action page fires a full action name handle. A full action name is a string that combines the configured <route/> ID (hello_mvvm, usually identical to the front name) and the second and third URL segments (hello and world). In our case, the full action name is hello_mvvm_hello_world.

So, when we create a file named hello_mvvm_hello_world.xml, we’re telling Magento

If the handle hello_mvvm_hello_world is issued, use the layout instructions in this file.

Every module in Magento has a view folder for view related files. Under view are folders for each individual application area (frontend above), and within the area folder are folders for specific asset types (layout files, templates files, etc.)

Magento, please create a block object using the class Pulsestorm\HelloWorldMVVM\Block\Main. This block should render with the template in the content.phtml file, and let’s give the block a globally unique name of pulsestorm_helloworld_mvvm

A block’s name should be a globally unique string, and can be used by other code to get a reference to our block object. Speaking of blocks and templates, they’re up next!

Creating a Block Class

As mentioned previously, a Magento 2 page layout is a collection of nested containers and blocks. Above, using the full action name layout handle XML file, we told Magento we wanted to insert a Pulsestorm\HelloWorldMVVM\Block\Main block. When we created the following file, we were defining that block class.

A Magento block class is responsible for rendering an HTML string that’s a portion of the entire HTML page. The base block class above, Magento\Framework\View\Element\Template, is Magento’s base template class. When a block extends from this class, it will render the HTML contents in its template property. We set this template property (content.phtml) in the full action name layout handle XML file.

A block’s file path, like all PHP classes in Magento, is determined by the rules of the PSR-0 autoloader.

Template files are considered view assets, so they belong in a module’s view folder. Like routes, and layout files, they’re specific to a Magento area (in this case, frontend), and they belong in the templates subfolder since, well, they’re templates!

Use the full action name layout handle XML file to add a new block to the content container

Create a template for our block

There is, however, one last thing to cover. Earlier we described Magento as a Model, View, View Model system, with Magento blocks acting as the View Model, and the phtml template acting as the view. Here’s how that works.

Open up your block object, and add the following to the _prepareLayout method.

As a view developer, it’s your job to fetch or calculate any data needed for the template. You can do this via the magic set and get methods above, or by defining methods directly on the block object that you can call from the phtml template.

This is a bit of a shift from traditional PHP MVC systems, where view variables are set from controller actions. By shifting to a Model, View, View Model system, Magento 2 has created more separation between what’s normally considered “business logic” (saving models) and “template logic” (populating variables for HTML generation).

While this move is probably a net positive for a large team of corporate consulting developers, each with their own responsibility, it’s also probably a net negative for full stack developers, who now have one more level of abstraction to traverse when working on new site features and extensions.

This MVVM pattern was possible in Magento 1, but Magento 1’s “Zend Framework” roots (and its too many chefs problem) led to a heavy bias towards a traditional MVC approach, using either the global registry or direct setting of block properties after calling loadLayout. It’s no longer possible to directly set block object properties in Magento 2, and while a registry object still exists in Magento 2, the unofficial recommendation is to avoid its use.

Regardless of whether Magento 2’s patterns elicit a “finally PHP gets it”, or a “WTF is this”, you’ve just successfully created a new landing page and application entry point in Magento 2. You’ve also been exposed to core Magento 2 concepts like clearing the cache, clearing generated files, and the hierarchy of configuration and view files in a Magento 2 module. You’re well on your way to unlocking the mysteries of Magento 2, and all the opportunities that will open for you in the future.