We Wish You a Framework X-Mas 🎄

Just the Gist

How about an asynchronous model for your PHP web application? And what if it had a small footprint as well? Framework-X is a micro-framework built on ReactPHP, giving its users the ability to create their own asynchronous web applications. Today we wish you a Framework X-mas!

There's a joke going around, that each and every day there is another framework released for building web applications. The criticism of new frameworks is that they take labour that possibly could have put into making improvements to the frameworks that we have - which already is a bunch. But for some projects, the new framework could be a reflection of a new way to collaborate, or a new way to build a web application - despite its likeness to the old frameworks. But today's subject is leaning more towards a new way to build web applications that is not like our established frameworks in the PHP community. We're talking about Framework-X, a framework implemented on ReactPHP which enables asynchronous execution of PHP code.

This may be a bit hyperbolic as I've only experience with 4 other frameworks, so if you know of a similar framework as I'm writing of today, please let me know.

Before we dive deeper into this subject, let's clarify a few things:

  1. This is a surface-level look at Framework-X. Prior to Framework-X, I have no experience with ReactPHP.
  2. This article won't go into event-driven programming.
  3. As of writing, Framework-X is still to reach a stable release. So it's not a good idea to use it in production without considering what this means for your project.

That's it, and please comment if there are any questions or corrections you would like to point out.

Framework-X

As the project describes itself, it is a "Simple and fast PHP micro framework. Async made easy." So for today's article, we will build a very simple application with only one page. The page will display the title, description and link of my latest article on DEV. Most of what we will do here is pretty straight forward. We will not be relying on any other libraries but what comes out of the box with Framework-X and enjoying the comforts of PHP 8.1.

OH, if you haven't got Composer I'd recommend you to go get it for your system if you want to be building PHP projects. It's a very handy tool that will help you to manage your dependencies. Get it at getcomposer.org.

Create a new project

First things first, let's create a new project. How about a project called dev-x? Create this directory and cd into it. Then run the following command:

composer require clue/framework-x:dev-main

Note that this specifies that we will be using dev-main as it currently lacks any official release. It will create a new directory called dev-x and install the dependencies that we need for this project.

Let's open the dev-x directory in our favorite text editor (mine is VSCode, what is yours). We will have a vendor directory and our composer files. Now for our little fun:

  • Let's create a new folder called public in our project root
  • In that folder, create a new file called index.php ☝ That's where we will be playing next!

In the index.php file we will set it up to listen to a GET request on / - meaning the homepage. Just to see that we're doing something that works, let this be our first iteration:

use FrameworkX\App;
use React\Http\Message\Response;

require __DIR__ . '/../vendor/autoload.php';

$app = new App();

$app->get('/', function () {
    return new Response(body: 'Merry Christmas!');
});

$app->run();

We can run this file from our project root with: php public/index.php. This will start the built-in web server that Framework-X provides. If you have the Symfony CLI, you could start the project with symfony serve -d instead. The Symfony web server gives a SSL certificate that you can use to run the project on HTTPS - it also allows us to update the project while the server is running and show the updates to the user. The Framework-X server would need to be restarted.

I don't know the reason why the Framework-X server needs to be restarted to show updates. Maybe it has something to do with the event-driven architecture. If you know what's up, please leave a comment or mail me.

This very bare-bones application will now serve us a nice little Merry Christmas when we visit localhost:8080 (or localhost:8000 if you ran it with Symfony). Getting us this far, let's expand this a little bit so we can get my latest article from DEV on the page. Maybe (let's hope) the article will be a bit more intriguing than "Merry Christmas", but let that be a fallback for us.

I need control!

A good practice is the separation of concerns. So to not get a crowded index.php file let's create a new folder called src and another folder called Controller within that. Yes, you guessed it! We are building a controller that will be mapped to from the index.php file.

./src/Controller/DevController.php

<?php 

namespace App\Controller;

use React\Http\Message\Response;

class DevController
{
    public function __invoke()
    {
        return new Response(body: 'Merry Christmas!');
    }
}

With this added structure in mind, let's make it so the class can be autoloaded with a use-statement. For this to work we will map the src-folder to App namespace. So let's add this to the composer.json file in the project root:

{
    "require": {
        "clue/framework-x": "dev-main"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

You may need to run composer dump for it to register the new namespace.

Having registered the namespace, we can use the use statement to import the Controller-class. We will be mapping to this class from our index.php file, in essential this will mean that we connect a route to a specific controller. So here's how the index.php file will look like:

<?php

use App\Controller\DevController;
use FrameworkX\App;

require __DIR__ . '/../vendor/autoload.php';

$app = new App();

$app->get('/', new DevController());

$app->run();

Running this file again will result in the same output as before, so here's another "Merry Christmas" 🎅! Now, why don't we see to it that we actually get the latest article from DEV?

DevControl to major DEV 🎶

What we will do next is make an asynchronous call to DEV and get the latest article. We will use the Framework-X built-in HTTP client to make the request. But note that while we make an essentially asynchronous call, we are only calling one resource at one time - hardly making good use of this asynchronous structure. Anyway, here's how it will look like:

<?php 

namespace App\Controller;

use Psr\Http\Message\ResponseInterface;
use React\Http\Browser;
use React\Http\Message\Response;

class DevController
{
    public function __invoke()
    {
        $browser = new Browser();

        return $browser
            ->get('https://dev.to/api/articles?username=andersbjorkland&per_page=1')
            ->then(
                onFulfilled: function (ResponseInterface $response) {
                    $json = json_decode($response->getBody(), true);
                    $latestArticle = $json[0];
                    return $this->parseJsonArticleToResponse($latestArticle);
                },
                onRejected: function () {
                    return new Response(body: "<p>Well, that didn't work. But HEY: Merry Christmas!</p>");
                }
            );

    }

    protected function parseJsonArticleToResponse($article): Response 
    {
        $title = $article['title'];
        $description = $article['description'];
        $url = $article['url'];

        $body = <<<BODY
        <h1>$title</h1>
        <p>$description</p>
        <a href="$url">$url</a>
        BODY;

        return new Response(body: $body);
    }
}

Ok, now we are cooking! So there are a couple of things going on here. First off, we are introduced to a whole new class called Browser. This class is used to make HTTP-requests, and that's exactly what we need to get a response from DEV with that sweet article 😉. We specify that $browser will make a GET-request to DEV. Once we have received a response the then-method will be called. This method can take two callbacks (well, actually three but the third callback is deprecated). The first callback is used upon a successful response and the second callback is used for a failed response.

While we are here, did you see how we can see what each callback's purpose is? With named arguments introduced with PHP 8.0 we can specify fow which parameter each argument is supposed to be used with. It's quite obvious then that the onFulfilled-callback is used when the response is successful and the onRejected-callback is used when the response is not successful.

Taking a look at the onFulfilled-callback, we are going to parse the JSON-response from DEV into a response that we can return to the user. This is done by calling the parseJsonArticleToResponse-method. This method takes one argument, which is the article in a JSON-format. We are taking the title and description from the article and returning it with some basic HTML in a new response.

So this is all very basic, but it gives a good feel for how to work with Framework-X. If you want your application with a bit more style, you could either expand the HEREDOC or use a template engine such as Twig (very do-able, and if there is a need for it I could write a tutorial on how to do it).

What about you?

Have you used Framework-X or ReactPHP before? Do you have a favorite micro-framework, what is it? Does PHP benefit from asynchronous functions? Comment below and let us know what you think ✍

Further Reading

21