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

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.

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

$ 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.

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

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

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

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 the di.xml file

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 of any dependency injected argument.

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’s Pulsestorm\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

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.

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.

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 a class 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 to Pulsestorm\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 a Pulsestorm\TutorialObjectManagerArguments\Model\SomethingCompletelyDifferent class for the first argument.

Inserting Class Constants

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

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

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 class Magento\Integration\Model\Integration and the constant SETUP_TYPE

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

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 like number 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.

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 core Magento\Framework\Console\CommandList class. We’re merging in an Pulsestorm\TutorialObjectManagerArguments\Command\Testbed object.

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

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

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

Most of these are self explanatory. The only non-scaler type we didn’t discuss was init_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 of Pulsestorm\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 of Pulsestorm\TutorialObjectManagerArguments\Model\ExampleArgument1, but only when this class is used in the Pulsestorm\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 of Pulsestorm\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.