PHP started out as a simple language without a lot of the conventions of other C-like programming languages that make them challenging to learn and more difficult to write. Over time, as PHP matured, many of those features have been added as optional syntax. One example of this type of feature is type hinting.
Type hinting gives you the ability to define the type of values that can be passed for each argument of a function or method. Type hinting is optional, but when used it forces parameters to be a certain type or an error is thrown. Doing so makes it more obvious what type of input a function expects but can also lead to more errors.
Type hinting is a feature of PHP that has only matured recently. In PHP 5 (as of 5.4) there are only four types of type hints that can be used, and these do not include scalar values — bool, float, int, and string. Scalar type hinting was introduced in PHP 7.
What Is A Type?
All variables have a type. For example, look at this variable declaration:
$x = 1;
We can now say that $x has the type “int,” which is short for integer. If we pass $x through var_dump() it will show us its type.
Some languages are “strongly typed,” which means that you have to declare a variable’s type before you create it. This is not true in PHP. Moreover, you can also change the type of a variable, which is why PHP is considered to be “dynamically typed.”
For example, this is totally legal:
$x = 1; $x = array( 1 );
In other languages, this would throw an error for changing types. This is not the case, however, in PHP. Nor is it a problem is JavaScript, which is also dynamically typed.
Why Use Type Hinting?
Because PHP is a dynamically typed language, it is extremely flexible. It also means that we have to constantly check the contents of variables. For example, consider this function:
function slug_update_status( $post, $status ){ if( is_numeric( $post ) ) { $post = get_post( $post ); } if( ! is_object( $post ) ){ return; } $post->post_status = $status; return wp_update_post( $post ); }
Most of this function ensures that the variable $post is actually an object. If an integer or string that can be interpreted as an integer is passed, then we try and make an object out of it using get_post().
We can rewrite this with type hinting to force the first argument to be a WP_Post object, which removes the necessity of checking that $post is an object.
function slug_update_status( WP_Post $post, $status ){ $post->post_status = $status; return wp_update_post( $post ); }
Now, this function is shorter, has a single responsibility, and is more clear in its intent. The responsibility for ensuring that it is called with a valid post has shifted. Previously the responsibility for ensuring that we were only updating a valid post was on this function, which is not its actual purpose. Now the responsibility is on whatever code calls this function.
Why Not To Use Type Hinting?
The last paragraph of the previous section may have convinced you to use type hinting. That simple change made the code shorter and more concise, and the function now abides by the single responsibility principle, which is very important in software design.
There is a big difference between these two functions. If you ask yourself what happens if you pass a valid post ID to both, the difference becomes obvious. With the first function, you get the intended result and in the second you get a fatal error.
Ask yourself what happens if you pass an integer that does not correspond to a post to both functions. In the first, nothing substantial happens. With the second function, you get a fatal error.
If you’re building a bespoke web application in PHP, with total unit test coverage, type hinting is really great. The fatal errors that they may cause are going to cause your tests to fail and give you an exact reason why.
For a WordPress plugin, theme, and site development, you have way less control over how your code is used. Not everyone using your code will know what to do with these errors, or will even know they will happen.
I’m not saying not to use type hinting if you are a WordPress plugin developer. Instead, I am saying that I think the flexibility of using dynamically typed languages is a huge part of why WordPress is so successful.
Types of Type Hints
In PHP 5, there are four types of type hints you can use. You can specify that a parameter is of a specific class or interface, is an array, is “self” or is callable. Let’s go through each of these, one by one.
Class Or Interface Type Hinting
Class or interface type hinting is useful and flexible. If you specify a specific class name as your hint, which is what I did in the last code example, then only an instance of that class or an instance of a subclass of that class will be accepted.
Let’s look at this function again:
function slug_update_status( WP_Post $post, $status ){ $post->post_status = $status; return wp_update_post( $post ); }
The first argument has to be an instance of WP_Post or a subclass of it, so either of these two uses will work:
$post = get_post( 42 ); slug_update_status( $post, 'publish' ); $post = \WP_Post::get_instance( 42 ); slug_update_status( $post, 'publish' );
In both of these cases, $post is an instance of the WP_Post class. But, we can also use a subclass. That actually doesn’t apply with WP_Post since it can not be extended because it is defined with the final keyword.
But, if we had a function that was designed to create HTML markup from a WP_Query instance, we could pass it an instance of any class that extended WP_Query. In addition, we can also use interface names as our type hint, which provides a lot of flexibility as well.
Array Type Hinting
Type hinting as an array is, in my opinion, the most useful type hint in PHP5 for WordPress developers. We do a lot of array manipulation since WordPress developers tend to avoid creating specialized objects.
PHP and WordPress both provide a bunch of really helpful utility functions for working with arrays, but they all require an array be provided. If we can write a function or method that forces that an array be passed then we can use all of those utility functions safely.
When type hinting as an array, an empty array is a valid argument, but null is not. This example illustrates this:
function slug_flatten_to_json( array $array ){ return wp_json_encode( array_values( $array ) ); } //no error $x = slug_flatten_to_json( [] ); //fatal error $y = slug_flatten_to_json( null );
Because type hints are nullable, we could have avoided the fatal error by declaring the function like this:
function slug_flatten_to_json( array $array = null ){
But, while that would prevent the fatal error, we would still get a PHP warning because it would allow us to pass null to array_values(). In this example, that’s not good, but it may be useful in many cases.
Self Type Hints
Self type hints require a parameter of a class method — this does not work with functions out of object context — to be an instance of the same class the method is a part of. This is not a type hint typically used in a WordPress plugin or theme. Honestly, I can’t think of a practical use for it.
Callable Type Hints
A callable in PHP is any representation, as a string or array that can be used to call a function or string. Here are three examples of callables being used that should look very familiar to a WordPress developer should be familiar with:
add_action( 'wp_head', 'output_analytics' ); $analytics = new analytics(); add_action( 'wp_head', [ $analytics, 'get_code' ] ); add_action( 'wp_head', [ 'analytics_class', 'a_static_method' ] );
In the first example, we are using the name of a function — ‘output_analytics’ — stored in a string as a callable. In the second example, our callable is an array with an object and a string holding the name of a method of the class that object is an instance of. In the third example, the callable is in an array and references a static method of a class.
The WordPress Plugin API is powered by functions that accept callables — call_user_func() and call_user_func_array(). That is why if you don’t pass a valid callable to add_action() or add_filter() the error that is generated will be caused by one of those functions.
Errors caused by passing an invalid callback to add_filter() or add_action() are hard to debug as the errors are not very meaningful. Here is an example that would help avoid these errors and make the source of errors more obvious:
class plugins_api { static function add_action( $action, callable $callback ) { add_action( $action, $callback ); } static function add_filter( $action, callable $callback ){ add_filter( $action, $callback ); } }
Now if you pass an invalid callable then your error will tell you that this class generated it and what line, in what file called it. That is way easier to debug.
Scalar Type Hinting
One really cool feature of PHP7 is scalar type hinting. Scalar data types: strings, booleans, integers and floats can now be hinted.
The class I wrote as the example in the last section only solves one problem. While it forces the second argument of both methods to be a valid callable, it doesn’t prevent passing an invalid type — IE not a string — to the first argument.
In PHP7, we could rewrite this class like this:
class plugins_api { static function add_action( string $action, callable $callback ) { add_action( $action, $callback ); } static function add_filter( string $action, callable $callback ){ add_filter( $action, $callback ); } }
This rewrite makes it so that the first argument has to be a string. This isn’t a huge change, but again, it makes both methods clearer in their intent and makes errors caused by improper usage more obvious.
Do note, that in PHP5 this type hint would be interpreted as requiring the first argument to be an instance of the class “string” not a string. As a result in PHP5, passing a string to the class written this way would cause an error. In PHP7 a string is required.
There are three other types of scalar type hints: bool, int, and float. They can be used the same way as a string.
Just keep in mind that PHP is perfectly fine at dealing with being a dynamically typed language. When you try and use a number string, like “42,” as an integer, PHP will handle the conversion for you.
To Type Hint or Not To Type Hint?
In some ways, type hinting defeats the purpose of using a dynamically typed language. On the other hand, it’s good for clarity and performance. In PHP7, there is also return type declarations, which has similar advantages and disadvantages. PHP 7.1 might introduce property type declarations.
In many ways, these new language features are very exciting to me and I want to use them all of the time. That said, I do worry my code becomes overly pedantic and harder for other people to work with.
I’m not 100 percent sold on either side of this, but I do feel like everyone should understand these concepts. After all, it’s a free software and you can use it in any way that will compile. Since most of my work is in WordPress plugins for general release, I can’t use the PHP7 features and that limits the usefulness of type hinting for me. But, the code I do write in PHP7 is making heavy use of type hinting and return type declarations.
I hope this article has helped you understand this feature of the language. That way you will know what this syntax means and be excited for PHP7.
6 Comments