How to Create a Forum in PHP from Scratch

In this tutorial we’ll see how to make a forum using PHP and MySQL. We have to cover a lot of different things, so let’s start!

Some details about this tutorial

You can download a compressed folder with the whole project inside. So sometimes I won’t show all the code of a file in order to focus on the important parts of the project.

The code I’ll show is exactly the same than the one you can download, except for some comments. In the original project you’ll have everything well documented (using phpDocumentor).

I’ll ommit the <?php and ?> tags in the tutorial, but every time you type PHP code in a file you should put it between them.

This project doesn’t follow a MVC pattern, but we’ll use classes and try to separate the different functionalities.

What and how

What are we going to do? We have to think the answer very carefully, and not only “a forum”, also the functionality we want to offer.

Answer: We are going to develop a forum where anyone can write a post without registration and other people can see it and write a response.

How are we going to do that? Here we have to think about how we are going to organize the project, the files..etc

Answer: As you know we are going to use PHP and MySQL. The files are going to be organized by the code they implement, so a class file will be in a different folder than a CSS file, for instance.

Designing and Building the Database

When we start to code a project, the first thing we usually do is to think about the database. Because that decision is going to be very important, and a lot of the PHP code depends on it.

There are many options here, we could make a table of posts and another of users, even three tables if we want, but we’ll take the easiest way in order to focus on the PHP code.

This table is all we need, at least for a simple forum like ours.

So a thread is going to consist of several rows in the table. The first post of a thread is going to have the permalink_parent set to NULL, and the rest of them are going to have the permalink_parent with the permalink of their parent, as the name suggests.

To create the table we have to execute the following sentence in our database:

The lengths of some fields depend on how many people are going to use the forum, but these values are enough for the moment.

Note that we are defining an UNIQUE KEY over the permalink column, which means that in our forum a title must be unique, I know we usually can post a couple of threads with the same title in other forum, however for the sake of simplicity we are keeping the title as unique.

File Organization

As well as designing the database we need to think about how we are going to organize the project, instead of starting to code without a previous plan.

The title and permalink are going to be unique, so we have to check if a title is taken.

At this point we can also take different approaches, we should try to keep things as simple as we can. So this is a little schema of our final project.

As you can see there are many files, but all of them are necessary if we want to keep things organized. Let’s see them step by step, and try to get the techniques we’ll use.

The media folder

This tutorial is focus on the PHP side, so I won’t explain too much about the CSS and the HTML code, you can download all the code at the end of this tutorial, so don’t worry. Anyway I’ll try to summarize the content of this folder.

style.css. It’s the CSS file for all the forum, includes the reset CSS code and the rest of the styles like a regular CSS file.

bg_container.png. The background of the main container.

bg_body.png. The background of the body element.

The Configuration File

This file is going to be useful from almost every script of the project. There are different options to implement it, but probably the most used is just a file with some define() lines, like this:

We are loading the jQuery at the bottom of our page, which is a good practice, because the rest of the site will be rendered before we execute any jQuery code.

The jQuery code is basically to improve the interface, so when a user clicks on an input field all the text will be selected. The if works in this way: when we see the p element (in #message_header) has something, then we hide it with the slideUp effect after 5 seconds. You can see this code working in the live demo after you try to post a message with an empty field or if the title you have selected is already taken.

The difference between require and include is the include() construct will emit a warning if there is an error (script goes on) and require() will emit a fatal error (script stops). As you may guess, adding _once to those statements we prevent the file from being included twice. Let me show you the final result with something very simple like this:

Great! Now we have our “template” and all we need to output will be shown between the header and the footer code as long as we include these files.

The Database class

In order to separate the PHP code that is going to execute SQL queries and the rest of PHP code we’ll make a database class, this class will connect to the database, get some rows and save data.

If we think about it, if an object always need to connect to the database, the connection code must be in the constructor. So every time we create an object that object is going to be ready to execute queries with the function mysql_query().

Also, the methods we’ll need are going to depend on the rest of code, but here I show you the beginning of the class as it will be in the end of the development:

The different numbers we return in the savePost method have a meaning (we’ll see it later), but, basically we have to recognize different kind of errors to show a different message each time, so we can’t return just false, and the use of exceptions could be complicated for the beginners.

This is actually a classic approach to manage the database from the PHP code, we’ll see at the end of the tutorial what people are using now and why we should use it too. But this class is enough for us and I assume readers know a little bit about PHP probably also know this way to connect to the database and manage queries.

Validation Helper

This script (helpers/validation.php) is just a group of functions we’ll use all over the project. There are 3 functions here:

String to Permalink (strToPermalink)

Takes a string as input and creates a human-friendly URL string. Its implementation is:

To sum up all we do here is to remove “weird” characters like á or é and others, and then convert -, spaces and others into _. (Permalink is also known as slug or even permanent link).

Clean input (clean)

Here we take care of removing slashes if magic_quotes_gpc is enabled and escaping some characters with mysql_real_escape_string. Don’t worry if you don’t know what magic quotes are or why we code this function, when we talk about security you’ll understand it.

Process post (processPost)

This function has a lot of lines (compare to the rest of this file), I’ll try to explain all of them without code:

Prepare the array to be returned with all values initialized as NULL

If it has everything in the array to start, then checks if the Recaptcha answer is OK, generates the permalink, tries to save the post and returns a message.

If the input array is not empty but it hasn’t got all we need OR the Recaptcha input field is wrong, then it returns an error message.

Finally, the showBox variable in the array is set. The box we’ll be shown if there was an error, or always in case we are in the page where users can write a response.

I’ll explain Recaptcha later, now let’s focus on the rest of the code. The array it will return is initialized in this way:

/* This is not neccesary but it makes code cleaner,
and we could change this new array and without modifying
the original $_POST */
$input = $_POST;
$returnArray = array('errorHTML' => NULL,
'okMessage' => NULL,
'recaptchaError' => NULL,
'showBox' => NULL);

The htmlspecialchars function provides a way to change some HTML elements into entities, so every time we are going to output something from the database we should use this function. We’ll se more about this later.

The next method we’ll code is composeTable, and it will be useful to show all the posts of a thread.

If we call the method from view_thread.php it means we are in a thread that doesn’t exist, so we show a different message in that case.

Now we need to create a form for users who want to send new posts (responses or the beginning of a new thread).

Recaptcha or how to avoid spam

At this point we have to think about the spam problem. As you can see there is no registration process so anyone can just fill the form and post a message with the content they want every time they like. How to handle the input will be discussed later, but the problem here is a bot could fill the form and submit it.

How can we avoid that? Well, the easiest and probably most used solution is a captcha system. Yes, those boring images of characters we have to identify and replicate. Specifically the implementation bought by Google, Recaptcha.

But what are the guarantees? Well, Twitter uses it in the sign up process and if we forum doesn’t grow too much (actually like Amazon or Google) we won’t have more problems. The question now is: how can we use it?

Recaptcha provides a file (in our project is helpers/recaptcha.php) and there we have all the functions we need, actually even more. You can see the documentation at the the end of the tutorial.

Finally, the other two methods are messageBox and buttonPostThread. The first one will return a string with the HTML needed to compose a post (a form) when we need to and the second one just returns a div to go to post_message.php and create a new post.

The $new parameter indicates if the form will be shown in post_message.php to send a new thread or in view_thread.php (to send a response). This will helps, for instance, to ask for the title of the thread if it’s going to be a new one or not.

The $recaptchaError is what we need to render properly the box with the captcha and the input, because in case the user types wrong the code the recaptcha_get_html will take care of showing a message.

Finally, the $errorHTML could contain an string of an error and we have to show it here. (We’ll see later what kind of errors could happen).

The last method will be buttonPostThread, and as I’ve said before its only functionality will be to return a string, but the reason we make it is to keep consistent the idea of not writing HTML code in some scripts like index.php or view_thread.php, besides of that the code of these pages will be very clear if we keep that idea in mind.

Probably you are thinking: “why all this code if we haven’t written a script which shows anything directly?”. And it’s OK you wonder that, but now you will see the advantages of having a modular and independent code.

Home page

Let’s start with our first page, index.php. These are the things we have to show:

Create a new post

The post_message.php script is going to show a form, get the input and try to process this input as a new post. If everything is ok we’ll show the link to the new post, otherwise we’ll show an error message.

The processPost function is implemented in helpers/validation.php and, as I’ve explained before, processes the input, tries to save the post and return an array with different values. This is the output in Firefox:

View Thread

The view_thread.php script will combine a couple of things.

We have to get some posts, the initial one first, and show them properly.

The form to write a response is going to be almost the same than the one we did to write a new thread. (Hint: We’ll use the same function).

The first thing we do is to include all the files we need and clean the input ($_GET) with a function (implemented in the helpers/validation.php file).

MySQL from PHP alternatives

The reason I’ve used these functions is because they are well known by the most of novice PHP developers, but you should know these functions have lost their popularity. I’ll explain you why.

Actually, there are many reasons, the mysql functions provide a procedural interface, doesn’t support a lot of things in the new versions of MySQL (newer than 4.1.3) and have more security problems. So take a look at the alternatives:

MySQL Improved (mysqli)

This extension was developed to take advantage of new features found in MySQL systems versions 4.1.3 and newer. Some features are:

Object-oriented interface

Support for Prepared Statements

Support for Transactions

…

Apart from that is more secure than the mysql classic extension because we can use bound parameters (details in links at the conclusion).

PHP Data Objects (PDO)

The main difference is this API can be used with any kind of database (in theory), that means it doesn’t have to be MySQL. So the change of the database system in the project it’s going to be easier.

But like yin yang there is a disadvantage, some advanced new features of new MySQL versions are not supported. Anyway is usually better use this or the previous alternative than the classic mysql extension.

Comparative from official documentation

The details of how to use mysqli or PDO are in the official documentation.

The security issue

First of all I have to say I’m not a security expert but if you are going to make a web app you need to know the possible security holes, and try to prevent attacks, not being a hacker doesn’t mean you don’t need to know about security. So let’s see the most common attacks in a web app, and how to prevent them in our forum.

Cross Site scripting (XSS)

XSS exists when your PHP code outputs some data that has neither been filtered nor escaped. Some code like this is vulnerable to XSS:

echo $_POST['var1'];

That means someone could send whatever they want in a form and will be shown later, and that’s very dangerous. The simplest thing they can send is:

alert('This will be shown instead of a real var1!!')

So the the code will be executed by the browser. A more dangerous use, could be extract cookie content with javascript and log in a system using that cookie. So, now we have understood it’s dangerous, how can we prevent it?

We have to escape data which comes from users. In this forum we have made a couple of things. First of all we strip the HTML and PHP code with strip_tags(), and when we show data from database we escape using htmlspecialchars(). Let’s see some code:

As you can see the script tags are not shown so the alert will not be executed. But Hercules wanted to show his name with strong tags, and actually it’s harmless, but we removed those tags too. A possible solution is to provide a list of our own tags and then replace them with the actual HTML elements. We haven’t implemented this, but it would be very easy:

The other thing we do is to use htmlspecialchars(), this is redundant because with strip_tags HTML code will be removed and not inserted into our database, but there are some characters which do nothing so we can convert them into HTML entities. Remember it’s usually better to a be a little bit paranoid when we talk about security.

So the WHERE condition is always true because (1=1) is, and (false) || (true) is also true. Consequence? The query selects all the records.

A simple way to prevent this is to use the function mysql_real_escape_string() which escapes special characters in a string taking into account the current character set of the connection so that it is safe to place it in a mysql_query(), as we do in the clean function (helpers/validation.php file).

But still there could be a problem with slashes and magic_quotes. To summarize magic_quotes is something annoying and we have to deal with it. Magic quotes is a deprecated PHP configuration that, when turned on it automatically escapes characters for you.

The problem is sometimes we don’t want that, so in our forum when we clean the input we use stripslashes() in case magic_quotes is turned on, which un-quotes a quoted string, and from that point we escape the data. (see clean function at the beginning of this tutorial). So with this implementation we can insert O’connor, and it won’t be a problem, without this consideration, the input could be inserted as O\’Connor into the database.

There could be a problem with the magic_quotes_sybase configuration, but, apart from being deprecated, it’s turned off by default so usually it won’t be a problem, and in our forum we don’t deal with it.

If we had used other alternative to manage the database from PHP, a lot of these precautions wouldn’t be necessary, you have more details in the official documentation.

Cross-Site Request Forgery (CSRF)

According to Wikipedia, is a type of malicious exploit of a website whereby unauthorized commands are transmitted from a user that the website trusts. But we don’t have to worry about that since our forum is open to anyone who wants to write and there aren’t sign up and log in processes. Anyway at the conclusion you have some links in case you want to know more about this exploit.

How to set up your own forum

When you download the code, you have to follow the next steps to have your own forum working:

Create a database with the SQL query at the beginning of the tutorial.

Change the values of constants in the config.inc.php file with yours.

And, of course, put the files where you can access with PHP and Apache working (localhost’s directory).

Conclusion

I have to admit this tutorial is long but if you read it a couple of times and take a look at the code I’m sure you’ll be able to understand all the project.

The organization of code in classes and files is up to the developer. And as long as we don’t write the same code over and over again, or just type queries, html and PHP code in the same file, the project will be ok. I mean, everything about make more or less classes or create a specific function is not a science. It’s more a design problem, so feel free to think about other ways to develop this forum, it will help you to be a better developer.

For instance I could have written a post class, or make everything procedural, but this was just an approach, and you can see the power of this in how many lines we have written in our index or post_message web pages. In addition, if we need more features the development will be very easy since everything is well documented in the files you can download.

There is other feature we could implement, to set the value of the inputs to the content of $_POST if it’s received, but that could make the code the code less clear, so I’ve avoid it in order to make things easier.

Here you have some links to some sites where you can learn more about security, PHP and MySQL.

This entry was posted
on Monday, August 9th, 2010 at 10:46 and is filed under Tutorials.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback
from your own site.

I'm from Spain. I've been working in an Internet company for more than a year, I love everything about Internet, from designing to PHP.

There’s just one thing I’d like to mention.. your use of set_error_handler is a somewhat poor choice.

It is assumed in the code that the errors are only caused by your MySQL related code. However, set_error_handler places a *global* error handler, which means that any code causing an E_WARNING level error will produce output which claims there’s a problem with the MySQL connection.

It would be a better idea to handle mysql errors on a case by case basis (check return value of call)

set_error_handler saved me more explanations, but you’re right, any warning error will pass through that function, which couldn’t be enough clear.

About using die(), well, it’s because the same reason, if I had used exceptions I’d have to explain them, making the tutorial longer. You can see a lot of examples in the official documentation using die(), instead of something more correct, probably for the same reason.

yes, that would be better, but if I’d built an universal class, the code would be longer, since I had to pass more parameters to the methods in that class, and the tutorial is long enough for a beginner.

Anyway it’s useful you note that, I’ll take it into consideration for future tutorials.

First off, thanks for this. I had been looking for something like this for weeks. I tried to go in and remove all the Recapchta requirements and I believe I found the majority of them. The issue I’m having is that when I submit a post nothing happens. I just get a blank screen. I think it has something to do with the processPost() because there was quite a bit of recaptcha stuff in there. Do you have any idea what I could check? Thanks.

I’ve got everything to work, but I have one question. If you enter your comments and then enter reCaptcha wrong, everything you entered in the comments field will be lost. Are there any solutions for this problem?

I have a forum, I would like to understand the programming, and I love the step by step with explanation… but, WHERE IS THE COMPRESSED FOLDER WITH THE WHOLE PROJECT INSIDE? The link to the “Take the source files” only reloads the page.

Trackbacks

Leave a Response

We do love friendly, well-constructed and respective comments. We do not love bitchy, stupid and disrespectful comments. Find something wrong in this post?, feel free to send us a note and we will fix the issue asap.