How the Parameter Type Widening feature of PHP 7.2 can influence development

3/21/18

When PHP 7 was released at the end of 2015, it had a huge impact on the community. It came out with one of the most anticipated features we were waiting for: scalar type hints. As if this wasn't big enough, return types were also added to this new major version and the possibility to make PHP handle these types even in a strict mode, similar to other typed languages. Even for those who don't really care about features, PHP 7 brought a better performance for their applications and was hyped just like the new types. Some of the PHP critics recognized this as a step to become more mature as a language, but this wasn't the end of new types, because in PHP 7.1 the PHP team released the new iterable and void types and add them to the existing scalar ones. How can this be topped? By releasing the object type in PHP 7.2 and a new feature called Parameter Type Widening in which you can omit the type hints, even when you extend an interface or class, that uses types in its arguments.

Wait what? We introduced more and more new types and now we can remove the type hints and make our code inconsistent to an interface's type hints? This is a step back from what we achieved since PHP 7.0. What were they thinking?

What exactly is this Parameter Type Widening feature?


Before we end up in confusion or rage, let's take a look into this newly introduced PHP feature and what it does. Implementing PHP interfaces had to be done with the exact method definitions the interfaces provided in the past. It had to be the same method name and the same arguments. If the implementation was done poorly, PHP threw a fatal error:
<?php

interface FooInterface
{
    public function bar($str, $r);
}

class FooClass implements FooInterface
{
    public function bar($str)
    {
        return $str;
    }
}
Fatal error: Declaration of FooClass::bar($str) must be compatible with FooInterface::bar($str, $r)
The same happened when you didn't use the same type hints specified by the interface or if you didn't used the defined type at all. The same fatal error occurred on overriding a method on class and abstract class extension the wrong way. It had to be exactly the same type hint for every argument, so this example, even though it has the same amount of arguments, would also lead to a fatal error:
<?php

interface FooInterface
{
    public function bar(string $str);
}

class FooClass implements FooInterface
{
    public function bar($str)
    {
        return $str;
    }
}
There are some exceptions in PHP arguments, but I don't want to go deeper into those topics as I want to show that in PHP 7.2, the same typed example above will be a valid implementation. Yes, removing string in your implementation of FooInterface is totally okay now and you are free to inject any possible type into the FooClass::bar() method. While it's not really a feature for custom interfaces, abstract classes or regular classes, it might have a bigger impact when it comes to third party ones you're trying to implement. Those can get type hints in newer releases you'll usually have to add in your projects.

At first sight, it looks as bad as it is: Removing the type hint can change on how your implementation is working as our example has to handle an integer or an object implementing the __toString() magic method too. This may break your code in other places where a string was expected or where the string type hint was set to a strict mode with declare(strict_types=1);. You have to handle the arguments like in the pre-PHP7 days again and in a worst case scenario, PHP 7.2 opened a door for a lot of possible bugs. We're back to use type casts.

And now for something completely different: Backward Compatibility


Once upon a time, updating a library or a framework was a pretty messy work. Even after composer came along, the updates of dependencies wasn't done by simply running composer update in the console. Some methods changed, others were removed or different arguments were expected. These backward compatibility breaks were a hindrance for projects, especially customer projects for companies, who had to convince customers why they need to pay those expenses just to have their product work the same way as before, plus a few bugfixes and security patches, which were also hard to explain. So some libraries and frameworks introduced Semantic Versioning or a similar versioning, that took care of BC breaks for minor and/or patch versions. Some projects decided to prevent bigger BC breaks even in major versions to make a migration easier for the users of the software. It's hard to tell about backward compatibility without mentioning Symfony, which components became an essential part in the PHP projects environment, because it handles compatibility between versions so well. This also might be a reason for its success becoming a first choice for other projects like Spryker, Sylius, Laravel and others. If you don't need to adapt your libraries/frameworks because of some changes in your dependencies, the chances of introducing bugs in your next release are much lower and you have less extra work to do. You are able to concentrate your work on the business instead on old implementations.

But backward compatibility has a price: You can't introduce new language concepts just like that. Sure, new functions can be handled with polyfills, but some big changes like scalar types are unusable without introducing BC breaks or circumstances. This is why newer versions of frameworks with a backward compatibility policy haven't used these awesome features yet. The major release of Symfony 4 is using PHP 7.1.3 as minimum requirement, but they still haven't used scalar and return types in their existing code base, except for newly introduced features.

On the other side some library maintainers are early adopters and are bumping the minimum PHP requirement to the latest version. They also use the latest introduced PHP feature, but this is mostly the case for libraries that are simple to implement or are having an API that is not meant to be used directly by another developer. Using PHP 7.2 (with its Parameter Type Widening) as a minimum requirement opens the door to implement the new previously mentioned type hints without breaking older code bases. A feature introduced at the end of 2015 can finally be used by those who didn't want to break too much in releases.

You can't omit return types


While we can omit the type hints with Parameter Type Widening, this doesn't affect the return types of course. As a result, this isn't possible in PHP 7.2:
<?php

interface FooInterface
{
    public function bar(string $str) : string;
}

class FooClass implements FooInterface
{
    public function bar(string $str)
    {
        return $str;
    }
}
Extending a class or implementing an interface, that has a return type specified, forces you to keep this return type in your implementation or you'll still get the fatal error for being incompatible with the parent element.
Fatal error: Declaration of FooClass::bar(string $str) must be compatible with FooInterface::bar(string $str): string
If the dependency of choice introduced return types, you still have to adapt your code to match with the methods. At this point, you can also use type hints in your implementation if the same types are expected as parameters. The return type isn't a part of the Parameter Type Widening feature.

What we gain from Parameter Type Widening


If you're already working with types and prefer them, you won't need this feature as you would add them in your code if a dependency is expecting them. There might be a use case where you want, for example, to inject an array where an ArrayAccess is expected, but otherwise there will be only edge cases for you. In case you maintain your own open source projects and you're looking for your next major version update, you should consider PHP 7.2 instead of 7.1 as a minimum requirement.


About Claudio Zizza
I'm working as a software developer for more than 15 years and used many coding languages and frameworks during that time, like PHP (of course), ASP, .NET, Javascript, Symfony, Laminas and more. I also contribute to the open source communities and am a part of the PHP Usergroup in Karlsruhe, Germany.
Follow me on Twitter (@SenseException)