Making PHP the language you want it to be

Have you ever had an idea about how to improve PHP? Or maybe you yearn for a feature that another language has, that hasn’t made it into PHP yet?

The great news is that PHP is improving with every release. One day the PHP feature you wish for might make it into the language.

If you’re really keen on an idea, then you can suggest it gets implemented via PHP’s RFC process.

The RFC process

The RFC process for PHP features works along these lines:

  • You have an idea.
  • You create an RFC document that explains your idea.
  • Your RFC is discussed by those on the internals mailing list.
  • A vote is held.
  • If the vote passes (two-thirds have to say yes), the feature is created and eventually released.

The RFC process is great in that it allows anyone to contribute and set the direction of the language.
However, the time from idea to release takes months to years. Given time to discuss and vote, feature freezes, etc., if all the stars align perfectly you might be able to go from the initial idea to release in 6 months.

In most cases, this time frame will be a lot longer. More complex suggestions often take a few iterations of the RFC process before they finally get accepted. It’s not unheard of for features to take several years from idea to being part of the language.

There is another way…

The good news is that, for certain features, there is a quicker way to add them to the language.

An example

Suppose you have a method on a class, and you want to make sure that it is only called from a certain class.

E.g. you might want to limit a Person object’s __construct method to only be called from PersonFactory.

PHP’s visibility modifiers (public, protected and private) are not fine-grained enough for this.

Let’s just be friends

Some programming languages have the concept of “Friends”.

A method states that they are “friends” with another class. The method can only be called from the class it is friends with.

In our example, we would state that the Person‘s __construct method has a friend: PersonFactory.
This would mean that only code in the PersonFactory class is allowed to call new Person.

How do we do this in PHP without going through the RFC process?

Advanced static analysis tools like PHPStan and Psalm analyse your codebase. These tools have a set of rules they run over the codebase they are analysing. They report any violations of the rules.

Typically, you’d run static analysis tools on your codebase at the very least as part of your CI. Ideally, you’d run them as part of your workflow, before committing and pushing code.

As well as all the predefined rules provided by the static analysis tools, it is also possible to create custom rules. One such custom rule could enforce “friend” visibility.

Assuming we have a suitable custom rule to emulate friend functionality, we still need a way of telling the custom rule who is friends with whom. This is a great use-case for PHP attributes.

To add the friend functionality, two things are required:

  • A #[Friend] attribute that can be applied to a method.
  • A custom rule that enforces the friend functionality.

Back to our example, the code would look like this:

class Person
{
  #[Friend(PersonFactory::class)]
  public function __construct() {
    // Implementation
  }
}

The friend relationship is enforced not by PHP itself, but by a static analysis tool running custom rules.

If you want to use this (and other new functionality) on your code, then look up PHP language extension library and the PHPStan extension.

Find out more about writing custom PHPStan rules.

The formula for new features

Attributes + static analysis = new language features

We can emulate new language features using static analysis custom rules. Where additional information is required, this is provided via PHP Attributes.

Instead of relying on new features being added to the PHP language itself via the RFC process, we run static analysers with the custom rules on the codebase to enforce new language features.

Benefits

One major benefit of this approach is the speed new language features can be created. Once up to speed with writing custom rules, features like #Friend can be made in an afternoon.

There is more flexibility to tweak the behaviour of the feature, once it actually is in a codebase. Contrast this with the RFC process, where it is important to get the feature right the first time. There is an argument that all features that could be implemented via static analysis, should be, at least initially.
This allows it to get battle-tested in the real world and all the issues ironed out before it is finally added to the language via an RFC.

Drawbacks

There are a couple of drawbacks:

Firstly you have to be running static analysis on your codebase.
I’d argue that you should be doing this anyway, as it is so valuable.
But if you’re creating libraries, there is no way to force your users to run static analysis.

Secondly, this technique is not suitable for all features, e.g. it is not possible to change the syntax of the language.

What language features do you want?

Static analysers, like Psalm and PHPStan, are up there with PHPUnit and Composer, as some of the greatest tools in the PHP ecosystem.

Hopefully, you’ve seen how static analysis tools running custom rules, can be used to create new PHP language features. What new language features would you like?