Magento 2 Object Manager Plugin System

We’re finally here. After our long path through the object manager, automatic constructor dependency injection, class preferences, argument replacement, virtual types, proxy objects and code generation, we’re finally familiar enough with the object system to discuss the true replacement for Magento 1’s class rewrites — the plugin system.

No lollygagging about, let’s get to it.

Installing the Module

Like all our tutorials we’ve prepared a boilerplate Magento module to get us started. Grabthe latest tagged releases and manually install it into your system. If you’re not sure how to manually install a module, our first article in the series will set you right.

If you can successfully run the following command

$ php bin/magento ps:tutorial-plugin-installed
You've installed Pulsestorm_TutorialPlugin

you’re ready to start.

Magento 1 Class Rewrites

Magento 1 has a feature called “class rewrites”. This feature allows module developers to swap out the implementation of model, helper, or block class methods with their own class and method definitions. In Magento 1, each of these object types is created with a factory method

Mage::getModel('cms/page');    
Mage:helper('cms');
Mage::getModel('core/layout')->createBlock('cms/page');

Each of the strings above is a unique identifier for a specific class. For example, by default a cms/page model corresponds to the class Mage_Cms_Model_Page. Magento’s class rewrite system lets module developers say

Hey Magento — whenever you see a cms/page model, instantiate aMynamespace_Mymodule_Model_Some_Other_Page object instead of aMage_Cms_Modl_Page

Then, a module developer defines their own class to extend the original

class Mynamespace_Mymodule_Model_Some_Other_Page extends Mage_Cms_Page_Model
{
    //redefine any method here
}

In this way, the developer can redefine any method in the class they wanted to.

Class rewrites are popular because they allow a very specific redefinition of system functionality.

There are, however, some problems with class rewrites. One is a verbose Magento 1 XML configuration syntax that makes it easy to misconfigure a rewrite. The second is each class is only “rewritable” by a single module. Once one module claims a method, that means other modules are out of luck. This creates conflicts between modules that need to be fixed manually by someone with deep knowledge of both Magento and the two conflicting rewrites.

More than any specific issue though — the problem class rewrites solve (giving PHP a “duck-typing/monkey-patching system”) is not the problem they were used to solve (allowing third party developers to create modules and extensions that could listen for to and change any part of the Magento system).

As this series has shown, old style rewrites still exist in the form of class preferences and argument replacement — but Magento 2’s plugin systems sets out to solve the actualproblem extension developers needed solved.

Magento Plugins

Magento’s plugin system allows you, a module/extension developer, to

  • “Listen” to any method call made on an object manager controlled object and take programmatic action
  • Change the return value of any method call made on an object manager controlled object
  • Change the arguments of any method call made to an object manager controlled object
  • Do so while other modules are doing the same thing to the same method in a sane/predictable way

If you’ve never built a system like this before, you may hear listen to any method call and start having nightmares about performance characteristics. Fortunately, the Magento 2 core team has members who have built systems like this before, and they know exactly which software design patterns to apply to make this work. We’ll touch a little on the interceptorpatten in use here, but a full discussion is outside the scope of this article.

Our Program

We’re going to jump right in and create a new Magento plugin. Assuming you’ve installed the sample module, try running the following command.

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Result: hello hola!

If you’re still with us after all this time, this code should be self explanatory. If we jump to the command’s source

#File: app/code/Pulsestorm/TutorialPlugin/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $output->writeln(
        "\nWe're going to call the `getMessage` method on the class " . "\n\n" .
        '    ' . get_class($this->example) . "\n"
    );

    $result = $this->example->getMessage("Hola", true);
    $output->writeln('Result: ' . $result);
}

we see our command fetched the class name for $this->example using the get_classfunction. The $example property is a standard automatic constructor dependency injection parameter

#File: app/code/Pulsestorm/TutorialPlugin/Command/Testbed.php    
public function __construct(Example $example)
{
    $this->example = $example;
    return parent::__construct();
}

Then, the command calls this Example object’s getMessage method with two parameters, and outputs the result.

The

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage

text comes from the definition of getMessage inPulsestorm\TutorialPlugin\Model\Example.

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example.php    
public function getMessage($thing='World', $should_lc=false)
{
    echo "Calling the real " . __METHOD__, "\n";
    $string = 'Hello ' . $thing . '!';
    if($should_lc)
    {
        $string = strToLower($string);
    }
    return $string;
}

The purpose of this debugging code will become clear in a moment.

What we have here is a pretty standard, if silly, example program. If you’re having trouble following along, you might want to review the series so far.

Creating the Plugin

We want to create a Magento 2 Plugin for the getMessage method of thePulsestorm\TutorialPlugin\Model\Example class. It’s inevitable that the larger community will start using the word “plugin” as a place-holder for “extension” or “module”, but in the language of the object manager, a “plugin” refers to a special class that’s listening for any public method call to another object.

We say any public method, but that’s not quite accurate. If the public method uses thefinal keyword, or the class itself uses the final keyword, you won’t be able to use a plugin. Outside of that restriction, any class and public method is fair game, including your classes, Magento core classes, and third party classes.

To create a plugin, add the following configuration to your di.xml file

#File: app/code/Pulsestorm/TutorialPlugin/etc/di.xml
<config>   
    <!-- ... -->

    <type name="Pulsestorm\TutorialPlugin\Model\Example">
        <plugin name="pulsestorm_tutorial_plugin_model_example_plugin" 
                type="Pulsestorm\TutorialPlugin\Model\Example\Plugin" 
                sortOrder="10" 
                disabled="false"/>
    </type>
</config>

Once again, we’re creating a <type/> configuration. The name of the type should be the class whose behavior you’re trying to change.

Under type is the <plugin/> node. Here, the name should be a unique identifier that distinguishes this plugin configuration from every other plugin configuration in the world. The name is freeform, but you should use some combination of module name and description to make sure the name is unique.

The type of the plugin is a PHP class name. This PHP class will be your plugin class, and where we’ll define our custom behavior. We’ll want to create this class as well, but for now lets leave it empty of actual methods. To do so, create the following class file in the following location

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php
<?php
namespace Pulsestorm\TutorialPlugin\Model\Example;
class Plugin
{
}

Magento 2 convention dictates that this class’s short name should be \Plugin, although any class name will do.

Jumping back to di.xml, the sortOrder attribute controls how your plugin interacts with other plugins on the same class — we’ll talk more about this later.

A disabled attribute of true allows you to leave a plugin configuration in your di.xml, but have Magento ignore it. We include it here for completeness’s sake.

The above is all we need for a fully configured plugin. It’s a plugin that doesn’t do anything, but it’s a plugin nonetheless. Before we continue, lets clear our cache

$ php bin/magento cache:clean
Cleaned cache types:
config
layout
block_html
collections
db_ddl
eav
full_page
translate
config_integration
config_integration_api
config_webservice

and try running our program again.

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Result: hello hola!

You’ll notice the behavior of getMessage remains the same. However, the object manager no longer returns a Pulsestorm\TutorialPlugin\Model\Example object when we ask for a Pulsestorm\TutorialPlugin\Model\Example type. Instead, we’re now dealing with an interceptor class (Pulsestorm\TutorialPlugin\Model\Example\Interceptor).

While a full explanation is beyond the scope of this article, the interceptor pattern is how Magento has implemented their plugin system — and this is one of the side effects of that pattern. Whenever you see an interceptor class, you’re dealing with a class under observation by a Magento plugin.

The class itself (Pulsestorm\TutorialPlugin\Model\Example\Interceptor) is automatically generated by Magento, and can be found in the var/generation folder. This is the same sort of code generation we saw with proxy objects.

#File: var/generation/Pulsestorm/TutorialPlugin/Model/Example/Interceptor.php
class Interceptor extends \Pulsestorm\TutorialPlugin\Model\Example implements \Magento\Framework\Interception\InterceptorInterface
{
    //...
}

As you can see, the generated intercepter extends the originalPulsestorm\TutorialPlugin\Model\Example class, which means the interceptor object will behave the same as the original Pulsestorm\TutorialPlugin\Model\Example(except where it’s been extended to work with the plugin system). At the end of the day you shouldn’t need to think about interceptors. They’re just a class that sits between the programmer (you) and the original class we’re “plugging” into. Interceptors are what allow Magento’s plugin system to work.

Speaking of which, let’s take a look at exactly what plugins will let you do.

Plugins: After Methods

As mentioned previously, a Magento 2 plugin is sort of like an observer for class method calls. We’re going to start with an after plugin method that the system will call after thegetMessage method is called. Add the following method to your empty Plugin class

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php
<?php
namespace Pulsestorm\TutorialPlugin\Model\Example;
class Plugin
{
    public function afterGetMessage($subject, $result)
    {
        echo "Calling " , __METHOD__,"\n";
        return $result;
    }    
}

and then run the command again.

$ php bin/magento ps:tutorial-plugin

We're going to calls the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::afterGetMessage
Result: hello hola!

If you did everything correctly, you should see the debugging output

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::afterGetMessage

Congratulations — you just implemented your first Magento after plugin method.

An after plugin method gets its name by concatenating the word after to the actual method name, with the whole thing camel cased.

'after' + 'getMessage' = 'afterGetMessage'

An after plugin method has two parameters. The first ($subject above), is the object the method was called on (in our case, that’sPulsestorm\TutorialPlugin\Model\Example\Interceptor object). The second parameter ($result above) is the result of the original method call.

Since we returned $result above, our after plugin method didn’t change any behavior. Let’s try actually changing something. Edit your afterGetMessage method so it matches the following

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
public function afterGetMessage($subject, $result)
{        
    return 'We are so tired of saying hello';
} 

Run the command with the above in place, and you should see the following

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::afterGetMessage
Result: We are so tired of saying hello

Congratulations! You just used a plugin to change the value returned by the getMessagemethod.

Plugins: Before Methods

While the after plugin methods should be your go-to method for hooking into system behavior, they’re not appropriate for every situation. Let’s take a look at our getMessagecall again

#File: app/code/Pulsestorm/TutorialPlugin/Command/Testbed.php
$result = $this->example->getMessage("Hola", true);

You’ll notice we used a hard coded string as an argument here, which means, even withsystems like automatic constructor dependency injection, there’s no way to change this argument before the getMessage method gets called.

This sort of problem is what the before plugin methods can solve. Let’s try changing ourPlugin class to match the following. (removing our after plugin method for simplicity’s sake)

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
<?php
namespace Pulsestorm\TutorialPlugin\Model\Example;
class Plugin
{   
    public function beforeGetMessage($subject, $thing='World', $should_lc=false)
    {
        echo "Calling " . __METHOD__,"\n";
    }        
}

Run the command with the above in place, and you should see the following output

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::beforeGetMessage
Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Result: hello hola!

That is, our debugging text in beforeGetMessage is printed out before the call to the realgetMessage.

Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::beforeGetMessage
Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage

Similar to after plugin methods, the before plugin method naming convention is the word before concatenated with a camel case version of the original method, and the first parameter is the original object ($subject above). However, unlike the after plugin method, there’s no $result parameter. That’s because (in retrospect, obviously) we haven’t called our real method yet, so there can’t be a return/result value.

A before plugin method does have additional parameters.

public function beforeGetMessage($subject, $thing='World', $should_lc=false)

These second, third (fourth, fifth, etc) parameters are a copy of the original method parameters ($thing and $should_lc above). These should match the parameters from theoriginal method definition, including default values.

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example.php
public function getMessage($thing='World', $should_lc=false)
{
    echo "Calling the real " . __METHOD__, "\n";
    $string = 'Hello ' . $thing . '!';
    if($should_lc)
    {
        $string = strToLower($string);
    }
    return $string;
}

The reason these parameters are here is a before plugin method will let you change the values of the arguments before passing them along to the real method. You do this by returning an array of new arguments. Give the following a try

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
public function beforeGetMessage($subject, $thing='World', $should_lc=false)
{
    return ['Changing the argument', $should_lc];
}  

The code above replaces the first argument ($thing) with the string 'Changing the argument'. It also passes on the value of $should_lc without changing it. Run our command with the above in place, and you should see the new argument (Changing the argument) used in the actual method call to getMessage.

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Result: hello changing the argument!    

As you can see, in addition to being able to take programmatic action before a method is called, the before plugin methods let us change the arguments passed into the method.

The before plugin methods are slightly more dangerous that the after plugin methods. By changing the value of an argument, you may change the behavior of existing code in an undesirable way, or uncover a bug in a method that previously hadn’t surfaced. You’ll want to use extra caution when using before plugin methods, and make absolutely sure there isn’t another way to achieve your goal.

Plugins: Around Methods

There’s one last plugin listener type to cover, and that’s the around plugin methods. Thebefore methods fire before, the after methods fire after, and the around methods fireduring, or as a replacement to the original method.

If that’s a little confusing, an example should set us straight. Make your plugin code match the following (again, removing our previous before plugin method for the sake of simplicity)

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php
<?php
namespace Pulsestorm\TutorialPlugin\Model\Example;
class Plugin
{
    public function aroundGetMessage($subject, $procede, $thing='World', $should_lc=false)
    {
        echo 'Calling' . __METHOD__ . ' -- before',"\n";
        $result = $procede();
        echo 'Calling' . __METHOD__ . ' -- after',"\n";
        return $result;
    }  
}

If we run our program the debugging echo calls should make the execution order clear.

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::aroundGetMessage -- before
Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Calling Pulsestorm\TutorialPlugin\Model\Example\Plugin::aroundGetMessage -- after
Result: Hello World!

The around plugin methods give you the ability, in a single place, to have code run bothbefore the original method, and after the original method. How it does this is in the magic of the second parameter

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
public function aroundGetMessage($subject, $procede, $thing='World', $should_lc=false)

This second parameter (named $procede above) is an anonymous function (i.e. a PHP Closure). If you, as a plugin developer, are using an around plugin method, you call/invoke this closure

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
$result = $procede();

when you want the system to call the original method.

This is a powerful technique. It also means you can cancel the call to the original method, and substitute your own return value. Give the following a try.

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
<?php
namespace Pulsestorm\TutorialPlugin\Model\Example;
class Plugin
{
    public function aroundGetMessage($subject, $procede, $thing='World', $should_lc=false)
    {
        echo 'Calling' . __METHOD__ . ' -- before',"\n";
        //$result = $procede();
        echo 'Calling' . __METHOD__ . ' -- after',"\n";
        return 'New return value';
    }  
}

Run our program with the above in place, and you should see the following

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

CallingPulsestorm\TutorialPlugin\Model\Example\Plugin::aroundGetMessage -- before
CallingPulsestorm\TutorialPlugin\Model\Example\Plugin::aroundGetMessage -- after
Result: New return value

You’ll notice that both the result to our getMessage method call has changed, and we’ve lost the debugging code that let us know the real getMessage ran.

While the around plugin methods give you the power to completely replace a piece of system functionality, this also means that you’re responsible for making sure your new code is a suitable replacement for the code you’re replacing. While every developer and every team will need to forge their own path here, speaking for myself I’ll be treading lightly on the around plugin methods.

Playing Nice with Others

While the implementation is different, the object manager’s plugin system allows developers to do most of what the old class rewrite system did. For example — in Magento 1 an around plugin method situation might look something like this

#File: app/code/Pulsestorm/TutorialPlugin/Model/Example/Plugin.php    
<?php

class Namespace_Module_Model_Someclass extends Mage_Core_Model_Someclass
public function someMethod($foo, $baz, $bar)
{
    $this->_doSomeBeforeStuff($foo, $baz, $bar);
    $result = parent::someMethod($foo, $baz, $bar);
    $result = $this->_doSomeAfterStuff($result);
    return $result;        
}

The difference with plugins is, since we’re not actually inheriting from the original class, we

  1. Con: Lose access to calling protected methods on $subject
  2. Pro: Avoid accidentally changing the behavior of the subject
    class by redefining methods

However, where plugins really distinguish themselves from class rewrites is in the system’s ability to mesh together plugins that observe the same method. Magento 1 is rife with real world conflicts where two modules/extensions try to rewrite the same class, with confusing and disastrous results.

We’ve prepared a quick example of this. Let’s change our di.xml file so it looks like this (removing the above plugin configuration)

<!-- File: app/code/Pulsestorm/TutorialPlugin/etc/config.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <!-- START: argument replacement that enables our command -->
    <type name="Magento\Framework\Console\CommandList">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="Pulsestorm\TutorialPluginTestbedCommand" xsi:type="object">Pulsestorm\TutorialPlugin\Command\Testbed</item>
            </argument>
        </arguments>
    </type>    
    <!-- END: argument replacement that enables our command -->

    <!-- START: two new plugins -->
    <type name="Pulsestorm\TutorialPlugin\Model\Example">
        <plugin name="pulsestorm_tutorial_plugin_model_conflict_plugin1" 
                type="Pulsestorm\TutorialPlugin\Model\Conflict\Plugin1" 
                sortOrder="10" 
                disabled="false"/>
    </type>

    <type name="Pulsestorm\TutorialPlugin\Model\Example">
        <plugin name="pulsestorm_tutorial_plugin_model_conflict_plugin2" 
                type="Pulsestorm\TutorialPlugin\Model\Conflict\Plugin2" 
                sortOrder="15" 
                disabled="false"/>
    </type>       
    <!-- START: two new plugins -->
</config>

Here, we’ve configured two plugins on thePulsestorm\TutorialPlugin\Model\Example class, and each plugin has an afterplugin method defined for the getMessage method.

#File: app/code/Pulsestorm/TutorialPlugin/Model/Conflict/Plugin1.php
public function afterGetMessage($subject, $result)
{
    echo "Calling " , __METHOD__ , "\n";
    return 'From Plugin 1';
}

#File: app/code/Pulsestorm/TutorialPlugin/Model/Conflict/Plugin2.php    
public function afterGetMessage($subject, $result)
{
    echo "Calling " , __METHOD__ , "\n";
    return 'From Plugin 2';
}

If we run our program with the above configuration (after clearing our cache), we’ll see the following output.

$ php bin/magento ps:tutorial-plugin

We're going to call the `getMessage` method on the class 

    Pulsestorm\TutorialPlugin\Model\Example\Interceptor

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Calling Pulsestorm\TutorialPlugin\Model\Conflict\Plugin1::afterGetMessage
Calling Pulsestorm\TutorialPlugin\Model\Conflict\Plugin2::afterGetMessage
Result: From Plugin 2

There’s two interesting things here. First — thePulsestorm\TutorialPlugin\Model\Conflict\Plugin2 plugin “won” the return value game. Unlike other systems/patterns (Drupal’s hooks, for example), Magento’s plugin system still has a winner-take-all situation when it comes to return values. The reasonPlugin2 “won” is because it had a higher sortOrder (15 vs. 10)

<!-- File: app/code/Pulsestorm/TutorialPlugin/etc/di.xml -->
<plugin name="pulsestorm_tutorial_plugin_model_conflict_plugin1" 
        type="Pulsestorm\TutorialPlugin\Model\Conflict\Plugin1" 
        sortOrder="10" 
        disabled="false"/>

<plugin name="pulsestorm_tutorial_plugin_model_conflict_plugin2" 
        type="Pulsestorm\TutorialPlugin\Model\Conflict\Plugin2" 
        sortOrder="15" 
        disabled="false"/>

However, if we look at our debugging methods

Calling the real Pulsestorm\TutorialPlugin\Model\Example::getMessage
Calling Pulsestorm\TutorialPlugin\Model\Conflict\Plugin1::afterGetMessage
Calling Pulsestorm\TutorialPlugin\Model\Conflict\Plugin2::afterGetMessage

We see that Plugin1 still has its after plugin method called. This means that state and behavioral system changes due to other plugin code still happen, but that one plugin’s results still win out.

Here’s another bit of interesting behavior. Change the two plugin methods so they match the following

#File: app/code/Pulsestorm/TutorialPlugin/Model/Conflict/Plugin1.php 
public function afterGetMessage($subject, $result)
{
    echo "Calling " , __METHOD__ , "\n";    
    echo "Value of \$result: " . $result,"\n";
    return 'From Plugin 1';
}

#File: app/code/Pulsestorm/TutorialPlugin/Model/Conflict/Plugin2.php     
public function afterGetMessage($subject, $result)
{
    echo "Calling " , __METHOD__ , "\n"; 
    echo "Value of \$result: " . $result,"\n";   
    return 'From Plugin 2';
}

And then run the program

$ php bin/magento ps:tutorial-plugin

//...
Calling Pulsestorm\TutorialPlugin\Model\Conflict\Plugin1::afterGetMessage
Value of $result: hello hola!
Calling Pulsestorm\TutorialPlugin\Model\Conflict\Plugin2::afterGetMessage
Value of $result: From Plugin 1
Result: From Plugin 2

As you can see, each after plugin method is aware of the return value from the other plugin.

While not fool proof, the plugin system greatly reduces the surface area for potential extension conflicts, and gives end users a pure configuration approach (sortOrder) for solving conflicts on their own.

The Magento “Public API”

There’s one last thing to cover w/r/t the object manager’s plugin system, something that’s more political than it is technical. If you’ve been looking at Magento 2 source code, you may notice that certain class methods have an @api doc block annotation

#File: app/code/Magento/Widget/Model/Widget.php
/**
 * Return widget config based on its class type
 *
 * @param string $type Widget type
 * @return null|array
 * @api
 */
public function getWidgetByClassType($type)
{
    //...
}

This @api isn’t something that affects the running of your program. Instead, it’s a documentation flag from the Magento core team that says

We promise this method will be there in future versions of Magento 2

While I can’t speak for other developers — after half a decade plus of building Magento extensions this sort of “public API” is a welcome addition to the platform. Knowing which methods you can safely use in a plugin, or even just call, is a big win for extension developers, and should help tremendously with the “new release of Magento causes fatal errors in my extension/module” problem.

As to whether all extension developers respect this public API setting or not, that should be an “An Interesting Technically Obtuse Challenge™”.

Caveats and Wrap up

Before we wrap things up, there’s a few random caveats to mention that we don’t have time to dive deeply into today. Feel free to ask about the items below in the comments here, or atthe Magento Stack Exchange if it’s technically in-depth.

First, the interceptor class that makes this all possible is generated code, which means if the class you’re observing with a plugin changes — you’ll need to regenerate the interceptor.

Next, if you fail to call the $procede() closure in an around plugin method, not only are you skipping a method call to the original class method, you’re also skipping any plugins that have a higher sort order. I’m not wild about this tradeoff, and I expect it to be a cause of consternation among early extension developers.

Next, with plugins (vs. rewrites) you lose the ability to change the behavior of a protectedmethod. Some will see this as a con (less flexibility), and some will see this a pro (respecting the protected access levels). It you need to change the behavior of a protected method, class preferences or argument replacement are more suitable tools.

Finally, there’s no great rule of thumb for tracking a return value through the variousbefore, around, and after methods, and if you’re deep in the weeds doing this your first step should probably be reconsidering a plugin as your solution. For the stubborn masochists, Christof/FoomanNZ has a series of tests that dive into this, and the code that runs through all configured plugins calling before, around, and after methods is found here

#File: lib/internal/Magento/Framework/Interception/Interceptor.php    
protected function ___callPlugins($method, array $arguments, array $pluginInfo)
{
    //...
}    

And with that, this series is brought to a close. Magento 2’s big bet on Enterprise Software Design Patterns™ has less to do with what the community will do with Magento, and more to do with wrangling a large development team towards the unified goal of a Q4 2015 Magento 2 release. While it’s important that you understand that basics of how the object manager and Magento’s automatic constructor dependency injection system works — it’s up to you to decide how much of your own code will mirror the patterns in Magento, or if these patterns will be the industrial scaffolding you hang your own, simpler code on.

Magento 2 Object Manager: Instance Objects

It’s been a long and winding road, but the end is in sight! In this, the penultimate article in our object manager tutorial, we’re going to discuss working with instance and non-injectable objects in Magento 2.

This article assumes a basic familiarity with Magento 2 concepts, and may brush past something complicated. If you’re confused you may want to start from the beginning, or use the comment system below to point to your Magento Stack Exchange question.

Let’s get to it!

Sample Code

We’ve prepared a small sample Magento 2 module that we’ll reference and use in this article. The module’s on GitHub, and if you’re not familiar with manually installing Magento 2 modules the first article in this series has a good overview for installing a module via one of the tagged releases

To test that you’ve installed the module correctly, try running the following command

$ php bin/magento ps:tutorial-instance-objects
You've installed Pulsestorm_TutorialInstanceObjects!

If you see the You've installed Pulsestorm_TutorialInstanceObjects! message, you’re all set.

Shared/Unshared, Singleton/Instance

Back in our first article, we introduced two object manager methods for instantiating objects

$object  = $manager->create('Pulsestorm\TutorialInstanceObjects\Model\Example');
$object  = $manager->get('Pulsestorm\TutorialInstanceObjects\Model\Example');

The create method will instantiate a new object each time it’s called. The get method will instantiate an object once, and then future calls to get will return the same object. This behavior is similar to Magento 1’s getModel vs. getSingleton factories

Mage::getModel('group/class');         // ->create, or instance object
Mage::getSingleton('group/class');     // ->get,    or instance object

What we didn’t cover was how the automatic constructor dependency injection system decides which method to use when it encounters a constructor parameter. Consider a constructor that looks like this.

//...
use Pulsestorm\TutorialInstanceObjects\Model\Example;
//...
public function __construct(Example $example)
{
    //is $example created with `get` or `create`?
    $this->example = $example?
}

We know the parameter will be aPulsestorm\TutorialInstanceObjects\Model\Example object, but we don’t know if it will be a new instance of a Pulsestorm\TutorialInstanceObjects\Model\Exampleobject, or the same Pulsestorm\TutorialInstanceObjects\Model\Example object passed/injected into other constructors.

By default, all objects created via automatic constructor dependency injection are “singleton-ish” objects — i.e. they’re created via the object manager’s get method.

If you want a new instance of an object, i.e. you want the object manager to use create, you’ll need to add some additional <type/> configuration to your module’s di.xml file.

If we wanted the object manager to instantiatePulsestorm\TutorialInstanceObjects\Model\Example as an instance object every time, we’d add the following configuration to our di.xml

<!-- File: app/code/Pulsestorm/TutorialInstanceObjects/etc/di.xml --> 
<config>
    <!-- ... -->
    <type name="Pulsestorm\TutorialInstanceObjects\Model\Example" shared="false">
        <!-- ... arguments/argument tags here if you want to change injected arguments -->
    </type>
</config>

This is the same <type/> tag we saw back in our argument replacement tutorial. The nameattribute should be the name of the class whose behavior you want to change.

The new-to-us attribute is shared. If shared is set to false, then Magento 2 will use thecreate method to instantiate an object every time it encountersPulsestorm\TutorialObjectManager1\Model\Example as an automatically injected constructor argument. The shared attribute has no effect on objects instantiated directly via PHP’s new keyword, or the object manager’s two methods.

This attribute is named shared due to an implementation detail in the object manager. When you use get to instantiate an object, the object manager stores all the already instantiated objects in a _sharedInstances array.

#File: lib/internal/Magento/Framework/ObjectManager/ObjectManager.php
public function get($type)
{
    $type = ltrim($type, '\\');
    $type = $this->_config->getPreference($type);
    if (!isset($this->_sharedInstances[$type])) {
        $this->_sharedInstances[$type] = $this->_factory->create($type);
    }
    return $this->_sharedInstances[$type];
}

When you configure a specific type (i.e. a specific PHP class) with shared="false", you’re telling Magento 2 that you don’t want to use this _sharedInstances array.

So, all you need to remember here is shared="true" is the default, and you’ll get a singleton-ish/global object. If you change your type configuration to shared="false", the automatic constructor dependency injection system will start instantiating a new parameter every-time a programmer instantiates the attribute’s owner object.

Magento 2 Factories

While the shared attribute is a useful bit of duct tape for those times you need an injected dependency to be a brand new instance object, it’s not an ideal solution for every (or even most) cases where you don’t want singletons.

One problem with shared is the injected dependency is still dependent on its owner object being shared or un-shared. There’s lots of times where you just need a new instance of an object and might not be able to refactor your object relationships to accomplish that.

A perfect example of this are CRUD data objects, such as Magento’s CMS page objects or the catalog product objects. In Magento 1 you’d create a CMS page object like this

Mage::getModel('page/cms')->load($id);

In Magento 2, these sorts of objects are called “non-injectables”. Dependency injection is meant for objects that “do some thing”, or “provide some service”. These data objects, however, are meant to “identify this specific thing”. What we’re going to look at next is how to use these sorts of objects without automatic constructor dependency injection tying our hands.

First, there’s nothing stopping you from directly instantiating an object via PHP’s newmethod

$product = new \Magento\Cms\Model\Page;

However, if you were to do this, your CMS Page object loses out on all of Magento’s object manager features.

Fortunately, the Magento 2 core developers haven’t abandoned us. In Magento 2, you instantiate these sorts of non-injectable objects via factory objects. Like so much in Magento 2, an example is worth 1,000 words.

#File: app/code/Pulsestorm/TutorialInstanceObjects/Command/Testbed.php

public function __construct(
    \Magento\Cms\Model\PageFactory $pageFactory = 
)
{
    $this->pageFactory = $pageFactory;
    return parent::__construct();
}
//...
public function execute(InputInterface $input, OutputInterface $output)
{
    $page = $this->pageFactory->create();
    foreach($page->getCollection() as $item)
    {
        $output->writeln($item->getId() . '::' . $item->getTitle());
    }

    $page = $this->pageFactory->create()->load(1);        
    var_dump($page->getData());
}

Here’s what’s going on: In the __constructor method, the command uses Magento’s automatic constructor dependency injection to create a Magento\Cms\Model\PageFactoryobject, and then (per Magento convention) assigns that object to the pageFactory property.

#File: app/code/Pulsestorm/TutorialInstanceObjects/Command/Testbed.php
public function __construct(
    \Magento\Cms\Model\PageFactory $pageFactory = 
)
{
    $this->pageFactory = $pageFactory;
}

Then, in execute, we use this factory object to create a CMS page object (using the factory’s create method)

$page = $this->pageFactory->create();    

In Magento 1, the above is roughly equivalent to

$page = Mage::getModel('cms/page');

In Magento 1, we had a set of factory methods (getModel, ‘helper’, ‘createBlock’). In Magento 2 — every non-injectable object has its own factory object.

You can find the factory for any model class by appending the text Factory to that model class’s name. Above we wanted to instantiate Magento\Cms\Model\Page objects, so the factory class was

Object we Want: Magento\Cms\Model\Page
Factory to Use: Magento\Cms\Model\PageFactory

Similarly, here’s the two classes for a product object

Object we Want: Magento\Catalog\Model\Product         
Factory to Use: Magento\Catalog\Model\ProductFactory

Once we use a factory to instantiate a class, we have most (if not all) of our old Magento 1 CRUD methods available to us (load, getData, getCollection, etc.)

#File: app/code/Pulsestorm/TutorialInstanceObjects/Command/Testbed.php
$page = $this->pageFactory->create();
foreach($page->getCollection() as $item)
{
    $this->output($item->getId() . '::' . $item->getTitle());
}

$page = $this->pageFactory->create()->load(1);        

This may take a little getting used to, but compared to the error prone XML configuration needed to use Magento 1’s factory methods, this is already a big win.

Factory Definitions and Code Generation

There’s one last thing to cover about factory objects in Magento 2 that might answer a few questions in your head

  • Ugh! I need to define a bunch of boiler plate factory code? Lame.
  • Where can I see what a factory object looks like?

If we start with the second and more adult question, you may be in for a small surprise. Based on the factory’s full class name, (Magento\Cms\Model\PageFactory), you might expect to find it at one of the following locations

app/code/Magento/Cms/Model/PageFactory.php
lib/internal/Magento/Cms/Model/PageFactory.php

However, neither of these files exist.

That’s because Magento 2 uses automatic code generation to create factory classes. You may remember this code generation from the proxy object article. If you’ve actually run the code above, you’ll find the PageFactory class in the following location.

var/generation/Magento/Cms/Model/PageFactory.php

While the specifics are beyond the scope of this article, whenever

  • PHP encounters a class name that ends in Factory
  • And the autoloader can’t load that class because there’s no definition file

Magento 2 will automatically create the factory.

If you’re curious how this happens this Stack Exchange answer showing theMagento/Framework/Code/Generator/Autoloader kickoff point is a good place to start.

Factories for All

Factories aren’t just for Magento core code — they’ll work with any module class. The sample module we had you install includes aPulsestorm\TutorialInstanceObjects\Model\Example object. Let’s replace the__construct method with one that adds a factory class for the Example object.

//...
use Pulsestorm\TutorialInstanceObjects\Model\ExampleFactory;
//...
class Testbed extends Command
{
    protected $exampleFactory;
    public function __construct(ExampleFactory $example)
    {
        $this->exampleFactory = $example;
        return parent::__construct();
    }
}

Then, we’ll use that factory in execute.

protected function execute(InputInterface $input, OutputInterface $output)
{
    $example = $this->exampleFactory->create();
    $output->writeln(
        "You just used a"                . "\n\n    "
        get_class($this->exampleFactory) . "\n\n" . 
        "to create a \n\n    "           . 
        get_class($example) . "\n"); 
}

Run our command with the above execute method in place, and you should see the following.

$ php bin/magento ps:tutorial-instance-objects
You just used a

    Pulsestorm\TutorialInstanceObjects\Model\ExampleFactory

to create a 

    Pulsestorm\TutorialInstanceObjects\Model\Example

As you can see, this code ran without issue, despite our never defining aPulsestorm\TutorialInstanceObjects\Model\ExampleFactory class. You can find the factory definition in the generated code folder

#File: var/generation/Pulsestorm/TutorialInstanceObjects/Model/ExampleFactory.php
<?php
namespace Pulsestorm\TutorialInstanceObjects\Model;

/**
 * Factory class for @see \Pulsestorm\TutorialInstanceObjects\Model\Example
 */
class ExampleFactory
{
    protected $_objectManager = null;

    protected $_instanceName = null;

    public function __construct(
        \Magento\Framework\ObjectManagerInterface $objectManager, 
        $instanceName = '\\Pulsestorm\\TutorialInstanceObjects\\Model\\Example'
    )
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
    }

    public function create(array $data = array())
    {
        return $this->_objectManager->create($this->_instanceName, $data);
    }
}

As for the specific implementation — right now a factory’s create method accepts an array of parameters and users the object manager to create the object. However, future versions of Magento may change how these factories work. By having the framework generate these factories Magento 2 saves us the error prone busy work of coding up this boilerplate and the core team maintains control over how the factories work.

Wrap Up

With this article and its six predecessors complete, we have a pretty good understanding of Magento’s object system, how that system impacts the shape of the Magento code base, and (most importantly) the basic understanding that’s critical if we want to apply reason to Magento 2 code in the real world.

There is, however, one last thing we need to cover, and that’s the object plugin system. The plugin system is the true successor to Magento 1’s class rewrite system, and with a solid understanding of the object manager and automatic constructor dependency injection, we’re ready to tackle this next time in our final Object Manager tutorial.

Magento 2 Object Manager: Proxy Objects

  1. With their take on an object manager/container and dependency injection, Magento have created a new way for PHP programmers to work. With that new way of working comes a new set of unanticipated challenges. Put another way, new patterns create new problems.

    Fortunately, Magento 2’s extended development cycle has given the core team time to address some of these problems with — you guessed it — more design patterns and more object manager features.

    Today we’re going to take a look the problem of ill-performant code while using other people’s objects. Along the way we’ll discuss Magento 2’s use of the proxy pattern to solve this problem, as well as our first real discussion of Magento 2’s code generation features.

    We’re deep in the weeds here — while you may find something of value as a newcomer, you’ll definitely want to get familiar with the basics of Magento’s object system to get the full value out of the article.

    Installing the Module

    Rather than have you spend 30 minutes copying and pasting code, we’ve created two modules that simulate a common problem you’ll run into with Magento automatic constructor dependency injection. The modules (Pulsestorm_TutorialProxy1 andPulsestorm_TutorialProxy2) are available on GitHub

    The official installation procedure for a Magento module is still being worked out, so we recommend installing these tutorial modules manually using the latest tagged release. If you’re not sure how to install a module manually, the first article in this series has the instructions you’re looking for.

    To test that you’ve installed the module correctly, try running the following command

    $ php bin/magento ps:tutorial-proxy
    You've installed Pulsestorm_TutorialProxy2!
    You've also installed Pulsestorm_TutorialProxy1!
    

    If you see output telling you you’ve installed both modules, you’re ready to go!

    Slow Loading Dependencies

    These two modules simulate a common situation when using other people’s code — you’d like to make use of the their objects, but their code includes slow loading dependencies that you don’t need.

    Let’s open up the command class and comment out our install check and uncomment the other lines

    #File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        //$this->installedCheck($output);
        $service = $this->createService($output);        
        $this->sayHelloWithFastObject($service, $output);
        $this->sayHelloWithSlowObject($service, $output);
    }
    

    The first thing this code does ($this->createService($output);), is create aPulsestorm\TutorialProxy1\Model\Example object using the object manager.

    #File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
    protected function createService($output)
    {
        //...
        $om = $this->getObjectManager();
        //...
        $service = $om->get('Pulsestorm\TutorialProxy1\Model\Example');
        //...
        return $service;    
    }
    

    We’re using the object manager directly here for simplicity’s sake — in real Magento 2 code you’ll create most of your objects via automatic constructor dependency injection. Since automatic constructor dependency injection uses the object manager, everything we show you today will be available to your injected objects as well.

    After instantiating that object, we call the two sayHello... methods

    #File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
    protected function sayHelloWithFastObject($service, $output)
    {
        //...
        $service->sayHelloWithFastObject();
        //...
    }
    
    protected function sayHelloWithSlowObject($service, $output)
    {
        //...
        $service->sayHelloWithSlowObject();
        //...        
    }
    

    All these methods do is pass on method calls to thePulsestorm\TutorialProxy1\Model\Example object. Also, and omitted above, these methods contain some simple profiling code that will let us know how long each method took to run. We’ve also dropped similar profiling code in the Example service object and its dependencies.

    If we run our command, we should see output something like the following

    $ php bin/magento ps:tutorial-proxy
    About to Create Service
    Constructing FastLoading Object
    Constructing SlowLoading Object
    Created Service, approximate time to load: 3005.656 ms
    
    About to say hello with fast object
    Hello
    Said hello with fast object, approximate time to load: 0.0172 ms
    
    About to say hello with slow object
    Hello
    Said hello with slow object, approximate time to load: 0.0491 ms
    

    While we’ll eventually get to all the output, the line we care about right now is this one

    Created Service, approximate time to load: 3005.656 ms
    

    Something is taking over 3 seconds (3000 milliseconds) to load. In our example, this three second load is simulated using a PHP sleep statement — but in the real world slow loading constructors can be murder to objects musing dependency injection.

    Digging Deeper

    Let’s take another look at our execute method

    #File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        //this->installedCheck($output);
        $service = $this->createService($output);        
        $this->sayHelloWithFastObject($service, $output);
        $this->sayHelloWithSlowObject($service, $output);
    }
    

    At this level of abstraction, all we’re doing is

    • Creating a Pulsestorm\TutorialProxy1\Model\Example object
    • Calling that object’s sayHelloWithSlowObject andsayHelloWithFastObject methods

    If we take a quick look at the source for our Example object

    #File: app/code/Pulsestorm/TutorialProxy1/Model/Example.php
    namespace Pulsestorm\TutorialProxy1\Model;
    //...
    public function __construct(FastLoading $fast, SlowLoading $slow)
    {
        $this->fast = $fast;
        $this->slow = $slow;
    }
    

    We see it has two dependencies — Pulsestorm\TutorialProxy1\Model\FastLoadingand Pulsestorm\TutorialProxy1\Model\SlowLoading. In turn, thesayHelloWithFastObject and sayHelloWithSlowObject methods

    #File: app/code/Pulsestorm/TutorialProxy1/Model/Example.php
    public function sayHelloWithFastObject()
    {
        $this->fast->hello();
    }
    
    public function sayHelloWithSlowObject()
    {
        $this->slow->hello();
    }  
    

    pass on a call to the hello method of the fast and slow arguments (now stored as object properties).

    We’ve also added profiling to

    1. The creation of the Example object
    2. The calling of both sayHelloWithFastObject andsayHelloWithSlowObject

    We’ve also added some debugging messages to the constructors of our FastLoading andSlowLoading classes.

    #File: app/code/Pulsestorm/TutorialProxy1/Model/FastLoading.php
    public function __construct()
    {
        echo "Constructing FastLoading Object","\n";
    }
    
    #File: app/code/Pulsestorm/TutorialProxy1/Model/SlowLoading.php
    public function __construct()
    {
        echo "Constructing SlowLoad Object","\n";
        //...
    }
    

    Finally, in the __construct method of the SlowLoading class, we’ll see the following

    #File: app/code/Pulsestorm/TutorialProxy1/Model/SlowLoading.php
    
    public function __construct()
    {
        echo "Constructing SlowLoading Object","\n";
        //simulate slow loading object with sleep
        sleep(3);
    }
    

    That is — we’ve placed a three second sleep in the constructor to simulate a slow loading object.

    The end result is, when we run our command, we have a detailed report of what’s going on, and how long it’s taking.

    $ php bin/magento ps:tutorial-proxy
    About to Create Service
    Constructing FastLoading Object
    Constructing SlowLoading Object
    Created Service, aproximate time to load: 3000.7651 ms
    
    About to say hello with fast object
    Hello
    Said hello with fast object, approximate time to load: 0.0162 ms
    
    About to say hello with slow object
    Hello
    Said hello with slow object, approximate time to load: 0.052 ms
    

    Based on the above, it’s taking approximately three seconds to create aPulsestorm\TutorialProxy1\Model\Example object. We now have a crude, if exaggerated, version of the sort of performance problem you might run into in the real world.

    Why so Slow?

    The first question we need to answer is

    Why is the Example class loading slowly?

    Based on what we’ve learned in this article so far, it’s the SlowLoading objects that are our slow ones, not the Example object.

    The problem here is Magento’s automatic constructor dependency injection system. If we take another look at the Example object’s constructor

    #File: app/code/Pulsestorm/TutorialProxy1/Model/Example.php
    namespace Pulsestorm\TutorialProxy1\Model;
    //...
    public function __construct(FastLoading $fast, SlowLoading $slow)
    {
        $this->fast = $fast;
        $this->slow = $slow;
    }
    

    We see two objects injected. Per Magento’s automatic constructor dependency injection, we know this means before the object manager instantiates aPulsestorm\TutorialProxy1\Model\Example object, it will need to instantiate both aPulsestorm\TutorialProxy1\Model\FastLoading andPulsestorm\TutorialProxy1\Model\SlowLoading object.

    So, our problem isn’t really a slow loading Example object — it’s a slow loadingPulsestorm\TutorialProxy1\Model\SlowLoading argument. This distinction might seem trivial, but it will be important in the solution Magento’s object manager provides.

    Magento 2 Proxy Objects

    The Magento core team must have run into this sort of a problem a lot — because not only do they have a solution for it, but that solution is baked into the object manager.

    Magento’s solution is an implementation of the proxy pattern, used here to defer the loading of an object’s arguments.

    If that didn’t make sense, don’t worry, we’ll guide you through the process step by step, and then explain what we did. If that did make sense to you, you’ll still want to proceed carefully — there’s some Magento 2 specific magic that, at first glance, makes everything a little confusing. However, once you’ve created a few proxy objects the magic should start making sense.

    As previously discussed, our problem is

    When Magento uses a Pulsestorm\TutorialProxy1\Model\SlowLoading object as an argument in a Pulsestorm\TutorialProxy1\Model\Example object, this slows down the instantiation of the Pulsestorm\TutorialProxy1\Model\Example object.

    The solution to this is, via Magento’s di.xml configuration, to replace thePulsestorm\TutorialProxy1\Model\SlowLoading argument with a proxy object. The proxy object will not suffer from the same slow loading as the original argument. Said another way, we’re going to use plain old argument replacement to replace the problem object with a different, special object called a proxy.

    To do this, add the following nodes to the Pulsestorm_TutorialProxy2 module’s di.xmlfile.

    #File: app/code/Pulsestorm/TutorialProxy2/etc/di.xml
    @highlightsyntax@xml
    <config>
        <!-- #File: app/code/Pulsestorm/TutorialProxy2/etc/di.xml -->
        <!-- Notice: we're using `TutorialProxy2`'s di.xml to change the
             behavior of `TutorialProxy1`.  This the far more common usage
             of object manager/dependency-injection than the examples in 
             our tutorial so far -->
    
        <type name="Pulsestorm\TutorialProxy1\Model\Example">
            <arguments>
                <argument name="slow" xsi:type="object">Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxy</argument>
            </arguments>        
        </type>
    </config>
    

    This is (mostly) standard argument replacement. We’re targeting the arguments in thePulsestorm\TutorialProxy1\Model\Example object, specifically the argument namedslow, and replacing it with a Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxyobject.

    The one new thing is the Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxyclass/object. We’re going to need to do a little hand waving and say “trust us” on what this is. All you need to know for now is, since we’re replacing thePulsestorm\TutorialProxy1\Model\SlowLoading class, we replace it with aPulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class (i.e. — we append\Proxy to the initial class name).

    If you clear your cache

    $ php bin/magento cache:clean 
    Cleaned cache types:
    config
    layout
    block_html
    view_files_fallback
    view_files_preprocessing
    collections
    db_ddl
    eav
    full_page
    translate
    config_integration
    config_integration_api
    config_webservice
    

    and re-run the command with the above in place, you should see the following.

    $ php bin/magento ps:tutorial-proxy
    About to Create Service
    Constructing FastLoading Object
    Created Service, aproximate time to load: 6.0711 ms
    
    //...
    

    That is — we’ve gone from 3000 ms to around 6 ms. I’d say that’s a substantial improvement. Run the command again though

    $ php bin/magento ps:tutorial-proxy
    About to Create Service
    Constructing FastLoading Object
    Created Service, aproximate time to load: 0.736 ms
    
    //...
    

    And you’ll see an even more dramatic improvement. Your times may vary in their specificity, but the ratios/pattern should be similar. Congratulations! You’ve successfully used a proxy to improve the construction time of a Magento object manager object.

    What Just Happened

    Of course, this raises myriad questions. A few might be

    1. We never defined aPulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class, so what is it? A virtualType? Something else?
    2. The proxy pattern is about substituting one object for another — why does this improve performance?
    3. Why did we need to run the command twice to see the full performance improvements?

    All valid questions, and they all get to the heart of the magic involved in Magento’s proxy objects.

    The answer to the first question: ThePulsestorm\TutorialProxy1\Model\SlowLoading\Proxy configuration is not a virtual type — it’s a plain old PHP class. The reason we didn’t need to create this class on our own is, Magento automatically generates proxy class files for us. Take a look at the following file

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php    
    <?php
    namespace Pulsestorm\TutorialProxy1\Model\SlowLoading;
    
    /**
     * Proxy class for @see \Pulsestorm\TutorialProxy1\Model\SlowLoading
     */
    class Proxy extends \Pulsestorm\TutorialProxy1\Model\SlowLoading
    {
        //...
    }
    

    This is the Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class we configured, and Magento generated. Notice it’s in the var/generation folder.

    When Magento’s object manage encounters a class whose “short name” is Proxy (i.e. whose full class name Ends\In\Proxy), it will automatically generate a proxy class like this one.

    If you don’t believe us, try deleting this file

    $ rm var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    $ ls -l var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    ls: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php: No such file or directory
    

    and then re-run the command

    $ php bin/magento ps:tutorial-proxy
    //...
    $ ls -l var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    -rw-rw-rw-  1 alanstorm  staff  2350 Aug  9 16:32 var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php        
    

    The file’s been restored after running our command! It’s not just di.xml configuration that will trigger Magento code generation — it’s anytime the object Manager encounters a class whose short name is Proxy. Give the following a try — add the following temporary code to our execute method

    #File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
    protected function execute(InputInterface $input, OutputInterface $output)
    {
    
        $object_manager = $this->getObjectManager();
        $object = $object_manager->create('Pulsestorm\TutorialProxy1\Model\Example\Proxy');
        $output->writeln('You just instantiated a ' . get_class($object) . ' class'); 
        $output->writeln('You also indirectly told Magento to create this class in the following location');
        $r = new \ReflectionClass($object);
        $output->writeln($r->getFilename());        
        exit;  
        //...
    }
    

    and then run it.

    $ php bin/magento ps:tutorial-proxy
    You just instantiated a Pulsestorm\TutorialProxy1\Model\Example\Proxy class
    You also indirectly told Magento to create this class in the following location
    /path/to/magento/var/generation/Pulsestorm/TutorialProxy1/Model/Example/Proxy.php
    

    So, above we told the object manager to create aPulsestorm\TutorialProxy1\Model\Example\Proxy object. The object manager, seeing that the class name ended in Proxy, automatically created a proxy object that extends Pulsestorm\TutorialProxy1\Model\Example

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/Example/Proxy.php
    <?php
    namespace Pulsestorm\TutorialProxy1\Model\Example;
    
    /**
     * Proxy class for @see \Pulsestorm\TutorialProxy1\Model\Example
     */
    class Proxy extends \Pulsestorm\TutorialProxy1\Model\Example
    {
    }    
    

    Be careful though, if you try to create a proxy for an object that doesn’t exist,

    $object_manager->create('Pulsestorm\TutorialProxy1\Model\Ghost\Proxy');
    

    the object manager will yell at you.

    $ php bin/magento ps:tutorial-proxy
    
    [Magento\Framework\Exception\LocalizedException]
    Source class "\Pulsestorm\TutorialProxy1\Model\Ghost" 
    for "Pulsestorm\TutorialProxy1\Model\Ghost\Proxy" generation does not exist.  
    

    While PHP frameworks have used automatic code generation for a while, it’s usually at the explicit request of a user (i.e. “make me a file”). In Magento 2, code generation exists to save a developer the time of writing a lot of the boiler plate code that accompanies “design patterns” style programming.

    This leads in well to our next topic: Why does a proxy object improve performance? Before we continue, don’t forget to remove the temporary object manager code from the executemethod.

    Magento’s Generated Proxy Objects

    From Wikipedia — the proxy pattern

    in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate.

    If we take a look at the generated code, we’ll immediately see why a proxy object gave our object instantiation the performance boost it did.

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    <?php
    namespace Pulsestorm\TutorialProxy1\Model\SlowLoading;
    
    class Proxy extends \Pulsestorm\TutorialProxy1\Model\SlowLoading
    {
        //...
        public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading', $shared = true)
        {
            $this->_objectManager = $objectManager;
            $this->_instanceName = $instanceName;
            $this->_isShared = $shared;
        }
        //...
    }
    

    The proxy object completely replaces the constructor of the proxied object, and does not make a parent::__construct call.

    Proxy objects (like all objects in Magento) are still subject to dependency injection, and all proxy objects will have the same three arguments

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading', $shared = true)
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
        $this->_isShared = $shared;
    }
    

    The first is an object manager instance, the second is the name of the class this object proxies, and the third is a shared argument. We’ll cover shared/unshared in a future article — for now just concentrate on the first two arguments.

    So, by replacing the entire constructor, we avoid any slow loading behavior.

    However — what if important things happen in the __construct method? What about the other properties assigned there? Aren’t we breaking thePulsestorm\TutorialProxy1\Model\SlowLoading object by doing this?

    Fortunately not. In addition to replacing the constructor, our proxy object also replacedeach public method of the original object.

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    /**
     * {@inheritdoc}
     */
    public function hello()
    {
        return $this->_getSubject()->hello();
    }
    

    If someone calls hello, the proxy will call _getSubject and pass on the call to hello. The_getSubject method

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get($this->_instanceName)
                : $this->_objectManager->create($this->_instanceName);
        }
        return $this->_subject;
    }
    

    will instantiate an instance of the original object! Remember, the _instanceName variable argument from the constructor? If we use some x-ray vision, this method actually looks like

    #File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get('\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading')
                : $this->_objectManager->create('\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading');
        }
        return $this->_subject;
    }
    

    In this way, Magento defers the loading of the slow loading object until it’s needed. In fact, if you re-examine our current output in its entirety.

    $ php bin/magento ps:tutorial-proxy
    About to Create Service
    Constructing FastLoading Object
    Created Service, aproximate time to load: 0.89 ms
    
    About to say hello with fast object
    Hello
    Said hello with fast object, approximate time to load: 0.0069 ms
    
    About to say hello with slow object
    Constructing SlowLoading Object
    Hello
    Said hello with slow object, approximate time to load: 3001.014 ms
    

    you’ll notice we still have a 3 second (3000 ms) delay, it’s just been deferred until we actually need the SlowLoading object

    About to say hello with slow object
    Constructing SlowLoading Object
    Hello
    Said hello with slow object, approximate time to load: 3001.014 ms
    

    So, in our silly example program, the proxy doesn’t save us much. In the real world, you’ll use proxy objects in situations where

    1. You have a slow loading dependency
    2. You know your particular use of this code doesn’t need the dependency

    Regardless of whether you choose to use proxies — they’re a part of the Magento 2 core system, and likely to show up in other people’s code, so make sure you understand them.

    Generation Caveats

    So, that’s questions one and two answered. As for the third

    Why did we need to run the command twice to see the full performance improvements?

    Some readers will have already figured this out. While code generation means we avoid having to create all the boiler plate code in our proxy class over and over again, there is a small performance cost involved (around 6 ms on my development machine). Once the code is generated, it stays generated — Magento 2 will only generate a file if it can’t instantiate the request object.

    So the first performance improvement

    [THREE SECONDS HERE]
    Created Service, aproximate time to load: 6.0711 ms
    
    //...
    

    was the proxy object skipping direct instantiation of the SlowLoading argument.

    The second performance improvement

    Created Service, aproximate time to load: 6.0711 ms
    Created Service, aproximate time to load: 0.736 ms
    

    was PHP not having to regenerate an already generated class.

    There’s another gotcha to code generation. If you change the original proxied class, Magento 2 won’t automatically re-generate any new public methods. This will create unpredictable behavior as you’ll still be able to call the new method (since the proxy object extends the original class), but the new methods won’t instantiate the subject object, and will cary a different state.

    During development it would be wise to periodically remove your generated folder

    var/generation
    

    to trigger re-generation of any code that needs it.

    For production systems — I expect this generated code folder will be one of those challenges early adopters will wrestle with. Magento 2 appears to offer two compilation commands) that will pre-generate code for the entire system — but these commands also go above and beyond generating code. Also, for anyone into scalable systems, on-the-fly generated code sets off all sort of trigger warnings for running in a multiple-app/frontend server environment. While these problems will, no doubt, be sorted out over the coming months and year(s), it’s a challenging climb ahead for Magento 2 devops engineers.

    Fortunately for us — we’re a series for programmers! Unfortunately for us, we’ve covered a lot of information today (proxies and code generation), and it’s probably a good idea to take a small breather. Next time our code generation exposure will come in handy as we explore shared/unshared objects, as well as Magento 2’s take on the age old factory pattern.

Magento 2 Object Manager Virtual Types

Last time we discussed the argument replacement feature of Magento 2’s object manager, and introduced the <type/> tag in di.xml. This week we’re going to talk about another feature related to argument replacement — Virtual Types.

Virtual types allow a module developer (you!) to create “configuration only classes” for the object manager’s argument replacement feature. If that didn’t make sense, don’t worry, by the end of this article you’ll understand how to read and trace <virtualType/>configurations, and have the information you’ll need to decide if they’re for you.

Warning: This article assumes you’ve been following along in our series, and have a general understanding of Magento’s automatic dependency injection feature, argument replacement, and the role of Magento’s object manager. This article makes direct use of the object manager to simplify concepts. In the real world Magento’s best practices dictate end-user-programmers (you!) not use the object manager directly. Instead you should rely on automatic constructor dependency injection for instantiating your objects. All the features we discuss will be available to objects created via automatic constructor dependency injection.

Additional Warning: Despite our attempts to simplify things, virtual types involve nested levels of automatic constructor dependency injection — if you’re having trouble grasping the concepts it’s not because you’re not smart, it’s because they’re complicated concepts and take time to understand.

Installing the Module

We’ve created a module with much of the boiler plate code you’ll need to get started with virtual types. The module is on GitHub. The official installation procedure for a Magento module is still being worked out, so we recommend installing these tutorial modules manually using the latest tagged release. If you’re not sure how to install a module manually, the first article in this series has the instructions you’re looking for.

To test that you’ve installed the module correctly, try running the following command

$ php bin/magento ps:tutorial-virtual-type
Installed Pulsestorm_TutorialVirtualType!

Once you see the Installed Pulsestorm_TutorialVirtualType! output, you’re ready to start.

The Object Setup

This module’s main purpose is to setup a few object relationships. At the top of our composition hierarchy, we have a Pulsestorm\TutorialVirtualType\Model\Exampleobject. This object’s class makes use of automatic constructor dependency injection

#File: app/code/Pulsestorm/TutorialVirtualType/Model/Example.php
<?php
namespace Pulsestorm\TutorialVirtualType\Model;
class Example
{
    public $property_of_example_object;
    public function __construct(Argument1 $the_object)
    {
        $this->property_of_example_object = $the_object;
    }
}

The automatic constructor dependency injection will inject aPulsestorm\TutorialVirtualType\Model\Argument1 object, which is then assigned to the property named property_of_example_object.

In turn, this Pulsestorm\TutorialVirtualType\Model\Argument1 class also uses automatic constructor dependency injection.

#File: app/code/Pulsestorm/TutorialVirtualType/Model/Argument1.php
<?php
namespace Pulsestorm\TutorialVirtualType\Model;
class Argument1
{
    public $property_of_argument1_object;
    public function __construct(Argument2 $the_argument)
    {
        $this->property_of_argument1_object = $the_argument;
    }
}

This time the object manager will inject aPulsestorm\TutorialVirtualType\Model\Argument2 object, which the constructor assigns to the object property named property_of_argument1_object.

In hierarchical terms, we have (using shorthand class names)

Example (contains a)
    Argument1 (contains a)
        Argument2

So far, all this is standard issue automatic dependency injection. If there’s a concept that’s confusing, you may want to review the series so far before asking questions in the comments or on Stack Overflow.

Reporting Command

Similar to our argument replacement article, we’ve prepared a simple program that reports on the above object hierarchy in real time using some of PHP’s reflection features. Open up the command class and find the execute method

#File: app/code/Pulsestorm/TutorialVirtualType/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $this->output = $output;
    $output->writeln("Installed Pulsestorm_TutorialVirtualType!");  
    //$this->showNestedPropertiesForObject();
}

Comment out the writeln line, and uncomment the call toshowNestedPropertiesForObject

#File: app/code/Pulsestorm/TutorialVirtualType/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $this->output = $output;
    $output->writeln("Installed Pulsestorm_TutorialVirtualType!");  
    //$this->showNestedPropertiesForObject();
}

If you run the command with the above in place, you’ll see the following output.

$ php bin/magento ps:tutorial-virtual-type
First, we'll report on the Pulsestorm\TutorialVirtualType\Model\Example object
The Property $property_of_example_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument1

Next, we're going to report on the Example object's one property (an Argument1 class)
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2

Finally, we'll report on an Argument1 object, instantiated separate from Example
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2

We’re not going to go too in-depth into how this reporting works, but if you look at theshowNestedPropertiesForObject method.

#File: app/code/Pulsestorm/TutorialVirtualType/Command/Testbed.php    
protected function showNestedPropertiesForObject()
{
    $object_manager = $this->getObjectManager();        
    $example         = $object_manager->create('Pulsestorm\TutorialVirtualType\Model\Example');
    $this->output("First, we'll report on the Pulsestorm\TutorialVirtualType\Model\Example object");        
    $properties     = get_object_vars($example);        
    foreach($properties as $name=>$property)
    {
        $this->reportOnVariable($name, $property);       
    }  

    $this->output("Next, we're going to report on the Example object's one property (an Argument1 class)");  
    $properties     = get_object_vars($example->property_of_example_object);        
    foreach($properties as $name=>$property)
    {
        $this->reportOnVariable($name, $property);       
    }  

    $this->output("Finally, we'll report on an Argument1 object, instantiated separate from Example");          
    $argument1  = $object_manager->create('Pulsestorm\TutorialVirtualType\Model\Argument1');
    $properties = get_object_vars($argument1);        
    foreach($properties as $name=>$property)
    {
        $this->reportOnVariable($name, $property);       
    }          
}                     

You’ll see the program

  1. Instantiates a Pulsestorm\TutorialVirtualType\Model\Example object via the object manager, and then reports on it
  2. Fetches the Example object’s property_of_example_object property and reports on the object inside of it (aPulsestorm\TutorialVirtualType\Model\Argument1 object)
  3. Finally, we use the object manager to directly instantiate aPulsestorm\TutorialVirtualType\Model\Argument1 object, and report on it.

We’ll use this reporting in the next section to analyze how our dependency injection configuration changes the system’s behavior.

Creating a Virtual Type

The stage is set. We’re ready to create our virtual type. As we mentioned in our introduction, creating a virtual type is sort of like creating a sub-class for an existing class

<?php
class OurVirtualTypeName extends \Pulsestorm\TutorialVirtualType\Model\Argument1
{
}

Except, we’re not doing it in code. To create a virtual type in Magento 2, just add the following configuration to the module’s di.xml

#File: app/code/Pulsestorm/TutorialVirtualType/etc/di.xml
<config>
    <!-- ... -->
    <virtualType name="ourVirtualTypeName" type="Pulsestorm\TutorialVirtualType\Model\Argument1">  
    </virtualType>        
</config>

The <virtualType/> nodes live directly under the main <config/> node. They have two attributes (name and type). The name attribute defines the name of our virtual type — this should be a globally unique identifier, similar to a class name. The type attribute is the realPHP class our virtual type is based on.

That’s all there is to defining a virtual type. Of course — simply defining a virtual type will have no effect on system behavior. If you clear your cache and re-run the command, the output will be exactly the same.

$ php bin/magento ps:tutorial-virtual-type
First, we'll report on the Pulsestorm\TutorialVirtualType\Model\Example object
The Property $property_of_example_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument1

Next, we're going to report on the Example object's one property (an Argument1 class)
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2

Finally, we'll report on an Argument1 object, instantiated separate from Example
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2

Using the Virtual Type

The purpose of a virtual type is to take the place of PHP classes in argument replacement. For example, without virtual types, if we wanted to change the argument injected into theExample class’s constructor, we’d add a <type/> configuration something like this.

#File: app/code/Pulsestorm/TutorialVirtualType/etc/di.xml    
<config>
    <!-- ... -->
    <virtualType name="ourVirtualTypeName" type="Pulsestorm\TutorialVirtualType\Model\Argument1">  
    </virtualType>        

    <type name="Pulsestorm\TutorialVirtualType\Model\Example">
        <arguments>
            <argument name="the_object" xsi:type="object">Some\Other\Class</argument>
        </arguments>
    </type>        

</config>

Of course, if you tried running the command with the above configuration in place (after clearing your cache), you’d see an error like the following.

$ php bin/magento ps:tutorial-virtual-type

  [ReflectionException]                  
  Class Some\Other\Class does not exist  

That’s because the class we tried to inject (Some\Other\Class) is not defined. Let’s try changing our configuration to match the following

#File: app/code/Pulsestorm/TutorialVirtualType/etc/di.xml      
<config>
    <!-- ... -->
    <virtualType name="ourVirtualTypeName" type="Pulsestorm\TutorialVirtualType\Model\Argument1">  
    </virtualType>        

    <type name="Pulsestorm\TutorialVirtualType\Model\Example">
        <arguments>
            <argument name="the_object" xsi:type="object">ourVirtualTypeName</argument>
        </arguments>
    </type>        

</config>

Here we’ve replaced Some\Other\Class with ourVirtualTypeName. You might expect the above configuration to also cause an error, (since there’s no class namedourVirtualTypeName), except if you run our command with the above in place —

$ php bin/magento ps:tutorial-virtual-type
First, we'll report on the Pulsestorm\TutorialVirtualType\Model\Example object
The Property $property_of_example_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument1

Next, we're going to report on the Example object's one property (an Argument1 class)
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2

Finally, we'll report on an Argument1 object, instantiated separate from Example
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2

— there’s no error! That’s because there is a “class” named ourVirtualTypeName. It’s just not a real PHP class — instead it’s our virtual type. At their most basic level, virtual types allow you to create what amounts to a class alias for a PHP class, and use that alias in yourdi.xml configuration.

That said — our output still looks the same. It turns out that it takes more than the simple creation and use of a virtual type to have a measurable impact on our system.

Changing Virtual Type Behavior

Earlier we said creating a virtual type was sort of like creating a sub-class of another class.

class OurVirtualTypeName extends \Pulsestorm\TutorialVirtualType\Model\Argument1
{
}

If we created a real sub-class of another class, we could change all sorts of things (method definitions, properties, constants, traits, etc) about that class.

With virtual types, the only behavior you can change in your virtual sub-class is which dependencies are injected. If that’s not clear, give the following configuration a try.

#File: app/code/Pulsestorm/TutorialVirtualType/etc/di.xml    
<config>
    <virtualType name="ourVirtualTypeName" type="Pulsestorm\TutorialVirtualType\Model\Argument1">  
        <arguments>
            <argument name="the_argument" xsi:type="object">Pulsestorm\TutorialVirtualType\Model\Argument3</argument>
        </arguments>
    </virtualType>

    <type name="Pulsestorm\TutorialVirtualType\Model\Example">
        <arguments>
            <argument name="the_object" xsi:type="object">ourVirtualTypeName</argument>
        </arguments>
    </type>  
</config>

This configuration is identical to our previous configuration with one exception — we’ve added argument sub-nodes to our virtual type. Same as they would under a <type/> node, the <arguments/>, <argument/> nodes under the <virtualType/> node replaces the argument with the name “the_argument” with aPulsestorm\TutorialVirtualType\Model\Argument3 object. The argument nodes for a virtual type behave exactly as they would for a regular <type/> node — except they onlyeffect the virtual type and not the original parent class.

If that was hard to follow (and it was), try clearing your cache and running the command again

$ php bin/magento ps:tutorial-virtual-type
First, we'll report on the Pulsestorm\TutorialVirtualType\Model\Example object
The Property $property_of_example_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument1

Next, we're going to report on the Example object's one property (an Argument1 class)
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument3

Finally, we'll report on an Argument1 object, instantiated separate from Example
The Property $property_of_argument1_object
  is an object
  created with the class: 
  Pulsestorm\TutorialVirtualType\Model\Argument2     

This is the main selling point of virtual types. You’ll notice that theproperty_of_argument1_object property is now an Argument3 object — but only when that parameter’s owner class (Argument1) is instantiated by dependency injection in theExample class. When we instantiate Argument1 by itself — Magento does not inject a dependency.

Worth It?

On one hand, virtual types allow us even more specificity in argument replacement. Whereas regular argument replacement lets us effectively change the behavior of a class dependency when it’s used in a specific class — virtual types allow us to effectively change the behavior of a dependency when it’s used in a specific class — and when that specific class is, itself, used in a specific class. In theory, this is great, and offers the potential for greater system stability.

However, for day-to-day Magento development, I’m not sure virtual types will be worth the confusion. While there’s lots of programmers out there who can keep track of those three level deep dependencies in their head, in my own limited interactions with virtual types, I’ve had a hard time keeping track of what configuration injects what dependency, while also keeping track of the problem at hand.

Beyond being, perhaps, an abstraction too far, there’s another sort of confusion virtual types introduce — consider this di.xml configuration from the core code.

#File: app/code/Magento/Catalog/etc/di.xml
<type name="Magento\Catalog\Model\Session">
    <arguments>
        <argument name="storage" xsi:type="object">Magento\Catalog\Model\Session\Storage</argument>
    </arguments>
</type>

This appears to be a straight forward configuration for automatic constructor dependency injection — Magento will replace the storage constructor argument inMagento\Catalog\Model\Session with the PHP classMagento\Catalog\Model\Session\Storage.

Except — there is no PHP class Magento\Catalog\Model\Session\Storage.

If you look in the same di.xml file, you’ll see the following.

#File: app/code/Magento/Catalog/etc/di.xml 
<virtualType name="Magento\Catalog\Model\Session\Storage" type="Magento\Framework\Session\Storage">
    <arguments>
        <argument name="namespace" xsi:type="string">catalog</argument>
    </arguments>
</virtualType>

It turns out the Magento 2 core team has created virtual types with names that look like real PHP class names. While this helps ensure the names are globally unique — it can create confusion for developers who aren’t aware Magento\Catalog\Model\Session\Storageis a virtual type — especially developers who are still learning the ins and outs of Magento’s object manager system and class autoloading.

All in all, while I can see why the team responsible for creating Magento 2 might find value in virtual types — I’m not sure they’re the best choice for developers creating Magento stores and extensions — especially when there’s a much more powerful, and controllable means for extending Magento system behavior in the plugin system. This plugin system will be our final stop in the Magento object manager tutorial.

However, before we can get there, there’s a few loose ends to tie up. Next week we’ll be covering instance vs. singleton objects with dependency injection, creating non-injectable instance objects using factories, as well as Magento 2’s proxy objects.

Magento 2 Object Manager Argument Replacement

In the last article of our series, we covered the “class preference” feature of Magento’s object manager. This week we’ll cover a similar, but more selective, class replacement system built into Magento 2. While this article should be accesible as a stand-alone tutorial for programmers familiar with modern object oriented concepts, if you’re having trouble try starting at the beginning.

Installing the Module

Like our other articles, we’ve prepared a module that sets up the basic commands and classes we’re going to use. You can find the module on GitHub. As of this writing, the installation procedure for a Magento module isn’t 100% clear, so we recommend installing these tutorial modules manually using the latest tagged release. If you need help installing a Magento module manually, the first article in this series contains detailed instructions for doing so.

You’ll know you have the module installed correctly when you get the following output when running the ps:tutorial-object-preference command.

$ php bin/magento  ps:tutorial-object-manager-arguments
Installed!

Assuming you can run the ps:tutorial-object-preference command, we’re ready to start.

Constructor Arguments

We’re going to need a little more exposition before we can get to the meat of this article. The exposition should also serve as a good review of some basic Magento 2 concepts.

With the command installed, open up its definition file and examine the execute method

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $this->output = $output;
    $output->writeln("Installed!");          
    //$this->showPropertiesForObject();
}

You’ll notice the writeln command, along side a commented out call to the command’sshowPropertiesForObject method. Let’s change the command to call theshowPropertiesForObject command instead of outputting the Installed! text

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $this->output = $output;
    //$output->writeln("Installed!");          
    $this->showPropertiesForObject();
}

If you run the command now, you’ll see the following output

$ php bin/magento ps:tutorial-object-manager-arguments
The Property $object1
  is an object
  created with the class: 
  Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1

The Property $object2
  is an object
  created with the class: 
  Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument2

The Property $scaler1
  is a string
  with a value of: foo

The Property $scaler2
  is an integer
  with a value of: 0

The Property $scaler3
  is a boolean
  with a value of: false

The Property $thearray
  is an array
  with the elements:
  0=>foo

All this is simple enough, although probably a little confusing given our complete lack of context. Let’s take a look at the definition of showPropertiesForObject.

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Command/Testbed.php
protected function showPropertiesForObject()
{
    $object_manager = $this->getObjectManager();        
    $object         = $object_manager->create('Pulsestorm\TutorialObjectManagerArguments\Model\Example');
    $properties     = get_object_vars($object);
    foreach($properties as $name=>$property)
    {
        $this->reportOnVariable($name, $property);       
    }
}

This method is pretty straight forward. We fetch an instance of the object manager and use it to instantiate an object from thePulsestorm\TutorialObjectManagerArguments\Model\Example class.

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Command/Testbed.php
$object_manager = $this->getObjectManager();        
$object         = $object_manager->create('Pulsestorm\TutorialObjectManagerArguments\Model\Example');

Then, using PHP’s built-in global get_object_vars function, we fetch an array of theExample object’s properties, and then for each of these we pass the property to thereportOnVariable method

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Command/Testbed.php
$properties     = get_object_vars($object);
foreach($properties as $name=>$property)
{
    $this->reportOnVariable($name, $property);       
}

This reportOnVariable method produces the output we saw above.

The Property $object1
  is an object
  created with the class: 
  Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1

For each variable passed, the method will output the variable’s type, and then class and/or value (depending on the type). The implementation of reportOnVariable is beyond the scope of this article, but feel free to poke around if you’re feeling exploratory.

Going back to our command’s output

$ php bin/magento ps:tutorial-object-manager-arguments
The Property $object1
  is an object
  created with the class: 
  Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1

The Property $object2
  is an object
  created with the class: 
  Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument2

The Property $scaler1
  is a string
  with a value of: foo

The Property $scaler2
  is an integer
  with a value of: 0

The Property $scaler3
  is a boolean
  with a value of: false

The Property $thearray
  is an array
  with the elements:
  0=>foo 

We see our Example object has six properties. A quick look at its definition file should reveal this to be true

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Model/Example.php
<?php    
namespace Pulsestorm\TutorialObjectManagerArguments\Model;
class Example
{
    public $object1;
    public $object2;
    public $scaler1;
    public $scaler2;
    public $scaler3;
    public $thearray;

    public function __construct(
        ExampleArgument1 $object1,
        ExampleArgument2 $object2,
        $scaler1='foo',
        $scaler2=0,
        $scaler3=false,
        $thearray=['foo'])        
    {
        $this->object1 = $object1;
        $this->object2 = $object2;    

        $this->scaler1 = $scaler1;
        $this->scaler2 = $scaler2;
        $this->scaler3 = $scaler3;        
        $this->thearray   = $thearray;                
    }
}  

Here we’re dealing with a pretty standard issue Magento 2 class file. Six variables are included in the constructor. Magento 2’s automatic dependency injection automatically injects the two objects

Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1
Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1

and the remaining parameters are either plain old PHP scalers, or the final argument, which is a PHP array.

Regular arguments may coexist with PHP dependency injection — if you’re wondering why this would be of any use, hold on for a few more paragraphs.

Object Manager Reminder

With that lengthy exposition out of the way we’re almost ready to start talking about today’s feature: Argument Replacement. Argument replacement is another feature that, while labeled as part of “dependency injection”, is really (from another point of view), part of the underlying object manager system.

You’ll remember from previous tutorials that for most of your day-to-day Magento programming, you won’t instantiate an object directly with the object manager as we have

$object_manager->create('Pulsestorm\TutorialObjectManagerArguments\Model\Example');

Instead, you’d inject your object in the __construct method of whatever class file your’e working on

public function __constrcut(
    \Pulsestorm\TutorialObjectManagerArguments\Model\Example $example        
);

With the above in place Magento, behind the scenes, will use the object manager to create the Pulsestorm\TutorialObjectManagerArguments\Model\Example object. We’re using the object manager in these tutorials for simplicity — every feature we talk about is also available to objects created via dependency injection.

Argument Replacement

ENOUGH ALREADY! Let’s get to it. Argument replacement is a powerful feature that gives us full control, via configuration, of what the object Manager will inject in the__construct method.

Like a lot of features in Magento 2, argument replacement is best understood by example. Consider our Example class’s constructor

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Model/Example.php    
public function __construct(
    ExampleArgument1 $object1,
    ExampleArgument2 $object2,
    $scaler1='foo',
    $scaler2=0,
    $scaler3=false,
    $thearray=['foo'])        
{
    $this->object1 = $object1;
    $this->object2 = $object2;    

    $this->scaler1 = $scaler1;
    $this->scaler2 = $scaler2;
    $this->scaler3 = $scaler3;        
    $this->thearray   = $thearray;                
}

Let’s say we didn’t want $scaler1 to be equal to foo. The object manager and dependency injection block us from using normal constructor parameters, so up until now we’ve been stuck. This is what argument replacement lets us do. Add the following nodes to thedi.xml file

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <!-- ... snipped ... -->
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <arguments>
            <argument name="scaler1" xsi:type="string">bar</argument>
        </arguments>
    </type>
</config>

We’ll get to what our new <type/> node does in a second, but first lets clear our cache

$ php bin/magento cache:clean 
Cleaned cache types:
config
layout
block_html
view_files_fallback
view_files_preprocessing
collections
db_ddl
eav
full_page
translate
config_integration
config_integration_api
config_webservice

and then re-run our ps:tutorial-object-manager-arguments command. You’ll recall this command shows us the values of our Example object’s properties.

$ php bin/magento ps:tutorial-object-manager-arguments
#... snipped ...
The Property $scaler1
  is a string
  with a value of: bar
#... snipped ...       

You should see the value of the $scaler property has changed from foo to bar. This is what argument replacement does — it allows end-user-programmers to change the value ofany dependency injected argument.

How it Works

Let’s take a look at our di.xml configuration again

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->
<config>       
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <!-- ... --->
    </type>
</config>

Here we’ve introduced a new-to-us second-level configuration node named <type/>. The node refers to the class whose arguments we’re trying to change, in our case that’sPulsestorm\TutorialObjectManagerArguments\Model\Example. The type name refers not to native PHP types, but instead the idea that all the classes you define in your system/application form their own type system.

For Magento 1 developers, another way to think of types might be class aliases

Mage::getModel('catalog/product');

Back in Magento 1 these class aliases also formed a type system. While Magento 2 eschews class aliases, the object manager effectively turns each class name (or, with our new nomenclature, type name) into an alias.

So, the <type/> node lets us tell Magento 2 which class’s argument we want to target. The inner nodes let us tell Magento 2 what we want to do with the arguments

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->    
<config>       
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <arguments>
            <argument name="scaler1" xsi:type="string">bar</argument>
        </arguments>
    </type>
</config>

The outer <arguments/> node lets Magento know we’re dealing with arguments — there’s other <type/> sub-nodes we’ll cover in future articles. Each single <argument/> node (no S) lets us change the inject value of a single __construct argument.

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->    

<argument name="scaler1" xsi:type="string">bar</argument>                   

The name of an argument comes from its PHP variable name as a parameter. i.e., because the parameter is named $scaler1

#File: app/code/Pulsestorm/TutorialObjectManagerArguments/Model/Example.php
public function __construct(
    //...
    $scaler1='foo',
    //...
{

the <argument/> attribute name is set to scaler1. The xsi:type node lets us tell Magento what sort of value we want to replace the existing value with. With those attributes set — Magento will use the inner text value of <argument/> as the new value to inject, (in our case, that’s bar).

The first time I saw a non-object parameter in a Magento 2 class’s __construct method I didn’t understand why it was there. It seemed like the object manager and type hint dependency injection both would preclude this parameter from being anything other than its default value. Once I discovered the argument replacement feature though, they made a lot more sense.

Let’s dive a little deeper into argument replacement. There’s additional nuances you’ll need to get the most from this feature.

Replacing Object Arguments

Speaking of objects, what do you think would happen if we tried replacing one of the object parameters? Let’s give it a try! Add the following node to your di.xml configuration.

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <!-- ... snipped ... -->
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <arguments>
            <argument name="object1" xsi:type="string">bar</argument>
        </arguments>
    </type>
</config>

This is very similar to our previous configuration. The only difference is we’ve targeted the parameter named object1. Let’s clear our cache and try running our command with the above in place.

$ php bin/magento ps:tutorial-object-manager-arguments

  [ErrorException]                  
  Illegal string offset 'instance'  

Whoops! An error. While the error is somewhat cryptic, this is correct system behavior. We just tried to replace the $object1 parameter with a string, but remember that all dependency injected arguments have type hints.

namespace Pulsestorm\TutorialObjectManagerArguments\Model;
public function __construct(
    ExampleArgument1 $object1,
    //...
)

It’s impossible to replace an injected object with a non-object. Fortunately, it is possible to replace an injected object with a different object! Give the following configuration a try

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <!-- ... snipped ... -->
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <arguments>
            <argument name="object1" xsi:type="object">Pulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferent</argument>
        </arguments>
    </type>

</config>

We’ve changed two things in the above configuration. First, the xsi:type argument is now set to object. This lets the object manager know it should treat the node’s text content as aclass instead of a raw string. This leads us nicely into the other thing we’ve changed, which is to set the <argument/> node’s contents toPulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferent. This is the object we want to replace the original with.

Clear your cache, run your command, and you should see the following.

$ php bin/magento ps:tutorial-object-manager-arguments
The Property $object1
  is an object
  created with the class: 
  Pulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferent

That is, Magento is now injecting aPulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferentclass for the first argument.

Inserting Class Constants

Here’s another feature the xsi:type attribute enables. Give the following a try

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <!-- ... snipped ... -->
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <arguments>
            <argument name="scaler2" xsi:type="const">Magento\Integration\Model\Integration::SETUP_TYPE</argument>
        </arguments>
    </type>
</config>

Here we’ve used an xsi:type of const, and a node value ofMagento\Integration\Model\Integration::SETUP_TYPE to replace the scaler2argument. Run the reflection command with the above configuration and you’ll see the following

$ php bin/magento ps:tutorial-object-manager-arguments
//... snipped ...
The Property $scaler2
  is a string
  with a value of: setup_type
//... snipped ...

So — where did the setup_type value come from? The const value in xsi:type allows you to insert the value of a class constant. Magento interprets the node value (Magento\Integration\Model\Integration::SETUP_TYPE) as the classMagento\Integration\Model\Integration and the constant SETUP_TYPE

#File: app/code/Magento/Integration/Model/Integration.php
namespace Magento\Integration\Model;
//...
class Integration extends \Magento\Framework\Model\AbstractModel
{

    //...
        const SETUP_TYPE = 'setup_type';
    //...
}

Replacing Arrays

PHP arrays are a bit of a special case for argument replacement. Let’s try replacing ourthearray parameter by adding the following configuration to di.xml

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <!-- ... -->
    <type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
        <arguments>
            <argument name="thearray" xsi:type="array">
                <item name="0" xsi:type="string">science</item>
                <item name="baz" xsi:type="string">baz</item>
                <item name="bar" xsi:type="string">bar</item>
            </argument>
        </arguments>
    </type>
</config>    

You’ll notice that instead of a simple string inside of <argument/>, there’s a new set of<item/> nodes. If we run our command with the above configuration, we should see the following

$ php bin/magento ps:tutorial-object-manager-arguments -v
//...

The Property $thearray
  is an array
  with the elements: 
  0=>science
  baz=>baz
  bar=>bar

That is, we’ve replaced the default array with a multiple item, mixed key array. If we take a closer look at the <item/> tags

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->    
<item name="baz" xsi:type="string">baz</item>

We see that Magento will interpret an items name (baz) as an array key, and it will interpret the tag contents (baz) as the value. One interesting thing here is the xsi:type tag. This works the same as in argument — which means you can create an array with scalars (above), objects

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->    
<item name="baz" xsi:type="string">Some\Php\Class</item>

or even other, nested arrays!

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->    
<item name="baz" xsi:type="string">
    <item name="0" xsi:type="string">one</item>
    <item name="1" xsi:type="string">two</item>        
</item>    

Another interesting feature of arrays is how Magento handles multiple modules trying to “replace” the same array. For objects, and strings, and other scalar xsi:types likenumber or boolean, Magento operates on a “last module in wins” principle.

With arrays, however, Magento will merge the <items/>. This means it’s possible to have multiple modules contributing items to an array. In fact, for the past few articles, we’ve been relying on this functionality!

If you take a look at this module’s di.xml file, you’ll see the following nodes.

<!-- File: app/code/Pulsestorm/TutorialObjectManagerArguments/etc/di.xml -->    
<type name="Magento\Framework\Console\CommandList">
    <arguments>
        <argument name="commands" xsi:type="array">
            <item name="testbedCommand" xsi:type="object">Pulsestorm\TutorialObjectManagerArguments\Command\Testbed</item>
        </argument>
    </arguments>
</type>  

This is the same sort of argument replacement we’ve been doing in this article. That is, we’re replacing (or, since it’s an array, merging) the commands parameter in the coreMagento\Framework\Console\CommandList class. We’re merging in anPulsestorm\TutorialObjectManagerArguments\Command\Testbed object.

If we look at the class’s constructor

#File: lib/internal/Magento/Framework/Console/CommandList.php
namespace Magento\Framework\Console;
//...
class CommandList
{
    //...
    public function __construct(array $commands = [])
    {
        $this->commands = $commands;
    }
    //...
}

We see $commands is an array. In fact — this is an array that contains a list of commands for the bin/magento command. All Magento modules add commands this way. You should be familiar with the cache:clean command. This command’s class file is here

#File: app/code/Magento/Backend/Console/Command/CacheCleanCommand.php
<?php    //...
namespace Magento\Backend\Console\Command;

class CacheCleanCommand extends AbstractCacheTypeManageCommand
{
    //...
}

However, it’s this module’s di.xml file that makes it available to the command line framework.

<!-- #File: app/code/Magento/Backend/Console/Command/CacheCleanCommand.php -->
<type name="Magento\Framework\Console\CommandList">
    <arguments>
        <argument name="commands" xsi:type="array">
            <!-- ... snip ... -->
            <item name="cacheCleanCommand" xsi:type="object">Magento\Backend\Console\Command\CacheCleanCommand</item>
            <!-- ... snip ... -->
        </argument>
    </arguments>
</type>

This is just one example of how the core Magento framework treats dependency injection and the object manager as first class citizens.

Best Practices for Type Safety

While Magento’s use of PHP’s native type hints help enforce type safety during argument replacement, it is (as of this writing) technically possible to replace a scaler argument with an object. The configuration for that would look something like this

<!-- #File: app/code/Magento/Backend/Console/Command/CacheCleanCommand.php -->
<type name="Pulsestorm\TutorialObjectManagerArguments\Model\Example">
    <arguments>
        <argument name="scaler1" xsi:type="object">Pulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferent</argument>
    </arguments>
</type>

Although this configuration will run, it does lead to some odd results. If we run our reflection command with the above configuration in place

$ php bin/magento ps:tutorial-object-manager-arguments -v
The Property $scaler1
  is an array
  with the elements: 
  instance=>Pulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferent

We see that Magento has, for reasons that are unclear and likely an unintended side effect, replaced the argument with an array instead of an object, and that the array contains the name of the object.

While the more clever and resourceful among you might start thinking of ways to take advantage of this behavior, I’d advise against it. Even if it did work, replacing an argument PHP expects to use as a string, number, etc, with an object would likely have unforeseen consequences.

Also, while we’re here, here’s a list of all the valid (as of this writing) xsi:types

xsi:type="array"
xsi:type="string"
xsi:type="object"
xsi:type="boolean"
xsi:type="const"
xsi:type="number"
xsi:type="string"
xsi:type="init_parameter"
xsi:type="null"

Most of these are self explanatory. The only non-scaler type we didn’t discuss wasinit_parameter which, at this point, is just a de-facto alias for const.

Selective Rewrites

From the point of view of a Magento 1 developer, argument replacement offers a more selective version of the old class rewrite functionality. In our example above — if we needed to change the behavior ofPulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1 — (or something like tutorialobjectmanager/exampleargument1 in Magento 1 class alias speak) the only way to do it was with a global rewrite that changed the behavior of the class in the entire system. Not taking adequate care to maintain the old functionality was created extension conflicts and system instability in Magento 1.

While it’s not fool proof, Magento 2’s argument replacement feature allows us to effectively change the behavior ofPulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1, but onlywhen this class is used in thePulsestorm\TutorialObjectManagerArguments\Model\Example class. This means the rest of the system is still using the original class, and there’s zero chance our argument replacement will effect those systems.

Of course, you are still changing the behavior ofPulsestorm\TutorialObjectManagerArguments\Model\Example system-wide, so it’s not fool proof, but speaking for myself it’s a welcome addition to Magento’s functionality.

ALL That said, there are features beyond argument replacement that go even further in stabilizing Magento customization. Out next stop on the Magento object manager train will be the virtualType system.

Magento 2 Object Manager Preferences

With their take on an object manager/container and dependency injection, Magento have created a new way for PHP programmers to work. With that new way of working comes a new set of unanticipated challenges. Put another way, new patterns create new problems.

Fortunately, Magento 2’s extended development cycle has given the core team time to address some of these problems with — you guessed it — more design patterns and more object manager features.

Today we’re going to take a look the problem of ill-performant code while using other people’s objects. Along the way we’ll discuss Magento 2’s use of the proxy pattern to solve this problem, as well as our first real discussion of Magento 2’s code generation features.

We’re deep in the weeds here — while you may find something of value as a newcomer, you’ll definitely want to get familiar with the basics of Magento’s object system to get the full value out of the article.

Installing the Module

Rather than have you spend 30 minutes copying and pasting code, we’ve created two modules that simulate a common problem you’ll run into with Magento automatic constructor dependency injection. The modules (Pulsestorm_TutorialProxy1 andPulsestorm_TutorialProxy2) are available on GitHub

The official installation procedure for a Magento module is still being worked out, so we recommend installing these tutorial modules manually using the latest tagged release. If you’re not sure how to install a module manually, the first article in this series has the instructions you’re looking for.

To test that you’ve installed the module correctly, try running the following command

$ php bin/magento ps:tutorial-proxy
You've installed Pulsestorm_TutorialProxy2!
You've also installed Pulsestorm_TutorialProxy1!

If you see output telling you you’ve installed both modules, you’re ready to go!

Slow Loading Dependencies

These two modules simulate a common situation when using other people’s code — you’d like to make use of the their objects, but their code includes slow loading dependencies that you don’t need.

Let’s open up the command class and comment out our install check and uncomment the other lines

#File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    //$this->installedCheck($output);
    $service = $this->createService($output);        
    $this->sayHelloWithFastObject($service, $output);
    $this->sayHelloWithSlowObject($service, $output);
}

The first thing this code does ($this->createService($output);), is create aPulsestorm\TutorialProxy1\Model\Example object using the object manager.

#File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
protected function createService($output)
{
    //...
    $om = $this->getObjectManager();
    //...
    $service = $om->get('Pulsestorm\TutorialProxy1\Model\Example');
    //...
    return $service;    
}

We’re using the object manager directly here for simplicity’s sake — in real Magento 2 code you’ll create most of your objects via automatic constructor dependency injection. Since automatic constructor dependency injection uses the object manager, everything we show you today will be available to your injected objects as well.

After instantiating that object, we call the two sayHello... methods

#File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
protected function sayHelloWithFastObject($service, $output)
{
    //...
    $service->sayHelloWithFastObject();
    //...
}

protected function sayHelloWithSlowObject($service, $output)
{
    //...
    $service->sayHelloWithSlowObject();
    //...        
}

All these methods do is pass on method calls to thePulsestorm\TutorialProxy1\Model\Example object. Also, and omitted above, these methods contain some simple profiling code that will let us know how long each method took to run. We’ve also dropped similar profiling code in the Example service object and its dependencies.

If we run our command, we should see output something like the following

$ php bin/magento ps:tutorial-proxy
About to Create Service
Constructing FastLoading Object
Constructing SlowLoading Object
Created Service, approximate time to load: 3005.656 ms

About to say hello with fast object
Hello
Said hello with fast object, approximate time to load: 0.0172 ms

About to say hello with slow object
Hello
Said hello with slow object, approximate time to load: 0.0491 ms

While we’ll eventually get to all the output, the line we care about right now is this one

Created Service, approximate time to load: 3005.656 ms

Something is taking over 3 seconds (3000 milliseconds) to load. In our example, this three second load is simulated using a PHP sleep statement — but in the real world slow loading constructors can be murder to objects musing dependency injection.

Digging Deeper

Let’s take another look at our execute method

#File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    //this->installedCheck($output);
    $service = $this->createService($output);        
    $this->sayHelloWithFastObject($service, $output);
    $this->sayHelloWithSlowObject($service, $output);
}

At this level of abstraction, all we’re doing is

  • Creating a Pulsestorm\TutorialProxy1\Model\Example object
  • Calling that object’s sayHelloWithSlowObject andsayHelloWithFastObject methods

If we take a quick look at the source for our Example object

#File: app/code/Pulsestorm/TutorialProxy1/Model/Example.php
namespace Pulsestorm\TutorialProxy1\Model;
//...
public function __construct(FastLoading $fast, SlowLoading $slow)
{
    $this->fast = $fast;
    $this->slow = $slow;
}

We see it has two dependencies — Pulsestorm\TutorialProxy1\Model\FastLoadingand Pulsestorm\TutorialProxy1\Model\SlowLoading. In turn, thesayHelloWithFastObject and sayHelloWithSlowObject methods

#File: app/code/Pulsestorm/TutorialProxy1/Model/Example.php
public function sayHelloWithFastObject()
{
    $this->fast->hello();
}

public function sayHelloWithSlowObject()
{
    $this->slow->hello();
}  

pass on a call to the hello method of the fast and slow arguments (now stored as object properties).

We’ve also added profiling to

  1. The creation of the Example object
  2. The calling of both sayHelloWithFastObject andsayHelloWithSlowObject

We’ve also added some debugging messages to the constructors of our FastLoading andSlowLoading classes.

#File: app/code/Pulsestorm/TutorialProxy1/Model/FastLoading.php
public function __construct()
{
    echo "Constructing FastLoading Object","\n";
}

#File: app/code/Pulsestorm/TutorialProxy1/Model/SlowLoading.php
public function __construct()
{
    echo "Constructing SlowLoad Object","\n";
    //...
}

Finally, in the __construct method of the SlowLoading class, we’ll see the following

#File: app/code/Pulsestorm/TutorialProxy1/Model/SlowLoading.php

public function __construct()
{
    echo "Constructing SlowLoading Object","\n";
    //simulate slow loading object with sleep
    sleep(3);
}

That is — we’ve placed a three second sleep in the constructor to simulate a slow loading object.

The end result is, when we run our command, we have a detailed report of what’s going on, and how long it’s taking.

$ php bin/magento ps:tutorial-proxy
About to Create Service
Constructing FastLoading Object
Constructing SlowLoading Object
Created Service, aproximate time to load: 3000.7651 ms

About to say hello with fast object
Hello
Said hello with fast object, approximate time to load: 0.0162 ms

About to say hello with slow object
Hello
Said hello with slow object, approximate time to load: 0.052 ms

Based on the above, it’s taking approximately three seconds to create aPulsestorm\TutorialProxy1\Model\Example object. We now have a crude, if exaggerated, version of the sort of performance problem you might run into in the real world.

Why so Slow?

The first question we need to answer is

Why is the Example class loading slowly?

Based on what we’ve learned in this article so far, it’s the SlowLoading objects that are our slow ones, not the Example object.

The problem here is Magento’s automatic constructor dependency injection system. If we take another look at the Example object’s constructor

#File: app/code/Pulsestorm/TutorialProxy1/Model/Example.php
namespace Pulsestorm\TutorialProxy1\Model;
//...
public function __construct(FastLoading $fast, SlowLoading $slow)
{
    $this->fast = $fast;
    $this->slow = $slow;
}

We see two objects injected. Per Magento’s automatic constructor dependency injection, we know this means before the object manager instantiates aPulsestorm\TutorialProxy1\Model\Example object, it will need to instantiate both aPulsestorm\TutorialProxy1\Model\FastLoading andPulsestorm\TutorialProxy1\Model\SlowLoading object.

So, our problem isn’t really a slow loading Example object — it’s a slow loadingPulsestorm\TutorialProxy1\Model\SlowLoading argument. This distinction might seem trivial, but it will be important in the solution Magento’s object manager provides.

Magento 2 Proxy Objects

The Magento core team must have run into this sort of a problem a lot — because not only do they have a solution for it, but that solution is baked into the object manager.

Magento’s solution is an implementation of the proxy pattern, used here to defer the loading of an object’s arguments.

If that didn’t make sense, don’t worry, we’ll guide you through the process step by step, and then explain what we did. If that did make sense to you, you’ll still want to proceed carefully — there’s some Magento 2 specific magic that, at first glance, makes everything a little confusing. However, once you’ve created a few proxy objects the magic should start making sense.

As previously discussed, our problem is

When Magento uses a Pulsestorm\TutorialProxy1\Model\SlowLoading object as an argument in a Pulsestorm\TutorialProxy1\Model\Example object, this slows down the instantiation of the Pulsestorm\TutorialProxy1\Model\Example object.

The solution to this is, via Magento’s di.xml configuration, to replace thePulsestorm\TutorialProxy1\Model\SlowLoading argument with a proxy object. The proxy object will not suffer from the same slow loading as the original argument. Said another way, we’re going to use plain old argument replacement to replace the problem object with a different, special object called a proxy.

To do this, add the following nodes to the Pulsestorm_TutorialProxy2 module’s di.xmlfile.

#File: app/code/Pulsestorm/TutorialProxy2/etc/di.xml
@highlightsyntax@xml
<config>
    <!-- #File: app/code/Pulsestorm/TutorialProxy2/etc/di.xml -->
    <!-- Notice: we're using `TutorialProxy2`'s di.xml to change the
         behavior of `TutorialProxy1`.  This the far more common usage
         of object manager/dependency-injection than the examples in 
         our tutorial so far -->

    <type name="Pulsestorm\TutorialProxy1\Model\Example">
        <arguments>
            <argument name="slow" xsi:type="object">Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxy</argument>
        </arguments>        
    </type>
</config>

This is (mostly) standard argument replacement. We’re targeting the arguments in thePulsestorm\TutorialProxy1\Model\Example object, specifically the argument namedslow, and replacing it with a Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxyobject.

The one new thing is the Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxyclass/object. We’re going to need to do a little hand waving and say “trust us” on what this is. All you need to know for now is, since we’re replacing thePulsestorm\TutorialProxy1\Model\SlowLoading class, we replace it with aPulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class (i.e. — we append\Proxy to the initial class name).

If you clear your cache

$ php bin/magento cache:clean 
Cleaned cache types:
config
layout
block_html
view_files_fallback
view_files_preprocessing
collections
db_ddl
eav
full_page
translate
config_integration
config_integration_api
config_webservice

and re-run the command with the above in place, you should see the following.

$ php bin/magento ps:tutorial-proxy
About to Create Service
Constructing FastLoading Object
Created Service, aproximate time to load: 6.0711 ms

//...

That is — we’ve gone from 3000 ms to around 6 ms. I’d say that’s a substantial improvement. Run the command again though

$ php bin/magento ps:tutorial-proxy
About to Create Service
Constructing FastLoading Object
Created Service, aproximate time to load: 0.736 ms

//...

And you’ll see an even more dramatic improvement. Your times may vary in their specificity, but the ratios/pattern should be similar. Congratulations! You’ve successfully used a proxy to improve the construction time of a Magento object manager object.

What Just Happened

Of course, this raises myriad questions. A few might be

  1. We never defined aPulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class, so what is it? A virtualType? Something else?
  2. The proxy pattern is about substituting one object for another — why does this improve performance?
  3. Why did we need to run the command twice to see the full performance improvements?

All valid questions, and they all get to the heart of the magic involved in Magento’s proxy objects.

The answer to the first question: ThePulsestorm\TutorialProxy1\Model\SlowLoading\Proxy configuration is not a virtual type — it’s a plain old PHP class. The reason we didn’t need to create this class on our own is, Magento automatically generates proxy class files for us. Take a look at the following file

#File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php    
<?php
namespace Pulsestorm\TutorialProxy1\Model\SlowLoading;

/**
 * Proxy class for @see \Pulsestorm\TutorialProxy1\Model\SlowLoading
 */
class Proxy extends \Pulsestorm\TutorialProxy1\Model\SlowLoading
{
    //...
}

This is the Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class we configured, and Magento generated. Notice it’s in the var/generation folder.

When Magento’s object manage encounters a class whose “short name” is Proxy (i.e. whose full class name Ends\In\Proxy), it will automatically generate a proxy class like this one.

If you don’t believe us, try deleting this file

$ rm var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
$ ls -l var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
ls: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php: No such file or directory

and then re-run the command

$ php bin/magento ps:tutorial-proxy
//...
$ ls -l var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
-rw-rw-rw-  1 alanstorm  staff  2350 Aug  9 16:32 var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php        

The file’s been restored after running our command! It’s not just di.xml configuration that will trigger Magento code generation — it’s anytime the object Manager encounters a class whose short name is Proxy. Give the following a try — add the following temporary code to our execute method

#File: app/code/Pulsestorm/TutorialProxy2/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{

    $object_manager = $this->getObjectManager();
    $object = $object_manager->create('Pulsestorm\TutorialProxy1\Model\Example\Proxy');
    $output->writeln('You just instantiated a ' . get_class($object) . ' class'); 
    $output->writeln('You also indirectly told Magento to create this class in the following location');
    $r = new \ReflectionClass($object);
    $output->writeln($r->getFilename());        
    exit;  
    //...
}

and then run it.

$ php bin/magento ps:tutorial-proxy
You just instantiated a Pulsestorm\TutorialProxy1\Model\Example\Proxy class
You also indirectly told Magento to create this class in the following location
/path/to/magento/var/generation/Pulsestorm/TutorialProxy1/Model/Example/Proxy.php

So, above we told the object manager to create aPulsestorm\TutorialProxy1\Model\Example\Proxy object. The object manager, seeing that the class name ended in Proxy, automatically created a proxy object that extends Pulsestorm\TutorialProxy1\Model\Example

#File: var/generation/Pulsestorm/TutorialProxy1/Model/Example/Proxy.php
<?php
namespace Pulsestorm\TutorialProxy1\Model\Example;

/**
 * Proxy class for @see \Pulsestorm\TutorialProxy1\Model\Example
 */
class Proxy extends \Pulsestorm\TutorialProxy1\Model\Example
{
}    

Be careful though, if you try to create a proxy for an object that doesn’t exist,

$object_manager->create('Pulsestorm\TutorialProxy1\Model\Ghost\Proxy');

the object manager will yell at you.

$ php bin/magento ps:tutorial-proxy

[Magento\Framework\Exception\LocalizedException]
Source class "\Pulsestorm\TutorialProxy1\Model\Ghost" 
for "Pulsestorm\TutorialProxy1\Model\Ghost\Proxy" generation does not exist.  

While PHP frameworks have used automatic code generation for a while, it’s usually at the explicit request of a user (i.e. “make me a file”). In Magento 2, code generation exists to save a developer the time of writing a lot of the boiler plate code that accompanies “design patterns” style programming.

This leads in well to our next topic: Why does a proxy object improve performance? Before we continue, don’t forget to remove the temporary object manager code from the executemethod.

Magento’s Generated Proxy Objects

From Wikipedia — the proxy pattern

in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate.

If we take a look at the generated code, we’ll immediately see why a proxy object gave our object instantiation the performance boost it did.

#File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
<?php
namespace Pulsestorm\TutorialProxy1\Model\SlowLoading;

class Proxy extends \Pulsestorm\TutorialProxy1\Model\SlowLoading
{
    //...
    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading', $shared = true)
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
        $this->_isShared = $shared;
    }
    //...
}

The proxy object completely replaces the constructor of the proxied object, and does not make a parent::__construct call.

Proxy objects (like all objects in Magento) are still subject to dependency injection, and all proxy objects will have the same three arguments

#File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading', $shared = true)
{
    $this->_objectManager = $objectManager;
    $this->_instanceName = $instanceName;
    $this->_isShared = $shared;
}

The first is an object manager instance, the second is the name of the class this object proxies, and the third is a shared argument. We’ll cover shared/unshared in a future article — for now just concentrate on the first two arguments.

So, by replacing the entire constructor, we avoid any slow loading behavior.

However — what if important things happen in the __construct method? What about the other properties assigned there? Aren’t we breaking thePulsestorm\TutorialProxy1\Model\SlowLoading object by doing this?

Fortunately not. In addition to replacing the constructor, our proxy object also replacedeach public method of the original object.

#File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
/**
 * {@inheritdoc}
 */
public function hello()
{
    return $this->_getSubject()->hello();
}

If someone calls hello, the proxy will call _getSubject and pass on the call to hello. The_getSubject method

#File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
protected function _getSubject()
{
    if (!$this->_subject) {
        $this->_subject = true === $this->_isShared
            ? $this->_objectManager->get($this->_instanceName)
            : $this->_objectManager->create($this->_instanceName);
    }
    return $this->_subject;
}

will instantiate an instance of the original object! Remember, the _instanceName variable argument from the constructor? If we use some x-ray vision, this method actually looks like

#File: var/generation/Pulsestorm/TutorialProxy1/Model/SlowLoading/Proxy.php
protected function _getSubject()
{
    if (!$this->_subject) {
        $this->_subject = true === $this->_isShared
            ? $this->_objectManager->get('\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading')
            : $this->_objectManager->create('\\Pulsestorm\\TutorialProxy1\\Model\\SlowLoading');
    }
    return $this->_subject;
}

In this way, Magento defers the loading of the slow loading object until it’s needed. In fact, if you re-examine our current output in its entirety.

$ php bin/magento ps:tutorial-proxy
About to Create Service
Constructing FastLoading Object
Created Service, aproximate time to load: 0.89 ms

About to say hello with fast object
Hello
Said hello with fast object, approximate time to load: 0.0069 ms

About to say hello with slow object
Constructing SlowLoading Object
Hello
Said hello with slow object, approximate time to load: 3001.014 ms

you’ll notice we still have a 3 second (3000 ms) delay, it’s just been deferred until we actually need the SlowLoading object

About to say hello with slow object
Constructing SlowLoading Object
Hello
Said hello with slow object, approximate time to load: 3001.014 ms

So, in our silly example program, the proxy doesn’t save us much. In the real world, you’ll use proxy objects in situations where

  1. You have a slow loading dependency
  2. You know your particular use of this code doesn’t need the dependency

Regardless of whether you choose to use proxies — they’re a part of the Magento 2 core system, and likely to show up in other people’s code, so make sure you understand them.

Generation Caveats

So, that’s questions one and two answered. As for the third

Why did we need to run the command twice to see the full performance improvements?

Some readers will have already figured this out. While code generation means we avoid having to create all the boiler plate code in our proxy class over and over again, there is a small performance cost involved (around 6 ms on my development machine). Once the code is generated, it stays generated — Magento 2 will only generate a file if it can’t instantiate the request object.

So the first performance improvement

[THREE SECONDS HERE]
Created Service, aproximate time to load: 6.0711 ms

//...

was the proxy object skipping direct instantiation of the SlowLoading argument.

The second performance improvement

Created Service, aproximate time to load: 6.0711 ms
Created Service, aproximate time to load: 0.736 ms

was PHP not having to regenerate an already generated class.

There’s another gotcha to code generation. If you change the original proxied class, Magento 2 won’t automatically re-generate any new public methods. This will create unpredictable behavior as you’ll still be able to call the new method (since the proxy object extends the original class), but the new methods won’t instantiate the subject object, and will cary a different state.

During development it would be wise to periodically remove your generated folder

var/generation

to trigger re-generation of any code that needs it.

For production systems — I expect this generated code folder will be one of those challenges early adopters will wrestle with. Magento 2 appears to offer two compilation commands) that will pre-generate code for the entire system — but these commands also go above and beyond generating code. Also, for anyone into scalable systems, on-the-fly generated code sets off all sort of trigger warnings for running in a multiple-app/frontend server environment. While these problems will, no doubt, be sorted out over the coming months and year(s), it’s a challenging climb ahead for Magento 2 devops engineers.

Fortunately for us — we’re a series for programmers! Unfortunately for us, we’ve covered a lot of information today (proxies and code generation), and it’s probably a good idea to take a small breather. Next time our code generation exposure will come in handy as we explore shared/unshared objects, as well as Magento 2’s take on the age old factory pattern.

Magento 2’s Automatic Dependency Injection

This week’s article is the second in a series discussing Magento 2’s object system. Last time we covered the basics of Magento’s object manager. This week we’re going explain why you’ll rarely use Magento’s object manager, as well as Magento’s implementation of the popular “dependency injection” pattern. (hint — they’re related)

Many popular PHP frameworks implement a dependency injection system — although it would be more accurate to say they implement an automatic dependency injection system. Before we dive into Magento’s system, we’ll want to explain the problem dependency injection sets out to solve.

Understanding Dependency Injection

The easiest way to understand dependency injection is by example. Consider the following PHP method/pseudo-code

//Obviously contrived -- if only getting a price were this simple
public function getFormattedPrice($sku)
{
    $db  = new DBHandler;
    $row = $db->query('SELECT price FROM products WHERE sku = ?', $sku);

    $formatter = new PriceFormatter;

    return $formatter->asDollars($row['price']);
}

This is a simplified example of what a method for fetching a product’s price might look like. On the surface, there’s nothing wrong with this method. It

  • Instantiates a database handler
  • Queries for the price
  • Instantiates a formatter object
  • Uses the formatter object to return a formatted price

The problem comes later, when someone else wants to reuse this method. This method is now dependent on the specific DBHandler class, and the specific PriceFormatter class. Even if you think code reuse is an untrue industry myth, these two dependencies make this method harder to test in an automated framework. Your test framework is now reliant on making the same database connection as your real application.

The solution to this problems is to not have methods with these sorts of dependencies. Instead, you should inject dependencies into the method.

public function getFormattedPrice($sku, $db, $formatter)
{
    $row = $db->query('SELECT price FROM products WHERE sku = ?', $sku);
    return $formatter->asDollars($row['price']);
}

Rewritten, this method has two new parameters. The idea is the client programmer should pass in instantiated objects (i.e. inject the dependencies).

That’s all dependency injection is — the wikipedia entry has more examples of the concept and is worth a read, if you can stomach the java examples!

Modern Dependency Injection

Dependency injection is a pretty simple concept, but if you’ve never encountered it before there may be some doubts scratching at the back of your head. One of them may be this

public function prepareDataForDisplay()
{
    //...

    $data             = new stdClass;
    $db              = new DBHandler;
    $formatter         = new PriceFormatter;
    $data['price']     = $this->getFormattedPrice($row['price']);

    //...
}

public function getFormattedPrice($sku, $db, $formatter)
{
    $row = $db->query('SELECT price FROM products WHERE sku = ?', $sku);
    return $formatter->asDollars($row['price']);
}

While we’ve replaced the dependencies in getFormattedPrice — all we’ve really done is shift them up a level to the method that calls getFormattedPrice(prepareDataForDisplay above). So, conceptually dependency injection is simple, butwhere you inject your dependencies and where these objects are instantiated is left up in the air.

This is the problem that many PHP frameworks try to solve with some sort of automaticdependency injection. By creating a system for automatically injecting these sorts of dependencies the framework removes the question of by who and how a dependency is injected. If that didn’t make sense, don’t worry. After looking at an example of Magento’s dependency injection it should start to make more sense.

Magento Dependency Injection

Just like we did last time, we’ve prepared a module with some sample code. The module’son GitHub, and the easiest way to install it is to manually download the latest release.

If you need help manually installing a Magento extension our previous series article has complete instructions.

Let’s make sure you have the module installed correctly by running the following command

$ php bin/magento ps:tutorial-object-manager-2
Hello Again World!

If you see the “Hello Again World!” message, you’re good to go.

If we take a look at the class that implements our command, we see the following .

#File: app/code/Pulsestorm/TutorialObjectManager2/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $manager = $this->getObjectManager();
    $helper = $this->getObjectManager()->create(
        '\Pulsestorm\TutorialObjectManager2\Model\Example');
    $output->writeln(
        $helper->sendHelloAgainMessage()
    );        
}

All we’ve done in the execute method is instantiate aPulsestorm\TutorialObjectManager2\Model\Example object and call itssendHelloAgainMessage method to get some output text. If you take a look at thisPulsestorm\TutorialObjectManager2\Model\Example class’s constructor

#File: app/code/Pulsestorm/TutorialObjectManager2/Model/Example.php    
public function __construct()
{
    $object = new Message; //
    $this->messageObject = $object;
}

we see the class instantiates a Pulsestorm\TutorialObjectManager2\Model\Messageobject and assigned it to the messageObject property. We use the short class nameMessage since this file lives in the Pulsestorm\TutorialObjectManager2\Modelnamespace. If you need some help getting started with PHP namespaces, our primer is a good place to go.

Then, in the sendHelloAgainMessage method

#File: app/code/Pulsestorm/TutorialObjectManager2/Model/Example.php
public function sendHelloAgainMessage()
{
    return $this->messageObject->getMessage();
}

we use the Message object to return a text message.

Our (contrived) Pulsestorm\TutorialObjectManager2\Model\Example class has a hard coded dependency on the Pulsestorm\TutorialObjectManager2\Model\Messageobject. Here’s how Magento 2 solves this problem. Open up the Example.php class definition file, and let’s replace the constructor with the following code

#File: app/code/Pulsestorm/TutorialObjectManager2/Model/Example.php
public function __construct(Message $message)
{
    $this->messageObject = $message;
}

What we’ve done here is added a parameter named $message to the constructor, and then assigned that parameter to the messageObject property. In other words, we’ve replaced the hard coded dependency with a parameter. This allows developers to inject the dependency. You’ll also notice we’ve included a type hint with the parameter.

#File: app/code/Pulsestorm/TutorialObjectManager2/Model/Example.php
__construct(Message $message);

This type hint forces developers to pass in an object that either is aPulsestorm\TutorialObjectManager2\Model\Message object, or hasPulsestorm\TutorialObjectManager2\Model\Message in its ancestry chain. Again, we’ve used the short class name Message. The following would have been equivalent, but is much more verbose.

#File: app/code/Pulsestorm/TutorialObjectManager2/Model/Example.php
__construct(\Pulsestorm\TutorialObjectManager2\Model\Message $message);

If you’re not familiar with type hints, we’ve prepared a primer on them.

If this were a traditional PHP application, we’d inject the dependency with code that looks like this

$dependency = new \Pulsestorm\TutorialObjectManager2\Model\Message;
$helper        = new \Pulsestorm\TutorialObjectManager2\Model\Example($dependency);

However, with Magento 2’s object system, we don’t get to supply arguments

$helper = $this->getObjectManager()->create('Pulsestorm\TutorialObjectManager2\Model\Example');

So what are we supposed to do?

That’s the beauty of Magento 2’s automatic dependency injection system. You don’t need to do anything. Just run the command with our new code in place.

$ php bin/magento ps:tutorial-object-manager-2
Hello Again World!

Same results — even though we didn’t do anything to pass in a parameter.

This is automatic dependency injection. Behind the scenes, the object manager will usePHP’s reflection features to look at a class’s __construct type hints/parameters,automatically instantiate the object for us, and then pass it into the constructor as an argument.

That can seem a little weird the first time you encounter it, but if you don’t believe us just add a bit of debugging code to your constructor

#File: app/code/Pulsestorm/TutorialObjectManager2/Model/Example.php    
public function __construct(Message $message)
{
    var_dump(get_class($message));
    exit;
    $this->messageObject = $message;
}

re-run the command, and you’ll see the class name printed out.

$ php bin/magento ps:tutorial-object-manager-2
string(47) "Pulsestorm\TutorialObjectManager2\Model\Message"

Once you’re over the weirdness, you may wonder why this is any better. Isn’t the type hint just the hard coded dependency now?

The difference is the object manager has control over the instantiation of the dependency. As module developers, we get a great deal of power to change how the object manager instantiates an injected dependency. This happens via the module’s etc/di.xml file (the distands for dependency injection). While Magento 2 doesn’t have “class rewrites” — it does have similar, more powerful features, all configurable via the di.xml file. Over the next few articles we’re going to explore all these options, and you’ll learn how to gain complete control over these dependencies.

So Long Object Manager, We Hardly Knew You

Before we wrap up for today, there’s one last thing to attend to. If you poke around Magento 2’s current documentation for Magento 2’s object system (usually labeled Dependency Injection), you’ll see cryptic comments like the following

The object manager must be present only when composing code, composing code is performed early in the bootstrapping process

Also, if you’ve been following Magento 2 development closely, you’ll know there used to be a static factory method for grabbing an object manager instance, but that method wasdepreciated and removed.

The object manager class is not meant for day-to-day use in Magento 2. It’s reserved (by convention) for system level developers working on the code that bootstraps Magento. ThegetObjectManager method we’ve provided in these tutorials was a helper so you could understand what the object manager was.

If you’re following best practices for Magento extension development, you’ll use dependency injection to instantiate almost all your objects. This may seem impossible at first (I was rolling my eyes back in 2013), but as Magento 2 nears completion it’s becoming clearer that this actually is possible.

We’ll be covering the dependency injection features that make this possible in our future articles. However, here’s some high level reassurances that “no objet manager” isn’t as crazy as it sounds.

  • All objects in Magento are instantiated with the object manager — including dependencies. In other words, the objects you inject with __construct injection can, themselves, have a __construct method with automatically injected dependencies.
  • Data management objects (what we used to call “ORM Models” in simpler times) can be instantiated via factory objects — these factory objects can be injected via dependency injection.
  • If it’s dependency injection all the way down, you may wonder what’s on the top of the stack. Remember though, all valid code entry points (a controller action method, an observer object/method, a block object, a Symfony Console command, etc.) are created by configuration. This means Magento knows about them, and when it needs to instantiate them it uses the object manager. This means all these objects have __construct injection
  • Although it’s not recommended, and may go away in a future release, as of right now there is a way to get an instance of the object manager

Regarding that last item? Take a look at the basePulsestorm\TutorialObjectManager2\Command\AbstractCommand class

#File: app/code/Pulsestorm/TutorialObjectManager2/Command/AbstractCommand.php
use \Magento\Framework\ObjectManagerInterface;

//...

public function __construct(ObjectManagerInterface $manager)
{
    $this->objectManager = $manager;
    parent::__construct();
}

protected function getObjectManager()
{
    return $this->objectManager;
}

This is the base class for our tutorial module’s cli classes, and where getObjectManager is implemented. How did we get an instance of the object manager? By using dependency injection, of course!

#File: app/code/Pulsestorm/TutorialObjectManager2/Command/AbstractCommand.php    
public function __construct(ObjectManagerInterface $manager)

This pattern can take a little getting used to, and may seem like overkill. However, in an enterprise environment consistency is more important than a home run. Constricting day-to-day development along this path and leaving more creative work to the deep level systems developers, an agile focused team can do a better job of hitting its deadlines and calculating its velocity. Boring and corporate as they are, these design patterns are an important part of ensuring Magento 2’s steady, relentless march towards a release.

That’s a lot to digest for a single day, so we’ll leave it there. Next time we’ll explore some of the dependency injection features, including how we managed to inject an object manager using only a PHP interface (ObjectManagerInterface) instead of a class.

Magento 2 Object Manager

Magento 2 brings a slew of changes to the table for an experienced Magento 1 programmer. While you can still see bits and pieces of Magento 1 (EAV, areas, blocks, etc.), the new Magento 2 core team has spent the last few years swapping out the startup fueled internals of Magento 1 with a more mature, “enterprise-java” style system. More classes, more objects, and more design patterns.

Despite what some might consider an increase in code complexity, the Magento 2 core team has also spent a lot of time simplifying and clarifying ideas in Magento 1. Magento 1’s home grown class rewrite system — based around blocks, models, and helpers — has been replaced with a Kiev grown dependency injection container/object-manager system.

If your approach to Magento 1 development has been copy-paste, cookbook style development, then Magento 2 will be, for all intents and purposes, a new framework. Your old incantations will no longer work.

If, however, your approach to Magento 1 development has been an understanding of the design pattern implementations, and cultivating an ability to read code, then coming up to speed on Magento 2 will be a slight bump on the road — and in many ways a bump that should make development a more stable, predictable affair.

This article is the first in a series explaining Magento 2’s object manager/dependency-injection system. We’ll explore how programmers (you!) create objects in Magento 2, explore the additional features Magento 2’s object system brings to the table, and along the way we’ll discuss what’s changed from Magento 1 and start to explore the conventions of Magento 2.

Magento 2 Command Line Framework

One of the big changes that Magento 2’s architecture shift brings to the table is a command line framework. This is not the simple framework from Magento 1 (discussed in my short book, No Frills Command Line Magento). Instead, Magento 2 ships with a full implementation of Symfony’s Console component.

After installing Magento 2, you can see a list of the default commands that ship with the system by opening a terminal application and typing the following command.

$ php bin/magento 

You should see output that looks something like this.

Magento CLI version 0.74.0-beta16

Usage:
 command [options] [arguments]

Options:
 --help (-h)           Display this help message
 --quiet (-q)          Do not output any message
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal
                       output, 2 for more verbose output and 3 for debug
 --version (-V)        Display this application version
 --ansi                Force ANSI output
 --no-ansi             Disable ANSI output
 --no-interaction (-n) Do not ask any interactive question

Available commands:
 help                                      Displays help for a command
 list                                      Lists commands
 //... full command list snipped ...

In addition to shipping with a number of useful commands, third party developers can create Magento modules that add new commands to the system.

We’re going to use the command line framework to run our code samples in this tutorial. The command line is a nice clean place to run sample code, observe output, and not need to worry about the layers and layers of abstraction between a browser page load and your Magento code running.

Installing the Tutorial Sample Code

We’ve prepared a Magento 2 module with a simple command line interface (cli) command implemented for you. The hows and whys of Magento 2 module installation are still settling into place, so for now our best bet is to install this module manually.

We’ve placed the module source on GitHub. This module will add a command namedps:tutorial-object-manager-1, which we’ll use below. To install this module

  1. Navigate to the GitHub releases page
  2. Find the latest release (0.0.3 at the time this tutorial was written), and click on thezip or tar.gz link to download the release archive
  3. Extract the archive source into your Magento 2 installation, matching the appfolder structure

You should now be able to run the following ls command from the root of your Magento installation and see the top level files of the Pulsestorm_TutorialObjectManager1module.

$ ls -l app/code/Pulsestorm/TutorialObjectManager1/
total 0
drwxr-xr-x@ 4 alanstorm  staff  136 Jul  9 08:49 Command
drwxr-xr-x@ 3 alanstorm  staff  102 Jul  9 08:49 Model
drwxr-xr-x@ 4 alanstorm  staff  136 Jul  9 08:49 etc

You can also check that the module is installed correctly by running the module:statuscommand

$ php bin/magento module:status
List of enabled modules:
Magento_Store
//...

List of disabled modules:
Pulsestorm_TutorialObjectManager1    

If you’ve properly extracted the module to the app/code folder, you should see output similar to the above when you run module:status. Sharp eyed readers will notice a problem though — while Pulsestorm_TutorialObjectManager1 is present, it’s listed as a disabled module. We need to tell Magento 2 that this module is enabled.

To do this, we need to edit the app/etc/config.php file. Open this file in your favorite text editor and look for the following section

#File: app/etc/config.php
<?php
return array (
  'modules' => 
  array (
    'Magento_Store' => 1,
    //... full module list snipped ...
    'Magento_Wishlist' => 1,        
  ),
);

The app/etc/config.php file is a simple PHP include file that returns an array of configured modules. The modules you see are the core modules that ship with the system. We’ll want to add our module to the end of this list.

#File: app/etc/config.php
<?php
return array (
  'modules' => 
  array (
    'Magento_Store' => 1,
    //... full module list snipped ...
    'Magento_Wishlist' => 1,
    'Pulsestorm_TutorialObjectManager1' => 1
  ),
);

Now if we run module:status, we should see our module listed in the enabled list.

$ php bin/magento module:status
List of enabled modules:
Magento_Store
//...
Pulsestorm_TutorialObjectManager1 

List of disabled modules:
None

Same as Magento 1, a module’s full name comes from combining its “namespace” folder and its “module name” folder. However, there’s a few things here that might throw a Magento 1 developer for a loop. Specifically

  1. Magento’s module namespace is now Magento_ instead of Mage_
  2. The core, community and local code pools are gone. All modules live inapp/code
  3. The core team has replaced the app/etc/modules “module declaration files” with a simpler, PHP based configuration

The Magento to Mage change is, in my opinion, a neutral one, but the removal of both code pools and the module declaration files are net positive for Magento.

While the core, community, and local code-pools/include-folders allowed system owners to make quick changes to Magento functionality via code pool overrides, these files contributed heavily to a merchant’s unwillingness to upgrade their systems, and often led to subtle system problems when developers would change too much of a class file’s implicit contract.

The loss of module declaration files in app/etc/modules — while part of an interesting idea (one global config for the entire system) — will not be missed. While the original intent of these files may have been to configure which modules should, or should not, be in enabled in a system, poor early developer evangelism and core team practices meant that extension developers started packaging these files with their extensions, and any hope of these being user configurable files went out the window. The Magento 2 approach of a simple on/off configuration array makes much more sense.

Running Commands and Clearing Cache

Alright, now that we’ve installed our module, we’ll want to make sure the newps:tutorial-object-manager-1 command shows up. Before we do that, there’s one more step to take. We’ll need to clear Magento’s cache.

Conceptually, cache clearing in Magento 2 is the same as Magento 1. There are long running operations that will run once, and then Magento caches the results for quicker loading next time. For example, although we’ve added our new module to Magento’s configuration, Magento’s configuration is cached, so the system that’s running doesn’t actually know about our module yet.

Magento 2 provides a cli command for clearing the cache. Just run the following, and you should be good to go.

$ php bin/magento cache:clean 
Cleaned cache types:
config
layout
block_html
view_files_fallback
view_files_preprocessing
collections
db_ddl
eav
full_page
translate
config_integration
config_integration_api
config_webservice

We say should because although this will clear all Magento’s cache types, it’s not 100% clear if this will clear the entire cache. Magento 1 contained a number of cache entries that were untyped (Zend’s module column names, for example), and Magento 1’s cache clearing commands wouldn’t delete these. Magento 2 is still too young for us to have discovered these edge cases, but if you’re uncertain about Magento’s cache being cleared and you’re using the default cache storage, you can nuke the entire cache by manually removing all the files and folders in Magento’s var/cache folder.

$ rm -rf /path/to/magento/var/cache/*

Additionally — although it’s not relevant to us here, Magento 2 also uses some automatic code generation features that — while not strictly a cache — may need to be regenerated if certain system configuration or code files are changed/updated. If you’re clearing your cache, it’s also a good idea to clear out these generated code files. The quickest and easiest way to do this on your development machine is to remove all the files in

$ rm -rf /path/to/magento/var/generation/*

You’ll want to be careful doing this with production systems. Magento 2 is still new enough that all the pitfalls of code generation have yet to be uncovered.

Assuming you’ve cleared your cache, let’s look for our command. You can see a list of commands by using the list command

$ php bin/magento list

//...

 ps:tutorial-object-manager-1              A cli playground for testing
                                           commands
//...

Assuming you see the command above, lets try running it!

$ php bin/magento ps:tutorial-object-manager-1
You did it!

Congratulations! You just manually installed your first Magento module.

Update: Kristof Ringleff pointed out there’s actually two commands for enabling/disabling your Magento 2 modules (module:enable and module:disable). You’ll definitely want to use these commands when you’re setting up a new module

$ php bin/magento module:enable Pulsestorm_TutorialObjectManager1
The following modules have been enabled:
- Pulsestorm_TutorialObjectManager1

To make sure that the enabled modules are properly registered, run 
'setup:upgrade'.

Cache cleared successfully.
Generated classes cleared successfully.

Alert: Generated static view files were not cleared. You can clear them 
using the --clear-static-content option. Failure to clear static view files 
might cause display issues in the Admin and storefront.

What’s nice about the module:enable/module:disable commands are they’ll

  • Automatically clear the cache for you
  • Automatically remove old generated code for you
  • Give you notifications about other things you might need to do
  • Allow you to enable/disable modules even if the internal method for enabling/disabling modules changes

Changing our Command

We’re almost ready to start discussing the object manager. Now that we have the sample module installed, let’s try changing the implementation of ps:tutorial-object-manager-1.

Every cli command in Magento 2 is implemented with a PHP class. To change a command’s implementation, all you need to do is open this class file in your favorite text editor. Try opening up the following file, and locate the execute method

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $output->writeln("You did it!");
}

Then, edit the command definition file so it looks like this instead

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $output->writeln("Hello World!");
}    

If we try running the command now we should see the following output.

$ php bin/magento ps:tutorial-object-manager-1
Hello World!

All we’ve done above is pass a message to the command line framework’s writeln method — this is the framework’s equivalent of echo or print.

With that done, we’re finally ready to start talking about Magento’s object manager.

Magento 2 Objects

First off, let’s review some PHP 101. When you want to instantiate an object in PHP, you use the new keyword. Let’s give that a try in our execute method

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $object = new \Pulsestorm\TutorialObjectManager1\Model\Example;
    $message = $object->getHelloMessage();
    $output->writeln(
        $message
    );                
}    

Try running the above code, and you should see something like this

$ php bin/magento ps:tutorial-object-manager-1
Hello Magento!

All we’ve done above is

  1. Instantiate an object from the classPulsestorm\TutorialObjectManager1\Model\Example
  2. Call the resulting object’s getHelloWorld method to fetch a message string,
  3. Used the command line framework’s writeln method to output a message.

So far this is plain old PHP — if you’re not familiar with PHP namespaces or those backslash characters you may find this namespace primer useful.

There’s nothing stopping you from using plain old PHP classes in Magento 2. However, if you want to take advantage of Magento 2’s advanced object features (automatic constructor dependency injection, object proxying, etc) you’ll need to use Magento’s object manager. Give the following code a try

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $manager = $this->getObjectManager();
    $object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');
    $message = $object->getHelloMessage();
    $output->writeln(
        $message
    );                
}  

Run the above code, and you should see identical output

$ php bin/magento ps:tutorial-object-manager-1
Hello Magento!

The big difference between this new code and our old code are these two lines

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php        
$manager = $this->getObjectManager();
$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');

The first, $manager = $this->getObjectManager();, fetches the object manager for us. This is not something you’d normally do when writing a Magento 2 extension —getObjectManager is a helper method we’ve added for this tutorial. We’ll return to this concept later, but for now accept that the getObjectManager will return an instance of the Magento object manager.

The object manager is a special object that Magento uses to instantiate nearly all its objects. That’s what we’ve done in our second line

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    
$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');

We’ve called the object manager’s create method, and passed in a PHP class name as a string. Behind the scenes, the object manager instantiates aPulsestorm\TutorialObjectManager1\Model\Example for us.

That, in its simplest form, is Magento 2’s object manager. This may seem like a trivial difference, but by routing all object instantiation through this single point, Magento system engineers are able to give their objects a number of “super powers”.

Before we get to the super powers, and while covering it in full is outside the scope of this article, the Magento object manager’s source code is in the following class file.

#File: lib/internal/Magento/Framework/ObjectManager/ObjectManager.php
namespace Magento\Framework\ObjectManager;

class ObjectManager implements \Magento\Framework\ObjectManagerInterface
{
    //...    
}

If you’re a Magento 1 developer the object manager is a replacement for these three methods

Mage::getModel(...);
Mage::helper(...);
Mage::getSingleton('core/layout')->createBlock(...);    

While Magento 2 still has the concept of a model, a helper, and a block, you no longer need to know the class alias for these objects (core/template, model/product, etc.) and the object manager can instantiate any PHP class, not just a model, helper or block object.

Automatic Singleton Objects

We’re going to wrap up today by talking about one of the super powers Magento’s object manager gives to objects — automatic singletons. Before we do that though, we should probably describe what we mean by singleton!

Try running the following code

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $manager = $this->getObjectManager();
    $object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');        
    $object->message = 'Hello PHP!';
    $output->writeln(
        $object->getHelloMessage()
    );                

    $object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');        
    $output->writeln(
        $object->getHelloMessage()
    );                        
}

You should see output that’s something like this

$ php bin/magento ps:tutorial-object-manager-1
Hello PHP!
Hello Magento!

This code is similar to our previous example, with a few additions. First, we used the object manager to instantiate an object from thePulsestorm\TutorialObjectManager1\Model\Example class

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    

$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example'); 

Then we set a custom message string for the object

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    

$object->message = 'Hello PHP';

And then we used the cli framework’s writeln method to output the new message.

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    

$output->writeln(
    $object->getHelloMessage()
);                

That’s the first chunk of code. In the second chunk of code

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    

$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');        
$output->writeln(
    $object->getHelloMessage()
);                        

we instantiated a new Pulsestorm\TutorialObjectManager1\Model\Example object, and then output its default message. This meant our program output

Hello PHP!
Hello Magento!

was the custom message, followed by the default message.

Pretty straight forward stuff.

Now, let’s try the exact same code, except instead of using the object manager’s createmethod, we’ll use the object manager’s get method

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    

protected function execute(InputInterface $input, OutputInterface $output)
{
    $manager = $this->getObjectManager();
    $object  = $manager->get('Pulsestorm\TutorialObjectManager1\Model\Example');        
    $object->message = 'Hello PHP!';
    $output->writeln(
        $object->getHelloMessage()
    );                

    $object  = $manager->get('Pulsestorm\TutorialObjectManager1\Model\Example');        
    $output->writeln(
        $object->getHelloMessage()
    );                        
}

Run this code, and your output will be a little different.

$ php bin/magento ps:tutorial-object-manager-1
Hello PHP
Hello PHP

Instead of printing the default message the second time, our program printed the same custom message we set on the first object. Why’d this happen? Because the second object isthe first object.

This is the Magento object manager’s “automatic singleton” feature. If you’re not familiar with the concept — a singleton is an object that can only be instantiated once. With a singleton, future attempts to instantiate the same object will return the original instance. This is traditionally accomplished by adding special code to a class’s constructor — but with Magento 2’s object manager any class can be turned into a singleton object.

Relating the back to our code sample above, the second time we called get Magento returned the original object with a custom message set.

If you’re coming from Magento 1, the create/get difference is similar to the

Mage::getModel(...);
Mage::getSingleton(...);

difference. Again though, this applies to all classes and objects in Magento 2, not just the model objects.

Automatic singletons are just one of the super powers Magento 2’s object manager gives its objects. Next time we’ll talk about the big super power — automatic constructor parameter dependency injection — and we’ll also reveal why Magento’s documentation (seemingly paradoxically) tells you not to use the object manager.