Make your own blog
Introduction
In this tutorial, I'll present how to write a simple blog system, using the PHP language and the Apache web server. For the database, we'll use SQLite, to keep things simple. Don't worry if these terms don't mean anything yet, that's okay. I'm assuming readers are familiar with how to use their computer, but perhaps have not programmed before. That said, even a beginner's tutorial can get non-trivial quickly, so if you are brand new to programming, you may wish to research unfamiliar keywords (usually on the PHP website).
The application we will build together was built prior to writing the tutorial. The text is therefore written around the way I think development happens in real life; some steps may introduce bugs, or be written in ways the purist might consider non-ideal. However, for the most part, these problems will be ironed out as we go. At the end of this project, we'll have a functional, maintainable and secure application, but there'll be much that can still be improved on (which will likely make for a subsequent tutorial).
If you are new to programming, it is possible that you might feel somewhat lost as you work through the material. Whilst any prior practice will always help, the before-and-after code samples should see you right through to the end, even if you don't initially understand every step. You should find that some of this will sink in, and will contribute to your future "aha" moment, even if it is daunting at the start. The elation of getting it working will, I hope, encourage you to dive even deeper into programming. Enjoy the ride!
A word about PHP tutorials on the web
To become proficient with a programming language, it makes sense to obtain your learning materials from as wide a source as possible. PHP owes its success, I think, to its very low barriers to entry, but that situation has given birth to plenty of tutorials on the Internet that make two fundamental errors.
The first is that the
mysql_
library is still in use (a library is just a package of useful functions). This has been a staple of database access in PHP applications for some ten years, but has now been superceded by newer systems. In particular, libraries such as PDO and mysqli offer parameterisation, a feature that helps prevent miscreants and ne'erdowells from cracking system security.The other problem I see a great deal of are tutorials that interleave business logic (what an application does) with layout (what an application looks like). This isn't wrong exactly, but having separation between the two does tend to make for systems that are more maintainable.
For your development environment, here's what you will need:
- The Apache web server
- PHP, any version after 5.5
- SQLite PHP module, for database access
- Any programmer's editor, ideally with syntax highlighting
The first three things can be installed in one go, by installing
XAMPP — just download and run the appropriate
package for your operating system. Note that if you use Linux, PHP and Apache may be installed
already; there is often a ready-made web folder at /var/www
from which you can run
your files. By all means use that, but if in doubt, XAMPP works on Linux too, and adds a
user-friendly control panel.
You'll also need a programmer's editor. There are many different ones available, and the one that you choose will depend on your preference as well as what you find works best on your system. I'll therefore mention several, in no particular order, so you can decide for yourself. In most cases you can download the appropriate installer, double-click it from your Downloads folder, and install as you would any software.
For convenience, I have supplied official download links for each product. If you like, you can verify these links by exploring the appropriate website and looking for the 'Downloads' section.
First up is Brackets, which is available on all platforms. Download here:
Next is Light Table:
If you are running Linux, have a look at Gedit, which is pre-installed on the Ubuntu and Mint distributions (and probably others too). It does syntax highlighting for PHP as standard.
If the above editors don't work on your system, or you don't get on with them, that's no problem — there's a substantial list of editors on Wikipedia.
In designing this tutorial, I wrote out a list of things I thought would be great to have, and then developed until I had a basic complete product. In retrospect, it would have been better to decide this prior to development; regardless of whether you are setting up a business or programming for fun, having a simple and achievable plan is miles better than an all-singing, all-dancing one that ends up abandoned half-way through (either through boredom, bankruptcy, or both).
Here's the initial list of features/technologies I put on paper:
Login, Logout, Add comment, Create user, Amend user, Delete user, Email validation for new user accounts, Display post, Accept markdown format, Create post, Edit post, Unpublish post, Delete post, Unit tests, List posts, AJAX posting/commenting, Database profiler, Internationalisation, Pretty URLs, Use template engine, Per-environment configuration files, Post pagination, Comment pagination, Unpublish comment, Delete comment, Foreign key constraints, Unique constraints, User access levels.
To illustrate how much an "initial working product" should trim the initial wishlist, here's what made it into the first version:
Login, Logout, Add comment, Display post, Create post, Edit post, Delete post, List posts, Delete comment, Foreign key constraints.
It's worth pointing out that, with any software project, rejecting a feature now does not mean you can never add that feature. It just means that you are prioritising getting important things done, so that the product is in a releasable state quickly. Once it is out the door, you can then find a few more items on your list to add into the next iteration. In a commercial situation, this approach is helpful because if your users don't like a change you've made, you've wasted two months rather than two years — a saving that could be the difference between steering things back on track, and going bust.
One of the other features of this tutorial is that it uses no additional software to get it working. Software projects nearly always make use of libraries, each of which offer some particular pre-written functionality. These are well worth using in proper development, since they are better-tested by more people than an individual can usually achieve on their own. However, for a tutorial I think they can add integration complexity, and explaining the advanced-level code within is usually quite a distraction. Thus, I have eschewed libraries more than I would normally.
So, let us get started. I'll assume you know how to create, edit, save and delete files in your
editor, and that you can create folders as required. This might be in the root of your "htdocs"
or "www" folder, depending on what you used to install Apache. You can use a subfolder of "blog"
if you wish, or for more advanced users, feel free to set up a new vhost. So, when I use
http://localhost/
, you can assume I am referring to the root of your project, even
if your URL is somewhat different.
Let us first check that your server installation is working okay. Create a file called info.php in the root of your project folder:
- /info.php /info.php
<?php phpinfo();
Then, run this in your browser, by accessing the URL http://localhost/info.php
.
You should see a PHP configuration page. If you do, then check you are running a version later
than 5.5; if it is earlier than this then
you will need to upgrade. If you don't see this screen at all, then check that you have created
the file in the right folder.
It is also worth ensuring your PHP installation has the necessary database support. Double-check
this by searching the list of modules until you find the two modules highlighted in the
screenshot below: PDO
and pdo_sqlite
.
It may help to know that the modules are listed in alphabetical order:
Once you have this showing an acceptable version number and the required modules, you can delete this file.
Here's the first bit of code to add:
- index.php index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<head>
<title>A blog application</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
</head>
<body>
<h1>Blog title</h1>
<p>This paragraph summarises what the blog is about.</p>
<h2>Article 1 title</h2>
<div>dd Mon YYYY</div>
<p>A paragraph summarising article 1.</p>
<p>
<a href="#">Read more...</a>
</p>
<h2>Article 2 title</h2>
<div>dd Mon YYYY</div>
<p>A paragraph summarising article 2.</p>
<p>
<a href="#">Read more...</a>
</p>
</body>
</html>
This is the initial file, so all we have to do is copy and paste it in. To do so, create
index.php in the root of your project folder, open it in your
editor, and ensure it is empty before pasting in the code. Then visit
http://localhost/index.php
in your browser, to ensure that you can see the
initial mock-up of our blog's home page.
If you can, then your first piece of coding is done! The language is HTML, which is understood
by your web browser as a way to describe documents on the web. HTML is not actually a
programming language: it is properly referred to as a markup language. The markup itself
is made up of tags such as <body>
and
<h1>
, each of which has an opening tag (without the slash)
and a closing tag (with the slash).
These
are nested to form a hierarchy in the same way as an ordinary letter is hierarchical: instead of
an envelope containing a document containing headings and paragraphs, we have an
<html>
containing a <body>
containing
<h1>
and <p>
. Easy peasy!
What do the tags mean?
DOCTYPE
: this tells the browser to expect a particular dialect of HTML — in this case HTML5html
: this is the "envelope" for the whole document. It acts as a container for everythinghead
: this contains stuff about the document, such as the titlebody
: unsurprisingly, this contains the main part of the documenth1
,h2
,h3
: title headings of decreasing importancep
: a paragraph of textdiv
: a "division", i.e. a sectiona
: a hyperlinkWe'll use a few more as we go on, but those are the main ones.
Let us start improving our skeleton home page. One of the observations we can make is that there is a fair bit of repetition: there are two articles that both have a heading, date, summary and hyperlink. Since this is just test data, let us add a loop around the first article block and delete the second one.
Here is how I am presenting the changes in this tutorial. This will be good practice for you, since all changes will be shown in this way. The approach of showing code changes like this is known as a diff, for obvious reasons (though the diffs I use are more colourful than the usual monochrome versions).
- index.php index.php
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<h1>Blog title</h1>
<p>This paragraph summarises what the blog is about.</p>
<h2>Article 1 title</h2>
<div>dd Mon YYYY</div>
<p>A paragraph summarising article 1.</p>
<p>
<a href="#">Read more...</a>
</p>
<h2>Article 2 title</h2>
<div>dd Mon YYYY</div>
<p>A paragraph summarising article 2.</p>
<p>
<a href="#">Read more...</a>
</p>
</body>
</html>
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<h1>Blog title</h1>
<p>This paragraph summarises what the blog is about.</p>
<?php for ($postId = 1; $postId <= 3; $postId++): ?>
<h2>Article <?php echo $postId ?> title</h2>
<div>dd Mon YYYY</div>
<p>A paragraph summarising article <?php echo $postId ?>.</p>
<p>
<a href="#">Read more...</a>
</p>
<?php endfor ?>
</body>
</html>
The rules for making these changes are simple: the code on the left shows the old file, and the code on the right shows the new one. Also, red lines are deleted, green lines are added. So, the two red blocks on the left are in the existing file, and can be removed. The green block on the right should be added. Where you see a solid horizontal bar across the whole diff, that's just a sign that some identical lines have been missed out for brevity.
Thus, if you wish, you can delete both article blocks and replace them with the new loop block
(containing the for()
through to the endfor
). However, the way this was
actually written was as follows:
- Delete the second article
- Insert the start of the
for()
loop before the first article, on a new line - Insert the end of the loop (
endfor
) at the end of the first article, on a new line - Indent the contents of the loop (in most editors, you can do this by selecting the article markup and pressing the Tab key)
- Finally, the two snippets of PHP to output the
$postId
are inserted into the article
In summary, it doesn't matter too much how you apply these changes (or, indeed, any changes in this tutorial). That said, understanding the way in which changes are written in real life may help towards a greater understanding of the code. Don't worry though if you think you've made a mistake; the full file for each change is available above each one.
What are loops?
The
for()
andendfor
you have seen form a loop block. This is a way of telling PHP that you want the enclosed code to be run a number of times. In this case, it sets a value to 1, and carries on looping whilst that value is less than or equal to 3. For each iteration of the loop, the value is incremented by 1, which is carried out by the++
operator.
So, finally, refresh your browser screen, and make sure your changes work. You should now see three articles, each with a different heading and paragraph.
The next step we will take is to set up the database. We'll use a database system called SQLite (pronounced "see-kwel light") as it is really easy to connect to, and does not require a database server. Create the following two files in your project, paste in the contents, and then we'll discuss what they do.
You'll notice that some changes in this tutorial — including one of these files, data/init.sql — has a name that contains a forward slash. This means that the file is stored in a sub-directory, and that you'll need to create that manually if it does not already exist.
- data/init.sql data/init.sql
- install.php install.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* Database creation script
*/
DROP TABLE IF EXISTS post;
CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
title VARCHAR NOT NULL,
body VARCHAR NOT NULL,
user_id INTEGER NOT NULL,
created_at VARCHAR NOT NULL,
updated_at VARCHAR
);
INSERT INTO
post
(
title, body, user_id, created_at
)
VALUES(
"Here's our first post",
"This is the body of the first post.
It is split into paragraphs.",
1,
date('now', '-2 months')
)
;
INSERT INTO
post
(
title, body, user_id, created_at
)
VALUES(
"Now for a second article",
"This is the body of the second post.
This is another paragraph.",
1,
date('now', '-40 days')
)
;
INSERT INTO
post
(
title, body, user_id, created_at
)
VALUES(
"Here's a third post",
"This is the body of the third post.
This is split into paragraphs.",
1,
date('now', '-13 days')
)
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?php
// Get the PDO DSN string
$root = realpath(__DIR__);
$database = $root . '/data/data.sqlite';
$dsn = 'sqlite:' . $database;
$error = '';
// A security measure, to avoid anyone resetting the database if it already exists
if (is_readable($database) && filesize($database) > 0)
{
$error = 'Please delete the existing database manually before installing it afresh';
}
// Create an empty file for the database
if (!$error)
{
$createdOk = @touch($database);
if (!$createdOk)
{
$error = sprintf(
'Could not create the database, please allow the server to create new files in \'%s\'',
dirname($database)
);
}
}
// Grab the SQL commands we want to run on the database
if (!$error)
{
$sql = file_get_contents($root . '/data/init.sql');
if ($sql === false)
{
$error = 'Cannot find SQL file';
}
}
// Connect to the new database and try to run the SQL commands
if (!$error)
{
$pdo = new PDO($dsn);
$result = $pdo->exec($sql);
if ($result === false)
{
$error = 'Could not run SQL: ' . print_r($pdo->errorInfo(), true);
}
}
// See how many rows we created, if any
$count = null;
if (!$error)
{
$sql = "SELECT COUNT(*) AS c FROM post";
$stmt = $pdo->query($sql);
if ($stmt)
{
$count = $stmt->fetchColumn();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Blog installer</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<style type="text/css">
.box {
border: 1px dotted silver;
border-radius: 5px;
padding: 4px;
}
.error {
background-color: #ff6666;
}
.success {
background-color: #88ff88;
}
</style>
</head>
<body>
<?php if ($error): ?>
<div class="error box">
<?php echo $error ?>
</div>
<?php else: ?>
<div class="success box">
The database and demo data was created OK.
<?php if ($count): ?>
<?php echo $count ?> new rows were created.
<?php endif ?>
</div>
<?php endif ?>
</body>
</html>
This change comprises of two new files, a SQL file and a PHP file. I'll discuss the SQL file first.
What is SQL?
SQL is a programming language for databases. It has been mostly standardised between different database systems, so it looks nearly the same regardless of whether you are connecting to MySQL or PostgreSQL or (as we are) SQLite. We will use it to set up our database initially, save information to our blog, and query information from our blog.
Broadly, databases are stored in terms of tables, columns and rows. Each
different kind of data we wish to store needs its own table, but to start with, we only have one:
a post. The CREATE TABLE
statement specifies which properties we want a post to have,
namely:
- id: a unique identifier to help us tell posts apart
- title: the heading
- body: the main text of the article
- user_id: which user created the article (we'll use this later on)
- created_at: when the article was created
- updated_at: when the article was updated
The column id
has two particular features:
- We declare it as a
PRIMARY KEY
in order to mark it as the predominant unique identifier for the row. This makes it suitable to refer to the row from another table (which we will do later on) - We specify the
AUTOINCREMENT
option, so unique values are generated for us by the database server automatically
A blog post is therefore just a row added to the posts table, since each row has space to store
all of these values. Once the table is created, we also create some dummy article data, using
INSERT
. The format of this command, as used in the SQL script, is simply thus:
INSERT INTO table_name (column_1, column_2, ...) VALUES (value_1, value_2, ...);
Now, the SQL file won't do anything on its own: we need a program to send the commands to the database. This is where the install.php file comes in; we'll run that whenever we want to set up the blog (or to wipe it and start again). I'll describe its broad features here, but do also read the comments in the code.
The first half of the code is written in PHP. Here, we use a series of if()
statements to ensure everything goes smoothly. The variable $error
is set to an
error message if something goes wrong. Here are the steps we take:
- Ensure the database has not been created in the data.sqlite file. If it has, we require the file to be deleted manually, so that it is harder to accidentally overwrite data.
-
SQLite will work fine with just an empty file, so we create one with
touch()
and report if there was any problems doing that (such as being disallowed by file system permissions). -
We then read the SQL script in using
file_get_contents()
and report an error if the file cannot be found (this is unlikely, but it is good practice to check everything that can go wrong!). -
We then try running the SQL commands using
$pdo->exec()
and report any issues with that. - Finally, we count the number of post rows we have created.
The second half of the file (from <!DOCTYPE html>
) presents the results of
the script, in HTML.
So, if you have not already tried it, visit http://localhost/install.php
in your
browser, and make sure some posts are created. If you want to re-run it, delete the data.sqlite
file manually and refresh your browser page.