Magento 2: Composer Plugins

This entry is part 2 of 3 in the series Magento 2 and Composer. Earlier posts include Magento 2: Composer, Marketplace, and Satis. Later posts include Magento 2: Composer and Components.

In our last article, we talked a bit about Magento 2’s use of Composer, and touched on the Composer meta-package installation method. One high level take away was, when you use Composer’s create-project method to start a new Magento project, you are

  1. Fetching the latest version of a specific Composer package (magento/project-community-edition) hosted at repo.magento.com
  2. Running composer install for that project’s composer.json file

The curious among you may have noticed something strange about this. If we use the --no-install option to skip step #2 above (i.e. only fetch magento/project-community-edition),

$ composer create-project --no-install --repository-url=https://repo.magento.com/ magento/project-community-edition

we’ll see a pretty sparse project folder

$ ls project-community-edition
README.md    composer.json    update    

The README.md file is a basic Welcome to Magento affair, and the composer.json file contains the actual Composer packages you’ll need to install Magento. That’s whymagento/project-community-edition is called a “meta” package — it’s not he actual package, and most software engineers aren’t english or philosophy majors and enjoy stretching the definition of “meta”.

The update folder contains a snapshot of the Magento Component Manager application (i.e Market Place updater), which is a separate project from the Magento core. Why this feature is a separate application, and why Magento distributes it like this is a story for another time.

All in all, relatively straight forward. However, after running composer install (or running create-project without the --no-install flag), we end up with the following

$ ls project-community-edition/
CHANGELOG.md                        dev
CONTRIBUTING.md                     index.php
CONTRIBUTOR_LICENSE_AGREEMENT.html  lib
COPYING.txt                         nginx.conf.sample
Gruntfile.js                        package.json
LICENSE.txt                         php.ini.sample
LICENSE_AFL.txt                     phpserver
README.md                           pub
app                                 setup
bin                                 update
composer.json                       var
composer.lock                       vendor

That’s a huge difference. A plethora of files and folders. Many of these files are necessary for Magento to run, others are informational, and still others are sample configurations. However, if you’re new to the Composer eco-system, you may be wondering

Where the heck did all these extra files and folders come from? I thought Composer would only update files in /vendor.

Today we’re going to explain how these files get here, and why you’ll need to be hyper aware of this as a Magento 2 developer. To start, we’ll need to dive into some less known features of Composer

Composer: Plugins and Scripts

Composer bills itself as a Dependency Manager for PHP. While this is true, and dependency management is an important part of a PHP project, Composer is really a foundational framework for PHP development, and serves the same role that linkers do in the C/C++ world.

Yes yes, I know, from a computer science point of view linkers and Composer couldn’t be further apart. However, the end result of a linker is, the C programmer stops needing to worry about how they incorporate code from other libraries into their program. In a similar way, Composer does the same thing for PHP — if a project conforms to what Composer expects in terms of directory structure and autoloading, and a PHP developer conforms to what Composer expects from a PHP program (i.e., includes the Composer autoloader), the developer stops needing to worry about how they should include other people’s code in their own systems.

When considered from this point of view — that Composer is, itself, just another programmatic framework that your code sits on top of — it makes more sense that Composer would have a plugin system for changing, altering, and extending its behavior. There are two main systems programmers have for altering the behavior of Composer. These systems are scripts, and plugins.

Scripts and plugins share a base set of concepts, but have a few key distinctions. Scripts provide a way, in the project composer.json file, to take additional programmatic action when composer triggers certain events. These events are listed in the Composer manual, and include things like pre-and-post composer install running.

Plugins, on the other hand, provide the same mechanism for individual packages that are part of a larger project. In addition to listening for Composer events, plugins also have the ability to modify composer’s installation behavior.

Put another way, you configure scripts in your main composer.json file, you (or third parties) configure plugins in composer.json files that live in the vendor/ folder.

While understanding both systems is important for a well rounded Composer developer, today we’re going to focus on the plugin system.

Plugin Example

Rather than try to describe things from scratch, we’ve created a simple Composer pluginthat should demonstrate the plugin lifecycle, and help you understand what Magento 2 is doing with Composer plugins.

If you take a look at the plugin class

#File: src/Plugin.php
//...
public static function getSubscribedEvents()
{
    return array(
        'post-install-cmd' => 'installOrUpdate',
        'post-update-cmd' => 'installOrUpdate',            
    );
}    
//...    
public function installOrUpdate($event)
{
    file_put_contents('/tmp/composer.log', __METHOD__ . "\n",FILE_APPEND);
    file_put_contents('/tmp/composer.log', get_class($event) . "\n",FILE_APPEND);            
}

you can see that this plugin listens for the post-install-cmd and post-update-cmdevents. You tell a plugin which events it should listen to by defining agetSubscribedEvent method that returns an array in the above format. Keys are the event, and values are the method, (on the plugin class), that Composer calls as an observer.

In our case, both the post install and post update events call the installOrUpdate method, and this method logs some simple information to the /tmp/composer.log file in our temp directory.

The plugin class also has an activate method.

#File: src/Plugin.php
public function activate(Composer $composer, IOInterface $io)
{
    file_put_contents('/tmp/composer.log', __METHOD__ . "\n",FILE_APPEND);
}

Composer calls the activate method when it detects the plugin every time Composer runs. The activate method is where you instantiate any other objects your plugin will need. In our case, we’ve added a line to log when the method is called.

All in all, a mostly useless plugin, but one that’s useful to diagnose how plugins work.

Adding a Plugin to Your Project

Adding a plugin to your project is the same as adding any other Composer package to your project. Create a new folder

$ mkdir test-plugin
$ cd test-plugin

and then create the following composer.json file in that folder.

//File: composer.json
{
    "repositories":[
        {
            "type":"vcs",
            "url":"git@github.com:astorm/composer-plugin-example.git"
        }
    ],
    "require":{
        "pulsestorm/composer-plugin-example":"0.0.1"
    }
}

In the require section we’ve added our plugin (pulsestorm/composer-plugin-example) and the desired version (0.0.1). The plugin’s name comes from the plugin’scomposer.json file file. The version, 0.0.1, comes from the tagged releases.

Since I didn’t create a packagist.org listing for this package, we need the repositoriessection. This tells Composer to use the git (vcs/version control system) repository at the provided URL as a repository.

This Composer file in place, there’s one last thing we’ll want to do before we run composer install. In a separate terminal window, run the following.

$ touch /tmp/composer.log
$ tail -f /tmp/composer.log

This creates our composer.log file, and then tails it. Tailing a file means showing the last few lines of output. When we run tail with the -f option, we’re telling tail to show us the last line of the file whenever the file is changed. This is a decades old technique for monitoring log files in the *nix world.

Composer Plugin Lifecycle

OK! We’re ready to install our simple project. Run

$ composer install

and composer will install the plugin.

$ composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)                       
  - Installing pulsestorm/composer-plugin-example (0.0.1)
    Loading from cache

Writing lock file
Generating autoload files

More interesting to us though is the output in our /tmp/composer.log file.

Pulsestorm\Composer\Example\Plugin::activate
Pulsestorm\Composer\Example\Plugin::installOrUpdate
Composer\Script\Event

Here, we see Composer called the activate method, and then (per our events) called theinstallOrUpdate method. If we were to run update

$ composer update

We’d see the same lines

Pulsestorm\Composer\Example\Plugin::activate
Pulsestorm\Composer\Example\Plugin::installOrUpdate
Composer\Script\Event    

because we’re also listening for the update event.

A Composer plugin developer can, (via the Composer\Script\Event object Composer passed to our handler or the Composer\Composer object Composer passes to the activemethod), examine and change Composer’s state at run time, implementing all sorts of extra functionality whenever a Composer project updates.

Covering that functionality in full is beyond the scope of this article, but with Composer being open source, there’s nothing stopping you from diving right in.

What Makes a Package a Plugin?

As we mentioned earlier, a Composer plugin is just a standard Composer package. However, it’s a standard Composer package with a special composer.json file. Let’s take a look at our plugin’s composer.json file.

//File: composer.json
{
    //...
    "type": "composer-plugin",
    //...        
    "require": {
        "composer-plugin-api": "^1.0"
    },
    "autoload":{
        "psr-4":{
            "Pulsestorm\\Composer\\Example\\":"src/"
        }
    },    
    "extra":{     
        "class":"Pulsestorm\\Composer\\Example\\Plugin"
    }
    //...
}

The first configuration a plugin package needs is the following

//File: composer.json

"type": "composer-plugin"

This tells Composer that this is a plugin package.

The second configuration a plugin package needs is

//File: composer.json

"require": {
    "composer-plugin-api": "^1.0"
},    

This looks like a standard Composer require — but it’s not. When Composer encounters a package named composer-plugin-api, this indicated which Plugin API version your plugin targets.

Finally, in the extra section, the following configuration

//File: composer.json

"extra":{     
    "class":"Pulsestorm\\Composer\\Example\\Plugin"
}

points to our plugin class (Pulsestorm\Composer\Example\Plugin). Since Composer will need to instantiate this class, that means you’ll need something in your autoload section that ensures PHP will load the class definition file. In our case, we used a standardPSR-4 autoloader

//File: composer.json

"autoload":{
    "psr-4":{
        "Pulsestorm\\Composer\\Example\\":"src/"
    }
}

That’s all you’ll need for a Composer plugin!

Finding Magento 2’s Composer Plugins

Now that we have a better understanding of Composer plugins, we can come back to our Magento problem. As a reminder, we’re trying to figure out how the stock create-project files

$ ls project-community-edition
README.md    composer.json    update 

become a full fledged, many files outside of vendor, Magento installation

$ ls project-community-edition/
CHANGELOG.md                        dev
CONTRIBUTING.md                     index.php
CONTRIBUTOR_LICENSE_AGREEMENT.html  lib
COPYING.txt                         nginx.conf.sample
Gruntfile.js                        package.json
LICENSE.txt                         php.ini.sample
LICENSE_AFL.txt                     phpserver
README.md                           pub
app                                 setup
bin                                 update
composer.json                       var
composer.lock                       vendor

If it’s not obvious by now, these additional files are placed here by a plugin in one of the Composer packages that make up Magento 2.

Unfortunately, Composer doesn’t provide an easy way to check your project for any installed plugins. You’ll need to use some good old fashioned unix command line searching to figure out which Magento packages have plugins.

In plain english, we’ll want to

  1. Create a list of all our project’s composer.json files
  2. Search those files for the all important "type": "composer-plugin", text

In unix english, that’s

$ find vendor/ -name composer.json | xargs grep 'composer-plugin'
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json:    "type": "composer-plugin",
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v1/composer.json:        "composer-plugin-api": "1.0.0"
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json:    "type": "composer-plugin",
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v2/composer.json:        "composer-plugin-api": "1.0.0"
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json:    "type": "composer-plugin",
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v3/composer.json:        "composer-plugin-api": "1.0.0"
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json:    "type": "composer-plugin",
vendor//composer/composer/tests/Composer/Test/Plugin/Fixtures/plugin-v4/composer.json:        "composer-plugin-api": "1.0.0"
vendor//magento/magento-composer-installer/composer.json:    "type":"composer-plugin",
vendor//magento/magento-composer-installer/composer.json:        "composer-plugin-api": "^1.0"

We can safely ignore the results in composer/composer/tests — these are tests in the main Composer package. The result we are interested in is

vendor//magento/magento-composer-installer/composer.json

It looks like the magento/magento-composer-installer package is actually a Composer plugin. If we take a look at the contents of this composer.json file

#File: vendor//magento/magento-composer-installer/composer.json 
{
    //...

    "type":"composer-plugin",

    //...

    "extra":{
        //...
        "class":"MagentoHackathon\\Composer\\Magento\\Plugin"
    }
}

We see the composer-plugin type-tag our command line searching found, as well as the required extra configuration that configures theMagentoHackathon\Composer\Magento\Plugin class as a plugin.

Without getting into the specific technical details, this is the plugin that installs those extra files at the root level, above the vendor folder. In short, theMagentoHackathon\Composer\Magento\Plugin will

  1. Listen for composer install and composer update events
  2. Look at the extra->map section(s) for any composer.json file in the just installed or updated composer vendor packages
  3. Use that information to copy file from the installed package, to the root level project folder

If that didn’t make sense, let’s walk through it. First, let’s find any composer.json files with a "map" section.

$ find vendor/ -name composer.json | xargs ack '"map"'
vendor/magento/magento2-base/composer.json
75:        "map": [

vendor/magento/magento2-base/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/composer.json
12:    "map": [

vendor/magento/magento2-base/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/composer.json
12:    "map": [

Again, we can safely ignore the files in the tests folder — this leaves us (at the time of this writing) with a single result in the magento/magento2-base package. If we look at a snippet of this file

//File: vendor/magento/magento2-base/composer.json
{
    "name": "magento/magento2-base",
    //...
    "extra": {
        //...
        "map": [
            [
                "lib/internal/Cm",
                "lib/internal/Cm"
            ],
            [
                "lib/internal/LinLibertineFont",
                "lib/internal/LinLibertineFont"
            ],
            [
                "lib/internal/Credis",
                "lib/internal/Credis"
            ],
            //...
            [
                "LICENSE_AFL.txt",
                "LICENSE_AFL.txt"
            ],
            [
                "vendor/.htaccess",
                "vendor/.htaccess"
            ]
        ]
    }
}

When the MagentoHackathon\Composer\Magento\Plugin finds the above map section, it will start running PHP code that’s roughly equivalent to

cp -r vendor/magento/magento2-base/lib/internal/Cm lib/internal/Cm
cp -r vendor/magento/magento2-base/lib/internal/LinLibertineFont lib/internal/LinLibertineFont

cp -r vendor/magento/magento2-base/lib/internal/Credis lib/internal/Credis

//...
cp vendor/magento/magento2-base/LICENSE_AFL.txt LICENSE_AFL.txt

cp vendor/magento/magento2-base/vendor/.htaccess vendor/.htaccess"

This is how the non-vendor files Magento needs to operate get from Magento core vendorpackages into the root folder of your project.

History of magento/magento-composer-installer

Before we wrap up, it’s worth noting that the magento/magento-composer-installerComposer plugin is a fork of the original Magento 1 Composer installer plugin built at a Magento hackathon, promoted by Firegento, and maintained by Daniel Fahlke. The original goals of this plugin were to build a system that allowed developers to use Composer to fetch Magento 1 plugins into vendor, and then install them into a Magento 1 system via a number of different strategies. This was necessary since Magento 1 never officially adopted Composer.

The Magento core team has repurposed the project as an automatic installer which, on one hand, shows the power and usefulness of open source. On the other hand, if you’re not familiar with the project history and you start exploring the plugin’s implementation in

vendor/magento/magento-composer-installer//src/MagentoHackathon/Composer/Magento/Plugin.php

you may be left scratching your head.

However, if you keep the project’s original goals in mind, the source should make a little more sense.

Consequences

Between this article and last week’s, you should have a pretty good understanding of where all Magento’s files come from in a Composer “meta-package” installation. Understanding this is critical for Magento 2 developers and consultants looking to use and develop day-to-day on Magento’s systems. For example, it may be temping to drop some functions into

app/functions.php

as a shortcut way to get your code into a Magento system, but once you understand that any future composer update will wipe these changes away, the true cost of such short cuts become apparent. Don’t edit the core is as true as it ever was, but with Magento 2 it’s not always clear what is, and what isn’t, a core Magento file.

Next time we’ll be diving into Magento 2’s component system — the system that makes Composer distribution possible.

Magento 2: Composer, Marketplace, and Satis

This entry is part 1 of 3 in the series Magento 2 and Composer. Later posts include Magento 2: Composer Plugins, and Magento 2: Composer and Components.

As Magento 2 approaches its first half-birthday, one thing is clear: Magento 2 is leaning heavily on PHP Composer for its developer workflow, and for the merchant facing Marketplace.

If you’re a developer working with Magento, you may be familiar withrepo.magento.com. This is Magento 2’s composer repository, and up until Magento Imagine 2016, its main purpose was to provide modern PHP developers with a way to install Magento 2 using PHP composer.

At Imagine 2016 Magento unveiled their Magento Connect replacement, Magento Marketplace. Behind the scenes, Marketplace is running on PHP Composer, andrepo.magento.com is the source repository for purchased extensions.

In this article, we’re going to show you how you can mirror your Magento specific composer packages on a local server. While not necessary, this is a useful precaution to take if you want to avoid any unscheduled maintenance bringing down your deployment and development pipelines. Along the way, we’ll also discuss Marketplace’s new composer based architecture, and end up touching on many lesser known composer features.

Understanding Composer

If you’re interested in an in-depth look at how composer works, my Laravel, Composer, and the State of Autoloading article is still a great starting point. If you don’t have time for that right now, here’s the basics.

Default Composer Behavior

As a developer, if you want to install a PHP package, you say

Hey, composer, get me the source files for the foo/bar package

i.e.

$ composer require foo/bar

When you do this, behind the scenes, composer says

Hey, Packagist, where can I find the foo/bar package?

In turn, Packagist answers

The package you want is at [THIS Git/SVN/Mercurial URL] and I found an archive at[THIS URL]

Packagist is a composer repository. One thing that catches a few composer newcomers by surprise is packagist, and other composer repositories, don’t actually host any packages. They just point to the location of a package on another server.

Other Repositories

Packagist support is baked into composer. However, it’s possible to point composer atother Composer repositories with a composer.json configuration that looks something like this

//File: composer.json    
{
    //...
    "repositories": [
        {
            "type": "composer",
            "url": "https://repo.magento.com/"
        }
    ],
    //...        
}

So, in reality, this

Hey, Packagist, where can I find the foo/bar package

Has a preamble of

“Hmm”, composer said to itself “Do I have any custom repositories configured? If so, I should ask them first for the location of the foo/bar package”

Magento’s Composer Repository

Magento Inc. hosts a composer repository at the URL https://repo.magento.com/. Magento developers were introduced to this repository via the composer meta-package installer. This installation method fetches and installs Magento’s source code via Composer. Notice the --repository-url flag in the command

$ composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition <installation directory name>

This flag ensures the magento/project-community-edition package is downloaded from repo.magento.com and not packagist.

The meta-package method downloads and installs Magento components (modules, themes, language packs, and libraries) into the vendor/magento/ folder. Thanks to Magento’sregistration.php file and PSR-4 support, there’s no longer a strictly defined place where Magento modules, themes, language packs and libraries need to be located. These components can now be separated out into individual composer packages.

One thing that caught a lot of Magento developers off guard about this meta-package was the requirement for set of composer auth.json credentials, issued by Magento’s main website. It didn’t quite make sense that Magento would create this authenticated method for installing Magento CE when the source code was already publicly available via GitHub.

The key to understanding Magento’s need for credentials is Magento Marketplace.

Magento Marketplace

Magento Marketplace is Magento 2’s replacement for the old Magento Connect. Magento Connect only hosted free Magento modules — commercial module listings pointed off towards independent software vendor’s websites where you could purchase the extension or service directly from the independent vendor.

Magento Marketplace changes that. Marketplace has free extensions available, but it also provides a one-stop shop for purchasing your Magento extensions. Once purchased, you can download the extension package from the My Account section of Magento’s website.

In addition to this download, the extension will also be available via the Magento 2 Component Manager in Magento’s backend, at System -> Web Setup Wizard. The Component Manager is a GUI for installing composer packages from repo.magento.com. It turns out that repo.magento.com is a session-ed packagist repository.

When you log in to repo.magento.com using the aforementioned HTTP Auth credentials, Magento’s composer repository returns a custom list of packages for you to install. This will include

  1. The base Magento 2 CE packages
  2. Any Magento 2 EE versions your account has access to
  3. Any package you’ve purchased

This is what enables you to fetch purchased Magento Marketplace packages via Component Manager. You can also simply add new packages to your composer.json file and then update your system via the command line (i.e. composer update)

Mirroring repo.magento.com

While Magento’s adoption of composer is welcomed, it does add an additional wrinkle to deploying Magento 2 projects. The repo.magento.com repository is a new, single point of failure that, when down, could block a composer based deployment from being updated, or prevent your development team from getting started on a new project. Additionally, unlike the other (still not great) single points of failure in a composer project (packagist.org, github.com, etc.), repo.magento.com isn’t yet a battle tested system.

Because of this, it makes sense to create a local mirror of repo.magento.com. In addition to protecting you from unplanned maintenance/downtime at repo.magento.com, hosting your Magento packages on your local network (or even local development machine) should speed up Magento deployment tremendously.

Mirroring with Satis

The composer project has a second, sibling project called satis. Satis was created to allow developers to create their own local mirrors of packagist.org content. It turns out a stock composer repository requires zero dynamic processing — a repository is just a collection of static json files, and (optionally) mirrored archive packages.

To use satis, you’ll need to clone the GitHub repository to your local development machine with one of the following commands

git clone https://github.com/composer/satis
git clone git@github.com:composer/satis.git

Once cloned, you can run the satis command with php bin/satis

$ php bin/satis 
Satis version 1.0.0-dev

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:
 add     Add repository URL to satis JSON file
 build   Builds a composer repository out of a json file
 help    Displays help for a command
 init    Initialize Satis configuration file
 list    Lists commands
 purge   Purge packages

Satis needs two things to build a repository mirror.

  1. A JSON configuration file
  2. An output folder

Satis will output packages.json files and (optionally) package archives to the output folder you specify. You’ll be able to upload these files to any simple web server, and have a working packagist repository.

The JSON configuration file is where you tell satis which repositories you’d like to mirror, which packages in the repositories, as well as any other satis configuration needed.

The syntax for building a satis mirror is

$ php bin/satis build config.json build-folder

The file we’ve named config.json above is often named satis.json by convention.

Satis Configuration

Next up, we’re going to create a satis configuration file and review its options. While the composer manual has a section on satis, the file format isn’t as well documented as it could be. The following is one possible configuration, please get in touch or comment below if you see something egregiously wrong here — I’m learning with the rest of you here!

#File: satis.json
{
    "name": "Pulse Storm mirror of repo.magento.com",
    "homepage": "http://composer.pulsestorm.dev",
    "repositories": [
        { 
            "type": "composer", 
            "url": "https://repo.magento.com" 
        }
    ],
    "require-dependencies": true,
    "require-dev-dependencies": true, 
    "require-all": true,   
    "archive": {
        "directory": "dist",
        "format": "zip",
        "prefix-url": "http://composer.pulsestorm.dev",
        "skip-dev": false
    }    
}

The name property should be a simple description of your repository. It’s used in a static HTML file generated for the repository, so don’t use a name that would embarrass your mother or your supervisor.

The homepage property is the URL you’re planning on hosting your satis repository at. This can be a URL on the Internet, or a URL local to your network/dev machine. This URL will be used in a static HTML file generated for the repository, so make sure its accurate.

The repositories property is the first vital configuration field. This should be an array of the composer repositories you’d like to mirror with satis. In our case, this is the composerrepository at repo.magento.com. Other types of repositories you might see are VCS orpear.

The require-dependencies and require-dev-dependencies flags make sure composer will require any dependencies a specific package may have. Not strictly necessary in our case (see require-all below) but it never hurts to be explicit.

The require-all property tells satis we want to grab every package on the repository. Since our goal is to create a complete local mirror, we want this here. If we were interested in creating a mirror of select packages, we’d use a require property and list out the packages.

The final top level archive property tells satis that, in addition to creating apackages.json file for us that points to packages, we’d like satis to grab and/or build a project archive for us as well.

In the archive object configuration, the directory property tells satis where the archived files should be copied to (i.e. build-folder/dist), the format property tells satis which archive format we should use, the prefix-url should be, again, the URL for your repository, and setting the skip-dev option to false ensures we get every file in a package.

The prefix-url key is important here, as it’s the URL satis will use in the generatedpackages.json file to point to our local mirrored archive.

If you’re building for repo.magento.com, you’ll want to keep the archive format set tozip. There are some bugs and inconsistent behavior with Magento 2’s repository and composer that were only just recently fixed for zip archives, and still exist for tar archives.

Building the Mirror

Once you’ve created your satis.json, you can create the mirror with the following

$ php bin/satis build satis.json public -vvv

If your repo.magento.com HTTP credentials aren’t stored in ~/.composer/auth.json, satis will prompt you for them. The -vvv flag is optional, but will ensure satis is verbose in its output. This can help point to problems in your configuration, or with your network connection.

When the command finishes running, you’ll want to upload the files in public to the web root of whatever URL you configured in satis.json (composer.pulsestorm.dev in our case).

When satis is done running, you can take a look at the repository files using the unix findcommand

$ find public -type f
public/dist/auctane-api-2.0.6.zip
public/dist/belvg-module-facebookfree-1.0.1.zip
public/dist/magento-composer-1.0.2.zip
public/dist/magento-data-migration-tool-2.0.0.zip
//...
public/dist/magento-updater-10.0.0.zip
public/include/all$e89e3a381f6a71df66912bf26c12b89db1200cd8.json
public/index.html
public/packages.json

You should see archives of all the community edition files, any EE files your account license grants you access to, and any extension files you’ve purchased. Upload the entire contents of the public folder to your web server, and you’ll have a local composer repository up and running.

With our mirror created, we’re ready to install Magento 2.

Installing Magento 2 with our Mirror

Normally, when installing Magento via composer, you use the following command

composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition <installation directory name>

We’re going to change this to

composer create-project --no-install --repository-url=http://composer.pulsestorm.dev/ magento/project-community-edition <installation directory name>

i.e. — the same command, but with our repository, and the no-install flag. To understand why we’re doing this, we need to talk about what the create-projectcommand does.

Composer: Create Project

The create-project command is a shortcut command. When you use create-project, composer will

  1. Fetch the latest version of the specified package (magento/project-community-edition) and extract it to the <installation directory name>folder
  2. Run composer install from the <installation directory name> folder, installing all dependencies

If you’ve done any work in the modern PHP world, you’re probably familiar with this command from the various framework’s one-line installers.

This presents a problem for our mirror. If you unzip any of the magento/project-community-edition packages archived in our mirror.

$ ls public/dist/magento-project-community-edition-2.*
public/dist/magento-project-community-edition-2.0.0.zip
public/dist/magento-project-community-edition-2.0.1.zip
public/dist/magento-project-community-edition-2.0.2.zip
public/dist/magento-project-community-edition-2.0.3.zip
public/dist/magento-project-community-edition-2.0.4.zip

You’ll see that its composer.json file points to repo.magento.com. The --repository-url option only applies to the package that create-project grabs. Otherwise, composer will use whatever it finds in the project’s base composer.json file.

This is easy enough to work around — all we need to do is use the no-install flag — this way create-project will only download and extract the project, it won’t run composer install. This will give us a chance to edit the composer.json file before running install.

Installing Magento 2

OK! We’re ready. Step 1, lets clear our composer cache

$ composer clear-cache

This is not strictly necessary, but useful when you’re first setting up a mirror, and may have an invalid package reference stashed in cache somewhere.

Also, if your mirror’s not located on an https server, you may need to set composer’s global secure-http flag to false.

$ composer global config secure-http false

Recent versions of composer will refuse to run over non-encrypted HTTP.

Next, let’s run our create project command

$ composer create-project -vvv --no-install --repository-url=http://composer.pulsestorm.dev/ magento/project-community-edition
//...
Downloading http://composer.pulsestorm.dev/packages.json
Writing /Users/username/.composer/cache/repo/http---composer.pulsestorm.dev/packages.json into cache
Reading /Users/username/.composer/cache/repo/http---composer.pulsestorm.dev/include-all$e89e3a381f6a71df66912bf26c12b89db1200cd8.json from cache
Installing magento/project-community-edition (2.0.4)
  - Installing magento/project-community-edition (2.0.4)
Downloading http://composer.pulsestorm.dev/dist/magento-project-community-edition-2.0.4.zip
    Downloading: 100%         
Writing /Users/username/.composer/cache/files/magento/project-community-edition/648f32cf7a59f92769940d85435cf16d7385fa5f.zip into cache from /path/to/magento/project-community-edition//de123c8bffdb844a4093d235a37b66d3.zip
    Extracting archive
Executing command (CWD): unzip '/path/to/magento/project-community-edition//de123c8bffdb844a4093d235a37b66d3.zip' -d '/path/to/magento/vendor/composer/847ae1bb' && chmod -R u+w '/path/to/magento/vendor/composer/847ae1bb'

//...

Again, the -vvvs are optional, but viewing composer’s verbose output can help us ensure that no package was/is downloaded from repo.magento.com. When composer’s done running, change into the project-community-edition folder, and take a look atcomposer.json

$ cd project-community-edition
$ ls
README.md    composer.json    update
$ cat composer.json
{
    //...
    "repositories": [
        {
            "type": "composer",
            "url": "https://repo.magento.com/"
        }
    ],
    //...        
}

As mentioned, there’s repo.magento.com, getting in the way of our local mirror. Let’s edit that file to point at our repository.

#File: composer.json
//...
    "repositories": [
        {
            "type": "composer",
            "url": "http://composer.pulsestorm.dev/"
        }
    ],    
//...

With the above in place, run

$ composer install -vvv

and composer will grab all Magento’s packages from your local mirror — no access torepo.magento.com required.

Wrap Up

All of this, of course, is only a start to keeping and maintaining a local composer mirror. EE users will want to check their enterprise license agreement to make sure doing this falls within acceptable use of the EE source code, and regardless of which version of Magento you’re running you’ll want to make sure your mirror isn’t located anywhere online, as you may become an inadvertent distribution point for commercial extensions you don’t have the right to distribute.

You’ll also want to figure out a way to get satis running on a regular basis — otherwise you may miss important updates to the Magento repository. The very bold may want to expand their mirroring to packagist itself, but that’s a larger problem filled with all sorts of blind alleys and large hard drives.

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.