I want to be noted that this is not a tutorial about creating an MVC framework from zero. If you want that kind of tutorial, you can find a lot of them (https://www.youtube.com/playlist?list=PLfdtiltiRHWGXVHXX09fxXDi-DqInchFD https://www.youtube.com/playlist?list=PL7A20112CF84B2229).
There are moments in your programmer life when you are asked to do a small “landing page”, which actually turns out to be a “mini-website” with an “About” page, a “Contact” page and a few other pages.
At that moment you may simply tell yourself: “…well… we’ll simply install a framework and work with it”. But… WHY??? Why would you do such a thing for 3 or four “pages”? Why not simply create an index.php, an about.php and a contact.php?
All good and well… But the SEO specialist would say: “What about SEO friendly URL’s?”… “Oh, crap… We go back to a framework… No! A mini-framework!! No!! A micro-framework!!!”. Well… Let’s see how we can do it as they did it in the old days.
As you may well know, .htaccess file is the first one that gets “executed” when a server is receiving a request for a resource. So we first need to create a .htaccess file in the root of our… website…:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?path=$1 [L,QSA]
As you can see we first make sure that mod_rewrite is enabled (https://httpd.apache.org/docs/current/mod/mod_rewrite.html). After that, we must make sure that the resource selected is not a file, directory. If none of these applies, .htaccess directs the request to an index.php file appending the rest of the url to a GET type of variable named url.
Now we create the index.php which will take the variable…
<?php
$path = $_GET['path'];
echo $path;
Cool. We have the path. Now what? We will have to break the path into its components:
$path = filter_var(trim($path, '/'), FILTER_SANITIZE_URL);
$path_arr = explode('/',$path);
Now, we should have a default page that should deal with the request in case the user is accessing the homepage (namely, when the $path_arr is empty).
if(strlen($path)== 0 || empty($path_arr)){
echo 'serve the homepage';
exit;
}
else{
echo 'direct the user to the wanted php script';
}
Now, how do we redirect the user to the wanted php script? First of all we must find out if that script exists. If it doesn’t, then we redirect to the homepage.
First of all… let’s put all our script files in a directory named “App”. So, let us create a file named contact.php inside that app directory:
<?php
echo 'hello from contact.php script';
…and a homepage.php:
<?php
echo 'hello from homepage.php script';
Now, let’s return to our index.php and see the if-else block again:
if(strlen($path)== 0 || empty($path_arr)){
require_once('App/homepage.php');
exit;
}
else{
$page = str_replace('-','_',array_shift($path_arr)); // once we get the "page" we remove it from the $path_arr
if(file_exists('App/'.$page.'.php'))
{
require_once('App/'.$page.'.php');
exit;
}
else
{
require_once('App/homepage.php');
exit;
}
}
Easy enough, right? Now, if we go to our contact page (http://localhost/contact), we will be greeted by the contact.php script. But what about eventual subpages? What if we want something like (http://localhost/contact/by-email)? For this we would need to transform the scripts into classes, and add methods to them, so that a “subpage” would actually be a method of that class.
So, going to contact.php we would need to change it into a class, and also add an “index” method:
<?php
class Contact
{
public function index()
{
echo 'this is the contact page';
}
public function by_email()
{
echo 'this is the contact page with "by email" subpage';
}
}
It would be a great idea to name the file “Contact.php” (starting with uppercase). So, let’s do this also.
Let us also change “homepage.php” into “Homepage.php” and transform it into a class with an index() method:
<?php
class Homepage
{
public function index()
{
echo 'Homepage index';
}
}
Now returning to our index.php we need to call the script and instantiate the class.
else{
$class = ucfirst(str_replace('-','_',array_shift($path_arr))); // once we get the "page" we remove it from the $path_arr. As you can see we replaced the eventual "-" with "_" because we cant have dashed in a class name
if(file_exists('App/'.$class.'.php'))
{
require_once('App/'.$class.'.php');
$page = new $class;
exit;
}
else
{
require_once('App/Homepage.php');
$page = new Homepage;
$page->index();
exit;
}
}
After we instantiate the class we should call the method:
else{
$class = ucfirst(array_shift($path_arr)); // once we get the "page" we remove it from the $path_arr
if(file_exists('App/'.$class.'.php'))
{
require_once('App/'.$class.'.php');
$page = new $class;
if(sizeof($path_arr)>=1 && method_exists($page, $path_arr[0]))
{
$method = str_replace('-','_',array_shift($path_arr));
$page->{$method}();
}
else
{
$page->index();
}
exit;
}
else
{
require_once('App/Homepage.php');
$page = new Homepage;
$page->index();
exit;
}
}
Cool… but what if we want to pass the method some other parameters inside the url?
We simply change the call by passing it the rest of our $path_arr:
$page->{$method}($path_arr);
Let us see the index.php again:
<?php
$path = $_GET['path'];
$path = filter_var(trim($path, '/'), FILTER_SANITIZE_URL);
$path_arr = explode('/',$path);
if(strlen($path)== 0 || empty($path_arr)){
require_once('App/Homepage.php');
$page = new Homepage;
$page->index();
exit;
}
else{
$class = ucfirst(array_shift($path_arr)); // once we get the "page" we remove it from the $path_arr
if(file_exists('App/'.$class.'.php'))
{
require_once('App/'.$class.'.php');
$page = new $class;
if(sizeof($path_arr)>=1 && method_exists($page, $path_arr[0]))
{
$method = str_replace('-','_',array_shift($path_arr));
$page->{$method}($path_arr);
}
else
{
$page->index();
}
exit;
}
else
{
require_once('App/homepage.php');
$page = new Homepage;
$page->index();
exit;
}
}
Now, we only need to go to app/Contact.php and see if the array is passed to our method:
<?php
class Contact
{
function __construct()
{
}
public function index()
{
echo 'this is the contact page';
}
public function by_email($params = array())
{
echo 'this is the contact page with "by email" subpage<br />';
print_r($params);
}
}
That’s it. You now have the first steps toward thinking in PHP and, maybe creating a MVC (although I did say this is not about creating an MVC framework).
Entering Composer
But how about using composer in order to autoload the classes? This way we won’t need to “require_once” the files needed. I assume you already installed composer…
In order to use composer, we first need to create a composer.json file inside our root directory (where the index.php is):
{
"autoload": {
"psr-4": {
"App\\":"App/"
}
}
}
Now, going in the terminal (or console) where the composer.json is, we type “composer install” which will create the vendor directory and the autoload.php file.
Once those are created, we go into our index.php and, on the first line we require the autoload.php:
require_once 'vendor/autoload.php';
Now we need to update the script so that it won’t require the files but simply instantiate the classes:
<?php
require_once 'vendor/autoload.php';
$path = $_GET['path'];
$path = filter_var(trim($path, '/'), FILTER_SANITIZE_URL);
$path_arr = explode('/',$path);
if(strlen($path)== 0 || empty($path_arr)){
$page = new App\Homepage;
$page->index();
exit;
}
else {
$class = 'App\\' . ucfirst(str_replace('-', '_', array_shift($path_arr))); // once we get the "page" we remove it from the $path_arr
if (class_exists($class)) {
$page = new $class;
if (sizeof($path_arr) >= 1 && method_exists($page, str_replace('-', '_', $path_arr[0]))) {
$method = str_replace('-', '_', array_shift($path_arr));
$page->{$method}($path_arr);
} else {
$page->index();
}
exit;
} else {
$page = new App\Homepage;
$page->index();
exit;
}
}
After this, we need to make Contact and Homepage classes parts of the App namespace:
This is how Contact.php looks now:
<?php
namespace App;
class Contact
{
function __construct()
{
//echo 'hello from contact';
}
public function index()
{
echo 'this is the contact page';
}
public function by_email($params = array())
{
echo 'this is the contact page with "by email" subpage<br />';
print_r($params);
}
}
…and this is how Homepage.php looks now:
<?php
namespace App;
class Homepage
{
public function index()
{
echo 'Homepage index';
}
}
Cool. We’ve finished with the composer too.
Is all this safe???
Of course not! You would need to only leave index.php inside the public area (public_html, htdocs, www…). Move everything else in a directory above the public area, and simply change the path to the autoload.php.
i love your tutorials. i see that you have worked with ion auth in a prevous tutorial. i was wondering if you have used community auth and if so what do you think of it? Im doing a site that needs roles and permissions so i figured it would be the best for that application. if you want to do a tutorial on it also that would cool but just your professional opinions would be great. I also using thre hmvc by wiredesignz.
Hello. I haven’t used community auth. Although it looks very professional, and I heard people being very satisfied with it, I haven’t yet felt the need to use it. I did use hmvc in the past, but now, when I look at it, I often find myself lost inside all the directories that I created then, and all the calls to other modules. I ended up being the adept of “don’t complicate yourself when you don’t have to”. You don’t need to use modules (libraries) that are too complex in order to do simple things (as a result you can see this tutorial, which is simply telling you that you don’t need a framework to create basic stuff). Programmers tend to overcomplicate things, considering that if it’s not complicated then it doesn’t worth their attention. So, my advice is: Make sure you really need those modules or libraries you think you need, before implementing them. 🙂 I won’t do a tutorial about hmvc as I don’t really use it anymore (I am not in the habit of doing tutorials about things I do not use – this site is not a link baiter).
Lovely and useful tutorial…as usual your post are a great inspiration.
Thanks again for all your efforts and post