How to Create a Custom Router Class in PHP
Are you looking for a way to hide your .PHP extension from the URL and secure your app. This article explores how you can create a custom router class to handle all your HTTP requests from the browser.
Working with Routers
When using a PHP framework, you map file paths in the address bar directly to corresponding controllers. For example, when using Laravel, if you send a get request to the contact page, the controller will look for a file called contact.blade.php
to return a valid response.
You register routes on your application in the routes > web.php
file to be loaded when a certain request is made. A good example provided by Laravel is:
Router::get('/', function(){
return view('welcome');
})
This returns the HTML template on resources > views > welcome.blade.php.
So to create a another page like contact or about us, you create another router and a second template on the resources > views
folder.
It should look like this:
Router::get('about-us', function(){
return view('about-us');
})
If you don’t create a router, it’ll return a 404 page or error in some frameworks.
Why Build a Custom PHP Router Class?
There are several reasons you might consider a custom router for your application:
Control Over Requests
Custom controllers allow you to create a single entry point into your application where you access all requests sent from the browser. Whenever a user sends as a $_get, $_post, or $_file
request, you can call a certain class to handle the request.
Some developers also argue that you can secure your application better by using htaccess file. Since you’re using that to create a custom router, you might make your web application more secure.
Concise and Shorter Code
Most frameworks come with a lot of stuff you barely need for your application. While some can help manage users and databases, you may only want a small website with a few pages and blog posts.
Using controllers is a good idea. However, you might want to reduce your code, especially if you’re building large projects, accessing different databases, or dealing with different kinds of data.
Better Organization
In PHP, organization is key to managing large projects. If you don’t organize your code, it’s easy to get messy, making it hard to make changes when needed.
The best way to organize your code is to shorten it with functions and classes. In the example I give below, you’ll find that most repeating lines of codes are placed in a loop or templates.
Build Core Files and Folders
In your root folder, you’ll need four more folders:
- Assets: For your assets files, including images, CSS, JavaScript, and SCSS
- Database: For your database configuration files to connect, create, delete, and update the database
- Includes: For your controllers
- Views: For your templates
You also need htaccess, functions.php and directories.php
files inside the root folder. Htaccess will handle all URLs passed through the website and redirect them to the views folder. The directories.php file will define constants and autoload classes for us.
Handle GET Requests Through the Htaccess File
To start, we need to redirect all URL requests to one PHP file, which will handle all requests. To do this, we’ll use rewrite rules to redirect all requests to the views/app.php
file.
Add these lines of code in your .htaccess file on your root folder:
//Prevent direct access to sub-folders
Options -Indexes
RewriteEngine on
//Choose files users can access directly via the browser
RewriteRule \.(js|css|swf|jpg|gif|png|eot|tff|woff|woff2|map|wav|scss)(\?|$) - [NC,QSA,L]
//Redirect the user request
RewriteRule ^(.*)$ views/app.php?url=$1 [NC,L,QSA]
The code starts by disallowing users from viewing your files directly. Normally, when users navigate to a folder without the index file in PHP, they might see all files on your server. We obviously don’t want that.
They can only access select files via the browser. They are essential in the front-end, and the browser will download them automatically.
Lastly, the code rewrites all requests to the views > app.php
file preventing direct access to the root folder. This can help secure your core files in the database, assets, and includes folder.
Create Your Router Class
There are several steps to create a working router class, but they’re simple and easy to understand. We’ve broken down the steps into functions, so it’s easier for you to see what each one does.
Step 1: Get the URL from the Browser
In your includes folder, create a new folder called URL and a file inside it named url.php.
Inside the file create your class and add the following code:
final class url{
private rawurl;
private explodeurl;
public table;
public slug;
private function gettheurl(){
$rawurl = $_GET['url'];
$this->rawurl = cleanurl($rawurl);
return $this->rawurl;
}
This is a private function that returns a valid URL. You can create a function cleanurl in the functions.php file to filter illegal characters from the URL. We’ll show you how to link in another post.
Step 2: Convert the URL into an Array
Idealy, your MySQL database will have tables representing each post type on your app. For example, one table can hold data for pages, posts, users, tasks, or projects, etc. We think this makes it easier to find your files on your database and to configure your URLs.
In most cases, pages URLs aren’t preceded by anything; you just type in the page slug directly into the URL. For instance, if you want to go to the contact page, you just type yourhost.com/contact
, unlike for posts where you might type yourhost.com/post/category/post_slug
.
In this tutorial we’ll assume that all URLs contain a reference to the post type (i.e., if you want to get the contact page, you key in yourhost.com/page/contact
in your address bar.)
You can remove option if you like. With that, let’s create a new private method similar to the one shown below:
private function explodetheurl(){
$this->rawurl = $this->gettheurl();
$this->explodeurl = explode('/', $this->rawurl);
return $this->explodeurl;
}
The function returns an array of the broken down URL. If your URL looks like this page/contact,
you get an array that looks like Array ( [0] => page [1] => contact )
.
Step 3: Sort Items Based on Tables and Slugs
At this point, we want to assume three things:
- If returned array has no items, the user has requested for the homepage
- If there’s only one item in the array, the user wants a list of all items in a table (i.e., the posts table)
- If there’s a second item on the array, the user wants a specific item from a table
With that, we can create methods to sort the array well to send to a class that’ll query the data from the database. We need to know which part is the table and which is the slug.
We created a method that looks like this:
private function listquery(){
$this->explodeurl = $this->explodetheurl();
$this->table = $this->explodeurl[0];
if($this->table === '') $this->table = 'homepage';
return $this->table;
}
private function singlequery(){
$this->explodeurl = $this->explodetheurl();
$this->table = $this->explodeurl[0];
$this->slug = $this->explodeurl[1];
return $this->table;
}
Both functions return the table name on MySQL, but we can still access the slug property throughout the class; we don’t have to return each property. This is just to make it easier to assign the methods to one variable in the future.
To get the table and slug from another class, you might call this method since both properties are public:
public function geturlpath(){
//We're fetching the data from the exploded url
$this->explodeurl = $this->explodetheurl();
$counturl = count($this->explodeurl);
if($counturl === 1){
//we're only getting the table because there's only one url
return $this->table = $this->listquery();
}
if($counturl === 2){
//we're getting the table and the action or the slug to show single item or params to search query
return $this->table = $this->singlequery();
}
}
You can now create another method that fetches the classes with the information regarding HTML templates and data from the database.
For example, we did something like this:
public function gotourl(){
//Get the table name and post slug
$table = $this->geturlpath();
//we can check if the user is logged in before querying the database
if(!isset($_SESSION['user_id'])){
$rq = new login;
//we'll need the information later to redirect the user to their page/post
return $rq->login($table, $this->slug);
}
//we check if the user is logged in
if(isset($_SESSION['user_id'])){
//Check if a file with the same name exists in the includes directory
if(file_exists('includes/logged_in/'.$table.'.php')){
$rq = new $table;
return $rq->$table($table, $this->slug);
}
return 'Class Doesn't Exist';
}
}
As you can see, we first check to see whether the user is logged in. We can use a different class to redirect them to the login, registration, email verification, and password change pages.
If the user is logged in, we call a class with the same name as the table to shorten our code. To do that, you’ll need the PHP autoloader function.
Create a PHP AutoLoader Function
You don’t have to include a list of all your classes on the app.php file. The spl_autoload_register() function can register any number of classes on your app. You just need to tell it where to look.
In your directories.php file, add the following code:
//Define the app root path so we don't have to keep checking this code
define('root_path', $_SERVER["DOCUMENT_ROOT"].'/');
//define the folders with your core files
$def = array(
'assets' => root_path."assets/",
'views' => root_path."views/",
'ul' => root_path."includes/url",
'db' => root_path."database/",
'logged_out' => root_path."includes/logged-out/",
'logged_in' => root_path."includes/logged-in/",
);
foreach ($def as $key => $value) {
define($key, $value);
}
//whenever you invoke a class, PHP will check these before it returns an error
spl_autoload_register(
function($class){
global $def;
foreach ($def as $key => $value) {
if(file_exists(constant($key)."$class.php")){
return include constant($key)."$class.php";
}
}
}
);
In the above code, we’ve declared constants so you can use them to fetch files in the views and assets folders, and included the classes in the PHP autoloader function.
That means you only need to include the directories.php
file on your app.php file, and your app should start running.
Invoking the Class From Your App.php File
Since all URLs rewrite to views > app.php
, you must call the class and include the directories file. This is easy. Just add this to your app.php file:
<?php
include_once '../directories.php';
include_once '../functions.php';
$url = new geturl;
$url->gotourl();
That is the only code you need on your app.php file, which will now act as your index.php file. You can create classes in your app’s includes and database folders, but make sure to include the directories in the spl_autoload_register() function.