If you are looking to make your Module Loadable Package (MLP), a.k.a Addon, 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):
Remove/Comment:
// register a single rule $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
Specify PHP 8.2 as your phpVersion:
$rectorConfig->phpVersion(PhpVersion::PHP_82);
The following rules should be skipped to prevent known issues with Sugar (it will skip those but will run all the other rules available in your rector version).
$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', ]);
If you'd like to have only the incompatible rules between PHP 7.4 to PHP 8.2, you can use the following:
$rectorConfig->rules([ AddParamBasedOnParentClassMethodRector::class, SetStateToStaticRector::class, Php8ResourceReturnToObjectRector::class, Php81ResourceReturnToObjectRector::class, ArrayKeyExistsOnPropertyRector::class, ExportToReflectionFunctionRector::class, FilterVarToAddSlashesRector::class, MbStrrposEncodingArgumentPositionRector::class, MoneyFormatToNumberFormatRector::class, RealToFloatTypeCastRector::class, RestoreDefaultNullToNullableTypePropertyRector::class, StringifyStrNeedlesRector::class, GetClassOnNullRector::class, ListEachRector::class, ReplaceEachAssignmentWithKeyCurrentRector::class, ParseStrWithResultArgumentRector::class, StringifyDefineRector::class, WhileEachToForeachRector::class, ConsistentImplodeRector::class, CurlyToSquareBracketArrayStringRector::class, ]);
Add safeCount
rule to your config if you are in a supported Sugar version
<?php ... use Rector\Renaming\Rector\FuncCall\RenameFunctionRector; ... return static function (RectorConfig $rectorConfig): void { ... $rectorConfig->ruleWithConfiguration(RenameFunctionRector::class, [ 'count' => 'safeCount', ]); ...
Full config file example
By following those steps, you should have a rector.php similar to this to run all Rector rules against your code:
<?php declare(strict_types=1); use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; use Rector\Renaming\Rector\FuncCall\RenameFunctionRector; 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->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', ]); $rectorConfig->ruleWithConfiguration(RenameFunctionRector::class, [ 'count' => 'safeCount', ]); };
Full config file example (incompatibilities only)
By following those steps, you should have a rector.php similar to this to run incompatible rules between PHP 7.4 to PHP 8.2:
<?php declare(strict_types=1); use Rector\CodingStyle\Rector\FuncCall\ConsistentImplodeRector; use Rector\Php72\Rector\Assign\ListEachRector; use Rector\Php72\Rector\Assign\ReplaceEachAssignmentWithKeyCurrentRector; use Rector\Php72\Rector\FuncCall\GetClassOnNullRector; use Rector\Php72\Rector\FuncCall\ParseStrWithResultArgumentRector; use Rector\Php72\Rector\FuncCall\StringifyDefineRector; use Rector\Php72\Rector\While_\WhileEachToForeachRector; use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector; use Rector\Php74\Rector\Double\RealToFloatTypeCastRector; use Rector\Php74\Rector\FuncCall\ArrayKeyExistsOnPropertyRector; use Rector\Php74\Rector\FuncCall\FilterVarToAddSlashesRector; use Rector\Php74\Rector\FuncCall\MbStrrposEncodingArgumentPositionRector; use Rector\Php74\Rector\FuncCall\MoneyFormatToNumberFormatRector; use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector; use Rector\Php74\Rector\StaticCall\ExportToReflectionFunctionRector; use Rector\Php80\Rector\ClassMethod\AddParamBasedOnParentClassMethodRector; use Rector\Php80\Rector\ClassMethod\SetStateToStaticRector; use Rector\Php80\Rector\FuncCall\Php8ResourceReturnToObjectRector; use Rector\Config\RectorConfig; use Rector\Core\ValueObject\PhpVersion; use Rector\Php74\Rector\ArrayDimFetch\CurlyToSquareBracketArrayStringRector; use Rector\Caching\ValueObject\Storage\MemoryCacheStorage; use Rector\Php81\Rector\FuncCall\Php81ResourceReturnToObjectRector; use Rector\Renaming\Rector\FuncCall\RenameFunctionRector; use Rector\Php70\Rector\StaticCall\StaticCallOnNonStaticToInstanceCallRector; return static function (RectorConfig $rectorConfig): void { $rectorConfig->cacheClass(MemoryCacheStorage::class); $rectorConfig->disableParallel(); $rectorConfig->phpVersion(PhpVersion::PHP_82); $rectorConfig->rules([ AddParamBasedOnParentClassMethodRector::class, SetStateToStaticRector::class, Php8ResourceReturnToObjectRector::class, Php81ResourceReturnToObjectRector::class, ArrayKeyExistsOnPropertyRector::class, ExportToReflectionFunctionRector::class, FilterVarToAddSlashesRector::class, MbStrrposEncodingArgumentPositionRector::class, MoneyFormatToNumberFormatRector::class, RealToFloatTypeCastRector::class, RestoreDefaultNullToNullableTypePropertyRector::class, StringifyStrNeedlesRector::class, GetClassOnNullRector::class, ListEachRector::class, ReplaceEachAssignmentWithKeyCurrentRector::class, ParseStrWithResultArgumentRector::class, StringifyDefineRector::class, WhileEachToForeachRector::class, ConsistentImplodeRector::class, CurlyToSquareBracketArrayStringRector::class, StaticCallOnNonStaticToInstanceCallRector::class ]); $rectorConfig->ruleWithConfiguration(RenameFunctionRector::class, [ 'count' => 'safeCount', ]); };