technology and webhosting news

You then use the rule class’s getNodeTypes() method to declare which node types you are interested in. Here, I made a mis-step at first. I wanted to replace Array_ nodes, but only in the getFileInfo() method. So in my first attempt, I specified ClassMethod nodes, and then in refactor() I wrote code to drill down into them to get the array Array_ nodes. This went well until I tried returning a new replacement node, and then Rector complained, and I realised the obvious thing I’d skipped over: the refactor() method expects you to return a node to replace the found node. So my approach was completely wrong.Changes like this are really easy to do (though by this point, I had made a git repository for my Rector rule, so I could make further enhancements without breaking what I’d got working already).

use PhpParserNodeName;
use PhpParserNodeExprNew_;

$class = new Name('DrupalCodeBuilderFileCodeFile');
return new New_($class);

The first thing I’ll say is that the same sort of approach as I use with migrations works well: work with a small indicative sample, and iterate small changes. With a migration, I will find a small number of source rows which represent different variations (or if there is too much variation, I’ll iterate the iteration multiple times). I’ll run the migration with just those sources, examine the result, make refinements to the migration, then roll back and repeat.

Inspecting the Arg class’s own constructor showed how to do this:

With the arrays that made it through the filter, I needed to make a new node that’s a class instantiation, to replace the array, passing the same values to the new statement as the array keys (mostly).

It’s also possible to copy over any inline comments that are above one node to a new node:

return new New_($class, $new_call_args);

Once that worked, I needed to convert array items to constructor parameters. Fortunately, the value from the array items work for parameters too:

What I’ve not figured out is how to extract namespaces from full class names to an import statement, or how to put line breaks in the new statement. I’m hoping that a pass through with PHP_CodeSniffer and Drupal Coder’s rules will fix those. If not, there’s always good old regexes!

return new CodeFile(
body_pieces: $this->component_data['filename'],
merged: $merged,
);

So faced with a refactoring from this return from the getFileInfo() method:

return [
'path' => '',
'filename' => $this->component_data['filename'],
'body' => [],
'merged' =>$merged,
];

Although it’s taken me far more time than changing each file by hand, it’s been far more interesting, and I’ve learned a lot about how Rector works, which will be useful to me in the future. I can definitely see how it’s a very useful tool even for refactoring a small codebase such as DrupalCodeBuilder, where a rule is only going to be used once. It might even prompt me to undertake some minor refactoring tasks I’ve been putting off because of how tedious they’ll be.

use PhpParserNodeArg;

foreach ($node->items as $array_item) {
$construct_argument = new Arg(
$array_item->value,
);

Rector is a tool for making changes to PHP code, which powers tools that assist with upgrading deprecated code in Drupal. When I recently made some refactoring changes in Drupal Code Builder, which were too complex to do with search and replace regexes, it seemed like a good opportunity to experiment with Rector, and learn a bit more about it.

foreach ($node->items as $item) {
$seen_array_keys[] = $item->key->value;
}
if (array_intersect(static::EXPECTED_MINIMUM_ARRAY_KEYS, $seen_array_keys) != static::EXPECTED_MINIMUM_ARRAY_KEYS) {
return NULL;
}

foreach ($node->items as $array_item) {
$key = $array_item->key->value;

// Rename the 'body' key.
if ($key == 'body') {
$key = 'body_pieces';
}

I rewrote getNodeTypes() to search for Array_ nodes: those represent the creation of an array value. This felt more dangerous: arrays are defined in lots of places in my code! And I haven’t been able to see a way to determine the parentage of a node: there do not appear to be pointers that go back up the PhpParser syntax tree (it would be handy, but would make the dump() output even worse to read!). Fortunately, the combination of array keys was unique in DrupalCodeBuilder, or at least I hoped it was fairly unique. So I wrote code to get a list of the array’s keys, and then compare it to what was expected:

// Preserve comments.
$construct_argument->setAttribute('comments', $array_item->getComments());

Rector’s list of commonly used PhpParser nodes was really useful here.

Besides, I’m an inveterate condiment-passer: I tend to prefer spending longer on a generalisation of a problem than on the problem itself, and the more dull the problem and the more interesting the generalisation, the more the probability increases.

With Rector, you can specify just a single class in the code that registers the rule to RectorConfig in the rector.php file, so I picked a class which had very little code, as the dump() output of an entire PHP file’s PhpParser analysis is enormous.

use PhpParserNodeArg;
use PhpParserNodeIdentifier;

$construct_argument = new Arg(
value: $array_item->value,
name: new Identifier($key),
);

A new statement node is made thus:

Similar Posts