🌑

Named parameters in PHP8

10 May 2022

Have you ever dreamed of shooting your legs with tools which were designed particularly for that purpose?
Then this might be just a thing for you.

I stopped tracking new features in PHP since version 7.4 because it felt like community stuck after those major improvements in 7.0-7.2 and started to put effort in controversial things that had a little value.
So, PHP8 changes just moved around me without any notice.
Recently I had a discussion with my colleague and he suggested using named parameters that were introduced in PHP8.0.
First I was highly confused by the concept, then I became concerned about multiple things. Let me share them.

How it works.

So, here is a function to explain the base concept and reasoning behind named params:

<?php

function printUserDetails(string $name, string $surname, ?int $age = null, ?string $address = null): void {
    printf('First name: %s, last name: %s', $name, $surname);
    
    if ($age !== null) {
        printf('Age %d', $age);
    }

    if ($address !== null) {
        printf('Lives at %s', $address);
    }
}

As you can see the function has two optional parameters: age and address.
Imagine that you don't want to tell age but only address of the user.

Old and the most convenient way would look as follows

<?php

printUserDetails(
    'Temirkhan',
    'Nasukhov',
    null,
    'Some str. 8'
);

Looks like someone thought that it is too messy to have such a signature and decided to simplify things.
Here is a result

<?php

printUserDetails(
    name: 'Temirkhan',
    surname: 'Nasukhov',
    address: 'Some str. 8'
);

Looks good, right?

Maybe, but I promise you will pay... You will pay!!!

What's wrong

The good.

We will start with the point that the reason for such approach usually hides behind poor architectural solutions.
The most common way to use named parameters is to avoid drawbacks and bad aftertaste of using a telescoping constructor.
PHP does not have method overload and thus objects with huge amount of properties automatically become something like:


class Person
{
    //
    public function __construct(
        string $name,
        string $surname,
        ?string $age = null,
        ?string $address = null,
        ?float $height = null,
        ?float $weight = null
    ) {
      //  
    }
}

You could notice that there is something wrong in that class and probably know how to fix it. Yet, why?
We have named parameters and can act as if everything is alright.

Instead of applying a builder pattern or fixing data/domain structure we just throw named parameters at it and it's good to go.

It is not! Tech debt won't go anywhere. It will haunt and consume more nerves and time at each development iteration.

The bad.

In the fulltime-work PHP world there is no way to avoid using package management such as composer.
Composer uses semantic versioning to manage packages and semantic versioning in general follows the rule of X.Y.Z.

X.Y.Z is a version of a package. Like 3.2.14

If something changes in a library, and it is not compatible with the current version, the library has to increase X by 1.
If changes are compatible but mean some new features it requires increasing Y by 1.
If none of the above it will go to patch increasing Z by 1.

What does it have to do with named parameters?
Let's say we have a function from previous example:

<?php

function printUserDetails(string $name, string $surname, ?int $age = null, ?string $address = null): void {
    // Some implementation here that does not matter for this example
}

printUserDetails(
    name: 'Temirkhan',
    surname: 'Nasukhov',
    address: 'Some str. 8'
);

Imagine that we renamed argument $name to $firstName in function as follows:

<?php

function printUserDetails(string $firstName, string $surname, ?int $age = null, ?string $address = null): void {
    // Some implementation here that does not matter for this example
}

It is a totally compatible change. Actually, I am not sure if it is since PHP8, because right now we have an error in the code
for there is no parameter named $name

<?php

printUserDetails(
    name: 'Temirkhan', // Uncaught Error: Unknown named parameter $name
    surname: 'Nasukhov',
    address: 'Some str. 8'
);

Looks like now we have to release major versions for a package if we renamed any argument just in case if someone is using
named parameter. Adorable feature.
Before some of you might start throwing stones let us go through the last point.

The ugly.

Do you like interfaces? Do you like inversion of control? Damn, I do. Let us have some dummy example.

Here is some printer interface from vendor package:

<?php

interface PrinterInterface
{
    public function printPersonDetails(string $name, string $surname, ?int $age = null, ?string $address = null): void;
}

We will implement it in our source:

<?php

class Printer implements PrinterInterface
{
    public function printPersonDetails(string $name, string $surname, ?int $age = null, ?string $address = null): void
    {
        printf('First name: %s, last name: %s', $name, $surname);
        
        if ($age !== null) {
            printf('Age %d', $age);
        }
    
        if ($address !== null) {
            printf('Lives at %s', $address);
        }
    }
}

Here is a class that has no clue which implementation it receives because control is inverted through dependency injection:


class MyProfilerViewer
{
    private PrinterInterface $printer;
    
    public function __construct(PrinterInterface $printer)
    {
        $this->printer = $printer;
    }

    public function showMyDetails(): void
    {
        $this->printer->printPersonDetails(
            name: 'Temirkhan',
            surname: 'Nasukhov',
            address: 'Main str. 8'
        );
    }
}

This will work.
Make a wild guess, what will happen if the vendor renames $name to $firstName in the interface?
Nothing. It will work. Though, the interface would not have such an argument and a static analysis tool might highlight it.

What will happen if we rename $name to $firstName in our Printer? There will be an error.
Even though the interface is perfectly right, and it says that there is an argument named $name there won't be such.
Nobody will warn you until an error occurs in runtime.

Resolution

Except the fact that named parameters was a mistake?
Never use named parameters when it comes to external libraries and use them in your code only if you absolutely need to.
It is better to be guilty of the mistakes you can make and avoid by yourself rather than suddenly discover that external changes crashed your application.

Don't take everything plain: we have to challenge and prove the information we face.

Here is what really helps me to do it.