Definitely Typed PHP — Four years later

Ivan Voskoboinyk
4 min readOct 6, 2019
A result of refactoring an overloaded function from Laravel Framework

Four years ago, in 2015, I’ve published an article on an idea of providing extensive type definition files for arbitrary PHP code. Primarily keeping in mind frameworks & libraries. “Definitely Typed PHP” it is. At the time that idea seemed to be the only good way to solve the existing problem of insufficient type definitions capabilities available with phpDoc to document & describe existing code we currently have.

TLDR

  • Don’t use overloaded functions.
  • Use native PHP language argument and return type hints everywhere.
  • Refactor overload functions to sets of functions with proper type hints.

Strictly Typed PHP

But there’s a lot changed since 2015. PHP 7.0 was released, got a few rounds of bug-fixing updates and was finally stable enough for businesses to consider upgrading. Then 7.1, 7.2, 7.3 and pretty soon we’ll see official release of PHP 7.4.

PHP 7.x brought so much new stuff to the table that it was a no-brainier for us at Prezly to upgrade our main application. Suddenly we’ve got native language syntax for documenting our code: return types, scalar & nullable types, void, object and iterable were added.

We’ve immediately started to use them everywhere — native type hints were much more reliable comparing to phpDoc. Native ones never outdate silently, they are invariants to guarantee that you get exactly what the method is declared to return.

And then the advantages of using type-hints outweighed the unnecessary niceties of overloaded functions and union-type parameters. Our code has gradually become type-strict.

And you know what? We’ve liked it!

The (weak) PHP type system forced us to simplify our code, make it straightforward and thus less prone to errors. It made us to be disciplined. Yes, the code became more verbose in some cases, but the gained stability made us to not worry about it.

Overloaded functions

Yes, they’re nice, but they are not worth the hassle.

Every overloaded function can be rewritten to a set of strictly typed functions. One for every use-case. As there’s no native language support, we consider them poor practice that gives very little, but takes away our confidence about the code.

Let’s see how can we fix one of the examples I’ve been using back then in 2015: Session helper function from Laravel framework.

/**
* Get Session object
*/
session(): SessionInterface;
/**
* Get specified session value
*/
session(string $key, mixed $default = null): mixed;
/**
* Set specified session values [$key => $value, ... ]
*/
session(array $values): void;

So, in fact, this is not a single function, there are three functions merged into a single entity. Splitting them will make the code about 417%* more clear and straightforward:

abstract class Session 
{
public static function instance(): SessionInterface
{
//
}
public static function getValue(string $key)
{
//
}
public static function setValues(array $values): void
{
//
}
}

Now the code became more verbose, but it’s also super clear on what’s going on. Have you noticed that now we don’t even need phpDoc here to explain what a developer can do with this code? Method names, input arguments and return types do now clearly communicate every specific method specs and purpose (unlike the initial implementation of the session() helper, where you have to either remember it or consult the documentation every time).

* —rough estimate based on author’s intuitive perception ;)

Union types

Union type arguments and return values, just like overloaded functions, can be easily refactored to a set of functions that accept strictly one type of argument, return strictly one type of value. This is a much better practice, backed by current PHP language capabilities.

Missing functionality

Obviously, the type system PHP 7.x is currently offering is rather weak and is still lacking features to cover most of practical use-cases. Especially comparing to languages designed with a type-system initially.

Most notably we miss:

  • type-hinting array items and keys (i.e. array<Book>or array<int,Author>)
  • type-hinting generic functions and classes (i.e. Collection<Book>)
  • type-hinting shapes of associative arrays and stdClass POPO objects (i.e. { id: int, name: string })

But we believe PHP community will get there soon.

Conclusion

Now in 2019 I believe that we don’t really need type definitions files, but a more powerful native type system instead.

A lot of use-cases that seemed to justify the existence of .d.php files before can be refactored to simpler, more clear and more reliable versions of themselves. With proper native type hints provided for every argument and return value.

Of course, there’s still a long way to take. But I believe the existing native type syntax is helping the PHP community to become better by encouraging us to follow best practices and write cleaner code.

I also highly recommend to watch this kinda related “Extremely defensive PHP video by Marco Pivetta:

--

--

Ivan Voskoboinyk

Web developer, PHP, JS, React, SQL, Java, web-design, OSS believer, 8bit fan