Christopher Pitt Software Developer

“What is this s#*t?!”

Last updated on 25th March 2024

This was originally written on 19th January 2019, some things may have changed.

A few days ago, Caleb Porzio demonstrated some work he’s been doing; to replicate Phoenix LiveView in Laravel. I’ve been building PHP preprocessor stuff for a long time, and this really inspired me to try the same thing.

Topics

  1. Getting started
  2. Using phpx
  3. Creating custom renderers
  4. Structuring components differently
  5. Building stateless components
  6. Building stateful components
  7. Sending initial data from the server to the client
  8. Sending serialised data from the client to the server
  9. Sending events from the browser
  10. Sending more complex forms of data
  11. Circling back to those timer functions
  12. Going further

Getting started

I started with a few questions:

  • How can I do this for any PHP project, not just Laravel?
  • What could the syntax look like?
  • What would the developer experience be?

It was easy to answer the first two questions. One of the libraries I am working on is called pre/phpx. It’s a library which can be installed into any PHP project.

For the version of phpx we’re using here; the only dependency is pre/plugin. This is achieved through some gross dependency hiding. Future versions of pre/plugin will openly require yay/yay and nikic/php-parser.

If you’ve got a PHP project, and you can use it with PHP ^7.1, then you can install and use phpx. I don’t want to go too into detail about how Pre’s libraries work; but I will say that there are two ways to load files which use the custom syntax:

  1. You can include one-off files (routes, helper functions etc.) using a function: \Pre\Plugin\Process("/path/to/file.pre"). This behaves similarly to require, except that it also preprocesses the file. file.php is generated next to file.pre, and file.php is included.
  2. You can add class files to the Composer PSR-4 autoloader. If you name a file MyClass and there’s a MyClass.pre; then a MyClass.php will be generated alongside and included.

There are ways to optimise these loaders, but I’m not going to talk about them here. Ask me on Twitter if you want to know more.

All this is to say: we can use phpx in just about any modern PHP application, and the syntax is recognisable. What does that syntax look like? We’re about to see…

The code for this article can be found on Github. I’ve tested in a recent version of PHP and Safari. Don’t install it in a Tesla.

To show that this could work with any framework or app, I wanted to use the simplest setup I could think of. I started by installing Slim and the HTML renderer for phpx:

composer require slim/slim
composer require pre/phpx-html

I followed this up with a small server script:

if (PHP_SAPI == "cli-server") {
  $url  = parse_url($_SERVER["REQUEST_URI"]);

  if (is_file(__DIR__ . $url["path"])) {
    return false;
  }
}

require_once __DIR__ . "/../vendor/autoload.php";

$app = new Slim\App();

$routes = require __DIR__ . "/../source/routes.php";
$routes($app);

$app->run();

This is from public/index.php

The first bit of code is useful for when we run this server using the local development server. It activates when it detects that the local server is running; and prevents the server from handling requests to files that exist in the same folder.

After that, I create a Slim App and pass it to a function returned from the routes files. This routes file is where I’m going to register the different endpoints for the app. For now, there’s only one:

use Psr\Http\Message\ServerRequestInterface as Req;
use Psr\Http\Message\ResponseInterface as Res;
use Slim\App;

return function(App $app) {
  $app->get("/", function(Req $req, Res $res) {
    $res->getBody()->write(new HomeResponder());
    return $res;
  });
};

This is from source/routes.php

I’ve started to use this approach to define routes in every app, no matter the framework. There’s nothing too remarkable about how Slim wants the responses to be composed, so I’ll leave you to the Slim docs if you want to know more about them.

Finally, I turn to HomeResponder, which is where I start to show the first bit of user-facing text:

namespace App\Responder;

class HomeResponder
{
  public function render()
  {
    return "hello world";
  }

  public function __toString()
  {
    return $this->render();
  }
}

This is from source/Responder/HomeResponder.php

It’s abnormal to see a render method and __toString casting when dealing with HTTP responses; but it’s a pattern we’re going to be seeing more of, today…

I’ve skipped over the process of creating folders/files; and registering the source folder with Composer’s PSR-4 autoloader. I’ll leave that to you!

We’re going to be starting (and restarting this server) often; so let’s create a Composer script to remember the particulars for us:

"scripts": {
  "serve": "@php -S 127.0.0.1:8888 -t public public/index.php"
},

This is from composer.json

This exposes a serve script, which starts a local development server on 127.0.0.1:8888, and targets public/index.php as the file to handle requests. We can run it with:

composer run serve

A server is born!

Using phpx

Here’s where we start down the rabbit-hole. I’ve mentioned phpx, but now I’m going to show you how it looks/works. Given the structure of HomeResponder, we can slide into phpx relatively easily:

namespace App\Responder;

use function Pre\phpx\Html\render;

class HomeResponder
{
  public function render()
  {
    return <div>hello world</div>;
  }

  public function __toString()
  {
    return $this->render();
  }
}

This is from source/Responder/HomeResponder.pre

Before I changed any code, I renamed HomeResponder.php to HomeResponder.pre. This enables preprocessing for the file, in a similar way to how Hack enables opt-in behaviour by replacing <?php with <?hh.

The most interesting thing happens in the render method. I replace the string for something that looks like JSX. This is the core mechanic of phpx! Let’s look at what this file compiles to:

namespace App\Responder;

use function Pre\phpx\Html\render;

class HomeResponder
{
  public function render()
  {
    return render("div", [
      "children" => "hello world"
    ]);
  }

  public function __toString()
  {
    return $this->render();
  }
}

This is from (the generated) source/Responder/HomeResponder.php

There’s a custom compiler, behind the scenes, which converts <div>hello world</div> to render("div", [...]). This is why I included the Pre\phpx\Html\render but didn’t appear to be using it. It’s the same as trying to use JSX without importing React.

If you don’t include or define a render function; your code will not work. You’ll have a harder time debugging this because stack traces are suppressed inside __toString.

Pre\phpx\Html\render converts these parameters to an HTML string. This results in the same content appearing in the browser, but more control over the templating in the responder.

You either love or hate this kind of templating. If it’s the latter, keep reading to learn how the demo works.

Creating custom renderers

When I created phpx, I wanted to allow people to customise the behaviour of the render function. The compiler converts elements to invocations of the render function, but it’s up to you to tell it which renderfunction to use (via use statements or defining one close-by).

To make this useful for web development; I also created another library, called pre/phpx-html. It provides a render function that takes the input the base pre/phpx library provides, and outputs valid HTML.

I installed it alongside Slim, and it is what converted the <div> phpx code into HTML. The output looks identical to the input, but it goes through an entirely custom compiler to get from the one end to the other. Let’s take a quick look at the main conversion function:

public function render($name, $props = null)
{
  $props = $this->propsFrom($props);

  if (
    !in_array($name, $this->ignoredFunctions)
    && ($function = globalFunctionMatching($name))
  ) {
    return call_user_func($function, $props);
  }

  if ($class = globalClassMatching($name)) {
    $instance = new $class($props);

    if (is_callable($instance)) {
      return $instance($props);
    }

    return $instance->render();
  }

  $className = $this->classNameFrom($props->className);
  unset($props->className);

  $style = $this->styleFrom($props->style);
  unset($props->style);

  $children = $this->childrenFrom($props->children);
  unset($props->children);

  $open = join(" ", array_filter([
    $name,
    $className,
    $style,
    $this->attributesFrom($props),
  ]));

  if (
    in_array($name, $this->selfClosingTags)
    && !$children
  ) {
    return "<{$open} />";
  }

  return "<{$open}>{$children}</{$name}>";
}

This is from pre/phpx-html

I don’t want to spend too much time here, but there are a few things to point out:

  1. There’s special handling of children, style attributes, className attributes, and initial props.
  2. PHP includes some global functions which share names with HTML elements. I don’t expect people to use <header>...</header> to access the built-in header function, so I have a list of global functions to ignore. It’s not a long list…
  3. If tags are typically self-closing, and have no children; then they are rendered as self-closing tags.

None of the “tags” in pre/phpx-html are functions. They’re generated from a list of tags known to exist in HTML 4 + 5 specifications, with some omissions for deprecated and poorly named things. Long-story short; you can probably use all the HTML elements you like and you’ll get the expected output.

Here’s where it gets interesting. If you want to render other things, using phpx syntax; then you’re going to have to create a custom render function. You can, of course, compose the HTML render function into your custom one.

Here’s one I’ve defined for this website:

use function Pre\Phpx\classMatching;
use function Pre\Phpx\functionMatching;
use function Pre\Phpx\Html\propsFrom;
use function Pre\Phpx\Html\render as renderHTML;

function render($name, $props = null)
{
  $namespaces = [
    "App\\Component",
  ];

  $props = propsFrom($props);

  if (
    $function = functionMatching($namespaces, $name)
  ) {
    return call_user_func($function, $props);
  }

  if ($class = classMatching($namespaces, $name)) {
    $instance = new $class($props);

    if (is_callable($instance)) {
      return $instance($props);
    }

    return $instance->render();
  }

  return renderHTML($name, $props);
}

This [was] from assertchris.io

The base phpx library provides a few helper functions, to make it easier to resolve your component classes. You can call functionMatching and classMatching, with an array of namespaces; and they will return the first namespaced thing they find.

With this render function; if I don’t have a matching function or class in App\Component, it will fall back to trying to render HTML components. The pre/phpx-html render function doesn’t really handle missing or malformed functions or classes, gracefully so that’s definitely something I can improve…

Structuring components differently

Our goals are slightly loftier than rendering custom components, though. We want our components to be interactable and composable. This will prove a challenge if we continue down the path of immediately rendering strings whenever we call a render function or method. The render function I’ve settled on looks like this:

namespace App;

function render($name, $props = [])
{
  $props = (array) $props;

  if (class_exists($name)) {
    return new $name($props);
  }

  return new HtmlComponent($name, $props);
}

This is from source/helpers.php

It’s much simpler than the one I’m using for this website, but it has other strengths we’ll begin to explore. The first is that it checks to see if a “component” class is being referenced; and if so creates it. If I create a class called MyComponent, and use the syntax <MyComponent />, then this render function will create a new instance of it. Notice I’m not calling any render method on it.

Notice, also, that if no class is found I assume the component is a simple HTML one. I’m not directly calling the pre/phpx-html render function on it. This is the job of the HtmlComponent class:

namespace App;

use function Pre\Phpx\Html\render;

class HtmlComponent extends Component
{
  protected $name;
  protected $props;

  public function __construct($name, array $props = []){
    $this->name = $name;
    $this->props = $props;
  }

  public function render(){
    // ...do nothing
  }

  public function __toString(){
    return render($this->name, $this->props);
  }
}

This is from source/HtmlComponent.pre

The point of this class is to delay calling the render function in pre/phpx-html. In fact, the only time it will be called is when someone or something tries to get a string value for the class. Until then, it will remain a living object.

Building stateless components

If you’ve worked with ReactJS, you’ve probably heard the term “stateless” or “functional” to refer to components that don’t maintain any sort of internal state.

I’ve opted for a similar naming convention, in as much as it applies to PHP. Stateless Components, in the context of this article, are component classes that can only effectively define a render function. They can have other private methods to help, but the only thing that will be required of them is a way to render.

I still assume they will have props, so I have a bit of groundwork to do:

namespace App;

abstract class Component
{
  protected $props;

  public function __construct($props = []){
    $this->props = (array) $props;
  }

  public function __toString(){
    return (string) $this->render();
  }

  abstract public function render();
}

This is from source/Component.pre

This class follows a similar delayed-render pattern to HtmlComponent. Classes which extend this one need to define a render method, but this will only be called if someone or something tries to get a string representation of the component.

This is extended to handle stateless components:

namespace App;

use Exception;

abstract class StatelessComponent extends Component
{
  public function componentDidMount(){
    throw new Exception(
      "Stateless components cannot..."
    );
  }

  public function update(){
    throw new Exception(
      "Stateless components cannot..."
    );
  }

  public function setTimeout(
    callable $delayed, int $ms
  ): string{
    throw new Exception(
      "Stateless components cannot..."
    );
  }

  public function setInterval(
    callable $delayed, int $ms
  ): string{
    throw new Exception(
      "Stateless components cannot..."
    );
  }

  public function clearTimeout(string $watcherId){
    throw new Exception(
      "Stateless components cannot..."
    );
  }
}

This is from source/StatelessComponent.pre

This class is more about what a consumer can’t do than what a consumer can do. That’s because I want to provide some assistance to folks using this; so that they are fully aware what the limitations are of stateless components vs. stateful components.

So, how does a reusable component look?

namespace App\Component;

use App\StatelessComponent;
use function App\render;

class ButtonComponent extends StatelessComponent
{
  public function render()
  {
    $props = $this->props;

    $click = isset($props["phpx-click"])
      ? $props["phpx-click"]
      : null;

    $className = isset($props["className"])
      ? $props["className"]
      : "";

    $style = isset($props["style"])
      ? $props["style"]
      : [];

    $children = isset($props["children"])
      ? $props["children"]
      : "";

    return (
      <button
        phpx-click={$click}
        className={[
          "font-semibold py-2 px-4 ...",
          $className,
        ]}
        style={$style}
      >
        {$children}
      </button>
    );
  }
}

This is from source/Component/ButtonComponent.pre

Imagine, for a moment, I have access to something like Tailwind. We could create a reusable button, with all the styles a button in our application should have.

Don’t worry about phpx-click just yet. We’ll get there…

I can now reuse this button elsewhere in our application:

namespace App\Responder;

use App\StatefulComponent;
use function App\render;

class CounterResponder extends StatefulComponent
{
  private $counter = 0;

  public function increment()
  {
    $this->counter++;
  }

  public function decrement()
  {
    $this->counter--;
  }

  public function render()
  {
    return (
        <App.Component.ExampleComponent 
          title={"Counter"}
        >
          <div className={"mb-4"}>
            <label className={"..."}>
              Count:
            </label>
            {$this->counter}
          </div>
          <App.Component.ButtonComponent
            phpx-click={"increment"} 
            className={"..."}
          >
            ++
          </App.Component.ButtonComponent>
          <App.Component.ButtonComponent
            phpx-click={"decrement"} 
            className={"..."}
          >
            --
          </App.Component.ButtonComponent>
      </App.Component.ExampleComponent>
    );
  }
}

This is from source/Responder/Counter.pre

pre/phpx does this handy thing, where it converts component names like App.Component.ButtonComponent to class names like \App\Component\ButtonComponent.

These component names can get quite long, so try to define the namespaces for where your components are likely to come from; and add them to your render function.

The only significant difference, between JSX and phpx (apart from capitalisation, I guess) is that all tag attributes need to be in the form of key={/* value /}. / value */ can be any PHP expression. It should probably be something that can be cast to a string, but that ultimately depends on the renderfunction.

Beautiful buttons, strange code

Building stateful components

None of this is new. Facebook developers once built a plugin, called XHP. You can even still use the technology in HHVM; albeit it with arguably worse syntax. Facebook developers also built JSX.

The difference between the two, of course, is that the latter is in the browser. It generates “just Javascript”, so it can hook into the all the browser events and interactions a mountain of jQuery code ever could.

XHP doesn’t provide this kind of interactivity (at least it didn’t last time I checked). I’ve long considered what it would take to build browser interactivity into pre/phpx-html, but every time I got close I balked at the struggle I thought I would have.

Then I saw Caleb’s demonstration and it dawned on me: if he could I could. To be fair to him, it’s a very different environment he’s developing for and he was first to show that it could be done. This work would not have happened without him.

That said, I still need to demonstrate to you how it’s possible. Let’s look, again at what we’re aiming for:

namespace App\Responder;

use App\StatefulComponent;
use function App\render;

class CounterResponder extends StatefulComponent
{
  private $counter = 0;

  public function increment()
  {
    $this->counter++;
  }

  public function decrement()
  {
    $this->counter--;
  }

  public function render()
  {
    return (
      <div>
        <div>
          Count: {$this->counter}
        </div>
        <button phpx-click={"increment"}>
          ++
        </button>
        <button phpx-click={"decrement"}>
          --
        </button>
      </div>
    );
  }
}

This is a simplified version of source/Responder/Counter.pre

The most basic kind of function I want to achieve, is a way to make clickable buttons that call PHP functionality. I don’t want the person who uses this to have to write any javascript for that purpose. They should be able to throw a few phpx-click attributes into the mix, and it should just work.

It turns out this is a difficult problem to solve well. I went through maybe 3-4 entire rebuilds of the underlying architecture; just to handle all the edge-cases of composition and interactivity. Don’t believe, for a second, I just knew how to build this. It was a struggle.

This kind of interaction requires near-constant communication between the client and the server. I expect calling those increment and decrement functions to lead to an updated UI. Keep that requirement in the back of your mind while I show you what the underlying StatefulComponent looks like:

namespace App;

use Amp\Loop;
use Closure;
use Ramsey\Uuid\Uuid;

abstract class StatefulComponent extends Component
{
  protected $props;
  protected $id;
  protected $sender;

  public function __construct($props)
  {
    $this->props = $props;
    $this->id = Uuid::uuid4()->toString();
  }

  public function setId(string $id): self
  {
    $this->id = $id;
    return $this;
  }

  public function getId(): ?string
  {
    return $this->id;
  }

  public function setSender(Closure $sender): self
  {
    $this->sender = $sender;
    return $this;
  }

  public function getSender(): ?Closure
  {
    return $this->sender;
  }

  public function update()
  {
      if (!defined("PHPX_LIVE_IN_WORKER")) {
          return;
      }

      if (
        is_null($this->id) || is_null($this->sender)
      ) {
        return;
      }

      call_user_func(
        $this->sender,
        json_encode([
          "cause" => "phpx-update",
          "type" => "phpx-render",
          "data" => (string) $this->statefulRender(),
          "root" => $this->id,
        ])
      );
  }

  public function componentDidMount()
  {
    // ...do nothing
  }

  public function setTimeout(
    callable $delayed, int $ms
  ): string
  {
    if (!defined("PHPX_LIVE_IN_WORKER")) {
      return "noop";
    }

    return Loop::delay($ms, $delayed);
  }

  public function setInterval(
    callable $delayed, int $ms
  ): string
  {
    if (!defined("PHPX_LIVE_IN_WORKER")) {
      return "noop";
    }

    return Loop::repeat($ms, $delayed);
  }

  public function clearTimeout(string $watcherId){
    if (!defined("PHPX_LIVE_IN_WORKER")) {
      return;
    }

    Loop::cancel($watcherId);
  }

  public function statefulRender()
  {
    return (
      <div
        phpx-id={$this->id}
        phpx-root={
          defined("PHPX_LIVE_IN_WORKER")
            ? null
            : base64_encode(serialize($this))
        }
      >
        {$this->render()}
      </div>
    );
  }

  public function __toString()
  {
    return (string) $this->statefulRender();
  }
}

This is from source/StatefulComponent.pre

Lots going on here, so let’s work though it:

  1. 30% of this class is just getters and setters.
  2. Each stateful component needs a unique identifier. This can be a hash from spl_object_hash, or it can be something slightly nicer-looking. I opted for Ben Ramsey’s ramsey/uuid library.
  3. Stateful components also need a way to send state updates to websocket clients.
  4. There are quite a few places where I’m checking for defined("PHPX_LIVE_IN_WORKER"). This is a constant I’m going to define in the “worker” script. There’s code that I only want run inside or outside of the worker, and this seems like the simplest way to achieve that.
  5. The update method is responsible for sending updated state to the websocket client. This should lead to updates of the browser UI, so it runs the stateful component’s statefulRender method; and returns the string of that to the browser.
  6. Here, we also see the sender (which is a closure). I pass this data to the “sender” closure, and it should get “sent” to the browser. The worker creates and provides this closure to the component.
  7. We also see a root attribute, which gets the component’s ID value. This is how we will connect state updates to a specific component’s HTML structure.
  8. Stateful components can define a componentDidUpdate method, which is similar to the one in ReactJS. It’s really for operations that shouldn’t affect the performance of the render method. Operations that involve creating timers or loading remote data.
  9. I use this, in interesting ways, later on…
  10. There are three timer-related methods to see: setTimeout, setInterval, and clearTimeout. These mimic the browser functions with the same names. setTimeout is for a single delayed invocation of a callable. setInterval is for multiple delayed invocations of a callable.
  11. setTimeout and setInterval return a string, which represents the “ID” of the timer/callable combo. These strings can be passed to clearTimeout to prevent or stop them from executing.
  12. Stateful components also defer their rendering, using __toString(). They return their rendered content inside a special wrapper div, which has two important attributes: phpx-id and phpx-root.

We’ll see how these values are used in a little while.

phpx-id and phpx-root connect Slim and the worker

If you were to clone and try this application, you may wonder where the root data has gone. There’s a bit of Javascript that removes it just as soon as it has been sent to the worker.

This is one of the tricks that make this architecture work really well. It’s very difficult to share instances of a class between Slim, the worker, and the browser. I achieve a kind of sharing by serialising the state of the first instance, and sending it to the browser.

The browser, in turn, send the serialised data to the worker. I guess I could transmit the data from Slim to the worker; but it would still involve synchronising the websocket clients via the browser. This just seems like a simpler approach.

There are security implications, when it comes to trusting any data from the browser. For the purposes of this demonstration, I am not paying attention to those implications; but you should (long before this reaches a production environment). A quick solution would be to sign or encrypt the state before it reaches the browser, and then verify or decrypt the data when it comes back...

Sending initial data from the server to the client

Let’s take a quick look at the basic websocket server setup I need, before we start looking at the Javascript required to use it:

define("PHPX_LIVE_IN_WORKER", true);

require_once __DIR__ . "/vendor/autoload.php";

use Amp\Http\Server\Options;
use Amp\Http\Server\Request as Req;
use Amp\Http\Server\Response as Res;
use Amp\Http\Server\Router;
use Amp\Http\Server\Server;
use Amp\Http\Server\Websocket\Message;
use Amp\Http\Server\Websocket\Websocket;
use Amp\Loop;
use Amp\Socket;
use App\Updates;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$websocket = new class extends Websocket
{
  private $roots = [];
  private $binds = [];
  private $senders = [];

  public function onHandshake(
    Req $request, Res $response
  )
  {
    // this is where we could authenticate the
    // websocket request, to make sure it comes
    // from a reputable source

    return $response;
  }

  public function onOpen(
    int $clientId, Req $request
  )
  {
    $this->senders[$clientId] =
      function($data) use ($clientId) {
        $this->send($data, $clientId);
      };
  }

  public function onData(
    int $clientId, Message $message
  ){
    $text = yield $message->buffer();
    $json = json_decode($text);

    // do something with the JSON data
    // coming through the websocket
  }

  public function onClose(
    int $clientId, int $code, string $reason
  )
  {
    // do nothing
  }
};

$sockets = [
  Socket\listen("127.0.0.1:8889"),
];

$router = new Router();
$router->addRoute("GET", "/", $websocket);

if (file_exists(__DIR__ . "/worker.log")) {
  unlink(__DIR__ . "/worker.log");
}

$logger = new Logger("phpx-live");

$logger->pushHandler(new StreamHandler(
  __DIR__ . "/worker.log", Logger::WARNING
));

$options = (new Options())
  ->withDebugMode()
  ->withHttp2Upgrade();

$server = new Server(
  $sockets, $router, $logger, $options
);

Loop::run(function() use ($server) {
  yield $server->start();
});

This is from worker.php

I don’t want to spend a lot of time talking about websocket servers, or how to achieve them with something as cool as Amphp’s amphp/websocket-server. If you prefer, you could also use something compatible with ReactPHP, like Ratchet.

I need to install a few more dependencies, before this script will start working:

composer require amphp/http-server-router
composer require amphp/websocket-server
composer require monolog/monolog

After this, I create a personalised “sender” function, which will send that StatefulComponent JSON data to a specific websocket client. The websocket server is listening on port 8889, but that might be something you want to configure or load-balance.

Sending serialised data from the client to the server

I can connect to this, using Javascript resembling:

const socket = new WebSocket("ws://127.0.0.1:8889/")

socket.addEventListener("open", function() {
  document.querySelectorAll("[phpx-root]")
      .forEach(root => {
        socket.send(
          JSON.stringify({
            type: "phpx-root",
            id: root.getAttribute("phpx-id"),
              root: root.getAttribute("phpx-root")
          })
        )

        root.setAttribute("phpx-root", "")
      })
})

This is from public/runtime.js

This is fairly standard Javascript syntax. I start off with a connection to the websocket server. When the connection is open, I can find all the elements which have that special phpx-root attribute.

By default, these attributes have a serialised representation of the original StatefulComponent object. So, I can wrap that up, along with the element’s ID, and send it off to the websocket server.

After the serialised state is sent to the server, I blank the phpx-root attribute out. This is so that inspecting the DOM is a lot easier, when we’re trying to debug what the server is sending or responding to.

Inside the websocket server’s onData listener, I need to interpret this root data, so I have real objects to work with:

$text = yield $message->buffer();
$json = json_decode($text);

if (!isset($this->roots[$clientId])) {
  $this->roots[$clientId] = [];
}

if ($json->type === "phpx-root") {
  $component = unserialize(base64_decode($json->root));

  $component->setId($json->id);
  $component->setSender($this->senders[$clientId]);

  $this->roots[$clientId][$json->id] = $component;

  $component->render();
  $component->componentDidMount();
}

This is from worker.php

I’m calling these serialised objects “roots”, which is short for “root nodes”. The data is base64 encoded in the browser, so I decode and unserialise it.

Component should at this point be the same structure as what it was on the server. It’s not the same object, but if it serialised correctly then it should have the same internal data.

We’re going to interact more with it, so I store it in an array specific to the websocket client. Multiple clients can be connected, at the same time, so I want to isolate their roots a bit. Each root is stored by its ID.

Then, I call the render method; so that nested components will also be rendered recursively. I also call the componentDidMount to kick off the delayed processes the stateful component might have.

Sending events from the browser

Now comes the fun part. I’ve create some more Javascript, so that those custom phpx-click events actually send their data to the server:

document.body.addEventListener("click", function(e) {
  if (e.target.hasAttribute("phpx-click")) {
    const [method, arguments] = e.target
      .getAttribute("phpx-click")
      .split(":")

    const root = e.target.closest("[phpx-root]")

    socket.send(
      JSON.stringify({
        type: "phpx-click",
        method,
        arguments,
        root: root.getAttribute("phpx-id")
      })
    )
  }
})

This is from public/runtime.js

I want to be able to support simple method arguments, so I’m finding all elements with the special phpx-click attribute; and splitting their value to separate method name and arguments.

I also use the Element.prototype.closest method to find the closest ancestor with a phpx-rootattribute. This is likely to be the class with the function I want a click to call. I attach its ID value to the “request”.

On the server, I respond to this event:

if ($json->type === "phpx-click") {
  $binds = isset($this->binds[$clientId][$json->root])
    ? $this->binds[$clientId][$json->root]
    : [];

  $arguments = !empty($json->arguments)
    ? explode(",", (string) $json->arguments)
    : [];

  $object = $this->roots[$clientId][$json->root];
  $object->{$json->method}($binds, ...$arguments);

  $data = json_encode([
    "cause" => $json->type,
    "type" => "phpx-render",
    "data" => (string) $object->statefulRender(),
    "root" => $json->root,
  ]);

  $this->send($data, $clientId);
}

This is from worker.php

I’ll explain “binds” in a minute, but it’s just another kind of data, similar to the simple arguments. I prepare binds and arguments, and default them to empty arrays.

Then, I call the intended method on the root. It doesn’t matter what it returns, because the only thing that may change is what the statefulRender method returns. I send this, and the root’s ID, back to the client.

On the client, I have to respond to this phpx-render event:

socket.addEventListener("message", function({ data }) {
  const json = JSON.parse(data)

  if (json.type === "phpx-render") {
    const root = document.querySelector(
      `[phpx-id='${json.root}']`
    )

    if (!root) {
      throw new Error("Can't find the root")
    }

    root.innerHTML = json.data
    root.replaceWith(...root.childNodes)

    flushBinds()
  }
})

This is from public/runtime.js

Once I get the phpx-render event, in the browser, I look for the corresponding phpx-root element. I set it’s content to the returned data, and then I sneakily “unwrap” the children, so that I don’t get recursively-nested <div phpx-id={...} phpx-root> elements.

Interacting with the counter

Sending more complex forms of data

What is flushBinds(), you ask..? To be really useful, this interactivity model needs a better way of communicating non-static data. I need to allow users to input their own data, and have this data communicated to the stateful components.

Binds is an attempt to achieve this, with a little inspiration from Caleb and VueJS. It starts with adding a new phpx-bind attribute, on the server:

namespace App\Responder;

use App\StatefulComponent;
use function App\render;

class TodosResponder extends StatefulComponent
{
  private $todos = [];

  public function addTodo(array $binds)
  {
    $this->todos[] = $binds["todo"];
  }

  public function removeTodo(array $binds, $hash)
  {
    $this->todos = array_filter(
      $this->todos,
      $todo ~> md5($todo) !== $hash
    );
  }

  public function render(){
    return (
      <App.Component.ExampleComponent 
        title={"Todos"}
      >
        <input
          type={"text"}
          phpx-bind={"todo"}
          phpx-enter={"addTodo"}
          className={"..."}
        />
        <App.Component.ButtonComponent
          phpx-click={"addTodo"}
          className={"..."}
        >
          +
        </App.Component.ButtonComponent>
        <ol className={"..."}>
          {array_map(
            $todo ~> $this->renderTodo($todo),
            $this->todos
          )}
        </ol>
      </App.Component.ExampleComponent>
    );
  }

  public function renderTodo(string $todo){
    return (
      <li className={"..."}>
        {$todo}
        <App.Component.ButtonComponent
          phpx-click={"removeTodo:" . md5($todo)}
          className={"..."}
        >
          -
        </App.Component.ButtonComponent>
      </li>
    );
  }
}

This is from source/Responder/TodosResponder.pre

Here we see more of the component composition we’ve seen thus far. We also see the phpx-clickattributes, in addition to phpx-enter and phpx-bind (on that input field).

I’ll mention phpx-enter again, but I’m not going to demonstrate how to implement it. Check out the source code!

You should also take note of the short-closure syntax, in the call to array_map. It’s a useful addition to any PHP codebase, but you need to install it with:

composer require pre/short-closures

This phpx-bind attribute relates to the “binds” array we see in the worker. In the browser, I need a bit more code to initialise as well as transmit the bound data:

const flushBinds = function() {
  document
    .querySelectorAll("[phpx-bind]")
    .forEach(bound => {
      const root = bound.closest("[phpx-root]")

      socket.send(
        JSON.stringify({
          type: "phpx-bind",
          root: root.getAttribute("phpx-id"),
          key: bound.getAttribute("phpx-bind"),
          value: bound.value
        })
      )
    })
}

flushBinds()

const flushBind = function(bind) {
  if (bind.hasAttribute("phpx-bind")) {
    const root = bind.closest("[phpx-root]")

    socket.send(
      JSON.stringify({
        type: "phpx-bind",
        key: bind.getAttribute("phpx-bind"),
        value: bind.value,
        root: root.getAttribute("phpx-id")
      })
    )
  }
}

document.body.addEventListener("change", function(e) {
  flushBind(e.target)
})

document.body.addEventListener("keydown", function(e) {
  flushBind(e.target)
})

document.body.addEventListener("keyup", function(e) {
  flushBind(e.target)
})

This is from public/runtime.js

When the page loads, with the initial markup, I send each bound field’s value to the websocket server. This is to avoid any ugly “undefined index” errors in PHP.

I also call this method with each “render” from a root. If the root returns new bound fields, I want them to also be initialised. Then, on each change and keyboard event, I send bound fields’ values to the server.

On the server, I store these:

if ($json->type === "phpx-bind") {
  if (!isset($this->binds[$clientId][$json->root])) {
    $this->binds[$clientId][$json->root] = [];
  }

  $this->binds[$clientId][$json->root][$json->key] =  $json->value;
}

This is from worker.php

If this is the first time we’re storing “bind” data, for the root, I initialise an array for the root. The rest is simple array storage. The result is that I can access any bound data at the moment stateful component methods are called.

“Remember those TPS reports, Peter”

Circling back to those timer functions

There’s one last thing I want to show you. Remember those timer functions we saw earlier? When I was building this, I realised it needed a way to push data from the server to the client; without any interaction from the client.

The simplest example of this I could think of, was a component that told the client what the time was on the server. A surprisingly difficult task in this ludicrous experiment…

Let’s look at how it went:

namespace App\Responder;

use App\StatefulComponent;
use function App\render;

class TimeResponder extends StatefulComponent
{
  private $time;

  public function componentDidMount()
  {
    // $seconds = 0;

    $this->timeout = $this->setInterval(() ~> {
      $this->time = date("H:i:s");
      $this->update();

      // $seconds++;

      // if ($seconds > 30) {
      //     $this->clearTimeout($this->timeout);
      // }
    }, 1000);
  }

  public function render()
  {
    return (
      <App.Component.ExampleComponent
        title={"Time"}
      >
        {
          $this->time
            ? $this->time
            : date("H:i:s")
        }
      </App.Component.ExampleComponent>
    );
  }
}

This is from source/Responder/TimeResponder.pre

This is the first component I’m showing you with a componentDidMount method. The initial render doesn’t show the time data, but subsequent renders should. The componentDidMount method starts a repeating timer, which pushes the new time to the client.

There’s also a bit of commented-out code, which demonstrates how we could selectively cancel the timer. Thanks to the short closures, we can interact with variables outside of the closure; so it’s really simple to write this sort of self-reflective code.

We’ve already seen how the update method works, and in practise it works with the existing phpx-render event code we’ve defined. I don’t need to make any further changes to the codebase to support this behaviour!

Going further

Where do we go from here? The truth is that I don’t yet know how serious to take this experiment. I have spent days (!) polishing this code up, thinking about alternative implementations, imagining how it could be used. I’m no closer to deciding whether I’d like to make a competing project to Caleb’s.

In reality, I don’t expect this to be accepted by the mainstream PHP community. It’s an amazing technical feat; not because it’s doing something fundamentally new but because it’s doing it so easily.

In under 500 lines of code I have demonstrated a significant portion of the kind of behaviour someone used to ReactJS might expect from a purely PHP implementation. It’s based on the flexibility of pre/phpx and the extensibility of pre/phpx-html. It demonstrates the raw power of preprocessed PHP code. It is beautiful and strange and I love it.

Nobody else will.

I think the problem is that the same love for flexibility of tooling and rapid evolution of the language – that preprocessing has brought to Javascript – isn’t well understood in the PHP community. We don’t do it because we don’t have enough exposure to the good parts.

We try a few poorly implemented Javascript libraries, and sometimes buggy dependency management schemes; and get a bad taste for the whole idea.

I’m not saying the Javascript tools and approaches should be immune to scrutiny. I just think we need to grow up, as a community, and realise the idea is sound; even if we have yet to see a compelling implementation of it in our language.

Pre is a good start. Though it suffers the same drawbacks of any new language, it works. It has made these days of work exciting for me. If you take the time to think what we could achieve, with this tool in our hands, I think you’d come around to the idea that it would work.

Smarter people than me have seen this.

There are some technical things you could try:

  • Clone the application and try the code.
  • Try to figure out how phpx-enter works, before looking at the implementation.
  • Create a complex component, that shows you the limits (or lack of them) this architecture offers.
  • Look at the implementation pre/phpx-html and pre/phpx. Custom compilers are fun.
  • Build something useful with this approach: a website just like this one, an email sign-up form, an online code formatter.

Thanks for reading this far.