Magento 2 Object Manager: Instance Objects

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

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 a Pulsestorm\TutorialInstanceObjects\Model\Example object, but we don’t know if it will be a new instance of a Pulsestorm\TutorialInstanceObjects\Model\Example object, 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 instantiate Pulsestorm\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 name attribute 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 the create method to instantiate an object every time it encounters Pulsestorm\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 new method

$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\PageFactory object, 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 the Magento/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 a Pulsestorm\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 a Pulsestorm\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.

Originally published August 30, 2015

Magento 2 Object Manager: Proxy Objects

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

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 and Pulsestorm_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 a Pulsestorm\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 the Pulsestorm\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 and sayHelloWithFastObject 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\FastLoading and Pulsestorm\TutorialProxy1\Model\SlowLoading. In turn, the sayHelloWithFastObject 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 and sayHelloWithSlowObject

We’ve also added some debugging messages to the constructors of our FastLoading and SlowLoading 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 a Pulsestorm\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 a Pulsestorm\TutorialProxy1\Model\Example object, it will need to instantiate both a Pulsestorm\TutorialProxy1\Model\FastLoading and Pulsestorm\TutorialProxy1\Model\SlowLoading object.

So, our problem isn’t really a slow loading Example object — it’s a slow loading Pulsestorm\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 the Pulsestorm\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.xml file.

#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 the Pulsestorm\TutorialProxy1\Model\Example object, specifically the argument named slow, and replacing it with a Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxy object.

The one new thing is the Pulsestorm\TutorialProxy1\Model\SlowLoading\Proxy class/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 the Pulsestorm\TutorialProxy1\Model\SlowLoading class, we replace it with a Pulsestorm\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 --all
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 a Pulsestorm\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: The Pulsestorm\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 a Pulsestorm\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 execute method.

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 the Pulsestorm\TutorialProxy1\Model\SlowLoading object by doing this?

Fortunately not. In addition to replacing the constructor, our proxy object also replaced each 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.

Originally published August 23, 2015

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\Example object. 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 a Pulsestorm\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 a Pulsestorm\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 to showNestedPropertiesForObject

#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 the showNestedPropertiesForObject 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 (a Pulsestorm\TutorialVirtualType\Model\Argument1 object)

  3. Finally, we use the object manager to directly instantiate a Pulsestorm\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 real PHP 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 the Example 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 named ourVirtualTypeName), 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 your di.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 a Pulsestorm\TutorialVirtualType\Model\Argument3 object. The argument nodes for a virtual type behave exactly as they would for a regular <type/> node — except they only effect 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 the property_of_argument1_object property is now an Argument3 object — but only when that parameter’s owner class (Argument1) is instantiated by dependency injection in the Example 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 in Magento\Catalog\Model\Session with the PHP class Magento\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\Storage is 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.

Originally published August 2, 2015