back to contents

Web Dev: YOUR VERY OWN BLOG

what is this i don't even

this is a big bad example that culminates all that you've learned so far. put your thinking caps on. we're going to cover HTML, CSS, PHP, MySQL, AJAX, etc. (actually i'm not sure about javascript.)

we are going to make a blog ourselves. none of that wordpress crap. let's do this by hand. that's how cool we are.

what is blog? (baby don't hurt me)

A blog is just a place you write stuff on the web! I mean, that's mostly what it is, right?

the first step to making a blog - or any project! - is to seriously consider what it is

I'd argue that one of the paramount reasons you have a developer around is to think about the whole process as opposed to segmenting pieces of it to different people. Yes, you can do this with a project manager or a creative director, but far too often those people don't actually do anything. As a developer, you should be able to see the whole thing through, and cry your own tears and pour your own sweat into it.

(Not saying that it's the only way to do things - I'm just saying that it's what a good web developer should be capable of and why they're valuable.)

so back to the question, or more specifically, what makes up a blog? well...

So here we have some ideas about what makes up a blog. The pieces.

Now, let's refine it more. What parts are data and what parts are function and what parts are aesthetic? And how do they mesh together?

For example, a post is a piece of data. But it also will have a page for every post, so we need to consider what it'll look like. And how do we retrieve the post? That's a functional decision.

So break each piece down even further: a post, as data, has a title, the body content, an author, a date, and maybe some tags associated with it.

An author can have posts attributed to them, a name, a join date, maybe we hold onto their email address.

The first page we see would probably have a list of the latest posts - what could that look like? what data would we like to be on that page?

See how we need to break everything down? We haven't written any code, nor should we be thinking in those terms yet. The planning stage is merely conceptual.

the skill here is to employ critical thinking to break down a series of problems (in the form of feature-requests and concepts) into smaller, manageable parts.

starting with data

I always find the data to be the easiest to start with. (Well, maybe not easiest, but it's a useful foundation.) What's the data needed for this to work well?

A good exercise is to go onto your favorite blog (you have a favorite blog, right?) and see what data is available to you.

Let's think on the per-post level: you'll probably see a post title, a body of text, an author name, a date of publication, some tags, and some comments.

But how would we structure that in a database? Let's say we'll use a traditional MySQL database. What would that model or schema of information look like?

blog_db
posts_tbl
idtitlecontentpubdateauthortagscomments

Ok so that's a schema and it's fairly straightforward so far - but wait, that comments column doesn't really make sense. We'd make a separate table for those, right?

Here in this conceptual stage is when we try to break down everything into what will be discreet data models. posts is one model, comments is a separate one. the two are related, but they are not the same.

blog_db
posts_tbl
idtitlecontentpubdateauthortags
comments_tbl
idcontentpubdateauthorpost_id

So there we've separated them and created a relation between the comments and the posts by using the post_id in the comments_tbl. This is the process of normalizing the data.

We can already normalize it even more! Take out the author columns and replace them with an author_id and an authors_tbl

blog_db
posts_tbl
idtitlecontentpubdateauthor_idtags
comments_tbl
idcontentpubdateauthor_idpost_id
authors_tbl
idnamejoindateemail

Okay, but the tags column would be better served if we normalized that, too. What would we have planned on putting in that column -- a comma-separated list? No way, we can normalize it better than that.

blog_db
posts_tbl
idtitlecontentpubdateauthor_id
comments_tbl
idcontentpubdateauthor_idpost_id
authors_tbl
idnamejoindateemail
tags_tbl
idtagtextpost_id

Wow. Well, we just turned one table into four. Congrats. That's what normalization will do to you. So in terms of these tables, for every post, we'd expect one author, potentially multiple comments, and potentially multiple tags.

You could draw that out if you wanted. Literally draw a web of possible information. It's helpful to do so when you need to manage large amounts of possible schemas. Draw boxes for tables, and arrows to illustrate how they are related by what columns.

At this stage you can be even more picky about what data you want to include, but we'll just use this for now. This looks good.

Well - wait a minute. We want some way to make sure that only authorized people can make posts. How would we do that? One possible way would be to password protect the ability to post.

Go ahead an add one column to the authors_tbl called password. We'll talk about how to actually use this later. But for now, just know that we will treat an author as a user account on the site, and we'll store that information in the authors_tbl

thinking with functions

after you have a good idea of what data you expect to have, it's useful to think about how you'll need to use that data. on a blog, you can usually do the following with this data:

these are merely functions - they're operations - they're actions. when you actually write the code for them, each will probably be made up of several functions.

you could also refer to that list as a list of features. it's important that you never get too overburdened by a list of features. part of being a good web developer is being able to know what features are functionally important to demonstrate the site.

for example, maybe four of those are critical to really see a blog in action: the list of latest posts, an individual post, adding a post, and commenting on a post. those would be the core features to be concerned about first.

because when you think about it, once you've written how to add a post, the next step of editing a post will probably use the same form - just with pre-filled data.

also, when you think about it, searching, whether it's by author or tag, would use the same kind of general searching function, so writing one (searching by author) would make the other (searching by tag) easier.

that process - seeing what features are merely branches off of existing features - is key to narrowing down what you need to write first, and what's most important.

You'll be writing these functions in scripts. In this case, I'll use PHP as the example language.

how it'll look

now that you have a good idea of what will need to be presented and the actions a user will take on the site, you can begin to effectively design some pages.

and don't worry about making them super-pretty from the get-go, any good web developer will get the functionality done before really worrying about how fancy it looks.

Again, a developer doesn't toil over anything! Well, we do, two things specifically:

  1. think about what it is you're doing
  2. get it done.

ahahaha... anyway

open up notepad (or whatever, you may have moved on to use something like Espresso or Coda or TextMate by now (i use coda religiously)) and work out some functional designs in HTML and CSS.

Some pages will be purely functional so they'll just be PHP, but most will be a mix of HTML and PHP eventually. for now you can write your HTML/CSS and just inject the PHP later.

so what kind of pages do you need? sometimes designers like to think about what views you need, and what experiences the user will have. this is neat and sometimes useful.

for example, what's a page versus a view? a view can be a piece of a page, like an individual comment in a series of comments. how should individual comments look? that's a kind of view. or an individual blog post, which is both a view and a page, because while there will be an individual page per post, the post will also exist in a list of posts. it's important to realize this to keep consistency.

and experiences is what it sounds like: what do you want the user to do when they go to your page? do you want them to click on your first post? how do you increase the chances of that? (make the first post's title bigger than the others? make its background-color slightly different?)

anyway, think of the pages you need. they align kind of nicely with the list of functions:

Sketching out (with paper and then, in the next section, with HTML/CSS) these pages and views is important. Take your time in doing so.

so where to start building things

By now you should have a good idea about the data you'll use, the functions you need to make happen, and the pages they will be used on. I'd say start actually building things by writing some HTML and CSS.

Let's start with a basic list of posts. For now we should just use some dummy content that we'll replace later with actual data-retrieval. (Using "dummy data" or "filler data" is very common, just to sketch out an idea that looks real-world-ish.)

Call this file index.php and put it in a new directory on your web server. (You should have some kind of web server from the PHP guide.)

<html>
<head>
<title>our awesome blog</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<h1>our awesome blog!</h1>
<div id="posts">

<div class="post">
<h2>A post title!</h2>
<p class="small">by cyle, posted on 6-13-2011</p>
<p>post content goes here!</p>
</div>

<div class="post">
<h2>Another post title!</h2>
<p class="small">by cyle, posted on 6-12-2011</p>
<p>post content goes here!</p>
</div>

</div>
</body>
</html>

Okay so there's our index.php file. We'll end it with a .php extension because we'll put some PHP in there soon. We need the main.css file we mentioned in that code.

But first, notice the structure: we have an h1 tag which holds our blog's title. we have a div with the id posts that'll hold our individual posts. and then inside there we have individual divs for each post, each with the class post. this'll make CSS rules easy to do.

There's also a class called small for the attribution and date line under the individual post titles. here's what I'll do for CSS, but you could do anything you want:

* {
  padding: 0;
  margin: 0;
}
body, #posts {
  margin: 50px;
}
body {
  font-family: Verdana, Arial, sans-serif;
  font-size: 14px;
}
#posts .post {
  margin-bottom: 20px;
}
.small {
  font-size: 0.75em;
}

That's all I'll do... very simple. Notice some conventions you may not have known before:

Put those two files in the same directory if you haven't already. Awesome. If you load the page in your browser, it'll probably look pretty basic. That's fine. It looks good enough (I guess).

start that database

So now we need to set up MySQL with our databases as I described above. Can you do that? I hope so, but I'll show you some example queries anyway. Honestly, if you want to use phpMyAdmin to create the tables, I wouldn't blame you.

But here are the raw SQL statements, because I want you to understand how they work. Make sure you replace the password with an actual password.

CREATE DATABASE theblog;

CREATE USER 'blog_user'@'localhost' IDENTIFIED BY 'password';

GRANT ALL ON theblog.* TO 'blog_user'@'localhost';

USE theblog;

CREATE TABLE posts_tbl (
   id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   title VARCHAR(255) NOT NULL,
   content TEXT NOT NULL,
   pubdate DATETIME NOT NULL,
   author_id INT NOT NULL
 );
 
CREATE TABLE comments_tbl (
   id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   content TEXT NOT NULL,
   pubdate DATETIME NOT NULL,
   author_id INT NOT NULL,
   post_id INT NOT NULL
 );

CREATE TABLE authors_tbl (
   id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   name VARCHAR(255) NOT NULL,
   joindate DATETIME NOT NULL,
   email VARCHAR(255) NOT NULL,
   password VARCHAR(255) NOT NULL
 );
 
CREATE TABLE tags_tbl (
   id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
   tagtext VARCHAR(255) NOT NULL,
   post_id INT NOT NULL
 );

So that sets up the tables for you. Basically there's no data that is allowed to be null, haha.

From here you have two options... you can put some dummy data directly into the database, or you can build how you'll add posts as a page.

Let's go the first way. It's easier, I think. Use dummy data so you can at least establish the bridge between PHP and MySQL. Here's how we'd insert some data, the same way we learned in the MySQL guide:

INSERT INTO authors_tbl 
  (name, joindate, email, password) 
 VALUES 
  ('cool kid', NOW(), 'lol@whatever.com', SHA1('password'));

Ok so I'll stop right there and note that you're using the SHA1 MySQL function to store the person's password as a hash instead of in plain text. I mean, who would store people's passwords as plain text? haha. A hash is the least you can do to protect a person's password.

A hash function simply scrambles data so that it can't be read anymore. However, if you scramble the same string again, it'll come out with the same hash. It's a consistent one-way encryption. This'll come in handy later.

You're also using MySQL's NOW() function to get the current date and time. That's useful.

Anyway, continuing. I'm breaking these up into multi-line stuff so you can read it easier, but MySQL doesn't care, it'll understand it as only one line until it reaches the semicolon.

INSERT INTO posts_tbl 
  (title, content, pubdate, author_id) 
 VALUES 
  ('a really cool post!', 'soooo cooooooool!', NOW(), 1);
  
INSERT INTO posts_tbl 
  (title, content, pubdate, author_id)
 VALUES 
  ('this blog', 'is the freshest blog around', NOW(), 1);
  
INSERT INTO posts_tbl 
  (title, content, pubdate, author_id)
 VALUES
  ('welp', 'yet another tiny post', NOW(), 1);

That's it. We now have a database with one author and three posts in it. Now we need to use PHP to bridge the database into the page we've set up.

turn on the power

Let's put into index.php the means for it to get the latest posts from the database. This part of the process is us adding functionality to views or pages we've laid out.

it looks a little somethin' like this...

<div id="posts">

<?php


?>

<div class="post">
<h2>A post title!</h2>
<p class="small">by cyle, posted on 6-13-2011</p>
<p>post content goes here!</p>
</div>

<div class="post">
<h2>Another post title!</h2>
<p class="small">by cyle, posted on 6-12-2011</p>
<p>post content goes here!</p>
</div>

</div>

Or at least that sets the stage. The rest of the code will go between those <?php ?> tags.

mysql_connect('localhost', 'blog_user', 'password');
mysql_select_db('theblog');

So here you can see we're connecting to MySQL using the username and password we set up before. (Please tell me you didn't just use "password" as the password...) And then we select the blog's database.

$get_posts_query = mysql_query('SELECT posts_tbl.*, authors_tbl.name FROM posts_tbl, authors_tbl WHERE authors_tbl.id=posts_tbl.author_id ORDER BY posts_tbl.pubdate DESC');

That's the next line. It uses PHP's built-in mysql_query() function to make a query, which returns a Resource, which is a data type the same way Number or String is, which we capture within the $get_posts_query variable.

while ($post_row = mysql_fetch_assoc($get_posts_query)) {
  
}

Okay that's an empty while loop because I want to tell you about it first. What does this do? It'll keep doing the while loop until $post_row is false. And $post_row will be false when there's no rows left.

The mysql_fetch_assoc() function will get the next row and return it as an associative array, which is very useful. We give that function the query we ran so it knows what rows to fetch!

Okay, so inside the while loop:

while ($post_row = mysql_fetch_assoc($get_posts_query)) {
  echo '<div class="post">';
  echo '<h2>'.$post_row['title'].'</h2>';
  echo '<p class="small">by '.$post_row['name'].', posted on '.date('m-d-Y', strtotime($post_row['pubdate'])).'</p>';
  echo '<p>'.$post_row['content'].'</p>';
  echo '</div>';
}

Okay there are a few important things to digest here:

yes, you can digest all that from that small code snippet. it amazes me, too. if you understood all of it, then awesome! you're learning fast. if you don't get it, that's fine, because it's a lot of information. read through it again, and try to follow along in the code with the bullet points.

Once that's in index.php, upload the file, and go to in your browser.

if you go to the index.php page and see the three posts from your database, go ahead and delete the entries you had manually written in your index.php files.

So by the end, your php file should look something like this:

<html>
<head>
<title>our awesome blog</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<h1>our awesome blog!</h1>
<div id="posts">

<?php

mysql_connect('localhost', 'blog_user', 'password');
mysql_select_db('theblog');

$get_posts_query = mysql_query('SELECT posts_tbl.*, authors_tbl.name FROM posts_tbl, authors_tbl WHERE authors_tbl.id=posts_tbl.author_id ORDER BY posts_tbl.pubdate DESC');

while ($post_row = mysql_fetch_assoc($get_posts_query)) {
  echo '<div class="post">';
  echo '<h2>'.$post_row['title'].'</h2>';
  echo '<p class="small">by '.$post_row['name'].', posted on '.date('m-d-Y', strtotime($post_row['pubdate'])).'</p>';
  echo '<p>'.$post_row['content'].'</p>';
  echo '</div>';
}

?>

</div>
</body>
</html>

So we have PHP code inside an HTML structure. If you load that in your browser and look at the source, notice you'll never see the PHP code. Only the results of it! Magic.

reducing redundancy

I'll butt in right now with a quick tip: PHP makes it very easy to reduce redundancy, I think, simply by separating your most-used things (functions, etc) into separate files.

For example, mysql_connect() and mysql_select_db() will be used anywhere we want to do something with the database. We have two choices:

  1. write those two functions whenever we connect
  2. put those two functions in another file and just require() that file whenever we need it

The second option also has the advantage of keeping those functions in only one place. Imagine if we needed to change the name of the database. In the first scenario, we'd need to change every single place we put that function. In the second, we'd only need to change it in that one file.

So to do this, just start another file, call it dbconnection.php and put it in the same directory. It'll look like this:

<?php
mysql_connect('localhost', 'blog_user', 'password');
mysql_select_db('theblog');
?>

Now take out those two functions from where they were in the index.php file! Now you only need to require this new file, like so:

require('dbconnection.php');

Put that where the functions were, and use that line once in a file to gain access to the database (you don't need to add that more than once per file).

The require() function is built in to PHP and will make sure that file is loaded, or else it'll throw an error. Very useful. It only takes one argument: a string of the path to the file. In this case, it's in the same directory, so we only need to put in its filename.

welp. step two done.

Now you have a super simple blog. All it does is fetch the latest posts from a database. Step one was the concept stage, step two was breaking ground. It gets easier from here, I think. It's time we figured out how to add posts.

Let's make a new file called add_post.php (note that i'm really not going for any standardized file-naming convention here, usually somebody will come up with one. i really don't care.)

<html>
<head>
<title>posting adding mechanism page</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<h1>add a post!</h1>
<div id="add_post">

<form method="post" action="insert_post.php">
<div>Title: <input type="text" name="post_title" /></div>
<div>Content:<br /><textarea name="post_content"></textarea></div>
<div><input type="submit" value="add post" /></div>
</form>

</div>
</body>
</html>

That's pretty simple. We won't put any PHP in here yet. Here will just be the form.

Forms are HTML, and when you hit the submit button, it'll send the form data to whatever you specify in the form tag's action attribute. In this case, we want the form data to go to insert_post.php

Notice the other attribute of the form tag: the method attribute. This determines whether the form should send the data as GET or POST variables. Remember those from PHP? You're on the right track. Use POST, because it's more secure.

Using GET will put it in the next page's query string, which might not be compatible with your browser, and is just generally muddy unless you're doing it for a specific reason.

dealing with data

Now we have to actually insert this form data into the database. Let's start that insert_post.php file like so, just to demonstrate something:

<?php
print_r($_POST);
?>

Okay upload that and go back to the add_post.php page. Enter some stuff into the form and hit add post!

Oh dear, we see our data! The print_r() function (and the var_dump() function) is very useful because it shows you the contents of a variable. In this case, we want to see the $_POST superglobal variable. That variable holds all of the POST data that is sent to the script.

In this case, we've told the form in our add_post.php page to send its form data as POST data to the insert_post.php script. So in the insert_post.php script, we access that data through the $_POST variable. Get it? This just describes how data is handed between pages.

Each form field has a name attribute. That name attribute will be the name of the key inside the $_POST associative array. See? In this case, we named one field post_title and the other one post_content. The print_r() function will show these two keys and their values.

Cool cool cool. Let's actually handle the data. Delete that print_r() function call. First of all, we want to make sure the right variables are set. Let's start with making sure the post title is set:

if (!isset($_POST['post_title'])) {
  die('you forgot to write a post title!');
}

What did that do!? Let's break it down! First, it's just an if statement. The expression that it checks is whether the variable $_POST['post_title'] exists, that's what the isset() function does. It sees if a variable has been set.

But what about that exclamation mark before it? This is a shortcut that asks if the result is false. We could've written it this way:

if (isset($whatever) == false) { }

The ! before a variable or function does the same thing as that. It just checks to see if it returns false!

The die() function is as dramatic as it sounds: it stops the script dead in its tracks and prints out whatever is within the first argument.

So all we've done so far is check to see if the post_title key exists... but what if it's an empty string? It's still set, no matter what its value. So we should also make sure to check for that:

if (!isset($_POST['post_title']) || trim($_POST['post_title']) == '') {
  die('you forgot to write a post title!');
}

All we added is an or and another expression inside the if statement.

The two pipe characters || mean or! (Likewise, two ampersand characters && between expressions means and.) So in plain english this if statement is saying:

If the $_POST['post_title'] variable is not set, or if the trimmed value of $_POST['post_title'] is an empty string, stop everything!

We've successfully stopped someone from adding this blog post if it has no title.

Okay so... I want you to do the same thing for post_content... I'll wait. Haha. Make sure you change the text inside the die() function, too.

Moving right along: so now that we know the data is set, we want to add it to the database! But first there are a few things you should always do.

  1. make sure to require the database connection file! if you don't you'll get errors about PHP not even knowing what database to use.
  2. parse your data for viewing! this is subtle, but i'll elaborate. sometimes you won't have to do this.
  3. sanitize your data for the database! i'll tell you what this means. with MySQL and PHP, it's typically easy.

Okay so first we need to require() the dbconnection.php file. Do so, and put it after the two if statements.

Next we want to make sure the data is parsed if we need to parse it. In this case, I think we should, because right now you could write any text into those text input fields, and whatever comes out will be rendered.

So imagine this: if we put some <script> tags in there and put in some malicious code, when we view the blog post, it'll just run that code! That's not good. For simplicity's sake, let's say we don't want any HTML coming through those fields.

Luckily there's a PHP function to wipe out HTML tags and make them "inert". While we're at it, we can assign a variable the result, like so:

$post_title = htmlspecialchars(trim($_POST['post_title']));

All that did was first trim(), which gets rid of all accidental white space around the text, and then it htmlspecialchars() which replaces stuff like tag openings < and closings > with HTML entities. That way they won't render.

Honestly, we could do this on either end... meaning we could strip out these characters when you save the post or we could do it every time you display the post. For our purposes, it doesn't matter. I'd rather do it now. We can always convert it back later.

So now that that's done, we need to sanitize the data for MySQL, but what does that mean?

Simply put, if you don't do this, it becomes very easy to hack your website. (I don't want to get into the specifics of how here.) Sanitizing your data whenever you make a SQL query is essential. Just do it. Don't even think about it. It's pretty easy, too.

$post_title = mysql_real_escape_string($post_title);

That's it... all we need to do is use that PHP function to properly make sure that everything that could be malicious is not! Remember, do this before you enter data into a MySQL SQL query, never to the whole query.

Okay okay okay, we've now sanitized our post_title. Do the same thing for post_content! I'll wait.

Now to build the MySQL query. We want to INSERT the data into the posts_tbl, right?

$insert_post = mysql_query("INSERT INTO posts_tbl (title, content, pubdate, author_id) VALUES ('$post_title', '$post_content', NOW(), $author_id)");

A couple things to note:

yeah. so hold on a second, we've got a problem. how do we know who is submitting the form?

For now... put this above the query:

$author_id = 1;

We'll figure out how to track what user is doing things in a little bit.

Let's make sure that INSERT query worked out okay, we can use an if statement to check. The $insert_post variable holds the result of the query. It simply returns true or false.

if (!$insert_post) {
  die('something went wrong! '.mysql_error());
}

So that checks to see if $insert_post is false, meaning it failed. Then it kills the script, and tacks onto the end of the string argument whatever the MySQL error is with the mysql_error() function.

If the post inserted successfully, that if statement won't matter. And to finish it off, we want to take the user somewhere else when the process is done.

For this, I like to use the header() function. It sets the low-level HTTP header that the PHP script will send to the browser. That's complex but trust me... it's magic.

Just put this as the last line before the closing php tag:

header('Location: index.php');

That'll send the browser back to the index.php page when the script is done!

try adding some posts!

how magical is this!? it adds posts!

but we have a few missing features...

easy problem first: individual post pages

let's go back to our index.php and make it so our post titles are links to the individual post.

so we're going to change this line:

echo '<h2>'.$post_row['title'].'</h2>';

to this:

echo '<h2><a href="post.php?id='.$post_row['id'].'">'.$post_row['title'].'</a></h2>';

what does this add? a link that goes to post.php with the query string ?id=# with the # being the individual post ID.

so individual posts will be accessed through post.php - let's make it now!

the first thing we should write is kind of like what we wrong for the insert_post.php script, since we had to deal with incoming data. we need to make sure that data is correct.

<?php

if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
  die('no post ID to look up!');
}

?>

That does the simple job of making sure there is an ID to look up and make sure that it's a number. now let's actually look it up. Add this:

require('dbconnection.php');

$post_id = (int) $_GET['id'];

$get_post_query = mysql_query("SELECT posts_tbl.*, authors_tbl.name FROM posts_tbl, authors_tbl WHERE posts_tbl.id=$post_id");

So first we require() our database connection. In the next line we set a variable to the ID we got from $_GET. You'll notice that we use a thing called type casting to convert the $_GET['id'] variable into an integer. Type casting is just putting the type name in parentheses before a value.

And finally we query for the post which has that specific ID.

What's important to do next is actually see if it returned anything! We can do this by using the mysql_num_rows() function to see if the query returned anything.

if (mysql_num_rows($get_post_query) != 1) {
  die('there was no post with that ID');
}

this checks to make sure a post was returned by the query! if no posts were returned, then there isn't a post for that ID. so quit, I guess.

We know that there will be one row for this post. Let's fetch it into an associative array:

$post = mysql_fetch_assoc($get_post_query);

Now the $post variable holds our post information! If you want, you can use the print_r() function to see what's there for us to use:

print_r($post);

Try going to this page now and see what's there. you should see an array with a bunch of keys. that's good.

Also take this time, and any time you can, to try to break your own script! For instance, try taking the post ID out of the URL and go see what happens. You should see your error! That's good! Breaking things is just as productive as making things. You need to know how your stuff will break before someone else (a user, or a hacker) finds out.

But anyway, get rid of or comment out that print_r() function. We know what's in the $post variable.

Now that we have the post info, we can make a page to display it! This goes after your closing php tag:

<html>
<head>
<title>our awesome blog - <?php echo $post['title']; ?></title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<h1><?php echo $post['title']; ?></h1>
<div id="post">

<?php

echo '<p class="small">by '.$post['name'].', posted on '.date('m-d-Y', strtotime($post['pubdate'])).'</p>';
echo '<p>'.$post['content'].'</p>';

?>

</div>
</body>
</html>

So in the post.php file you should have a chunk of PHP on the top, a chunk of HTML on the bottom (with a chunk of PHP in the middle of that chunk).

That's our basic post page. Pretty simple. But let's add comments and the ability to make them. This'll be a lot like the posts list on the index page!

commenting

Under the #post div, let's add another div, before the end body tag:

<div id="comments">
<?php

$get_comments_query = mysql_query("SELECT comments_tbl.*, authors_tbl.name FROM comments_tbl, authors_tbl WHERE post_id=$post_id ORDER BY pubdate DESC");
if (mysql_num_rows($get_comments_query) == 0) {
  echo 'No comments yet!';
} else {
  while ($comment = mysql_fetch_assoc($get_comments_query)) {
    echo '<div class="comment">';
    echo '<p>'.$comment['content'].'</p>';
    echo '<p class="small">by '.$comment['name'].' on '.date('m-d-Y', strtotime($comment['pubdate'])).'</p>';
    echo '</div>';
  }
}

?>
</div>

You put that in and try it out, but there are no comments! (And we made sure to put in a little if statement to catch if there's no comments.) Also, we should style this a little... but I'll leave that up to you. Style the #comments and .comment elements somehow in your main.css file.

Even though there are no comments, we should be reasonably assured that the above code will work. Because you should be confident in your code! (We'll find out soon enough if it's broken.)

That's just showing comments - let's include a mechanism to add comments. Let's start by adding the form in a div after the #comments div.

<div id="addcomment">
<form action="insert_comment.php" method="post">
<div><textarea name="comment_content"></textarea></div>
<div><input type="submit" value="add comment!" /</div>
</form>
</div>

Really that's all there needs to be to adding a comment, right?

Well - not so fast. We also need to transmit to the script what post we're commenting on. To do this we can use a hidden input field that isn't editable. So put this after the opening form tag:

<input type="hidden" name="post_id" value="<?php echo $post_id; ?>" />

That way when we submit this little form to insert_comment.php, it'll be given the comment_content and the post_id to associate it with!

Okay let's make the insert_comment.php page - it'll look remarkably like our insert_post.php page.

<?php

if (!isset($_POST['comment_content']) || trim($_POST['comment_content']) == '') {
  die('you forgot to write the comment!');
}

if (!isset($_POST['post_id']) || !is_numeric($_POST['post_id'])) {
  die('there is no post ID!');
}

require('dbconnection.php');

$post_id = (int) $_POST['post_id'];

$comment_content = htmlspecialchars(trim($_POST['comment_content']));
$comment_content = mysql_real_escape_string($comment_content);

$author_id = 1;

$insert_comment = mysql_query("INSERT INTO comments_tbl (post_id, content, pubdate, author_id) VALUES ($post_id, '$comment_content', NOW(), $author_id)");

if (!$insert_comment) {
  die('something went wrong! '.mysql_error());
}

header("Location: post.php?id=$post_id");

?>

That's mostly like the insert_post.php script, with a few small differences. Read over it just to be sure, check out what's different. And add some comments! See how well it works.

But we still have a problem. The one of who's logged in. I think we should tackle that now.

basic user sessions

there's a lot of ways of doing user authentication. lots. but PHP's built in $_SESSION superglobal is one easy way; it keeps data available in the server-side on a per-user basis. we could do the same thing with browser cookies, but for security and this small scale I'd prefer to start with sessions.

The $_SESSION superglobal is just another associative array. You can set the keys and values of it to whatever you want, and the server will store it for you for the length of your browser session (hence: session). Close the browser window, lose your session.

Inside this session array we'll store things about the user, like their ID and their name.

Sessions in PHP requires starting the session on every PHP page you need it on, which is a simple function call:

session_start();

Put that on top of every page that'll need session data. Put it right after the opening php tag. In this case, we only need it in insert_post.php and insert_comment.php

But we need a login form! And we need a mechanism to log a user in! Once they're logged in and we've set up their session data, we need to go in and make sure that only things that logged-in users can do are protected.

setting up a login process

First we need the login form. Simple enough. Let's do this on the index page. Why not! Open up index.php and let's add a form at the bottom, in a new div under the #posts div.

<div id="loginform">
<form action="login.php" method="post">
<input type="text" name="u" /> <input type="password" name="p" /> <input type="submit" value="log in" />
</form>
</div>

ok so that's the form. easy easy easy. this should be like walking on cake. (is that right?)

next we need to actually handle the login. what does that mean? well, first it has to make sure you submitted data, then it has to validate it against what's in the database, and then fill out your session data correctly. step by step, now.

login.php might look a little something like this:

<?php

session_start();

if (!isset($_POST['u']) || trim($_POST['u']) == '') {
  die('no username supplied');
}

if (!isset($_POST['u']) || trim($_POST['u']) == '') {
  die('no password supplied');
}

require('dbconnection.php');

$username = mysql_real_escape_string(trim($_POST['u']));
$password = sha1(trim($_POST['p']));

$get_user_query = mysql_query("SELECT * FROM authors_tbl WHERE name='$username' AND password='$password'");

if (mysql_num_rows($get_user_query) != 1) {
  die('no user with that name, or invalid password');
} else {
  $user = mysql_fetch_assoc($get_user_query);
  $_SESSION['user_id'] = $user['id'];
  $_SESSION['user_name'] = $user['name'];
}

header('Location: index.php');

?>

Please look over that example and make sure you understand it. Especially the part where we use the sha1() function to convert the password to an encrypted hash before we ask the database about it.

You should never ever be handing around passwords in plain text if you don't need to. Not even between PHP and the database.

We use these one-way hashes so that we can compare text without using that text. The sha1() function creates a one-way hash. Every time you use the same text string with it, it'll produce the same end-result hash. But the hash is not human-readable, so your password is protected. If a hacker were to get your database, they would not have anyone's password, just the hashes! And you can't go backwards with a hash. It's not like you can unencrypt it. (Well, at least with the SHA1 algorithm.)

So now that we have a login process, we can start using that login process to determine what's visible on certain pages. We can protect things!

Let's start by taking out the login form on the index page if the user is logged in.

To do this, we simply need to wrap it in an if statement, like so:

if (isset($_SESSION['user_id'])) {
  // ok do whatever you want to be visible only to users who are logged in
}

Also be sure that wherever you use session data, you have to add session_start() to the top of the page! Or else it won't know there even is session data!

So the bottom of the index.php page could look like this:

<?php
if (!isset($_SESSION['user_id'])) {
?>
<div id="loginform">
<form action="login.php" method="post">
<input type="text" name="u" /> <input type="password" name="p" /> <input type="submit" value="log in" />
</form>
</div>
<?php
} // end login check
?>

We've wrapped an if statement around that block of HTML! It won't show up if you're logged in. Maybe we should make a way to log out! Add an else statement to that, like this, and add a link to a logout script. While we're add it, let's also add a link to the New Post form:

<?php
if (!isset($_SESSION['user_id'])) {
?>
<div id="loginform">
<form action="login.php" method="post">
<input type="text" name="u" /> <input type="password" name="p" /> <input type="submit" value="log in" />
</form>
</div>
<?php
} else {
?>
<div><a href="logout.php">logout!</a></div>
<div><a href="add_post.php">add a new post!</a></div>
<?php
}
?>

So now if you go to the index page and log in with the author you set up way back when in the first SQL queries, you should be able to get in and see a logout! link.

The only thing we'd have to do on that logout.php page is destroy the session and send them back. It'd look like this:

<?php
session_start(); // start the session
$_SESSION = array(); // make the session a blank array
session_destroy(); // DESTROY THE SESSION!
header('Location: index.php'); // send em packin
?>

awesome! now let's start applying this session neatness across the whole site.

first of all, let's protect our scripts that insert information into the database, and also the forms that are for submitting data. so you should have added session_start() to insert_post.php and insert_comment.php but let's add some logic:

put this at the top of after the session_start()

if (!isset($_SESSION['user_id']) || !is_numeric($_SESSION['user_id'])) {
  die('you are not logged in!');
}

add the same thing to the top of add_post.php along with a session_start() function call. so the top of those pages should look like this:

<?php

session_start();

if (!isset($_SESSION['user_id']) || !is_numeric($_SESSION['user_id'])) {
  die('you are not logged in!');
}

That'll protect insert_post.php and insert_comment.php and add_post.php... now we need to protect the add comment form inside post.php

Again, just wrap it in an if statement which checks for the session variable! You could even add an else clause which tells the user they have to log in to leave a comment. I'll let you do this one all on your own. Come back when you've done it!

And now also we can replace that pre-set $author_id = 1; inside the two insert_ scripts.

You can replace it with the following:

$author_id = $_SESSION['user_id'];

And that'll make sure that the author ID we put in the database is the current user's ID.

oh dear oh dear we've come so far

wow, let's take a break for a second. here's what we've done so far:

that's pretty awesome! that's the framework for many simple web apps. let's abstract it a little more. all we've done is this:

right now you could use the same skills to create your own Twitter. because what's twitter, really?

replace "tweet" with "post" and you've got most of what you've done so far.

okay anyway, what do we have left to add to this super awesome blog?

that last one wasn't really planned for in the beginning - but that's what happens. as you build out your project, you may find new features are needed. this is fine as long as they don't get too overwhelming!

searching by author

let's begin again by working on how to get all posts by a specific author. this isn't as hard as it may seem; it'll look a lot like our index page, just with an additional WHERE clause!

so, literally, take your index.php and copy the contents of it into search.php

What are we going to change? First, we need to send the search a query string with what we want to search for. So we'll need to check and make sure they exist.

Then we'll need to modify the MySQL query to use that variable. Here's what we can add at the top after the session_start():

// check if there's an author ID provided
if (isset($_GET['author_id']) && is_numeric($_GET['author_id'])) {
	$author_id = (int) $_GET['author_id'];
	$search_clause = ' AND posts_tbl.author_id=' . $author_id;
}

// make sure there's something to search for
if (!isset($search_clause)) {
	die('nothing to search for!');
}

That checks to see if there's an author_id GET variable set! And then we use it if so, and build a WHERE SQL query to look for it.

After that, we make sure that the $search_clause is set... or else there's nothing to search for! This provides us with some expandability later on, when we search based on tags.

And we can replace the query with this:

$get_posts_query = mysql_query('SELECT posts_tbl.*, authors_tbl.name FROM posts_tbl, authors_tbl WHERE authors_tbl.id=posts_tbl.author_id '.$search_clause.' ORDER BY posts_tbl.pubdate DESC');

Notice that there's a variable interjected into the middle of the query string which will stand for an additional WHERE clause!

So right now when we provide an author_id in the URL, it'll add that WHERE clause to the query and run it!

Everywhere we put the username of the author, we can now surround it with a link to search for their posts, like so:

'<a href="search.php?author_id='.$post['author_id'].'">'.$post['name'].'</a>'

That'll make it so you can click on the user's name in their post and it'll go to the search page with all their posts. That's searching by user! Of course, you'd need more than one user to really see the effect of this...

tags!

doing tags on a blog is fairly straightforward. there are a number of ways to do it, i'll show you my favorite way. it's rather simple. it involves simply adding tags as a comma-separated list, and relying on PHP to parse the tags.

so what do we actually need to do to get that information into the database? we already have the table itself, if you'd recall, it looks like this:

blog_db
tags_tbl
idtagtextpost_id

pretty simple! we need to get the individual tags and save them as strings in the database and associate them with a post. so first, let's add a field to our form in add_post.php

<form method="post" action="insert_post.php">
<div>Title: <input type="text" name="post_title" /></div>
<div>Content:<br /><textarea name="post_content"></textarea></div>
<div>Tags: <input type="text" name="post_tags" /></div>
<div><input type="submit" value="add post" /></div>
</form>

All we did there was add another div with an input field for tags. In this you'll write a list of tags, separated by commas, like so: fun, awesome, stupid, whatever. each one of those being a tag.

but that logic - separating the input by comma - hasn't been written yet! we need to add that to insert_post.php

let's insert this code right before the header() function in insert_post.php so that it's the last thing we do after adding a post:

$post_id = mysql_insert_id();

if (isset($_POST['post_tags']) && trim($_POST['post_tags']) != '') {
  // parse tags!
  $tags = trim($_POST['post_tags']);
  $tags_array = explode(',', $tags);
  foreach ($tags_array as $tag) {
    $tag_db = mysql_real_escape_string(htmlspecialchars(trim($tag)));
    $insert_tag = mysql_query("INSERT INTO tags_tbl (tagtext, post_id) VALUES ('$tag_db', $post_id)");
  }
}

So first of all, we're calling a very important function: mysql_insert_id(), which returns the Primary Key ID of the last inserted query. And the last inserted query was our insertion of the post! So that'll give us the unique ID of the post we just inserted. Cool.

The next thing we do is check if the person submitting the new post even provided any tags for us to add! If they didn't, we can just move on.

But if they did give us tags, then we need to parse them. This is the process, like we did with cleaning the data, of transforming user input to normalized data. A part of your job as a developer is to make the process easy for the user, and let the computer do all the hard work. (Isn't that what all computers should be doing?)

So in this case, we ask the user merely to separate their tags with commas. So then we use the explode() function to transform that string into an array! It does this by using a specific string to delineate the pieces of the array. In this case, we chose a comma.

The explode() function will look at the string, and for every comma it finds, it'll split that piece into a new segment of the array! Awesome stuff. Very useful.

After we've split it by comma, we then go through each tag it found with a foreach loop. foreach works very simply: for each value inside an array, do something... it's just a simpler way of writing a for loop, specifically for arrays.

For each tag inside the tags array we created, first sanitize it for the database like we did with the other strings (post title and content) and then put it in the tags_tbl! Simple, right?

Wait here and take a moment to realize that really, I had to write out six or seven paragraphs to describe what's going on within those seven or so lines of code. If you digested all of it and know what I was talking about, then the hard part is over. You're a developer.

Anyway, so now we can have tags in the database for posts! So cool. Now you need to do two things:

  1. show the tags on the post page (and, if you want, on the index page)
  2. search by tag

I'll let you figure out how to show the tags on the post page. Really, it's simple, I'll at least give you an idea how to do it:

Okay, so do that for me. Really, I want you to be able to walk on your own a bit more as this guide goes on. Apply your knowledge!

searching by tag

I asked you to make the tags point to search.php and utilize the urlencode() method. Here's exactly what I mean, or rather, what it could look like:

echo '<a href="search.php?tag='.urlencode($tag_row['tagtext']).'">'.$tag_row['tagtext'].'</a>';

That simply prints out the text of the tag and makes a link to the search.php script with the tag text as its argument. So why do we need the urlencode() function?

Imagine if this was our list of tags:

The second two strings, on their own, can't be in a URL. You can't have spaces in a URL. It just won't work! Most web browsers will just cut off anything after a space. So we need to encode them for a URL... hence, urlencode()

What it does, specifically, is replace anything that can't be in a URL with a character-code that can. Remember how we need to escape certain characters inside PHP strings, or else the string will break? Same idea.

So urlencode()-ing the string so cool and putting it in the query string for search.php looks like this:

search.php?tag=so+cool

The plus sign there is a standard way of replacing a space. When the script gets that GET variable, it'll automatically translate those URL encodings back into a string.

Ok, cool, so now you know how that'll work. Now let's search by tag! It's a lot like searching by author, we just use a different WHERE clause. The current search.php has this chunk at the top:

// check if there's an author ID provided
if (isset($_GET['author_id']) && is_numeric($_GET['author_id'])) {
  $author_id = (int) $_GET['author_id'];
  $search_clause = ' AND posts_tbl.author_id=' . $author_id;
}

// make sure there's something to search for
if (!isset($author_id)) {
  die('nothing to search for!');
}

That's cool. Let's add another chunk for tags. Put it between the two if statements. So it should look like this:

// check if there's an author ID provided
if (isset($_GET['author_id']) && is_numeric($_GET['author_id'])) {
  $author_id = (int) $_GET['author_id'];
  $search_clause = ' AND posts_tbl.author_id=' . $author_id;
}

// check if there's a tag name provided
if (isset($_GET['tag']) && trim($_GET['tag']) != '') {
  $tag = trim($_GET['tag']);
  $tag_db = mysql_real_escape_string($tag);
  $search_clause = " AND posts_tbl.id IN (SELECT post_id FROM tags_tbl WHERE tagtext = '$tag_db')";
}

// make sure there's something to search for
if (!isset($search_clause)) {
  die('nothing to search for!');
}

Note that if the require('dbconnection.php'); is not above those clauses, it'll throw an error. So move that line above these chunks.

What did we just add? It looks a lot like the first if statement -- if there's a tag, search for it! However, we needed to use the mysql_real_escape_string() function to sanitize the tag first, to prevent any potentially malicious activity. Don't forget to do this!

It creates a WHERE clause for the search query, one that uses a subquery to ask the tags_tbl for any post_ids that use the tag we're searching for!

So the subquery says, find all post IDs using the tag text we supply, and with those IDs, the main query says, retrieve those posts with the supplied IDs. That's what the IN SQL keyword means. Find all rows where they meet any of the conditions within the specified array. In this case, the specified array is our tag rows.

This is a great example of how you build relational models and normalized data and how you end up displaying that data. The user has no idea about the models or anything - they simply see a list of tags! Wonderful. The user should never have to know how your data is structured.

creating new users

Creating new users is pretty easy - make a signup form! In fact, it's a perfect way to demonstrate pretty much everything I've taught you here. To make a signup form, you'd need at least two pages:

Things to think about while making it:

So keep those ideas in mind. Using just what you've learned so far, the easiest thing for you to do would be to make a page that simply adds a new user and lets them choose their own password.

Build it!

editing posts and deleting posts

Again, these are two things that simply build upon things we've already done, so I'll leave them for you to do.

Think of making the editing posts page a combination of the post.php page and the add_post.php page... because you are retrieving the post and filling it into a form to edit! Then you just need to update the existing database row.

Deleting a post is as simple as making a tiny script that just runs a DELETE SQL query. You just need to make sure to protect that link so only the post author can see it.

how far we've come

Here are the important lessons I want you to walk away with, neatly presented as bullet points:

Now that you've learned about Web Development, maybe you want to learn about Systems... and maybe more advanced architecture type stuff. Expand your knowledge!

If you have any questions, comments, whatever, please feel free to email me at cyle_gage@emerson.edu