Magento Toolkit
The PhpStorm Magento plugin's navigation, generation and inspections — in VS Code.
Jump from di.xml to the class, from a method to the implementation Magento actually
instantiates, and from an interface back to everything that uses it.
What you get ·
Settings ·
Troubleshooting ·
MIT
Getting started
Open your Magento 2 project. The status bar shows Magento <n> once the index is ready — that is the
number of modules found, and everything below works from that moment.
The installation is located automatically whether you open the Magento root, a repository that holds
it in a subfolder (repo/src, repo/htdocs), or just app/code/Vendor/Module. If nothing resolves,
run Magento: Diagnose Class Resolution from the command palette.
Is this for you?
You work on Magento 2 or Adobe Commerce, you prefer VS Code, and you keep losing time to things the
PhpStorm Magento plugin does for you:
- Ctrl+Click a class in
di.xml, events.xml or a layout file and land in the right file — including vendor/.
- Ask who plugs into this? without grepping
di.xml by hand.
- Jump from
SomeInterface::save() to the class bound by <preference>, not to a test mock.
- Scaffold a module, plugin, observer, cron job or CLI command with its XML wiring already merged in.
- Catch a plugin whose
afterFoo() will never run because the target has no foo().
No language server, no PHP process, no daemon. The index is built from the files in your workspace in
about a second and costs a few megabytes of memory.
What you get
Navigation — Ctrl+Click / F12
| From |
To |
class, instance, type, for in any Magento XML |
the PHP class, interface or virtual type |
template="Vendor_Module::path.phtml" |
the .phtml file (also web/, web/template/) |
Vendor_Module::path.phtml inside PHP / PHTML strings |
the same file |
a class name in PHP / PHTML (qualified, use import, alias, same namespace) |
its file, in app/code or vendor/ |
a method call ($var->m(), $this->prop->m(), Class::m(), parent::m()) |
its declaration, the ones it inherits, and the di.xml preference implementation |
| a method declaration |
the interface or parent that declares it |
a class constant (self::TABLE, $entry::STATUS_SENT) |
the const line, following parents and interfaces |
a const declaration |
every place it is read |
| a local variable |
where it is assigned — and from there, its uses |
<event name="..."> |
every observer of that event |
<module name="..."> |
that module's etc/module.xml |
<update handle="..."> |
the layout files declaring the handle |
ACL resource="..." |
its acl.xml declaration |
Go to Implementations (Ctrl+F12) on an interface or one of its methods lists
the classes bound by a di.xml preference plus every class declaring implements/extends, and
lands on the matching method inside them.
Find All References (Shift+Alt+F12) on a method or a constant
lists its call sites. Each hit's receiver is typed and hits on unrelated classes are dropped —
searching a transport method does not return every $e->getMessage() in the project.
Code lenses
On a class: plugins targeting it · preferences for and of it · plugin targets · observed events ·
cron schedule · Web API routes · every configuration reference to it.
On a method: which plugin methods intercept it (before / after / around), the Web API route
bound to it, the interface it implements, and — on an interface — the implementing class.
Inspections
XML configuration: unresolved class, interface or virtual type · missing template · module not
installed · undeclared ACL resource · duplicate plugin name for one type · duplicate observer name ·
observer that does not implement ObserverInterface · class used from a module missing in
<sequence> or composer.json.
Plugin classes: an interceptor with no matching method on the plugged type, a final plugged type,
and target methods that are final, static or non-public.
Generators
Magento: in the command palette, or right-click a folder → Magento: Generate.
Module · Controller (+ routes.xml) · Block · View model · Helper · Model + ResourceModel +
Collection · Observer (+ events.xml) · Plugin (+ di.xml, signatures copied from the target
method) · Preference (+ di.xml) · CLI command (+ di.xml) · Cron job (+ crontab.xml) · GraphQL
resolver (+ schema.graphqls) · Data patch · UI form component + DataProvider.
XML entries merge into the element that is already there, so no duplicate <type>, <event> or
<group> nodes appear.
Snippets
PHP: mclass mobserver mbefore mafter maround mconstruct mblock mviewmodel mpatch mregistration
XML: mmodule mdi mplugin mpreference mvirtualtype margument mevent mcron mlayout mlayoutblock msystemfield macl mwebapi
Completion (XML)
Class names walked namespace by namespace (composer PSR-4 plus every registered module), virtual
types, template ids, module names, event names, ACL resources and layout handles.
How a receiver gets typed
Navigation needs to know what $x is. In order: parameter type hints, promoted and typed properties,
@var, catch, then the assignment — new Foo, FooFactory::create() (unwrapped to Foo), and
call chains such as $this->resource->getConnection(), whose return type is read from the native
declaration or the @return docblock. Locals are resolved inside the function that declares them,
and a docblock that merely mentions a variable is not treated as its declaration.
Settings
| Setting |
Default |
Meaning |
magento.root |
"" |
Magento install path; empty means auto-detect, above or below the open folder |
magento.indexVendor |
true |
index vendor/ — needed for core class navigation |
magento.exclude |
**/{node_modules,var,generated,pub/static,pub/media,.git,dev/tests}/** |
paths skipped while indexing |
magento.diagnostics.enabled |
true |
XML and plugin inspections |
magento.codeLens.enabled |
true |
PHP code lenses |
magento.codeLens.usages |
false |
"N usages" lens above each method (one workspace search per method) |
magento.references.scope |
project |
all also searches vendor/ for call sites |
magento.licenseHeader |
"" |
copyright block prepended to generated PHP |
Run Magento: Rebuild Index after composer install or a large branch switch; etc/**/*.xml
changes re-index on their own.
Synthetic tree of 400 modules / 12 400 files (npm run bench):
|
|
| full index build |
1.1 s (0.09 ms/file) |
| heap retained by the index |
4.5 MB |
| class, constant and variable lookups |
0.001 – 0.05 ms each |
| code lenses |
0.35 ms per file |
| Go to Implementations, whole tree |
0.9 s |
| Find All References, whole tree, type filtered |
2.1 s |
On a real 2.4 install (86 000 files) the index builds in 0.8 s and a filtered reference search over
app/ takes 1.7 s.
It stays cheap because only the XML files that carry references are read, templates and layouts are
indexed by path, the index yields to the event loop while it works, workspace searches read files in
parallel batches, and every parse is cached and invalidated by mtime.
Troubleshooting
Navigation does nothing. Run Magento: Diagnose Class Resolution from the command palette. It
prints the PSR-4 roots that were loaded, the module count, and every path tried for a class. An empty
"Magento roots" line means vendor/composer/autoload_psr4.php was not found — point magento.root
at the installation.
The status bar shows a small module count. The index only sees files inside the workspace. Open
the folder that contains vendor/, or set magento.root.
A reference search misses call sites in core. That is magento.references.scope; set it to all.
Not covered yet
XSD-driven attribute completion, getUrl() route navigation, UI component component / template
JS path resolution, db_schema.xml column completion, and a Magento CLI runner. The index and the
providers are the extension points for those.
License
MIT — see the LICENSE file in this repository. Use it, fork it, ship it.