Testing Unreleased Features of PHP

How to compile an unreleased RFC feature for PHP source :: PHP internals

Jun 25, 2018

We'll be discovering and testing a completely unreleased feature of php-src from an RFC that's still under discussion.

If you've ever wanted to be ahead of the curve of PHP features or you've just wanted to contribute back to PHP internals, testing an unreleased feature from an RFC is a fun and educational way to do so.

RFC TL;DR

Before features can be added to PHP or before any breaking changes can occur, the change must first go through the Request for Comments (RFC) process.

It's pretty rare for an RFC to exist without a link to the implementation which exists as a pull request to the official php-src repo with the "RFC" label.

So how do we pull down one of these implementations to start playing with it while it's still under development? Let's look at one that's under development at the time of writing.

Typed properties 2.0 RFC

Back in 2016, Joe Watkins & Phil Sturgeon created an RFC to add typed properties to PHP 7.1. Unfortunately there were some issues with the implementation that ultimately caused the RFC to be declined in the voting phase.

Fast-forward to June 2018, Bob Weinand & Nikita Popov are reviving Joe & Phil's work and giving typed properties another shot with the typed properties 2.0 RFC.

Typed properties TL;DR

In a nutshell, typed properties allow us to add type declarations directly to class properties. So instead of defining the types with DocBlocs like we do in PHP today...

class Foo
{
    /** @var int $id */
    public $id;
}

...with typed properties we can declare the types directly on the property before the variable name.

class Foo
{
    public int $id;
}

This new feature is much less verbose, easier to read, and enforced by the PHP runtime. All really great things!

Compiling PHP from source

If you haven't already cloned the php-src repo to your machine and compiled it from source, follow the steps outlined in my post on compiling PHP from source.

Git setup

The following steps assume you have a remote called upstream that points to the original php-src repo. You can check your remotes by running git remote -v from the command line in the php-src directory.

$ git remote -v
origin  git@github.com:SammyK/php-src.git (fetch)
origin  git@github.com:SammyK/php-src.git (push)
upstream    git@github.com:php/php-src.git (fetch)
upstream    git@github.com:php/php-src.git (push)

If you don't see a reference to upstream, you can add it with the following command.

$ git remote add upstream git@github.com:php/php-src.git

Fetch the RFC implementation from GitHub

You can fetch a pull request (PR) from GitHub into a branch that you define with the following template command.

$ git fetch upstream pull/{pr-id}/head:{some-branch-name}

For example, in the typed properties 2.0 RFC, there's a link at the top to the implementation which is a PR on GitHub (#3313). Using our template command from above, we replace {pr-id} with the PR ID 3313 and replace {some-branch-name} with whatever we want - we'll just use rfc-typed-properties. Then we'll check out our new branch.

$ git fetch upstream pull/3313/head:rfc-typed-properties
$ git checkout rfc-typed-properties

Now all we have to do is compile PHP!

$ make distclean \
  && ./vcsclean \
  && ./buildconf \
  && ./configure --enable-debug --enable-cli
  && make

Hopefully everything worked out well. Let's check the version of our compiled PHP binary.

$ sapi/cli/php --version
PHP 7.3.0-dev (cli) (built: Jun 23 2018 20:08:49) ( DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.0-dev, Copyright (c) 1998-2018 Zend Technologies

If you don't get any errors, you should be good to go.

Playing with the implementation

Create a new file called typed-properties.php in the php-src directory (or wherever you like) and paste in the following code:

<?php

class User
{
    public int $id;
    public string $name;
 
    public function __construct(int $id, string $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function dump(): void
    {
        var_dump($this->id, $this->name);
    }
}

$user = new User(42, "Foo User");
$user->dump();

Then see if it runs with the compiled PHP binary.

$ sapi/cli/php typed-properties.php
int(42)
string(8) "Foo User"

W00t! We're now able to play around with the new typed properties implementation before it's released to the public! Try to tweak the PHP script and add other properties with other type declarations to get a feel for how typed properties works. Nullable types are also supported.

I recommend trying to declare a typed property with no default value and then trying to access the property somewhere else. Did you get an error? Spoiler: You will.

Testing the implementation in the real-world

If we really wanted to test the typed properties implementation in a larger capacity, we could choose an existing unit-tested project that we're familiar with. The key is to run the unit tests with our compiled version of PHP. You can do that by prefixing the PHPUnit command with the path to the compiled PHP binary.

$ /path/to/php-src/sapi/cli/php ./vendor/bin/phpunit

Add an alias to the complied PHP version

To save some keystrokes & since my compiled version of PHP is always in the same location for all my work on php-src, I've set up an alias in my bash profile called cphp (compiled PHP) that points to the compiled version.

alias cphp="/path/to/php-src/sapi/cli/php"

Now I can just run cphp in the CLI any time I want to reference my compiled PHP version.

$ cphp --version
PHP 7.3.0-dev (cli) (built: Jun 23 2018 20:08:49) ( ZTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.0-dev, Copyright (c) 1998-2018 Zend Technologies

Assuming your bash profile is located at ~/.bash_profile and your PHP runtime is located at /path/to/php-src/sapi/cli/php, you can add the same alias like so.

$ echo 'alias cphp="/path/to/php-src/sapi/cli/php"' >> ~/.bash_profile \
  && source ~/.bash_profile

Now you should be able to run PHPUnit with your complied PHP version as the runtime.

$ cphp ./vendor/bin/phpunit

To be sure of the specific PHP runtime being used, you can add the -v flag (verbose mode) to see which runtime PHPUnit is running.

$ cphp ./vendor/bin/phpunit -v
PHPUnit 5.7.20 by Sebastian Bergmann and contributors.

Runtime:       PHP 7.3.0-dev
Configuration: /path/to/phpunit.xml

......................................................        54 / 54 (100%)

Time: 683 ms, Memory: 14.00MB

OK (54 tests, 76 assertions)

Adding typed properties to the real-world project

Once you've run the implementation with PHPUnit as described above and all the tests are passing, we can start adding types to properties in our code.

Before we start typing our properties, I recommend creating a new branch in our project so that we don't accidentally commit unreleased PHP features to the master branch of our project.

In our project root, let's create a new branch called test-typed-properties to isolate our changes.

$ git checkout -b test-typed-properties

Now we can just go class-by-class and add types to the properties. If there's already DocBlocs in place, it's as easy as cutting and pasting the declaration. For example, we can change the following.

<?php

namespace App\Champions;

class ChampionStore
{
    /** @var Champion|null */
    private static $champion;

    // ...
}

...to...

<?php

namespace App\Champions;

class ChampionStore
{
    /** @var Champion|null */
    private static ?Champion $champion;

    // ...
}

And in this particular case the DocBlocs aren't giving any additional information other than declaring the property type, so we can go ahead and kill the DocBloc altogether.

<?php

namespace App\Champions;

class ChampionStore
{
    private static ?Champion $champion;

    // ...
}

Ah. So much cleaner. Now run the unit tests and fix any errors that pop up.

What to do if you find a bug

If while testing the implementation you run into a bug, don't submit it to bugs.php.net like you would normally do with bugs in PHP. Instead you can comment directly on the PR with a small repro snippet to reproduce the unintended behavior.

For example, while I was testing the typed-properties implementation, I ran into a small bug with an error message and commented on the PR with a repro. Nikita responded with gratitude and patched the bug right away.

And now we're contributing back to PHP internals. Congrats and thank you!

Good luck!

I really hope you enjoyed being ahead of the curve of PHP features. May you continue to pull down new proposed implementations of PHP and keep this fun little party going. Until next time!

If you found this guide helpful, say, "Hi" on twitter! I'd love to hear from you. :)