(Inspired by How it works in BetterReflection)
- The application finds files in the source code you provide and registered Rectors - from
--set
,--config
or localrector.php
- Then it iterates all found files and applies relevant Rectors to them.
- A Rector in this context is 1 single class that modifies 1 thing, e.g. changes the class name
The iteration of files, nodes and Rectors respects this lifecycle:
<?php
declare(strict_types=1);
use Rector\Contract\Rector\PhpRectorInterface;
use PhpParser\Parser;
/** @var SplFileInfo[] $fileInfos */
foreach ($fileInfos as $fileInfo) {
// 1 file => nodes
/** @var Parser $phpParser */
$nodes = $phpParser->parse(file_get_contents($fileInfo->getRealPath()));
// nodes => 1 node
foreach ($nodes as $node) { // rather traverse all of them
/** @var PhpRectorInterface[] $rectors */
foreach ($rectors as $rector) {
foreach ($rector->getNodeTypes() as $nodeType) {
if (is_a($node, $nodeType, true)) {
$rector->refactor($node);
}
}
}
}
}
- Files are parsed by
nikic/php-parser
, 4.0 that supports writing modified tree back to a file - Then nodes (array of objects by parser) are traversed by
StandaloneTraverseNodeTraverser
to prepare their metadata, e.g. the class name, the method node the node is in, the namespace name etc. added by$node->setAttribute(Attribute::CLASS_NODE, 'value')
.
- When all nodes are ready, the application iterates on all active Rectors
- Each node is compared with
$rector->getNodeTypes()
method to see if this Rector should do some work on it, e.g. is this class name calledOldClassName
? - If it doesn't match, it goes to next node.
- If it matches, the
$rector->reconstruct($node)
method is called - Active Rector change everything they have to and return changed nodes
- Rectors are run by they natural order in the configuration, meaning the first in the configuration will be run first.
E.g. in this case, first the @expectedException
annotation will be changed to a method,
then the setExpectedException
method will be changed to expectedException
.
<?php
// rector.php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(Rector\PHPUnit\Rector\ClassMethod\ExceptionAnnotationRector::class);
$services->set(Rector\Renaming\Rector\MethodCall\RenameMethodRector::class)
->arg('$oldToNewMethodsByClass', [
PHPUnit\Framework\TestClass::class => [
'setExpectedException' => 'expectedException',
'setExpectedExceptionRegExp' => 'expectedException',
],
]);
};
- When work on all nodes of 1 file is done, the file will be saved if it has some changes
- Or if the
--dry-run
option is on, it will store the git-like diff thanks to GeckoPackages/GeckoDiffOutputBuilder - Then Rector will go to the next file
- After this, Rector displays the list of changed files
- Or with
--dry-run
option the diff of these files
- ClangMR for C++ by Google (closed source) - almost idential workflow, developed independently though
- hhast - HHVM AST + format preserving + mirations
- facebook/jscodeshift for Javascript
- silverstripe/silverstripe-upgrader for PHP CMS, Silverstripe
- dereuromark/upgrade for PHP Framework, CakePHP