Inspecting What You Got

dev:di:info prints the preference, constructor arguments and plugins the object manager will actually use for a class.

Syntaxbin/magento dev:di:info "Vendor\Module\Api\WriterInterface" [frontend|adminhtml|webapi_rest|graphql|crontab]

dev:di:info prints what the object manager will really do with a class: which implementation a preference resolves to, what each constructor parameter is configured with, and which plugins are attached. Three modules can have an opinion about the same interface, and this command tells you which one won.

Usage

bin/magento dev:di:info <class> [<area>]. The class is required. The area is a second positional argument rather than an option and defaults to global; an unregistered code prints The "x" area code does not exist. Quote the class name so the shell does not eat the backslashes.

What it prints

A header naming the class and the area in capitals. Then Preference:, followed all the way down a chain of preferences to the class that is actually instantiated. Then a Constructor Parameters table of name, requested type and configured value, where a configured object reads instance of ... and a configured scalar reads string ... even when the parameter is an int, because di.xml values are text. Then any virtual types based on the class, and two plugin tables: one for the class you asked about and one for its preference. The Method column names the intercepted method, not the plugin method, so afterWrite appears as write with type after.

What trips people up

The command reads the cached configuration. Run bin/magento cache:clean config after editing di.xml or you are reading the previous answer.

Example

Example · php
<?php
// app/code/Vendor/Module/Model/Export/Writer.php — the class being inspected
declare(strict_types=1);

namespace Vendor\Module\Model\Export;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Vendor\Module\Api\WriterInterface;

class Writer implements WriterInterface
{
    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig,
        private readonly string $directory,
        private readonly int $batchSize = 100
    ) {
    }
}

// bin/magento dev:di:info "Vendor\Module\Api\WriterInterface"
//
// DI configuration for the class Vendor\Module\Api\WriterInterface in the GLOBAL area
//
// Preference: Vendor\Module\Model\Export\Writer
//
// Constructor Parameters:
// +-------------+---------------------------------------------------+---------------------------+
// | Name        | Requested Type                                    | Configured Value          |
// +-------------+---------------------------------------------------+---------------------------+
// | scopeConfig | Magento\Framework\App\Config\ScopeConfigInterface |                           |
// | directory   |                                                   | string var/export/vendor  |
// | batchSize   |                                                   | string 500                |
// +-------------+---------------------------------------------------+---------------------------+
//
// Plugins:
// +-------------------------------------------+--------+-------+
// | Plugin                                    | Method | Type  |
// +-------------------------------------------+--------+-------+
// | Vendor\Reporting\Plugin\StampWriterOutput | write  | after |
// +-------------------------------------------+--------+-------+

When to use it

  • Check which implementation an interface resolves to before adding a preference of your own.
  • Confirm a plugin you just declared is attached, and at which method.
  • Verify that a di.xml argument reached the constructor after a cache clean.
  • Prove to a reviewer that a virtual type is configured as the ticket describes.

More examples

Reading the two plugin tables apart

A plugin declared on an interface and a plugin declared on the implementation both run, but they are listed separately, and only the first table survives if someone later changes the preference. Use this when a plugin does not fire: an empty table means the declaration never reached this area, which is a different problem from a plugin that runs and does nothing. Plugins declared in etc/adminhtml/di.xml only appear when you pass adminhtml.

Example · shell
bin/magento dev:di:info "Vendor\Module\Api\WriterInterface"
#
# Plugins:                     <- declared on the interface you asked about
# Plugins for the Preference:  <- declared on Vendor\Module\Model\Export\Writer

# Same question, asked in the area where the code actually runs.
bin/magento cache:clean config
bin/magento dev:di:info "Vendor\Module\Api\WriterInterface" adminhtml

Checking a virtual type

A virtual type is a named, pre-configured variant of an existing class. The command recognises one and names the class it is based on, then shows that class's constructor parameters filled in with the virtual type's own arguments. This is the quickest way to confirm that a virtual type you added is being read, and that its name is spelled the same in both places.

Example · shell
bin/magento dev:di:info "vendorModuleExportLogger"
#
# DI configuration for the class vendorModuleExportLogger in the GLOBAL area
# It is Virtual Type for the Class Magento\Framework\Logger\Monolog
#
# Preference: vendorModuleExportLogger
#
# Constructor Parameters:
# ... the parameters of Magento\Framework\Logger\Monolog, with your configured values

Finding out whose preference won

Reach for this before debugging a class you assumed was running. Two modules declaring a preference for the same interface is legal, and the module that loads later silently wins; the command tells you the outcome and the grep tells you who is responsible. Do the same with the area argument when the symptom only shows in the admin or in a REST call.

Example · shell
bin/magento dev:di:info "Magento\Catalog\Api\ProductRepositoryInterface"
#
# Preference: ...the class that will actually be built

# Then find the file that declares it.
grep -rn "ProductRepositoryInterface\"" app/code vendor/*/*/etc --include=di.xml

Discussion

  • Be the first to comment on this lesson.