[ScreenCast] A Gentle Introduction To Magento 2 For Magento 1 Developers

If you are a visual learner, I highly recommend watching this tutorial. It’s short and sweet and covers many things:

  • module creation
  • routing
  • dependency injection
  • factories
  • code generation


Notes from the screencast

Although I always prefer video lessons, I completely understand when someone likes to learn from written words.

In the previous tutorial, we have created an extension that basically echos out Hello World when you visit first route. So let’s quickly see how it’s built and what is actually triggering this to appear on the screen.

Magento2 Module Structure

Modules are still defined under app/code, and we can immediately notice the first significant change – no more code pools are present. There are namespaces, all core modules live in Magento namespace and when you are creating your extension, you need to place it under your namespace as I did here, I called it MageClass and inside of it, we have a module called First.

Module Registration

In order to register your module, you don’t create xml file under etc folder as we used to do in Magento 1 – modules folder even doesn’t exist any more, that’s another difference. Instead you need to have a module.xml in your module with fairly simple declaration:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="MageClass_First" setup_version="1.0.0" />
</config>

Please ignore this overwhelming config tag, it’s a boilerplate code and I don’t want you to pay attention about it at all. A bit that is important here is the line, which has a fully qualified name of the module.

Module’s First Route

Next, because we have a custom route, called first we need to place some configuration in xml as you might expect. Now, in Magento 1, if you recall, we would declare routes tag in config.xml under frontend tag. In Magento2 though, that frontend node becomes a separate folder that contains routes file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
    <router id="standard">
        <route id="first" frontName="first">
            <module name="MageClass_First" />
        </route>
    </router>
</config>

And again, a bit more boilerplate code, the important part is this one where we have frontName and Module_Name. But in particular – no big changes here.

Brief Look at Controllers

And then, we have a registered module in place and our first route, let’s see how controllers look. Obviously it’s not controllers with lower-case c, it’s just Controller – singular with capital letter.

But before showing you how my controller works, let me discuss a bit how controllers are generally built in Mage2 on the example of Customer module.

Magento2 Customer Controller

So, here we see for example Account section and it has around 15 separate files, 15 separate controllers. Basically, what used to be an action in Magento1 in a single controller is now a dedicated controller.

And if we you open any of these – all of them has a method called execute(). And this is where real fun begins – Create controller has a constructor that accepts 4 arguments.

<?php

namespace Magento\Customer\Controller\Account;

class Create extends \Magento\Customer\Controller\Account
{
    public function __construct(
        Context $context,
        Session $customerSession,
        PageFactory $resultPageFactory,
        Registration $registration
    ) {
        $this->registration = $registration;
        parent::__construct($context, $customerSession, $resultPageFactory);
    }
    public function execute()
    {
        if ($this->_getSession()->isLoggedIn() || !$this->registration->isAllowed()) {
            /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */
            $resultRedirect = $this->resultRedirectFactory->create();
            $resultRedirect->setPath('*/*');
            return $resultRedirect;
        }

        /** @var \Magento\Framework\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        return $resultPage;
    }
}

That’s a big change and I guess it’s the right time to ask ourselves: What the hack is Context or Registration and above all – how does this all get passed to the constructor? Why do we have that execute method? When I first saw all of these, I was frustrated by all of these terms I knew nothing about. It’s so much different than Magento1.

Well, to decode all of this, let’s see how our controller looks – basically, it’s very similar to the Customer\Account\Create controller that we’ve just seen.

<?php 

namespace MageClass\First\Controller\Index;

class Index extends \Magento\Framework\App\Action\Action
{
    public function __construct(
        \Magento\Framework\App\Action\Context $context
    ) {
        parent::__construct($context);
    }
    
    public function execute()
    {  
        echo 'Hello World';
    }
}

We have a constructor and we typed some kind of object. We’re saying: we expect an instance of \Magento\Framework\App\Action\Context to be passed in. So if we open \Magento\Framework\App\Action – yes, there’s a file called Context and its class expects many instances in its constructor.

So what is very confusing is: how Magento knows to instantiate all of these classes? If we think about it in a simpler example:, if we have a class A that expects an instance of B in its constructor, in order to instantiate A, we need to say new A and then new B as an argument. Of course, assuming that class B does exist:

<?php

class A
{
	public function __construct(B $b)
	{
		// ...
	}
}

// instantiation
new A(new B);

But take note of the fact that nowhere in our module we did something like

new MageClass\First\Controller\Index\Index( /* new instances of all dependencies */)

We are not doing anything of this stuff. If we had to, it would be a real pain.

Magento 2 Relies on Reflection

So how does this all work? Well, Magento 2 makes use of PHP feature called Reflection which allows it to automatically get instances of the dependencies. So basically, what happens behind the scenes is – Magento will sort of pick in class constructor to figure out what’s going on and what we need. In other words, when a class is instantiated, Magento will inspect the arguments list and go ahead and instantiate them for us. And not only these; when it tries to instantiate Context object, it will notice Context’s dependencies, so it will go ahead and create all of them as well. And this will continue up recursively – it will just keep digging down until it instantiates all of the required dependencies for us!

What About Mage Class?

Ok, cool – but you might be thinking: Why don’t we just instantiate these objects by using good old Mage class and factory pattern like we always did? Well, that’s another important change – Mage class does not exist anymore. It’s replaced in Magento2 with the concept of dependency injection. So whenever you need an object of specific class, you need to inject it in the constructor, and the system will instantiate it for you, as we’ve seen.

There are many reasons for this change. Magento2 is trying to follow SOLID principles in which every class should have only one, single responsibility. We saw very thin controllers today, but honestly it’s not always the case, there are still many classes that are doing way too much in Magento2 and I hope it will be refactored until the final release.

But you get the idea, If a class instantiate an object on its own – that would break a very common software design principle called separation of concerns. In other words, our controller would know too much when it shouldn’t. It doesn’t need to know how to create an object – it just needs to know how to access it.

With that principle in mind, the code becomes decoupled and more maintainable as class responsibilities are clearly defined. Also, it becomes more testable as you can inject mock dependencies to isolate the code under the test. We’ll have videos dedicated to those subjects in the future, for now if you’d like to fully understand this, you need to understand ideas behind SOLID principles and for that I’m going to recommend another set of videos from the latest conference organized by PHPSerbia, called SOLIDay. All videos are available and they will get you in the mindset of thinking in terms of SOLID.

Adding Our Custom Dependency

Ok, let’s go back to our example and see how would we go about declaring our custom dependencies. What if we want to access any model class from our or any other module? So, to begin, within our constructor, just like we did with context, I am going to request an instance of \MageClass\First\Model\Test.

<?php 

namespace MageClass\First\Controller\Index;

class Index extends \Magento\Framework\App\Action\Action
{
	protected $_test;

    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \MageClass\First\Model\Test $test
	) {
    	$this->_test = $test;
        parent::__construct($context);
    }
    
    public function execute()
    {  
        echo 'Hello World';
    }
}

Now, this class does not exist yet, we’ll fix it shortly, let me go ahead and initialize this instance so we can access it later. Every dependency should be saved in a protected property for later use so that’s why we added

$this->_test = $test;

and of course:

protected $_test;

at the top. Generally, you will keep seeing this over and over, just remember the flow – you register a dependency as a constructor argument of particular type, you declare a protected property and save the instantiated object in the property for later use. That’s all there is too it for constructor dependency injection.

If we tries this in the browser, we know it’s going to fail, since we haven’t defined our Test model. So why don’t we go ahead and create one.

Adding Our Test Model

I’ll create Model folder and inside of it Test.php file with some boilerplate code:

<?php

namespace MageClass\First\Model;

class Test extends \Magento\Framework\Model\AbstractModel
{
	public function sayHi()
	{
		echo 'Hello World';
	}
}

I namespaced the class at the top,  a model class should extend AbstractModel in order to fully access model’s features and as we can see, there’s a simple function that echos Hello World.

We can call now our model function in execute method of the controller:

<?php 

public function execute()
{  
    $this->_test->sayHi();
}

This is still not going to work. In case you try this, you will get the exact same exception: Test model not found. So what’s the problem? We specified correct class type in the constructor and created it in the Model folder?

Cleaning /var/generation Directory

Well, the problem is in /var/generation. If we go there at this point, we see a new folder MageClass that we didn’t create. This is also a new concept in Mage2, in which the system generates many boilerplate code for you.

So if we take a look at MageClass->First->Controller->Index->Index->Interceptor, I want you to focus only on the constructor, it will look like it didn’t sync up with the latest changes – there won’t be $test dependency in the argument list. Basically, the file was generated in the first run and has never been updated. So as a rule of thumb, whenever you are in development mode and you work on a feature that should work, but for some reason it doesn’t, check var/generation folder and remove generated files.

A Brief Look At Factories

So now when things are starting to make a little more sense, I’d like to close this up by introducing another very important concept in Magento2 called factory. You’ll see many Factory suffixes in the code. So what are factories?

Well, by definition, factories are useful when you want to instantiate objects that can not be injected automatically, when the system doesn’t know how to instantiate a class for you. For example, let’s say that instead of displaying Hello World, we want to grab a product from the database and display its name. Well, in Mage1 we used to instantiate product model and call load with specific product ID.

In Magento2 though, it’s a bit different. You can’t really call load on product model even if you injected it in constructor. So if you have something like

public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \MageClass\First\Model\Test $test,
        \Magento\Catalog\Model\Product $product
    ) {
    	$this->_test = $test;
    	$this->_product = $product;
        parent::__construct($context);
    }

it would not work. You are not able to call load on this object.

So the way around this is to use productFactory. Rather than requesting Product model, I’m going to ask Magento to inject ProductFactory

public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \MageClass\First\Model\Test $test,
        \Magento\Catalog\Model\ProductFactory $productFactory
    ) {
    	$this->_test = $test;
    	$this->_productFactory = $productFactory;
        parent::__construct($context);
    }

and of course, we need to create that attribute at the top.

$this->_productFactory = $productFactory;

Now, this class productFactory doesn’t exist in the system, it’s going to be generated on the fly in var/generation.

Ok, so now, we’re going to ask our factory to create or instantiate a product object

public function execute()
    {  
        $product = $this->_productFactory->create();
        $product->load(2045);
        echo $product->getName();
    }

Now remember, whenever you edit your class that is generated in /var/generation, you need to remove it in order to force Mage to regenerate it with the latest source changes. If you try it in the browser, it should work.

Milan Stojanov is a certified Magento developer and Tuts+ author on Magento Fundamentals premium course. He likes to read and write about web development.

  • Pingback: #MagentoMonday Community Digest 2015.07.13 | Maxanoo()

  • Great resource! Hope there’s more to come:)

    • Milan Stojanov

      Thanks, I’m glad you liked it! Yes, definitelly – I’m on vacation at the moment; new episode should be out in around 10 days. Stay tuned. 🙂

  • Florin Poernbacher

    Thanks a lot for the video, i stay tuned for more Mage2 Vids!

  • break designs

    Great post! Simple and clear!
    keep it up the same way

  • Rocky Raccoon

    Hi Milan. Magento 2 was just released but there’s not as many tutorials yet on the web. What would You recommend for a beginner with some basic web development and php skills – is it still better to start from learning Magento1? Thanks!

    • Milan Stojanov

      Hi Rocky,

      You are completely right – at the moment, Magento2 learning resources list is pretty much empty. You can start with the following link https://github.com/creatuity/LearningMagento2. Also, make sure to follow #magento2 on Twitter. It will keep you up to date with all the latest articles/lessons etc on the web.

  • Yogesh

    Hello Milan,

    I am not cleared Why have you declare protected $_test in model Where it is used ?

    Thanks