Lesson 2 - .htaccess, autoloader and the common controller
In the previous lesson, MVC architecture explained, we introduced you to the MVC architecture. We now know that the website we're building will consist three kinds of components: controllers, models, and views. In today's tutorial, we'll prepare the environment. Starting with the .htaccess file, after which we'll configure PHP in the index.php, and create a parent for our application's controllers.
You're going to need a working local server (see Installing Apache + MySQL + PHP on Windows and making an app), which I'm sure you already have at this point. Now let's move to the localhost folder (in XAMPP it's c:/xampp/htdocs by default) and remove everything that's currently in there.
Warning! It's crucial that you place the system right in this root folder, if you put it in a subfolder, it won't work!
The reason behind this is nice-looking URLs (see further). If you have multiple projects on a localhost, you can make sub-domains for them.
Directory structure
Let's create three folders for each kind of application component. Just to be clear, the folders will be named controllers, models, and views. We'll also create .htaccess and index.php files, here, in the root directory.
.htaccess
I'm sure you're already familiar with the .htaccess file. It's an Apache web server configuration file, which specifies what happens when a user enters a URL address. This process happens before the PHP language is even executed.
Now, let's create said file (don't forget the dot at the beginning of the file name) in the root folder. A colleague of mine once said: "Just like I don't want to understand women, I don't want to understand .htaccess". Configuring Apache is a fairly complicated process, so don't worry too much about it, just remember what it is and does.
First of all, we'll disable indexing directory contents. This way, files won't be listed if a user types in a directory path in his browser. Add the following line to your file:
Options -Indexes
Pretty URLs
A classic PHP address looks something like this:
http://wwww.domain.com/index.php?article=article-name¶meter=value
Kind of ugly, isn't it? We'll use so-called pretty URLs in our application tp make things look nicer. The same exact address will look like this:
http://wwww.domain.com/article-name/value
This way of doing things looks much better. We'll let PHP handle the URL processing, so we'll "disable" it in Apache and redirect all requests to index.php (where we'll handle URLs manually). Otherwise, Apache would treat "article-name" like a folder name and keep looking for the "value" subfolder and for an index file in this non-existent directory. We'll turn the rewrite engine on and add a rewrite rule, where we tell it that we want to redirect everything except certain extensions to index.php:
RewriteEngine On # RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(css|js|icon|zip|rar|png|jpg|gif|pdf)$ index.php [L]
The "#" character is used to comment things out, so the "RewriteBase /" command will not be executed. We commented it out because we don't need it on localhost but some production web servers (when we upload the site to the internet) won't work without it. When you upload the completed website to a server, try it both ways and see what works
RewriteConds makes it so that no redirection is made when a file or folder exists. For example, if we were to open:
http://wwww.domain.com/file.txt
The file would be downloaded (if it exists), otherwise, we'd be redirected to the index. If we redirected in all cases (without said conditions), we wouldn't be able to download anything and the index would be displayed no matter what. We made sure we only redirect when it's not a file URL by listing several of the most common file extensions. Any other URL will be redirected to the index.
As the last thing for the .htaccess file, we'll process the .phtml extension in a way that makes it impossible for anyone to view our templates' source code. .phtml files behave just like .php files. Go ahead and add the following line:
AddType application/x-httpd-php .php .phtml
That's it, moving on!
index.php
Now, create the index.php file. All of the communication will take place here, as well as all of the URL address redirections.
We'll develop the entire website using the UTF-8 encoding, so set it as the default encoding in your editor. If you're using an IDE (PHPStorm, Netbeans, Eclipse), it should already be set by default. If your editor says something about BOM, don't worry, we won't be using it (UTF-8 encoding without BOM).
First of all, we'll add the <?php directive and set the internal PHP encoding to UTF-8. This will make PHP string functions work properly:
<?php mb_internal_encoding("UTF-8");
Autoloader
We'll create several instances of various classes in our system. As you may already know, it's very uncomfortable to require/include these classes manually. For this reason, PHP lets us register a function that is called every time an unloaded class is used. We'll load the class in said function so that classes are loaded automatically when we need them.
In fact, we'll load two types of classes: models (from the models folder) and controllers (from the controllers folder). Our views will not be classes, they'll look more like HTML pages. Our auto-load function will have to determine whether the class being loaded is a model or a controller and look through the appropriate folder based on that. Now, how could we accomplish this?
Easy, we'll simply have the controller class names end with "Controller". The autoload function could look something like this:
function autoloadFunction($class) { // Ends with a string "Controller"? if (preg_match('/Controller$/', $class)) require("controllers/" . $class . ".php"); else require("models/" . $class . ".php"); }
The first condition uses what is known as a regular expression, which determines whether the class name ends with the "Controller" substring. If so, it loads the file from the controllers folder, otherwise, it looks for the file in the models folder.
Now, we'll register the function so that PHP executes it as an autoloader:
spl_autoload_register("autoloadFunction");
Now that all of that is done, when we add controllers, we'll be able to work with them regularly (without any includes/requires):
$userManager = new UserManager(); $router = new RouterController();
Since PHP hasn't loaded the UserManager.php class from the models folder and the RouterController.php class from the controllers folder, it'll call our autoload function. It will then execute the following command:
require('models/UserManager.php');
After which it would execute this command:
require('controllers/RouterController.php');
This way, we can just instantiate a class and not worry about anything else.
Remember, a class name has to match the filename in which it's declared, but I trust you all are beyond such obviously necessary practices.
Note: In very old versions of PHP, which you probably won't encounter nowadays, we didn't have the spl_autoload_register() function. We had to call the older __autoload() function, see the official PHP manual for more information.
Abstract controller
We'll also create an abstract parent class for out application's controllers. Now, let's create a Controller.php file. Don't forget to add the <?php directive at the very beginning.
The class will contain 3 properties. The first one will be an array with data, which will be used to store data retrieved from models. This array will be passed to a view later on, which will display the data to the user. This way, we'll pass data between the model and the view without breaking the MVC architecture. The second property will be a view's name (the one that is to be rendered). The last property will be a website's HTML head, which consists of 2 properties: a title and a description (every HTML page has these). We'll set default values for these properties as well.
<?php abstract class Controller { protected $data = array(); protected $view = ""; protected $head = array('title' => '', 'description' => ''); }
The class will have 3 methods. The first one will be the main method in which the controller will process the parameters. Each controller will implement said method on their own, so we'll mark it as abstract. We'll name it process(), and name its argument $params:
abstract function process($params);
The next function will render the view to the user. If a view is specified, we'll simply require it. However, before doing that, we'll extract variables from the $data array using the extract() function. All of the array indexes (keys) will then be available as ordinary variables in the template.
public function renderView() { if ($this->view) { extract($this->data); require("views/" . $this->view . ".phtml"); } }
Meaning that if we save a key like this in the controller:
$this->data['variable'] = 'value';
It would be accessible from the view like this:
<strong>The variable has a value of: </strong><?= $variable ?>
As you may have noticed, HTML entities are not sanitized. We'll fix that and more soon.
As the last method for the controller, we'll add a simple redirection method. One that sends a user to another page and terminates the current script. This one, we'll use quite a lot.
public function redirect($url) { header("Location: /$url"); header("Connection: close"); exit; }
In the next lesson, Router, we'll create our first controller, a router. Today's code is available in the attachment below, as always.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 648x (6.01 kB)
Application includes source codes in language PHP