Renaming Parameters in a methodRenaming Parameters in a method

Renaming parameters in a method used to be an innocuous operation: one could do it without impact. Since PHP 8.0, such renaming may break existing calls. With named parameters, not only the method name, but also its parameters are part of the call.

And it is a common challenge to update parameter names, especially in large or old codebases. The problem arose from changing parameter positions, but nowadays, it is also linked to parameter names. And with a method being called many times, it is increasingly difficult to refactor it without impact all across the code base, and a perceived risk.

Ideally, the goal of renaming a parameter is to ensure backward compatibility while encouraging migration to the new parameter name. Reasons to rename a parameter are beyond the scope of this article.

Here are several strategies to refactor a method’s parameter name, without breaking code compatibility:

Parameter Aliasing, And Default Values

The strategy here is to add the new parameter with a default value, and use logic to check if the new parameter is set. If so, use its value; otherwise, use the old parameter.

<?php

class X {
    public function exampleMethod(int $oldParam = 3, ?int $newParam = null) {
      $value = $newParam ?? $oldParam;
    }
}
?>

This approach allows both parameters to coexist, and gives users a sunset period.

sunset period, precedence

One may also decide the precedence: has the old parameter priority, or the new? In fact, over the sunset period, it may actually change, until the old parameter is ultimately removed.

choosing the default value

The default value choice is a bit of a challenge. Here, it is set to null, which is outside the domain of the actual parameter. The detection is then obvious and non-conflictual. The drawback is to expand the type of the new parameter, which may become difficult to reduce later.

Another option would be to use a special value in the int domain, such as -1, or else, but it means such value cannot be used for anything else.

Cluttering the signature

On the other hand, this approach clutters the signature with twice the same parameter, and may make it harder to learn usage. In particular, anyone learning by empirically testing code may loose a lot of time figuring out the situation correctly. Most probably, the result of such experiment will hinder modernisation efforts.

Consider also as any AI training will keep emphasizing the old parameter for a long time, as per existing literature.

Consider a warning

This may be completed with a warning, that helps alert users about the evolution.

<?php

class X {
    public function exampleMethod(int $oldParam = 3, ?int $newParam = null) {
    if ($newParam === null) {
            // non blocking warning
        trigger_error("The parameter 'oldParam' is deprecated. Use 'newParam' instead.", E_USER_DEPRECATED);
    }

      $value = $newParam ?? $oldParam;
    }
}
?>

Adapter Pattern

A step above renaming the parameter is renaming the whole method. For this, create an adapter or wrapper method that accepts the old parameter and calls the new method with the new parameter. “`

 
<?php

class X { 
   public function exampleMethod($oldParam) { 
      return $this->exampleMethodNew($oldParam); 
   }

   public function exampleMethodNew($newParam) { 
      // Use $newParam 
      // The actual code is now here
   } 
} 
?> 

“`

sunset period

This way, the old version of the method is still available.

It is recommended to have the old method call the new one, so that the logic is not duplicated in the code. It also creates a natural penalty to using the old method, even if very small.

more clarity

The new parameter appears in a distinct method, and not as a refactor. The relay function introduce a duplicate method for the same feature, which may be confusing for users. It may also be time to rename that method anyway.

It is also possible to add a warning, like in the previous solution.

Middleware or Proxy Pattern

After renaming the method, comes renaming the whole class. For that, use a middleware or proxy to intercept calls and map old parameter names to new ones.

This moves the refactor from the current class to another one. The correct object may be injected where needed, until the refactor happens.

<?php

class XProxy {
    public function exampleMethod($oldParam = null) {
        $realObject = new X();
        return $realObject->exampleMethod($oldParam);
    }
}

class X {
    public function exampleMethod($newParam) {
    // Use $newParam
  }
}
?>

The proxy pattern decouples the old and new logic in a cleaner way, with an explicit intent for refactoring the code. It is also easy to remove the proxy later, when everyone has moved to the new signature.

It also adds some complexity to the code. Phasing out the proxy class is important, and will reduce complexity later.

Documentation and Communication

Finally, the last solution is the breaking change. Go forward and rename the parameter with the new name. No sunseting, just a breaking change. This means doing such a change in a major version of the application.

easing the transition

Update the documentation, mention the evolution in roadmap presentations, changelogs, and release notes to inform users about the change. They won’t be able to migrate until they have their refactor in place, but they might also be aware of the situation, and possible consequences. This gives users time to plan.

sunset period based on version

Here, the sunset period is managed by versioning. Users have to stay on the previous version until they are meeting the new compatibility requirements, and then, they can change. This is valid for a platform with release version, like a released framework or open source tools. It is harder to do when the versions are less obvious, as in a company platform upgrade.

Automated updates

To speed up the process, it is useful to mention automated solutions to handle the renaming:

  • Use the IDE rename parameter features
  • Use rector to set up a search and replace situation
  • Use AI to find and update all calls to the method with the correct new parameter name, when applicable

As usual, renaming things works well with static names. It get harder when the code uses dynamic parameter naming, or method.

<?php

function foo(int $a): int  { return $a + 2; }

// static call to foo
foo(a: 3);

// dynamic arguments with foo() 
$args = ['a' => 4];
foo(...$args);

// dynamic call to foo() 
$function = 'foo';
$function(a: 5);

?>

Just do it

This is the rip the architectural band-aid off approach. It’s simple for you, but your users might not appreciate the surprise errors on Monday morning. In particular, when little knowledge is shared before the even, it is a sign that the refactor was not organized.

The consequences may be a barrage of complaints from users, or, more silently, a delay in the upgrade to the newer versions, or simple quit from using the platform. Consequences don’t always make noise.

Summary Table

Strategy Backward Compatible Encourages Migration Complexity
Parameter Aliasing ✅ Yes ❌ No Low
Adapter Pattern ✅ Yes ✅ Yes Medium
Middleware/Proxy ✅ Yes ✅ Yes High
Documentation ❌ Yes ✅ Yes Low

Renaming parameters

Renaming parameters is harder than it looks. Since PHP 8.0, the internal variable names are no longer a secret; they are part of the public API. Named parameters do add an extra layer of attachement, although positional parameters had the same problems with…well… positions.

The situation grows with usage: the more often a method is called, the riskier it becomes to change anything to it: name, type, position, default value, etc. This might be a good starting point to decide what strategy to use. The other one is how much of a sunset period is needed.

It boils down to names, which are a permanent commitment: once a name is set, it is difficult to change it later. This applies to natural language itself, and it is often smoother to promote a new word and leave the previous one adrift, rather than force the replacement.