blog

  • Home
  • blog
  • How to Make Modern PHP More Modern? With Preprocessing!

How to Make Modern PHP More Modern? With Preprocessing!

Let’s have a bit of fun. A while ago, I experimented with PHP macros, adding Python range syntax. Then, the talented SaraMG mentioned an RFC, and LordKabelo suggested instead adding C#-style getters and setters to PHP.

Aware of how painfully slow it can be for an outsider to suggest and implement a new language feature, I took to my editor…

The code for this tutorial can be found on Github. It’s been tested with PHP ^7.1, and the generated code should run on PHP ^5.6|^7.0.

Vector illustration of cog with upward facing arrows running through it, indicating preprocessing, upgrading, improving

How Do Macros Work Again?

It’s been a while (and perhaps you’ve never heard of them) since I’ve talked about macros. To refresh your memory, they take code that looks like this:

macro {
  →(···expression)
} >> {
  ··stringify(···expression)
}

macro {
  T_VARIABLE·A[
    ···range
  ]
} >> {
  eval(
    '$list = ' . →(T_VARIABLE·A) . ';' .
    '$lower = ' . explode('..', →(···range))[0] . ';' .
    '$upper = ' . explode('..', →(···range))[1] . ';' .
    'return array_slice($list, $lower, $upper - $lower);'
  )
}

…and turn custom PHP syntax, like this:

$few = many[1..3];

…into valid PHP syntax, like this:

$few = eval(
    '$list = ' . '$many' . ';'.
    '$lower = ' . explode('..', '1..3')[0] . ';' .
    '$upper = ' . explode('..', '1..3')[1] . ';' .
    'return array_slice($list, $lower, $upper - $lower);'
);

If you’d like to see how this works, head over to the the post I wrote about it.

The trick is to understand how a parser tokenizes a string of code, build a macro pattern, and then apply that pattern recursively to the new syntax.

The macro library isn’t well documented, though. It’s difficult to know exactly what the pattern needs to look like, or what valid syntax to generate in the end. Every new application begs for a tutorial like this to be written, before others can understand what’s really going on.

Building A Base

So, let’s look at the application at hand. We’d like to add getter and setter syntax, resembling that of C#, to PHP. Before we can do that, we need to have a good base of code to work from. Perhaps something in the form of a trait that we can add to classes needing this new functionality.

We need to implement code that will inspect a class definition and create these dynamic getter and setter methods for each special property or comment it sees.

Perhaps we can start by defining a special method name format, and magic __get and __set methods:

namespace App;

trait AccessorTrait
{
  /**
   * @inheritdoc
   *
   * @param string $property
   * @param mixed $value
   */
  public function __get($property)
  {
    if (method_exists($this, "__get_{$property}")) {
      return $this->{"__get_{$property}"}();
    }
  }

  /**
   * @inheritdoc
   *
   * @param string $property
   * @param mixed $value
   */
  public function __set($property, $value)
  {
    if (method_exists($this, "__set_{$property}")) {
      return $this->{"__set_{$property}"}($value);
    }
  }
}

Each method starting with the name __get_ and __set_ needs to be connected to an as-yet undefined property. We can imagine this syntax:

namespace App;

class Sprocket
{
    private $type {
        get {
            return $this->type;
        }

        set {
            $this->type = strtoupper($value);
        }
    };
}

…being converted to something very much like:

namespace App;

class Sprocket {
    use AccessorTrait;

    private $type;

    private function __get_type() {
        return $this->type;  
    }

    private function __set_type($value) {
        $this->type = strtoupper($value);   
    }
}

Defining Macros

Defining the required macros is the hardest part of any of this. Given the lack of documentation (and widespread use), and with only a handful of helpful exception messages, it’s mostly a lot of trial and error.

I spent a few hours coming up with the following patterns:

macro ·unsafe {
  ·ns()·class {
    ···body
  }
} >> {
·class {
    use AccessorTrait;

    ···body
  }
}

macro ·unsafe {
  private T_VARIABLE·var {
    get {
      ···getter
    }

    set {
      ···setter
    }
  };
} >> {
  private T_VARIABLE·var;

  private function ··concat(__get_ ··unvar(T_VARIABLE·var))() {
    ···getter
  }

  private function ··concat(__set_ ··unvar(T_VARIABLE·var))($value) {
    ···setter
  }
}

Continue reading %How to Make Modern PHP More Modern? With Preprocessing!%

LEAVE A REPLY