Module Loadable Package (MLP) Refactor
If you are looking to make your Module Loadable Package compatible with different versions of PHP, this guide is for you.
You will install an MLP zip file into Sugar's instance where Rector is and git has been initialized, by doing that, Sugar will add the MLP in the filesystem and git will let us know what has changed.
Prerequisites:
- Basic Setup
- A module loadable package (zip file)
Upload and Install your MLP
Upload and install your MLP as you normally would through Sugar's UI or API. Sugar will copy your files per manifest definition and perform the database operations as well (which are irrelevant for Rector).
After it has been installed, execute git status
commands to see which files were updated/added by Sugar:
sh-3.2$ git status On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: config_override.php modified: custom/application/Ext/Include/modules.ext.php Untracked files: (use "git add <file>..." to include in what will be committed) custom/Extension/application/Ext/Include/xxxx.php custom/Extension/application/Ext/Include/orderMapping.php custom/modules/ActivityStream/ custom/modules/Forecasts/Ext/clients/ custom/modules/pmse_Project/ no changes added to commit (use "git add" and/or "git commit -a")
Prepare your Rector config
Copy the paths, and paste them into the rector.php config file that was generated in the previous step, replacing the default paths in $rectorConfig->paths([
array like this (you can ignore some of the files, like *ext.php
and *orderMapping.php
):
$rectorConfig->paths([ __DIR__ . '/custom/Extension/application/Ext/Include/BuildingBlock_HelloWorldDashlet.php', __DIR__ . '/custom/clients/', __DIR__ . '/custom/modules/ActivityStream/', __DIR__ . '/custom/modules/Forecasts/Ext/clients/', __DIR__ . '/custom/modules/pmse_Project/', ]);
* Please, notice the '/' after __DIR__
constant usage. It is required as __DIR__
does not have a trailing slash.
Modify the rector.php config file to upgrade the code to the newest supported PHP version (in our case, 8.2):
Replace
// register a single rule $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
With
$rectorConfig->phpstanConfig(__DIR__ . '/stan.neon');
Add the following rules:
$rectorConfig->sets([ LevelSetList::UP_TO_PHP_82, SetList::PHP_82, ]);
The following rules should be skipped to prevent known issues with Sugar.
$rectorConfig->skip([ \Rector\Php73\Rector\ConstFetch\SensitiveConstantNameRector::class, \Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::class, \Rector\Php80\Rector\FunctionLike\UnionTypesRector::class, \Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector::class, \Rector\Php80\Rector\FunctionLike\MixedTypeRector::class, \Rector\Php81\Rector\Property\ReadOnlyPropertyRector::class, \Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector::class, \Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector::class, \Rector\Php80\Rector\NotIdentical\StrContainsRector::class, \Rector\Php80\Rector\Identical\StrEndsWithRector::class, \Rector\Php80\Rector\Identical\StrStartsWithRector::class, \Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::class, \Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector::class, \Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector::class, \Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector::class, \Rector\Php81\Rector\Array_\FirstClassCallableRector::class, \Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector::class, \Rector\Php81\Rector\ClassMethod\NewInInitializerRector::class, \Rector\Php80\Rector\Class_\StringableForToStringRector::class, \Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector::class, \Rector\Php54\Rector\Array_\LongArrayToShortArrayRector::class, \Rector\Php74\Rector\FuncCall\ArrayKeyExistsOnPropertyRector::class, \Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector::class, \Rector\Php74\Rector\FuncCall\ArraySpreadInsteadOfArrayMergeRector::class, \Rector\Php80\Rector\FuncCall\ClassOnObjectRector::class, \Rector\Php80\Rector\ClassConstFetch\ClassOnThisVariableObjectRector::class, \Rector\DeadCode\Rector\StaticCall\RemoveParentCallWithoutParentRector::class, \Rector\Php82\Rector\New_\FilesystemIteratorSkipDotsRector::class, \Rector\Php74\Rector\LNumber\AddLiteralSeparatorToNumberRector::class, __DIR__ . '/include/SugarObjects/templates/file/views/view.edit.php', ]);
Full config file example
By following those steps, you should have a rector.php similar to this:
<?php declare(strict_types=1); use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/custom/Extension/application/Ext/Include/BuildingBlock_HelloWorldDashlet.php', __DIR__ . '/custom/clients/', __DIR__ . '/custom/modules/ActivityStream/', __DIR__ . '/custom/modules/Forecasts/Ext/clients/', __DIR__ . '/custom/modules/pmse_Project/', ]); $rectorConfig->phpstanConfig(__DIR__ . '/stan.neon'); $rectorConfig->sets([ LevelSetList::UP_TO_PHP_82, SetList::PHP_82, ]); $rectorConfig->skip([ \Rector\Php73\Rector\ConstFetch\SensitiveConstantNameRector::class, \Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::class, \Rector\Php80\Rector\FunctionLike\UnionTypesRector::class, \Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector::class, \Rector\Php80\Rector\FunctionLike\MixedTypeRector::class, \Rector\Php81\Rector\Property\ReadOnlyPropertyRector::class, \Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector::class, \Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector::class, \Rector\Php80\Rector\NotIdentical\StrContainsRector::class, \Rector\Php80\Rector\Identical\StrEndsWithRector::class, \Rector\Php80\Rector\Identical\StrStartsWithRector::class, \Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::class, \Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector::class, \Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector::class, \Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector::class, \Rector\Php81\Rector\Array_\FirstClassCallableRector::class, \Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector::class, \Rector\Php81\Rector\ClassMethod\NewInInitializerRector::class, \Rector\Php80\Rector\Class_\StringableForToStringRector::class, \Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector::class, \Rector\Php54\Rector\Array_\LongArrayToShortArrayRector::class, \Rector\Php74\Rector\FuncCall\ArrayKeyExistsOnPropertyRector::class, \Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector::class, \Rector\Php74\Rector\FuncCall\ArraySpreadInsteadOfArrayMergeRector::class, \Rector\Php80\Rector\FuncCall\ClassOnObjectRector::class, \Rector\Php80\Rector\ClassConstFetch\ClassOnThisVariableObjectRector::class, \Rector\DeadCode\Rector\StaticCall\RemoveParentCallWithoutParentRector::class, \Rector\Php82\Rector\New_\FilesystemIteratorSkipDotsRector::class, \Rector\Php74\Rector\LNumber\AddLiteralSeparatorToNumberRector::class, __DIR__ . '/include/SugarObjects/templates/file/views/view.edit.php', ]); };
Dry-run Rector
Now that you have your Rector properly configured with your MLP data.
Dry-run gives you the opportunity to see what Rector has found and will execute/change before it actually does it on your behalf.
it's time to dry-run rector by executing the following command.
cd /path/to/sugar && rector-0.15.20/bin/rector process --clear-cache --dry-run > php82.diff