Building my own PHP Framework (Part 1: Front Controller)

Jair Rios O'Hara
4 min readFeb 12, 2021
Developing a new standard is always a good idea

Yeah I know what you’re thinking, you read the title and thought of that xkcd comic, right? Well I’m not trying to create the new/best PHP framework ever, that is not what this article is about. So what is it about? It’s about learning. Maybe you’re here for that too.

Let me give you some context on why I chose to do this. When I was first starting PHP the first project I joined was a mess. It was done without a framework, no namespaces, just includes everywhere. I was coming from.NET and going into that, didn’t make me like PHP, luckily that didn’t last long, for my next project I had to start from scratch. The project was originally going to be developed by another person but at the last minute he couldn’t do it so I was assigned. He hadn’t developed anything but since he was more experienced before handing me the project, he gave me a few pointers, the most important was, he told me about this new-ish framework that he was using… CodeIgniter. Yes I know, I’m old enough that I remember when CodeIgniter was “new”. Granted this was the 2.0 version but still. He gave me quickstart on the framework and I was impressed. I know, CodeIgniter has fallen out of favor a long time ago but at the time Laravel was still just being developed so it wasn’t even on my radar. Now I still (at that point and to this day) prefer ASP.NET MVC over CodeIgniter, but this was… pretty good. And after working on a very outdated codebase I literally didn’t think PHP could even work like “that”. I was still learning so I didn’t know that much about PHP so everything seemed impossible. “How come my URLs don’t all end in .php like the other project?”, “How were they separating presentation from logic that well?”, “No but seriously, PHP could do that?”.

After a while I learned more about PHP and then Laravel came and changed the landscape. Now Laravel introduced even more new things, but the question got even more complicated, how did it all work?. If they were doing it, so could I, right? So let’s try to do it then. I won’t go into super detailed things, but I’ll try to get at least into something usable.

Front Controller

This was the part that got me the most curious (Keep in my mind I was a junior). How were requests being resolved to controllers? I thought every request had to go to the individual PHP file in the URL. Well this is what we call Front Controller, it’s also used in ASP.NET but how did PHP did it?

Actually the logic is quite simple, we have a simple PHP file in the public directory, this file then starts “bootstrapping” our application, loading the components. Eventually a Router component is loaded, it parses the URL, tells the application which URL is being accessed and the application instantiates the corresponding controller and the appropriate method.

So to start we need: a Front Controller, the application we’re starting. We’ll leave the router for later first, let’s start with the application. We need to define the minimum we need to get this running. So in order to start the application we need our autoloader and we can just go from there.

We’ll define our Application simply to get started:

<?phpnamespace Star\Framework;class Application
{
private static $instance = null;public static function get_instance()
{
return self::$instance;
}
public function __construct($app_path)
{
self::$instance = & $this;
$app_path = rtrim($app_path, '/') . '/';
define('APP_PATH', $app_path);
define('BASE_PATH', dirname($app_path) . '/'); }
public function run()
{
echo 'Application Started succesfully';
}
}

Notice I provide the path of our application (the app directory) in the constructor. We’ll use to know which directories we need, we will also define constants for this.

I want this to be reusable, so I’ll make it it’s own package. I’ll name it Star Framework and require it in the main application via composer. I’ll also require the vlucas/phpdotenv package that we’ll use later.

Now our front controller. First of all, since we are using a composer package we need to generate our autoload and require it.

php composer.phar dump-autoload

public/index.php

<?php//Autoloadrequire_once __DIR__.'/../vendor/autoload.php';//Create The Application$app = require_once __DIR__.'/../app/bootstrap.php';//Run The Application$app->run();

Quite simple, we are requiring our autoload, a bootstrap file located in a “app” directory” and calling the run method in our application. But what exactly are we bootstraping? Since we are starting minimal all we need is to return the application that the front controller runs, passing the path, but I’ll also add a DotEnv loader we’ll use later.

app/bootstrap.php

<?php//DotEnv
try {
(new Dotenv\Dotenv(__DIR__.'/../'))->load();
} catch (Dotenv\Exception\InvalidPathException $e) {
echo "Error loading .env: {$e->getMessage()}";
exit;
}
//Instantiate Application
$app = new Star\Framework\Application(
realpath(__DIR__)
);
return $app;

Now if you actually request index.php on your server our simple application should run without issues, but we don’t want “index.php” on our url. We need all request to our application to go to “index.php”, without actually requesting it. This is done on the Server Software actually, not PHP. For PHP you’ll usually use Apache, for Apache you’ll need the mod_rewrite module enabled and add a rule to send requests to our Front Controller. This can be done on the Apache configuration on the server or via an .htacess in the public directory.

public/.htaccess

<IfModule mod_rewrite.c>
RewriteEngine On
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php
</IfModule>

Now you can test requesting anything under that public folder and everything should resolve to our front controller and show the same result. Obviously we still need to route different requests to different controllers, which we’ll do on the next article. In the next article we’ll implement a router, a simple app configuration and loader and some basic controllers.

--

--