Legacy code refactoring with Rector PHP

Marek Karmelski | PHP | 02.03.2022

kod legacy to też kod pisany pod nową funkcjonalność

Working with “inherited” code or with code that has not yet been tested can lead to a great deal of concern. Getting rid of such code is not always possible – refactoring with suitable tools is often required. For over 15 years, PHP software developers have been able to utilize solutions to aid in the struggle with legacy code in their everyday work. But what exactly is legacy code and how can we learn to work with it? How can we get rid of technical debt? In this article, I would like to introduce you to a tool called Rector.

Legacy code – what is it?

Inherited code

Before I proceed to describe the capabilities of Rector, I would like to take a few sentences to outline what legacy code is and what the most frequently chosen ways of dealing with this problem are. There are numerous definitions of legacy code on the web. Literally translated, legacy code means nothing other than “inherited code”, or code that we de facto inherit from other software developers, for example when joining a new project. This code causes fear and concern – it works, but we do not know the intricacies of the logic behind it. Spaghetti code, which is particularly difficult to understand and harness, is especially feared.

The existing code

Legacy code is also code which we are in the process of writing, but it has not been tested yet, so it is not ready for future changes. In my opinion, each of the above definitions describes the essence of the problem.

legacy code is also the existing code for new functionality which has not been tested yet

Legacy code: refactoring or rewriting?

When working with legacy code, we have a choice of approaches that either minimize such code or get rid of it completely. We can therefore apply solutions such as code refactoring, or completely rewrite it. Code refactoring relates to the optimization of its small fragments, without changing the basic functionality, while the latter approach requires rewriting the entire application code from scratch, taking current standards into account.

Software code refactoring – tools to combat technological debt

As I mentioned in the introduction, we now have a variety of tools to combat technological debt in PHP code, among which are:

  • PHP_CodeSniffer,
  • PHP CS Fixer,
  • PHP-Parser,
  • PHPStan,
  • Psalm,
  • Rector.

Which tools should we choose? It all depends on what the development team needs at a given moment and whether the tool meets our expectations. The knowledge that members of the development team have of a given tool will also be a decisive factor. One of the above tools undertakes a static analysis of the code, another modifies it in accordance with the current standards, while others make it possible to do both – and Rector is part of the latter group.

Rector – a legacy code buster?

Rector was founded in 2017 by Tomas Votruba, and is an open source CLI (Command Line Interface) program based on Symfony components. It is a tool that, apart from analyzing static code, can also change it. The basic applications of Rector are efficient and fast updating and refactoring of the code, as well as changing the application architecture.

Refactoring

When it comes to updating and refactoring, the possibilities include:

  • migration from PHP 5.3 to PHP 8.1,
  • migration from Symfony 2.8 to Symfony 4.4,
  • removing so-called dead code, i.e. code that will never be used,
  • changing the names of classes, methods, and parameters.
Rector PHP - automated refactoring support helping you to perform refactoring process

Changing the code architecture

In terms of architecture changes, they can involve changing the facade in Laravel to DI or transferring the application from TYPO3 to Symfony. Of course, modern IDEs allow for code refactoring, but they can be slow and complicated to use. When using regular expressions to search for code snippets, they may not find all occurrences or, by contrast, search for too many. Programmers who copy and paste the code may also make some errors due to fatigue or distraction.

Why Rector and not IDE?

Rector won’t do anything the software developer doesn’t let it do! It is based on rules (pre-defined, individual “rules” that make a single change in the code each), grouped into sets of rules that make changes with similar characteristics.

Individual rules:

  • ArrayKeyFirstLastRector,
  • IsCountableRector,
  • JsonThrowOnErrorRector.

Sets of rules:

  • PSR4,
  • Php70,
  • Php71, TypeDeclaration,
  • DowngradePhp71.

At the time of writing, Rector made approximately 660 rules available, collected in over 48 sets.

Rector – configuration

The principle of how Rector works is very simple – we start by installing the tool and configuring it. Rector first searches for all files indicated by the programmer; then, after analyzing each of them separately, it builds an AST (Abstract Syntax Tree) for them. It applies developer-defined rules in the configuration file to each such tree. Once the entire process is complete, the console provides a report on the changes made. It is worth mentioning that Rector is not only based on default rules – the tool also allows you to create your own, more specialized ones.

/** @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);
                }
            }
        }
    }
}
return static function (
    ContainerConfigurator $containerConfigurator
): void {
    // get parameters
    $parameters = $containerConfigurator->parameters();
    $parameters->set(Option::PATHS, [
        __DIR__ . '/src'
    ]);
    // Define what rule sets will be applied
    $containerConfigurator->import(SetList::CODE_QUALITY);
};


ChangeArrayPushToArrayAssignRector
class SomeClass
  {
      public function run()
      {
          $items = [];
-         array_push($items, $item);
+         $items[] = $item;
      }
  }
CombinedAssignRector
-$value = $value + 5;
+$value += 5;


DateTimeToDateTimeInterfaceRector
class SomeClass {
-    public function methodWithDateTime(\DateTime $dateTime)
+    /**
+     * @param \DateTime|\DateTimeImmutable $dateTime
+     */
+    public function methodWithDateTime(
         \DateTimeInterface $dateTime
     ){
         return true;
     }
}


SimplifyArraySearchRector
-array_search("searching", $array) !== false;
+in_array("searching", $array);

Summary

Working with legacy code cannot be avoided – sooner or later every developer will come across it in a project. It is good to know the possibilities of code refactoring offered by tools such as Rector. I hope that I have persuaded you to find out more – and if so, I encourage you to watch a speech I gave at DrupalCamp Poland 2021:

The author of the post is:

PHP Developer

PHP developer with over 12 years of experience. He’s passionate about programming, focused on continuous development and learning about new, better solutions and technologies. In his private life, he loves sports and takes a keen interest in nutrition.

Add comment: