Can We Have Static Types in PHP without PHP 7 or HHVM?

Share this article

Can We Have Static Types in PHP without PHP 7 or HHVM?

Now that PHP 7 has been out for a while with interesting features like error handling, null coalescing operator, scalar type declarations, etc., we often hear the people still stuck with PHP 5 saying it has a weak typing system, and that things quickly become unpredictable.

Vector illustration of programmer's desktop

Even though this is partially true, PHP allows you to keep control of your application when you know what you’re doing. Let’s see some code examples to illustrate this:

function plusone($a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));
// output

int(2)
int(2)
int(2)

Our function will increment the number passed as an argument by one. However, the second and third calls are passing a string, and the function still returns integer values. This is called string conversion. We can make sure that the user passes a numeric value through validation.

function plusone($a)
{
    if ( !is_numeric($a) )
    {
        throw new InvalidArgumentException("I can only increment numbers!", 1);
    }

    return $a + 1;
}

This will throw an InvalidArgumentException on the third call as expected. If we specify the desired type on the function prototype…

function plusone(int $a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));
// output

PHP Catchable fatal error:  Argument 1 passed to plusone() must be an instance of int, integer given, called in /vagrant/test_at/test.php on line 7 and defined in /vagrant/test_at/test.php on line 2

This error seems a bit weird at first, because the first call to our function is using an integer!

If we read the message carefully, we’ll see that the error message says “must be an instance of int” – it assumes that integer is a class, because PHP prior to version 7 only supported type hinting of classes!

Things get even more awkward with function return arguments in PHP 5. In short, we can’t lock in their types automatically and we should check the expected value after the function call returns a value.

Augmented Types

Prior to the release of PHP 7, the team at Box came up with a nice idea to solve the typing safety problem on their PHP 5 application. After using assertions, type hints, etc., they decided to work on a cleaner solution for this problem.

We’ve seen how Facebook pushed PHP a little bit forward by launching HHVM and Hack, but the team at Box didn’t want to fork the PHP source code or modify anything in the core. Their solution was to create a separate extension called augmented types to parse the method’s phpDoc and assert types on runtime.

Installation

The below extension is meant to be used with PHP 5 – if you’re on PHP 7, just sit back and enjoy the ride!

The installation process is not very complicated, and doesn’t require any specific configuration. The instructions below apply to Ubuntu, but are easily applicable to other *nix based OS as well.

# update system
sudo apt-get update

# install required dependencies
sudo apt-get install php5-dev bison flex

# clone the repo
git clone git@github.com:box/augmented_types.git

# install extension

phpize
./configure --enable-augmented_types
make
make test
sudo make install

We must tell PHP to load our extension by editing the php.ini file.

# Get php.ini location from PHP info. This will print the cli configuration file, you may need to edit /etc/php5/fpm/php.ini )
php -i | grep 'Loaded Configuration File'

# output: /etc/php5/cli/php.ini
vim /etc/php5/cli/php.ini
# Get `extension_dir` the PHP info
php -i | grep extension_dir
# output: extension_dir => /usr/lib/php5/20100525 => /usr/lib/php5/20100525

# Add this line at the end of the file
zend_extension=/usr/lib/php5/20100525/augmented_types.so

The extension can be enabled on a per-file basis using the ini_set function.

ini_set("augmented_types.enforce_by_default",1);

Or directly on the php.ini configuration file. Be sure to check the documentation for more details about the installation process.

Usage

We mentioned earlier that the extension uses phpDoc for function/method prototypes. Most of its functionality can be explained through code examples.

/**
 * Add one
 *
 * @param   int $a
 * @return    int
 */
function plusone($a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));

You might think you’ll be able to guess the output of the above code, but you’d probably be wrong!

int(2)
PHP Fatal error:  Wrong type encountered for argument 1 of function plusone, was expecting a integer but got a (string) '1' in /vagrant/test_at/test.php on line 15

Even the second call doesn’t pass! This happens because we’re not doing the PHP conversion we talked about earlier, the function requires us to strictly pass an integer. Now, what about calling it using a float?

var_dump(plusone(1.5));
PHP Fatal error:  Wrong type encountered for argument 1 of function plusone, was expecting a integer but got a (float) 1.500000 in /vagrant/test_at/test.php on line 14

We made our function accept two types of arguments (int and float) by using the composite types definition.

/**
 * Add one
 *
 * @param   int|float $a
 * @return    int
 */
function plusone($a)
{
    return $a + 1;
}

var_dump(plusone(1));
var_dump(plusone(1.5));

Now our function should work as expected.

int(2)
PHP Fatal error:  Wrong type returned by function plusone, was expecting a integer but got a (float) 2.500000 in /vagrant/test_at/test.php on line 0

Oops! We should also define the return type for our function or cast the return value to match the phpDoc.

/**
 * Add one
 *
 * @param   int|float $a
 * @return    int|float
 */
function plusone($a)
{
    return $a + 1;
}

We can also work with compound types: the following example sums the elements of an array.

/**
 * Calculate sum
 *
 * @param   array $nums
 * @return    int
 */
function sum($nums)
{
    $sum = 0;
    foreach ($nums as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum([10, 12, 76]));

// output
int(98)

The function returns the expected value. What if the array contains something other than integers, though?

var_dump(sum([10, 12, "something"]));

// output
int(22)

The extension gives us the ability to work with array types. The previous example will throw a fatal error as expected.

/**
 * Calculate sum
 *
 * @param   int[] $nums
 * @return    int
 */
function sum($nums)
{
    $sum = 0;
    foreach ($nums as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum([10, 12, 76]));
var_dump(sum([10, 12, "something"]));
int(98)
PHP Fatal error:  Wrong type encountered for argument 1 of function sum, was expecting a (integer)[] but got a array in /vagrant/test_at/test.php on line 20

An interesting case is when we have a function that accepts an arbitrary number of arguments. We can make our previous function return the sum of all the passed arguments.

/**
 * Calculate sum
 *
 * @param   *int $nums
 * @return    int
 */
function sum($nums)
{
    $args = func_get_args();
    $sum = 0;
    foreach ($args as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum(10, 12, 76));

The *int type definition lets our function receive any number of arguments as long as it’s an integer. We can combine the *int and int[] type definitions to make our function accept both previous cases.

/**
 * Calculate sum
 *
 * @param   *int|int[] $nums
 * @return    int
 */
function sum($nums)
{
    if ( !is_array($nums) )
    {
        $nums = func_get_args();
    }

    $sum = 0;
    foreach ($nums as $value) {
        $sum += $value;
    }

    return $sum;
}

var_dump(sum(10, 12, 76));
var_dump(sum([10, 12, 76]));

Now both function calls will return the same value (int(98)).

Default Arguments

Most of the time, functions contain optional arguments which may be initialized using default values. We use the void type definition to tell the extension that our value is optional but OK if passed in.

/**
 * SSH to server.
 *
 * @param   string      $url
 * @param   int|void    $port
 * @return  bool
 */
function ssh($url, $port = 2222)
{
    return true;
}

Note: When an argument has a default value it will not enforce the specified type. It means that passing a string on the port argument won’t throw an error.

Return Types

The same thing we said earlier about argument types applies to return type definitions. We can specify normal scalar types, classes, composite types and array types. Every function should have a @return <type> definition and must use @return void if it doesn’t return anything.

class User
{
    protected $id;

    protected $name;

    /**
     * Constructor
     * @param int       $id
     * @param string    $name
     * @return void
     */
    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    /**
     * Return the user info as a string.
     *
     * @return    string
     */
    public function __toString()
    {
        return $this->name;
    }
}

Ignoring Files

Most of the time, our application contains other people’s packages, and we don’t want the augmented types extension to throw errors and halt our application because of missing definitions. Luckily, the extension provides a clean way to blacklist and whitelist files and folders.

augmented_types_blacklist([__DIR__."/vendor"]);

The extension will now ignore our vendor directory. If we have some of our own inspectable directories inside the vendor directory, we can use the whitelist function to add them back. You can read more about this in the documentation.

augmented_types_whitelist([__DIR__."/vendor/mylib"]);

We can also achieve the same result using the php.ini file, and this is the recommended way to do it, according to the extension’s developers.

# php.ini
augmented_types.whitelist=./vendor
augmented_types.blacklist=./vendor/mylib

PHP 7 Changes

PHP 7 introduced scalar type declarations and return types to functions/methods, thus eliminating the need for further validation on arguments and return types. Check out the list of changes on the official PHP website.

If PHP 7 already supports this, what could be the benefit of installing a new extension?

Well, the extension provides a wide variety of options that we don’t have in PHP 7, like:

  • Composite types (@param string|int $var)
  • Array types (@param int[] $var)
  • Forcing functions to return a value (at least void)

Conclusion

It’s always a good idea to upgrade to PHP 7 and benefit from all the exciting new features. However, we don’t always have the luxury of using cutting edge technology in our projects.

The augmented types extension will absolutely slow down the application’s runtime (as would be expected), which is why it should only be used during development and testing, to keep the production server clean and fast.

What do you think about the extension? Is it useful? Do you have other ideas on how to do what it does? We’re curious to know what you think, let us know!

Frequently Asked Questions (FAQs) about Static Types in PHP

What are static types in PHP?

Static types in PHP are a feature that allows developers to specify the type of variable that a function will accept or return. This can help to prevent bugs and make code easier to understand. Static types were introduced in PHP 7, but there are ways to use them in earlier versions of PHP as well.

How can I use static types in PHP without PHP 7 or HHVM?

While PHP 7 and HHVM both support static types, you can also use them in earlier versions of PHP by using a tool like Hack or a static analysis tool like PHPStan or Psalm. These tools can help you to enforce type safety in your PHP code, even if you’re not using a version of PHP that natively supports static types.

What is HHVM and how does it relate to static types in PHP?

HHVM, or HipHop Virtual Machine, is an open-source virtual machine developed by Facebook for executing programs written in Hack and PHP. HHVM was designed to increase the performance of PHP code, and it includes support for static types. If you’re using HHVM, you can use static types in your PHP code, even if you’re not using PHP 7.

What are the benefits of using static types in PHP?

Using static types in PHP can help to prevent bugs and make your code easier to understand. By specifying the type of variable that a function will accept or return, you can ensure that your code behaves as expected. This can be particularly useful in large codebases, where it can be difficult to keep track of the types of all variables.

Are there any downsides to using static types in PHP?

While static types can help to prevent bugs and make code easier to understand, they can also make code more verbose and less flexible. In some cases, you may find that using static types makes your code harder to read or write. However, many developers find that the benefits of static types outweigh these potential downsides.

How can I enforce static types in PHP?

You can enforce static types in PHP by using a tool like Hack, PHPStan, or Psalm. These tools can analyze your code and warn you if you’re not using static types correctly. You can also enforce static types in PHP 7 or HHVM by using the declare(strict_types=1) directive.

What is the difference between static and dynamic typing in PHP?

In static typing, the type of a variable is checked at compile time, while in dynamic typing, it’s checked at runtime. PHP is a dynamically typed language, but it also supports static types as of PHP 7. This means that you can choose to use static types in your PHP code if you prefer.

Can I use static types in PHP 5?

While PHP 5 does not natively support static types, you can use a tool like Hack or a static analysis tool like PHPStan or Psalm to enforce type safety in your PHP 5 code. These tools can help you to use static types in PHP 5, even though it’s not a built-in feature of the language.

What is type hinting in PHP?

Type hinting is a feature in PHP that allows you to specify the expected type of a function’s argument or return value. This can help to prevent bugs and make your code easier to understand. Type hinting is a form of static typing, and it’s supported in PHP 5 and later.

How can I learn more about static types in PHP?

There are many resources available if you want to learn more about static types in PHP. The PHP manual is a great place to start, and there are also many tutorials and articles available online. You can also try using a tool like Hack, PHPStan, or Psalm to get hands-on experience with static types in PHP.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

BrunoSdocblockhackhhvmOOPHPPHPphp extensionphp extensionsstatic typing
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week