Getting Started with Framework-X and Twig Templates
Framework-X is a modern, async-first PHP web framework built on ReactPHP. What I love about it is that it doesn't force you into a rigid structure. You can start with a simple script and grow it as your needs evolve. In this guide, we'll build a very simple application and integrate the Twig template engine to render HTML pages. Don't worry if you're new to async PHP. We'll keep things straightforward.
What is Framework-X?
Framework-X is a micro-framework built on ReactPHP. Unlike Laravel or Symfony, which come packed with batteries, Framework-X stays lean. It gives you routing, a container, and middleware support, but then gets out of the way. You decide what else you need.
What sets Framework-X apart is its architecture. Traditional PHP frameworks like Laravel run on PHP-FPM, which follows a "boot and die" model. Each request spins up a new PHP process, runs your code, and shuts down. This works, but it's wasteful for high-concurrency scenarios.
Framework-X runs on a persistent event-loop. The process starts once and stays alive, handling many concurrent requests without booting up for each one. This is a fundamentally different execution model. You get better performance, lower latency, and the ability to maintain state across requests if you need to.
This approach works well if you're building APIs, microservices, or applications that need to handle many concurrent connections efficiently.
Framework-X is a PHP web framework that:
- Runs asynchronously. Handles many concurrent connections efficiently.
- Starts simple. Begin with closure routes, add structure only when needed.
- Keeps hard things possible. Supports async/await patterns, fibers, and promises.
- Works anywhere. Runs with the built-in server, PHP-FPM, Docker, or behind a reverse proxy.
It's perfect for building REST APIs, web applications, real-time services, and traditional web apps.
Project Structure
A typical Framework-X project looks like this:
framework-x-blog/
├── public/
│ ├── index.php # Entry point
│ └── assets/ # CSS, JS, images
├── src/
│ ├── Controller/ # Controller classes
│ ├── Service/ # Business logic
│ └── Repository/ # Data access
├── templates/ # Twig templates
│ ├── layout.html.twig
│ ├── post/
│ │ ├── list.html.twig
│ │ └── show.html.twig
│ └── home.html.twig
├── composer.json
├── .env
└── .gitignore
Key Concepts
Routing: Define URL patterns that map to handlers (closures or controllers)
$app->get('/', $handler); // GET /
$app->post('/posts', $handler); // POST /posts
$app->get('/posts/{id}', $handler); // GET /posts/123
Controllers: Invokable classes that handle requests
class PostController {
public function __invoke(ServerRequestInterface $request) {
return new Response(...);
}
}
Async/Await: Handle I/O operations concurrently using PHP 8.1+ fibers
use function React\Async\await;
$result = await($database->query('SELECT * FROM posts'));
Step 1: Create a New Project
# Create project directory
mkdir framework-x-blog
cd framework-x-blog
# Initialize composer project
composer init
# Install dependencies
composer require clue/framework-x react/http twig/twig
Step 2: Create the Entry Point
Create public/index.php:
<?php
require __DIR__ . '/../vendor/autoload.php';
use FrameworkX\App;
use React\Http\Message\Response;
$app = new App();
// Simple route
$app->get('/', function () {
return Response::plaintext("Welcome to Framework-X!\n");
});
$app->run();
Start the server:
php public/index.php
# Listening on http://127.0.0.1:8080
Visit http://localhost:8080 and you'll see your response!
Step 3: Install and Configure Twig
Install Twig via Composer:
composer require twig/twig
Twig is Framework-X's recommended template engine. Create the Twig Environment and pass it to your app via the dependency injection container:
<?php
require __DIR__ . '/../vendor/autoload.php';
use FrameworkX\App;
use FrameworkX\Container;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
$loader = new FilesystemLoader(__DIR__ . '/../templates');
$twig = new Environment($loader, [
'cache' => false, // Disable cache in development
]);
$app = new App(new Container([
Environment::class => $twig,
]));
$app->get('/', Acme\Todo\HomeController::class);
$app->run();
For production, enable caching so templates compile once and persist in memory:
$twig = new Environment($loader, [
'cache' => __DIR__ . '/../cache/twig',
]);
Step 4: Set Up Templates
Create the templates/ directory and add a layout template templates/layout.html.twig:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Framework-X Blog{% endblock %}</title>
</head>
<body>
<header>
<h1>Gavin Kimpson : Blog</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2026 Built with Framework-X and Twig.</p>
</footer>
</body>
</html>
Create templates/home.html.twig:
{% extends "layout.html.twig" %}
{% block title %}Blog{% endblock %}
{% block content %}
{% for post in posts %}
<article>
<h2><a href="/posts/{{ post.id }}">{{ post.title }}</a></h2>
<p>{{ post.date|date('F j, Y') }}</p>
<p>{{ post.excerpt }}</p>
</article>
{% else %}
<p>No posts yet. Check back soon.</p>
{% endfor %}
{% endblock %}
Step 5: Create a Controller
Create src/Acme/Todo/HomeController.php:
<?php
namespace Acme\Todo;
use Twig\Environment;
use Psr\Http\Message\ServerRequestInterface;
use React\Http\Message\Response;
class HomeController
{
public function __construct(private Environment $twig)
{
}
public function __invoke(ServerRequestInterface $request): Response
{
$html = $this->twig->render('home.html.twig', [
'title' => 'Welcome to Framework X',
]);
return Response::html($html);
}
}
The Twig Environment is passed via dependency injection from the container. Render your template with data and return Response::html() to send HTML to the client.
Step 6: Other Template Engines
Twig is the recommended option, but Framework-X works with any PHP template engine:
- Plates - Simple, markup-based templates without a new language
- Latte - Nette's template engine with clean syntax
- Mustache - Logic-less templates
- Handlebars - Logic-less templates with familiar syntax
Choose based on your preference and project needs.
Step 7: Important Considerations
Don't reload templates from the filesystem on every request. Twig caches compiled templates in memory. Disable caching in development so changes show immediately:
$twig = new Environment($loader, [
'cache' => false, // Development
]);
In production, enable caching so templates compile once:
$twig = new Environment($loader, [
'cache' => __DIR__ . '/../cache/twig', // Production
]);
Test Your Application
php public/index.php
Visit http://localhost:8080 to see your HTML rendered by Twig.
Understanding the event-loop model
Since Framework-X keeps the process alive between requests, you need to be aware of a few things. Variables in your application live for the lifetime of the process, not just a single request. This means you can cache data, maintain database connections, or keep state if you're careful about it.
The downside is you can't just dump all your session state in a global variable and expect it to work like traditional PHP. You need to think about request isolation and cleanup. Reading the official docs helps here.
Also, never use blocking I/O calls (like file_get_contents() on a remote URL or synchronous database queries) in hot request paths. They'll block the entire event-loop and freeze all concurrent requests. This is where async methods like await() come in. But don't worry if this sounds complicated now, it becomes natural quickly.
A note on best practices
This is a very simple application to get you started. As you build more complex features, take time to read the official Framework-X documentation, especially the best practices guide. It covers controller architecture, middleware patterns, async handling, and more. The team behind Framework-X knows what they're doing, and their docs are genuinely helpful.
Key Takeaways
Framework-X is minimal but powerful. Start with simple routes, add structure when needed.
Twig separation. Keep your templates in separate files for cleaner code.
Async ready. The built-in server handles concurrent requests efficiently.
Easy to extend. Add services, middleware, or async database queries as your app grows.
Next Steps
Now that you have the basics down, explore:
- Database integration. Use
react/mysqlfor async queries. - Middleware. Add authentication, logging, or CORS handling.
- Asset pipeline. Minify and bundle CSS/JS.
- Deployment. Run behind Nginx or Docker.
- Testing. Write PHPUnit tests for your controllers.
Resources
I genuinely enjoy building with Framework-X. It respects your intelligence as a developer and gets out of the way when you need it to. Start with what we've covered here, and you'll find your own path as things get more complex. Let me know if you hit any snags with this setup.