Upgrade to Nette 4

Nette 4 is the set of libraries that moved to a new major version: nette/utils 4, nette/php-generator 4, nette/mail 4 and nette/robot-loader 4. Constants got new names, flags became named arguments, and a few methods disappeared. With the package dresscode/upgrade-nette, DressCode rewrites what can be rewritten and, for the rest, says what to write instead.

What It Looks Like

An application on Nette 3 wants to move to nette/utils 4.0. The estimate tells how much work that is before anything changes:

DRESS|CODE 1.3.0
Config     /var/www/shop/dresscode.neon
Upgrade    nette/utils 4.0 over 3 files

Must change, 3 topics in 12 places
  nette/utils                  7 places in 3 files     after the raise, automatic
  nette.flagsAsNamedArguments  3 places in 1 file      after the raise, automatic
  nette/utils                  2 places in 1 file      works today, automatic

Once nette/utils is raised to ^4.0, DressCode rewrites the code by itself:

$image->resize(300, 200, Image::FIT | Image::SHRINK_ONLY);
file_put_contents($this->file, Json::encode($products, Json::PRETTY));
return Strings::startsWith($code, 'sale-');
$image->resize(300, 200, Image::OrSmaller | Image::ShrinkOnly);
file_put_contents($this->file, Json::encode($products, pretty: true));
return \str_starts_with($code, 'sale-');

It rewrites a method call only where the types from PHPStan say the method belongs to that class, and in the style the code of the project has. Step by Step shows how to get there.

What Gets Rewritten

The tables show samples from the data of the package dresscode/upgrade-nette, each verified by a run of DressCode. The complete list is in the data of the package, one file per library, for example utils.neon.

nette/utils 4

old API new API since
Strings::startsWith($s, 'x') \str_starts_with($s, 'x') 4.0
Strings::normalizeNewLines($s) Strings::unixNewLines($s) 4.0
Image::FIT | Image::SHRINK_ONLY Image::OrSmaller | Image::ShrinkOnly 4.0
Json::encode($v, Json::PRETTY) Json::encode($v, pretty: true) 4.0
Arrays::grep($a, $p, PREG_GREP_INVERT) Arrays::grep($a, $p, invert: true) 4.0
Arrays::map($a, callback: $fn) Arrays::map($a, transformer: $fn) 4.0.4
Html::fromHtml($s) Html::html($s) 4.1.5

The other Image constants (EXACT, FILL, STRETCH, EMPTY_GIF), Strings::contains() and endsWith(), and the flags of Json::decode(), Strings::match(), matchAll() and split() are rewritten the same way.

nette/php-generator 4

old API new API since
ClassType::class('Order') new ClassType('Order') 4.0.1
ClassType::VISIBILITY_PUBLIC Visibility::Public 4.0.10
$method->body $method->getBody() 4.0
ClassType::withBodiesFrom($c) ClassType::from($c, withBodies: true) 4.0

DressCode adds the import of Nette\PhpGenerator\Visibility by itself.

nette/mail 4 and nette/robot-loader 4

old API new API since
Message::HIGH Message::High nette/mail 4.0
$mail->subject $mail->getSubject() nette/mail 4.0
$loader->setTempDirectory($dir) $loader->setCacheDirectory($dir) nette/robot-loader 4.1.3

What Gets Reported

Where there is no replacement, or a person has to choose it, DressCode reports the place and leaves the code alone. The message says what to write instead:

app/Samples.php:30:22: error: Static method `Nette\Utils\Finder::findFiles()` is deprecated: pass the masks as one array, since nette/utils 4.0. [upgrading.api.forbiddenMembers]
app/Samples.php:32:9: error: Static property `Nette\Utils\Html::$xhtml` is removed: there is no replacement; the output is always HTML5; drop the setting, since nette/utils 4.0. [upgrading.api.forbiddenMembers]
old API what to write instead since
Finder::findFiles('*.php', '*.phpt') the masks as one array, findFiles(['*.php', '*.phpt']) nette/utils 4.0
Html::$xhtml nothing, the output is always HTML5; delete the setting nette/utils 4.0
Image::rgb() ImageColor::rgb(), which returns an object and takes the opacity as a number from 0 to 1 nette/utils 4.0.3
Nette\Iterators\Mapper Iterables::map(), which returns a generator you can iterate only once nette/utils 4.0.5
ClassType::addExtend() setExtends(), a class extends at most one class nette/php-generator 4.0.1

You confirm such a place with the comment // dresscode:ignore <decision> -- <reason>, or rewrite it; Places Left for You has the details.

Step by Step

DressCode needs nette/utils 4.1, so it does not fit into an application on Nette 3 as a development dependency. Install it globally, with PHPStan and the data for Nette:

composer global require dresscode/dresscode dresscode/upgrade-nette phpstan/phpstan
  1. Measure how much work the upgrade takes: dresscode upgrade nette/utils 4.0. The line Composer says whether something in composer.lock refuses the target version.
  2. Save the plan with the test command, so that the team can review it: dresscode upgrade nette/utils 4.0 --save-plan --test-command "vendor/bin/tester tests -o junit:{junit}".
  3. Carry it out: dresscode upgrade --continue. The tests run after each step, and a step after which a test newly fails goes back. At the Composer step, DressCode asks whether it may raise nette/utils to ^4.0.
  4. Go through what is left for you, and run --continue again until the run ends with UPGRADED.

The other libraries move the same way, only with another target, such as dresscode upgrade nette/php-generator 4.2. Without a target, DressCode proposes every library it has newer data about than the project allows today. Upgrade Your Code Step by Step describes the whole procedure with tests, commits and places left for you.

Where to Go Next