Bringing Rector to TYPO3 for Automated Upgrades

Categories: Development, Community Created by Michiel Roos
Photo: Ross Findon / Unsplash.com
Keeping up with changes is challenging at the best of times. There are some good tools to help keep track of changes, but they still require tedious manual labor. What if you had a tool that would not only keep you but also your codebase up to date? That’s where Rector comes in! There are 69 fixed rules so far, and you can help us add more.

The Times They Are A-Changin’

Changes are everywhere, change is the only constant in life. Especially in our industry. What about the next major version of TYPO3, the next major PHP version? What do we have to do to keep up with all these changes? 

Normally, you should read all the changelogs, read the documentation, go to conferences or be part of an inner circle that is driving the innovation. This is really fun and you feel like a genius. But there are times you feel lazy or totally overwhelmed with this constant stream of information, or simply don’t have the time.

There are tools that keep track of the changes and that can help us to estimate the work that needs to be done to keep your code up-to-date. 

  • The Extension Scanner (formerly known as the install tool) in the more recent TYPO3 versions (9 and 10) can show you which files and extensions are affected by the breaking changes and deprecations. 
  • Another tool that does this is TYPO3scan (on GitHub). It's a command-line tool that can scan for, and report on, deprecations and breaking changes in TYPO3 versions 7 and up.

That's great! So now you may have just discovered you need to go over 1200 files to implement namespaces (replacing the old t3lib_div classes). Or replace all instances of @inject annotations with doctrine based annotations in 500+ files. Some IDEs like PHPStorm can help you to simplify these changes and can even do them across file trees. But for other changes, a lot of manual boring labor is still required.

Wouldn’t it be great if we had a helping hand that would take away all the tedious work of changing our codebase while keeping us informed about all the changes to stay up-to-date at the same time?

Rector to the Rescue!

Rector aims to provide instant upgrades and instant refactoring of any PHP 5.3+ code. Rector’s mission is “to help every developer on the planet with fresh or legacy PHP code to use the latest PHP, the most advanced version of their favorite framework, and the latest craftsman patterns there will be. Without work, instantly, so they can focus on what they love most - thinking about algorithms and writing fresh code.”

How Does Rector Work?

  • First Rector finds all files it needs to work on. It also loads all "rules" it needs to run. Rector will know what to do when you provide it with a configuration file to work with.
  • It will then iterate over all the files it should find and apply all defined "rules" to each file.

A "rule" in this context is a single PHP class that modifies 1 thing (like changing a class name). Currently, there are over 500 such rules for various PHP-style and PHP framework changes shipped with the Rector library itself.

Under the hood Rector will parse each file into an AST (abstract syntax tree) that supports altering properties of code and then writing it back to a valid PHP file. This all works with patterns we as mortal developers can understand. So no magic Regular Expressions are in place.

Rector in Action

Let's see some rules in action. Let's say you have a Fluid ViewHelper looking like this:

class InArrayViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper
{
    /**
     * Checks if given $uid is in a given $array
     *
     * @param int $uid the uid
     * @param array $arrayToCheckAgainst the array to check for the given $uid
     * @return bool
     */
    public function render($uid, array $arrayToCheckAgainst)
    {
        if (in_array($uid, $arrayToCheckAgainst)) {
           return true;
        } else {
           return false;
        }
    }
}

What's "wrong" with this code? Well, it depends on the context. But, if we assume you would like to have this code ready for TYPO3 v10 you would need to do two things: 

  1. Move the render method arguments to the method initializeArguments 
  2. Rename the namespace \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper to \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper.

And we are not talking about the superfluous else statement and not having Type Declarations if we would like to use modern PHP. That's a different story.

Do you like to do these changes manually on a codebase with let's say, 40-100 ViewHelpers? We don't. So let Rector do the heavy work for us and apply the rules MoveRenderArgumentsToInitializeArgumentsMethodRector and RenameClassMapAliasRector for Version 9.5.

Rector transforms this code for us to the following one:

class InArrayViewHelper extends \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper
{
    public function initializeArguments(): void
    {
        $this->registerArgument('uid', 'int', 'the uid', true);
        $this->registerArgument('arrayToCheckAgainst', 'array', 'the array to check for the given $uid', true);
    }

    /**
     * Checks if given $uid is in a given $array
     *
     * @return bool
     */
    public function render()
    {
        $uid = $this->arguments['uid'];
        $arrayToCheckAgainst = $this->arguments['arrayToCheckAgainst'];

        if (in_array($uid, $arrayToCheckAgainst)) {
           return true;
        } else {
           return false;
        }
    }
}

Isn't this amazing? You don't even have to know that these changes have to be done. Your changelog resides in living code.

Let's see another one:

final class SomeService
{
    /**
     * @var \Ssch\TYPO3Rector\Tests\Rector\Annotation\Source\InjectionClass
     * @inject
     */
    protected $injectMe;
}

So let’s guess everyone knows that TYPO3 switched to Doctrine Annotations. It’s better to use either constructor injection or setter injection. Again, if you have only one class, this change is not a problem. But most of the time you have hundreds of them and you have to remember what to do. This is cumbersome and error-prone. So let's run Rector for us with the InjectAnnotationRector and you get this:

use Ssch\TYPO3Rector\Tests\Rector\Annotation\Source\InjectionClass;

final class SomeInjectClass
{
    /**
     * @var \Ssch\TYPO3Rector\Tests\Rector\Annotation\Source\InjectionClass
     */
    protected $injectMe;

    public function injectInjectMe(InjectionClass $inject): void
    {
        $this->inject = $inject;
    }
}

Cool. Next one...

So TYPO3 Migrations Are Now Automated?

Well, no. First of all, TYPO3 is more than just PHP code. We have TypoScript, Fluid, nowadays YAML, JSON. And there are still a lot of changes not reflected in an appropriate Rector rule. 

During the 2019 TYPO3 Camp Rhein Ruhr in Essen Sebastian Schreiber demonstrated the power of Rector to the TYPO3 community. We were all amazed at the possibilities. During the camp, we decided that we need "rules" for each breaking change and deprecation in TYPO3. It did not take long to hack together a script that imported all changelog entries into GitHub as issues. And so it began!

At this moment there are about 69 fixed rules and a lot of configurable rules available for TYPO3. This is a great start!

We're working on adding more rules each day. We also are working on creating a binary distribution of typo3-rector so we can install it in any TYPO3 project without any composer dependency conflicts.

We Need You!

There are still over 500 issues open, however. If you want to help out, you're more than welcome. Let us automate the boring stuff for you and others together. Inspire people to share your rule.

Additional contributors for this article
  • Content Publisher : Mathias Bolt Lesniak
  • Proofreader : heather mcnamee