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.