Structural Search and Replace in PhpStorm for Clean Bulk Changes
AI generated
IDE
{ }
PhpStorm · SSR · Refactoring · PHP Migration · AST
Structural Search and Replace
for clean bulk changes in PhpStorm

Ordinary find/replace and regex search are blind to PHP semantics. Structural Search and Replace understands the Abstract Syntax Tree, and replaces exactly what the code structurally means, not what it literally contains. The result: safe, semantically correct bulk changes without false positives.

13 min read SSR templates · variable constraints · inspections · PHP 8.4 migration PhpStorm 2024.x · PHP 8.1 to 8.4 · Magento 2.4

1. Why ordinary find/replace fails for refactorings

A simple find/replace for the expression array() to [] sounds trivial, until you notice that the search also matches inside comments, strings and docblocks. Or that array(1, 2) is correctly turned into [1, 2], but array ( with extra whitespace is missed. Regex solves the whitespace problem, but it does not understand PHP semantics: it cannot distinguish an array literal inside a string from an actual PHP expression.

Structural Search and Replace (SSR) in PhpStorm operates on the Abstract Syntax Tree (AST), not on raw text. An SSR template for array($args$) matches exactly PHP array expressions, no matter how they are formatted, with or without whitespace, inline or multiline. Comments and strings are excluded automatically because they have a different structure in the AST than expressions do. The result is search matches that are semantically correct, not just textually similar.

The practical difference: on a Magento module with a thousand PHP files, a regex search for deprecated constructs regularly produces false positives inside comments and strings that then have to be filtered out manually. SSR only returns real matches on the execution path. During the subsequent replacement, SSR transforms the construct correctly even if the original matches were formatted differently, because the replacement operates at the AST level.

2. Structural Search: templates and AST fundamentals

SSR is opened in PhpStorm via Edit → Find → Search Structurally (search) or Search Structurally and Replace (search and replace). The search dialog contains a template field where PHP code is entered. This code is not interpreted as text, but as an AST pattern. $x$ instanceof $Type$ is a pattern that matches every instanceof expression, regardless of variable name and type name.

The Context dropdown defines which PHP context the template represents: expression, statement, class member, method and so on. For most refactorings, Expression is correct. When searching for a complete method or a class with certain properties, you select the corresponding context. A wrong context causes PhpStorm to miss valid patterns because the AST structure does not match the expected context.

The pattern syntax uses variables prefixed with a dollar sign: $variable$ stands for any expression at that position. $args$ used as a parameter list stands for an arbitrary number of arguments. Fixed parts of the template, such as keywords, operators and literal types, are matched exactly. new $ClassName$() matches every constructor call without arguments, new $ClassName$($args$) matches constructor calls with any number of arguments.

3. Configuring template variables and constraints

The power of SSR lies in the variable constraints. For every template variable you can restrict how often it may occur (quantifier), whether it is limited to a specific type, whether it can be matched by a regular expression and whether it should be negated. A constraint of Min: 0, Max: unlimited makes the variable optional. A regex filter on a variable restricts which text can match, for example only variable names starting with an uppercase letter for class constraints.

The Type filter is especially valuable: $object$ with type filter \Magento\Framework\App\RequestInterface matches only method calls on objects that have this type. PhpStorm uses its type inference to determine the type at analysis time. This turns SSR into a genuinely semantic search, not just a text pattern, but a type-aware search pattern that excludes false matches on other types.

The Not filter lets you exclude specific patterns. A template that searches all method calls except those on $this combines a variable with a not filter on this. This combination of quantification, type filter and negation makes SSR templates precise enough for real refactoring tasks in large codebases.


<?php
// SSR search template: find the deprecated array() constructor
// Template context: Expression
// Search field: array($args$)
// Replace field: [$args$]
//
// This turns:
$config = array('key' => 'value', 'enabled' => true);
// exactly into:
$config = ['key' => 'value', 'enabled' => true];

// SSR search template: replace isset() + ternary with null coalescing
// Search field: isset($x$) ? $x$ : $default$
// Replace field: $x$ ?? $default$
//
// Example matches (all semantically equivalent, all get found):
$name = isset($data['name']) ? $data['name'] : 'Unknown';
$price = isset($product->price) ? $product->price : 0.0;
$label = isset($config['label']) ? $config['label'] : null;

// SSR result (transformed correctly, semantics preserved):
$name = $data['name'] ?? 'Unknown';
$price = $product->price ?? 0.0;
$label = $config['label'] ?? null;

4. Replace templates: transforming matches safely

The replace template uses the same variable names as the search template. The variable $args$ in the search template is replaced in the replace template by the code that was actually found. The replace template itself is again PHP code, with the restriction that it must represent the same AST construct as the search result. A search template for an expression must also have an expression as its replace template.

During replacement, PhpStorm handles formatting automatically. The replace template is not inserted as raw text but routed through the code formatter. That means multiline matches are correctly indented too, without having to account for whitespace and indentation explicitly in the replacement. This is a significant advantage over regex replacements, where formatting details have to be mapped manually.

For complex transformations, where several parts of the found code are reassembled into a new structure, SSR supports several template variables in the replace part. $object$->$method$($args$) as the search and $object$->newMethod($method$, $args$) as the replacement rebuilds a method call, passing the old method name as the first argument of the new call. Such transformations would be nearly impossible to express correctly with regex.

5. SSR inspections as permanent code quality rules

SSR templates can be saved as custom inspections in PhpStorm. That turns them into permanent code quality rules that PhpStorm checks automatically in every open file, exactly like the built-in inspections. A custom inspection for array($args$) would underline every use of the old array syntax in yellow, with a message and a quick fix button for automatic transformation to [$args$].

Setting up a custom inspection from an SSR template happens via Settings → Editor → Inspections → PHP → General → Custom. The template is saved including the replacement. Severity, scope and quick fix availability are configurable. A custom inspection with severity Error is underlined red in the code and appears in the Problems tab, behaving exactly like a built-in inspection, but project-specific and versionable.

For teams, this is the most important use case for SSR. Instead of manually watching for deprecated patterns during code reviews, you define custom inspections that PhpStorm checks in the background. New developers get instant hints when they use deprecated constructs, without a reviewer having to step in. The custom inspections can be versioned as part of the inspection profile XML file.

6. Performing a PHP 8.4 migration with SSR

The migration from PHP 8.1 to 8.4 brings several syntactic improvements that are ideally suited for SSR-driven migration. Property hooks in PHP 8.4 allow getters and setters to be defined directly on class properties. Migrating classic getX()/setX() pairs is complex and belongs more in the Rector territory. But simpler migrations, such as replacing intval($x) with (int) $x or replacing strval($x) with (string) $x, are exactly SSR's home turf.

For Magento projects migrating to PHP 8.4, SSR templates for constructor property promotion are especially useful. The pattern: a class has a property declaration and, inside the constructor, an assignment $this->property = $param. The full SSR template for this transformation is complex because it needs class member as its context, but for simple cases without complex docblocks it is feasible and saves considerable manual effort.


<?php
// SSR template 1: replace intval() with an (int) cast
// Search template:  intval($x$)
// Replace template: (int)$x$
// Constraint on $x$: not empty, Expression context

// Before:
$entityId = intval($request->getParam('id'));
$storeId  = intval($this->storeManager->getStore()->getId());
$qty      = intval($item->getQty());

// After (all done with a single SSR replace):
$entityId = (int)$request->getParam('id');
$storeId  = (int)$this->storeManager->getStore()->getId();
$qty      = (int)$item->getQty();

// SSR template 2: replace strval() with (string)
// Search template:  strval($x$)
// Replace template: (string)$x$

// SSR template 3: count($arr$) > 0 with !empty($arr$)
// Search template:  count($arr$) > 0
// Replace template: !empty($arr$)
// Note: semantically equivalent for arrays, not for Countable objects
// Constraint $arr$: type filter array (use PhpStorm type inference)

7. Magento-specific SSR use cases

Magento projects have recurring patterns that are an excellent fit for SSR. Replacing direct Mage::getModel() calls in legacy code with proper dependency injection, finding every place where $block->getData('key') is used instead of type-safe getter methods, or identifying every ObjectManager::getInstance() call, which is considered an anti-pattern in Magento 2 and should be replaced with dependency injection.

A practical example: find every place where $this->_objectManager is used, a sign of direct ObjectManager access that should be avoided. The SSR template $this->_objectManager->$method$($args$) matches exactly those spots. Using inspection mode, they are permanently flagged as a warning until they are replaced with proper constructor injection. That is the direct path from a coding standard document to an automatically enforced rule.

Another Magento use case: finding every place where __DIR__ is used in a way that collides with the Magento module directory API. Or identifying every template file missing $block->escapeHtml() by searching the PHP code for direct echoing of variables without escaping. SSR templates can find these patterns precisely and without false positives.

8. Versioning and sharing SSR templates in a team

SSR templates are stored in PhpStorm as inspection profiles. Inspection profiles can be exported as XML and live in the project's .idea/ directory. Whoever checks these files into the repository ensures that every developer has the same custom inspections and quick fixes available. The setup is a one-time task, after that every new developer inherits the rules automatically on clone.

For structured versioning of SSR templates, an .idea/inspectionProfiles/Project_Default.xml that contains all project-specific custom inspections is recommended. This file can be reviewed in code review whenever new rules are added. Important: the inspection profile must be set as the active profile in the project so that every developer sees the same warnings. The active profile is stored in .idea/workspace.xml, which normally lives in .gitignore.

9. SSR vs. regex vs. Rector compared

Choosing the right tool for code transformations depends on the complexity of the change, the required precision and the team setup. SSR, regex and Rector have clearly distinct strengths and weaknesses.

Criterion SSR (PhpStorm) Regex find/replace Rector
PHP semantics AST-based, type-aware Text-based, blind AST-based, complete
False positives Very rare Frequent (strings, comments) Very rare
Learning curve Low (PHP syntax as template) Medium (regex knowledge) High (PHP, Rector API)
Inspection integration Yes, custom inspections No Via CI, not IDE-native
Complex refactorings Limited Very limited Complete

SSR is the right tool for medium complexity: searching and replacing specific expression patterns with PHP semantics, without having to write a full Rector rule. For simple text search, regex is enough. For complex migration rules that touch several classes, need type information from multiple files or perform complex AST transformations, Rector is the right choice. SSR and Rector are not mutually exclusive, many migration projects use SSR for simple patterns and Rector for the complex ones.

Mironsoft

Magento 2 refactoring · PHP 8.4 migration · code quality

Migrate your Magento code to PHP 8.4 safely?

We carry out Magento PHP migrations with SSR, Rector and PHPStan, with custom inspections as permanent code quality rules and complete test coverage before every refactoring step.

PHP migration

PHP 8.1 to 8.4 migration with SSR and Rector, complete deprecation cleanup

Custom inspections

Magento-specific SSR rules as custom inspections, so anti-patterns are permanently prevented

Code review

Analyze existing Magento code for deprecated patterns and build a refactoring plan

10. Summary

Structural Search and Replace in PhpStorm is the gap between simple find/replace and full Rector: AST-based, without false positives in strings and comments, with type constraints for precise match selection and automatic formatting during replacement. For PHP developers who regularly perform refactorings of medium complexity, SSR is the fastest and safest tool.

The jump from a one-off SSR search to a permanent custom inspection turns SSR into a strategic tool for code quality. Once saved as an inspection, PhpStorm checks every new line of code against the pattern, without manual code reviews. Versioned in the repository, the rules apply to the entire team. The result is a consistent codebase that automatically and permanently prevents Magento anti-patterns and deprecated PHP constructs.

Structural Search and Replace, the essentials at a glance

AST-based search

Edit → Find → Search Structurally. PHP syntax as the template, no false positives in strings and comments. Variables with $name$ for flexible patterns.

Variable constraints

Type filter, quantifier and negation for precise match selection. Type filter uses PhpStorm type inference for semantically correct search.

Custom inspections

Save SSR templates as a permanent inspection. Quick fix for automatic transformation right inside the IDE.

Team sharing

Version inspection profiles as XML in .idea/inspectionProfiles/. Set up once, every developer inherits the rules on clone.

11. FAQ: Structural Search and Replace in PhpStorm

1SSR vs. ordinary find/replace?
SSR operates on the AST, excluding strings and comments automatically. Find/replace is textual and produces false positives inside comments and string literals.
2Open SSR in PhpStorm?
Edit → Find → Search Structurally (search) or Search Structurally and Replace (with replacement).
3What are template variables?
$name$ placeholders for any AST node. $args$ means any argument list. Constraints: type filter, quantifier, negation.
4Use type filters in SSR?
Constraint dialog → Type filter. PhpStorm uses type inference and matches only calls on objects of the given type. Excludes false positives on other types.
5Save a template as an inspection?
In the SSR dialog → Save Template. Then configure it under Settings → Inspections → PHP → Custom with severity and quick fix.
6Share SSR templates in a team?
Check inspection profiles in as XML under .idea/inspectionProfiles/. Every developer gets the rules automatically on clone.
7SSR vs. Rector?
SSR for simple to medium expression transformations, no learning curve. Rector for complex project-wide migrations with full AST access.
8Which context to select?
Expression for method calls and operators. Statement for complete statements. Class Member for properties and methods. Wrong context means no matches.
9Find multiline code?
Yes. SSR is formatting-independent. Single-line and multiline code are treated the same, AST structure counts, not formatting.
10Find ObjectManager anti-patterns?
Search template: $this->_objectManager->$method$($args$). Save it as a custom inspection with severity Warning, every direct ObjectManager call gets permanently flagged.