Named arguments in PHP 8

Spoiler: When developing a software, we sometimes call functions (or methods) that have lots of arguments. It is often embarrassing, sometimes dangerous. To work around the problem, naming those arguments during the call is now possible in PHP 8.

To develop a software program, developers spend part of their time using libraries of functions already available to simplify their life. Regardless of the subject, there is often someone out there who has met the same need before and with luck they have published their solution.

And when you use an external library, you have to adapt to it. Most of the time things go well, but sometimes the library isn’t that well written. It was surely a good idea at the time, but since then we have found other ways to do it more practical and readable.

contact357 @ pixabay

Today I would like to discuss the call to functions that handle a large number of arguments, some of them may have default values. It’s very handy to have a function which does everything and having default values allows a base version to be usable, but when called, the code is not always readable.

You might want to fix the code (there’s a whole bunch of techniques to reduce that number of arguments) but, a lot of times, this isn’t possible and you have to deal with it. So how do we make our code readable if the base code is a bit rotten?

Two and a half years ago, I published a technique to provide named arguments in PHP 5 and 7. Much like a Christmas present in advance, it is now natively available in PHP8.

Example of method

Any function or method that has a lot of arguments will cause the same kind of problem. Even more if some of them are optionnal.

So, just for the sake of the demonstration, let’s say I’ve decided to adapt the GD library to object-oriented programming with the laudable idea of improving things…

class Image {
    
    public function __construct($width, $height) {
        $this->gdImage = imagecreatetruecolor ($width, $height) ;
    }
    
    public function __destruct() {
        imagedestroy($this->gdimage) ;
    }
    
    public function png() {
        header("Content-type: image/png");
        return imagepng($this->gdImage) ;
    }
    
    function color($r, $g, $b) {
        return imagecolorallocate ($this->gdImage, $r, $g, $b) ;
    }
    
    // ...
}

This class has all needed to create an image, draw it and build colors for subsequent uses. Lets say that I then add and adaptation of GD’s imagearc() function. To do well, I added some defaults… If height remains null, it will be a circle. By default start andend are set to draw the whole circle. I also added an argument to be able to pass the angles in radians.

function draw_arc(
    $cx,$cy, $color, $width,
    $height = null,
    $start = 0,
    $end = 360,
    $isradiant = false
) {
    if ($isradiant) {
        $start = $start * 180 / pi() ;
        $end   = $end   * 180 / pi() ;
    }
    imagearc($this->gdImage,
             $cx, $cy, $width,
             $height ?: $width,
             $start, $end, $color) ;
}

Note that I had to move forward the color parameter (because managing a default value would have complicated the example).

Obviously, this object oriented adaptation is not so handy but this is the point. When one has to deal with a library that is not the cleanest one.

What is the problem ?

The first method of the classe are not problematic, it is quite easy to create an image and a color.

$i = new Image(320, 240) ;
$c = $i->color(255, 255, 255) ;

The problem arise when one will call the draw_arc method. The call itself works but afterward, when reading it, the meaning of the arguments may not be obvious:

$i->draw_arc(150, 150, $c, 100, 100, $s, $e, true) ;
$i->png() ;

How far is the circle from the top left corner ? 150,150 or 100,100 ? What it’s radius ? (50, 100, 75,…) ? Are the angle $s and $e in radians or degree ?

The arc of the first example
$i->draw_arc(150, 150, $c, 100) ;
$i->png() ;

This time, the parameter $c is the color or the width of the shape ?

Circle of the second example

When in doubt, we will read the documentation (if it was written) or the code. And when the code contains a lot of calls, we spend a lot of time going back and forth. Of course, IDEs can make our lives easier by showing us the signatures and documentation of functions but we still waste some time.

Change the class? bad idea

One could thing that this would not happen if the code was developed differently. We have techniques to reduce the number of arguments and we could use them to clean the code.

That’s right, if you’re building a library, class, or even a function, thinking about it early on is best. But a big part of a developer’s job is not to build but to correct and adapt the existing one, and in this case, things are different.

C’est un vrai chantier. Aapo Haapanen @ flickr

So imagine that a zealous colleague decides to change a class to make it all better. In fact, it doesn’t matter how he improves it and even if his result is better or not. Because it will pose two significant problems:

  1. During the cleaning, he will have to go over all the old calls to update them. This is a difficult task because he have to simultaneously keep in mind two versions of the same thing and therefore remain very concentrated during all this time (the brain does not like and it quickly inverts, confuses, mixes …). Let him make the slightest small mistake (and he will inevitably make some) and there is a bug that will then have to be corrected. Immediately if we are lucky (the code is covered with automated tests) or later with a call from a not so happy customer.
  2. And then, once the migration is complete, the old developers who were used to the old version will have to adapt to the new version. Not only will they spend a lot of time reading the documentation to familiarize themselves with it but a little bit of oversight will arise and their brain can confuse and mix up the two versions and you end up with a bug again.

Warning. I’m not saying you should avoid cleaning code. I’m just saying this is not easy and require special time and effort. Which is scarce and not always available when other topics are more important for the project.

And without even going as far as good intention cleaning, we always end up needing to change something and as long as we have to change the order of the parameters, when one of them acquires a default value, or another loses it, the entire code base must be reviewed.

The named arguments

This feature has been available for a long time in other programming languages (like python) but after thinking about it since at least 2013, PHP just got it in version 8 🎉.

The basic idea is very simple. When you call a function and pass arguments, you can now do it in two complementary ways:

  1. Old fashioned, providing the values, in order, stopping when the remaining ones have defaults that you are satisfied with.
  2. The new fashion, naming them. You list the parameters you need by name and associate the value with them.

Back to our example, the first call can now be written more legibly in this way. It takes longer, but you know right away who’s for what (even if this particular case isn’t very useful).

$i->draw_arc(
    cx: 200,
    cy: 200,
    color: $c,
    width: 100,
    height: 100,
    start: $s,
    end: $e,
    isRadian: true
) ;

The interest is especially when you only have to specify some optional parameters, you don’t have to list them all anymore, you can only fill in those that you really need.

Here I don’t mention height anymore since I wanted a circle:

$i->draw_arc( 
    cx: 200,
    cy: 200,
    color: $c,
    width: 100,
    start: $s,
    end: $e,
    isRadian: true
) ;

To avoid confusion, or to respect ordering habits, you can also list the parameters in the order you want (since they have names). This time I put the color at the end:

$i->draw_arc(  
    cx: 200,
    cy: 200,
    width: 100,
    color: $c
) ;

And when the first are in the expected order, we can also avoid naming them:

$i->draw_arc(
    200, 200,
    width: 100,
    color: $c) ;

By naming your arguments, you can therefore omit those for which the default value already satisfies you, avoid problems when the order is no longer so obvious and in general, make your call more readable for others who will read it some times in the future.

And after ?

For the curious and those in a hurry who do not want to install PHP8 on their system, you can also test all this on online interpreters (i.e. extendsclass.com).

Small advertisement for friends… Site of Cyril Bois, French developer who provides a lot of free online tools to help developers. As of December 3, 2020, it was one of the few platforms to offer PHP8.

But when I see the number of projects that are still in PHP 5, I imagine that most developers will have to wait a long time before they can name their arguments natively…

Fortunately, the arsouyes have thought of you and have an article which shows you that even if it is not native, you can still do it in PHP 5 and 7 🎉.