From a9db3ff3dcf3520502112e086578798da568acd5 Mon Sep 17 00:00:00 2001
From: Jon Hinks
rows left. When that happens, this method will return false and the loop will exit.
For every row, there are two observations worth making. Firstly, rows are returned in an array, so we
access them using the array syntax such as $row['title']
. Secondly, you'll see that
@@ -622,6 +628,13 @@
+ Since the htmlspecialchars()
calls have a few parameters we don't want to have to
+ keep typing in, let's also move that to its own function:
+
htmlspecialchars()
now this has been
+ placed in a function?
+ Thus, we now set empty values for the GET case (no form submission) in view-post.php as well as @@ -1356,6 +1376,13 @@
htmlspecialchars()
now this has been
+ placed in a function?
+
As we have done before, this code uses htmlspecialchars()
to prevent users
from entering HTML, which could break our page layout or introduce security problems. It is
From d1aff2de4607f6fba91091dab63d233be4b5367b Mon Sep 17 00:00:00 2001
From: Jon Hinks
+
Since the htmlspecialchars()
calls have a few parameters we don't want to have to
keep typing in, let's also move that to its own function:
For every row, there are two observations worth making. Firstly, rows are returned in an array, so we
access them using the array syntax such as $row['title']
. Secondly, you'll see that
@@ -521,6 +515,29 @@
+++ What are the htmlspecialchars() parameters? +
++ The first parameter is, of course, the item of text we wish to safely output. The other + two are: +
+ ++
+ +- +
ENT_HTML5
: this tells PHP to understand tags as HTML5, rather than any + other dialect of HTML- +
'UTF-8'
: this specifies how characters are encoded. UTF-8 is + now the standard approach to encoding text because it supports a large number + of language-specific charactersIt's worth noting that while the default character set for this function is 'UTF-8' + (and so we don't really need to set it), the PHP manual advises that it should be + specified explicitly. +
+
You'll notice that most of the PHP code to deal with the database side of things is in the top half
of the file, and the second half is predominantly HTML. This isn't an accident: this arrangement creates
From c3023e38a7225ce63f5dd118bb4decbb45990100 Mon Sep 17 00:00:00 2001
From: Jon Hinks
a form does not by default contain values, so we have to add them manually.
htmlspecialchars()
now this has been
- placed in a function?
-
Thus, we now set empty values for the GET case (no form submission) in
view-post.php as well as
the already existing POST case. Where we output the user-supplied data, we pass it through
- the PHP function htmlspecialchars()
, which prevents any rendering problems if the
- user has used any HTML characters such as angle brackets.
+ the PHP function htmlspecialchars()
via our custom function htmlEscape()
,
+ which prevents any rendering problems if the user has used any HTML characters such as angle
+ brackets.
htmlspecialchars()
now this has been
- placed in a function?
-
- As we have done before, this code uses
The
The connection to the database is the same as before, but we now have a
- I'll assume that your development environment is set up. Here's what you will need:
+ For your development environment, here's what you will need:
- For Windows and Mac users, you can get all of this with
- XAMPP. If you use Linux, you may have all of this
- already — although XAMPP works on Linux too, and adds a user-friendly control panel.
- If you get stuck, turn to your favourite search engine, and
- ask "How do I install [whatever] on [Windows]". In 99% of cases, your question has already
- been answered, so be persistent.
+ 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
+ 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.
- You'll also need an editor to modify files. Most computers have a free text editor already
- installed, but these are rarely good enough. Use whatever you like, but if you have no
- preference, download and install NetBeans.
+
+ 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.
+ 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:
+
- Once you have this working, and showing an acceptable version number, you can delete this file.
+ 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:
+ The file you've just added is used to set options for the web server, and in this case
+ for a module known as
+ The first command is simple:
+ The condition is looking at a placeholder known as
+ These ones are in the data, lib,
+ templates and vendor folders, so
+ we add those into the second part of this command. The bar character means "or" and the
+
+ The
From 2107c26bf8f7fdba23f48aa8f46f94bb05d56ff1 Mon Sep 17 00:00:00 2001
From: Jon Hinks
- The first is that the
- Then, run this in your browser, by accessing the URL
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:
@@ -288,7 +288,7 @@ We'll use a few more as we go on, but those are the main ones.
Thus, if you wish, you can delete both article blocks and replace them with the new loop block
- (containing the
- The
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
- The column
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
-
-
- The first half of the code is written in PHP. Here, we use a series of
- The second half of the file (from So, if you have not already tried it, visit So, if you have not already tried it, visit
The first few lines determine the file path to the database, and then we create a new PDO object
- with
- The
- The
- So, refresh your browser's view of
After connecting to the database, the query is used to retrieve column values for each table row,
- returning ordering the rows in
- We use
For every row, there are two observations worth making. Firstly, rows are returned in an array, so we
- access them using the array syntax such as
Now, it is our intention to link this page from individual posts on the home page. However, we've
not quite done that bit yet, so let's visit the new page manually, in order to test it. Open a
- new tab, paste the link
- The connection to the database is the same as before, but we now have a
- Let's take the
- The other change is swapping out the call to
We saw in the common.php library a set of code blocks that
start with the word
-
- Since the
Since this involves database changes, we'll need to delete our database file, and re-run
the installer. So, delete the file in data/data.sqlite and then
- reinstall by visiting
@@ -778,7 +779,7 @@
Now we're ready to start adding some comment-related changes. To start with, let's add
- a count of comments on the front page. This uses the SQL command
When we visit a web site normally — such as the home page of our blog, say —
we are in GET mode. This is the state that is suitable for reading things from
- a web server. Some pages containing a
Forms that may result in data being saved, on the other hand, should use POST. You can
see this with the comment form we just installed; have a look for the
-
This was one of the reasons behind the earlier change to the installer: before the
@@ -973,7 +974,7 @@
- Adding a comment doesn't work yet, as the
There are two fields in the new user table that I added based on my experience rather than an
- immediate need. These are
So, what does the new code do? It makes use of a new function called
-
- If the password matches however, then we call
PHP makes this nice and simple for developers: we just turn on sessions using
-
- The purpose of the
- That applies two rules to the block: one called
We now turn our attention to an improvement to the database. This consists of the tables
-
- However, it is possible to insert any number in
@@ -1310,7 +1311,7 @@
The file you've just added is used to set options for the web server, and in this case
- for a module known as
- The first command is simple:
- The condition is looking at a placeholder known as
- The
Let's turn our attention to another major feature: writing a new article. We start off with
setting up a new page, laying out the necessary form input controls, and adding a new logged-in
- menu item. There's also a change here to add a generic class
- While we are here, let's take a look at how
@@ -1458,9 +1459,9 @@
In a similar way to saving comments, we first test if we are in a post operation, using
-
That was nice and easy: if we find we are not in a post operation and we have a
post primary key and that row exists, then show it in the edit form. You can see that
- now we need the database connection (in
- As we have done before, this code uses our htmlspecialchars()
to prevent users
+ As we have done before, this code uses our htmlEscape()
function to prevent users
from entering HTML, which could break our page layout or introduce security problems. It is
perhaps less of a worry here, since at least these users are authenticated, and hence
they might be considered more trustworthy than anonymous commenters.
From ac00901d4330104dbc09432396d1508d03ffa19e Mon Sep 17 00:00:00 2001
From: Jon Hinks
for()
and endfor
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 greater
- than 3. For each iteration of the loop, the value is incremented by 1, which is carried out by
+ 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.
WHERE
clause
- in our SELECT
statement, and we are we are now using prepare()
and
+ in our SELECT
statement, and we are now using prepare()
and
execute()
to send the statement to the database driver.
-
+
+
/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.
+
the file in the right folder.
+
PDO
and pdo_sqlite
. It may help to know that the
+ modules are listed in alphabetical order:
+
+
Tidy up
+mod_rewrite
. This module allows the creation of special rules
+ for various web addresses, and in our case to refuse to serve ones that are private.
+ I'll provide a little information about these rules below, but if you don't fully follow them,
+ don't worry. They are rather out of scope for a beginners' course, and we won't need to
+ consider them again.
+
+
+
__NEWFILE__
+ What do the mod_rewrite commands do?
+
+
+ RewriteEngine on
enables the module for the site.
+ After that, the RewriteCond
sets up a condition (i.e. when the rule will
+ apply) and the RewriteRule
does the action.
+ %{REQUEST_URI}
. This is the
+ URL of the page, relative to the domain. So it will always start with
+ / and then it will contain the address, e.g.
+ /styles/assets/main.css (our stylesheet) or
+ /install.php (a PHP page). However there are some resources
+ we want users specifically not to access directly.
+ ^
means "begins with". This format is known as a
+ regular expression, and is
+ a popular and flexible method of testing strings for certain conditions.
+ RewriteRule
basically says if you get a match, then this is the
+ (L
) last rule (i.e. don't process any more rules) and the server should
+ (R
) redirect to a 404 page (a special server code for "not found").
+ New post creation
From eef99f991cf45cb36d12a69ed313052f90ea3a39 Mon Sep 17 00:00:00 2001
From: michaellenahan Introduction
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).
+ may wish to research unfamiliar keywords (usually on the PHP website).
Internet that make two fundamental errors.
mysql_
library is still in use (a library is
+ 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
@@ -90,7 +90,7 @@
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
+ 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.
@@ -240,7 +240,7 @@
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
+ http://localhost/
, you can assume I am referring to the root of your project, even
if your URL is somewhat different.
@@ -252,7 +252,7 @@
http://localhost/info.php
.
+ 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.4; 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
@@ -262,8 +262,8 @@
PDO
and pdo_sqlite
. It may help to know that the
- modules are listed in alphabetical order:
+ screenshot below: PDO
and pdo_sqlite
.
+ It may help to know that the modules are listed in alphabetical order:
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
+ http://localhost/index.php
in your browser, to ensure that you can see the
initial mock-up of our blog's home page.
@@ -296,14 +296,15 @@
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).
+ 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!
+ <html>
containing a <body>
containing
+ <h1>
and <p>
. Easy peasy!
@@ -311,17 +312,17 @@
@@ -430,7 +431,7 @@
What do the tags mean?
-
DOCTYPE
: this tells the browser to expect a particular dialect of HTML
+ 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
+ html
: 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
+ head
: 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 hyperlinkp
: a paragraph of textdiv
: a "division", i.e. a sectiona
: a hyperlink
for()
through to the endfor
). However, the way this was
+ (containing the for()
through to the endfor
). However, the way this was
actually written was as follows:
@@ -379,11 +380,11 @@ for()
loop before the first article, on a new lineendfor
) at the end of the first article, on a new
+ for()
loop before the first article, on a new lineendfor
) at the end of the first article, on a new
line$postId
are inserted into the
+ $postId
are inserted into the
article
What are loops?
for()
and endfor
you have seen form a loop block. This is
+ The for()
and endfor
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.
+ the ++
operator.
CREATE TABLE
statement specifies which properties we want a post to have,
+ a post. The CREATE TABLE
statement specifies which properties we want a post to have,
namely:
id
has two particular features:
+ The column id
has two particular features:
-
PRIMARY KEY
in order to mark it as the predominant unique
+ 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)AUTOINCREMENT
option, so unique values are generated for us
+ AUTOINCREMENT
option, so unique values are generated for us
by the database server automaticallyINSERT
. The format of this command, as used in the SQL script, is simply thus:
+ 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, ...);
if()
- statements to ensure everything goes smoothly. The variable $error
is set to an
+ 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:
accidentally overwrite data.
touch()
+ 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).
file_get_contents()
and report an error
+ 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!).
$pdo->exec()
and report any issues
+ We then try running the SQL commands using $pdo->exec()
and report any issues
with that.
<!DOCTYPE html>
) presents the results of
+ The second half of the file (from <!DOCTYPE html>
) presents the results of
the script, in HTML.
http://localhost/install.php
in your
+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.
Using real data
new PDO()
, which we can use to access the data. We then use the
- query()
method to run a SQL statement that reads articles from the post
+ with new PDO()
, which we can use to access the data. We then use the
+ query()
method to run a SQL statement that reads articles from the post
table.
What is a SELECT statement?
SELECT
command is used to read from the database. For a single table, it takes a
+ The SELECT
command is used to read from the database. For a single table, it takes a
format like this:
SELECT
+
SELECT
(column names in a list)
FROM
(table)
@@ -559,7 +560,7 @@
(column names to sort on)
WHERE
is optional, and is useful if we want to filter on some particular
+ The WHERE
is optional, and is useful if we want to filter on some particular
feature of each row.
http://localhost/index.php
, and ensure that your
+ So, refresh your browser's view of http://localhost/index.php
, and ensure that your
new changes render the posts from the database without errors. I'll then explain what is happening.
created_at DESC
(i.e. in reverse creation order, or most
- recent first). It then enters a loop (in this case a while()
) to render all the posts it
+ returning ordering the rows in created_at DESC
(i.e. in reverse creation order, or most
+ recent first). It then enters a loop (in this case a while()
) to render all the posts it
finds.
$stmt->fetch()
to read the next available row, until the point when there are no
+ We use $stmt->fetch()
to read the next available row, until the point when there are no
rows left. When that happens, this method will return false and the loop will exit.
$row['title']
. Secondly, you'll see that
- where text strings are output, they are wrapped in the htmlspecialchars()
function. The
+ access them using the array syntax such as $row['title']
. Secondly, you'll see that
+ where text strings are output, they are wrapped in the htmlspecialchars()
function. The
reason for this is that, if user input (a blog title or blog post in this case) contains angle brackets,
it could break the HTML used in the page layout, and worse, might let a user inject unauthorised
JavaScript that would be run on other people's computers.
@@ -602,9 +603,9 @@
-
@@ -633,22 +634,22 @@ ENT_HTML5
: this tells PHP to understand tags as HTML5, rather than any
+ ENT_HTML5
: this tells PHP to understand tags as HTML5, rather than any
other dialect of HTML'UTF-8'
: this specifies how characters are encoded. UTF-8 is
+ 'UTF-8'
: this specifies how characters are encoded. UTF-8 is
now the standard approach to encoding text because it supports a large number
of language-specific characters
http://localhost/view-post.php
into the address
+ new tab, paste the link http://localhost/view-post.php
into the address
bar, and check that a simple post appears.
WHERE
clause
- in our SELECT
statement, and we are now using prepare()
and
- execute()
to send the statement to the database driver.
+ The connection to the database is the same as before, but we now have a WHERE
clause
+ in our SELECT
statement, and we are now using prepare()
and
+ execute()
to send the statement to the database driver.
WHERE
statement first. In the home page, we ran a query without
+ Let's take the WHERE
statement first. In the home page, we ran a query without
this, because we wanted everything in the table. However it is more often the case that we
want to limit returned rows to those matching one or more conditions. This is where the
- WHERE
comes in. For our new page, we only want rows that have a specific id (and
+ WHERE
comes in. For our new page, we only want rows that have a specific id (and
since "id" is unique, we know we won't get more than one).
query()
with two new methods.
- prepare()
is used to set up the statement, and indicates with a colon where values
- should go (i.e. ":id"). The execute()
statement then runs the query, swapping
+ The other change is swapping out the call to query()
with two new methods.
+ prepare()
is used to set up the statement, and indicates with a colon where values
+ should go (i.e. ":id"). The execute()
statement then runs the query, swapping
place-holders with real values (in this case, the number 1). This technique is known as
parameterisation, and is a good way to inject user-supplied input in a secure manner
(the post ID is not yet user-supplied, but it soon will be).
@@ -679,7 +680,7 @@
files shows that our blog title and synopsis is repeated in both files. That's not good, since
if these things need to be amended, we need to change them more than once. Happily, the
solution is easy: we create a PHP file for the duplicated snippet, and then use
-
require
to pull it in.
+ require
to pull it in.
@@ -707,7 +708,7 @@
function
. These are named blocks of code that can be called from different
+ function
. These are named blocks of code that can be called from different
places, and are an essential item in your toolkit. They help reduce duplication (since
you don't need to write code again) and they help improve modularity (since functions
can be stored in separate files).
@@ -717,13 +718,13 @@
The functions we've seen here all provide a return value, which is the result of
the function, and it can be of any type: a number, a string, whatever. This can either be
printed out to the screen, or more frequently, stored in a variable at the place it is
- called. For example, the function
getRootPath()
gets the full directory
+ called. For example, the function getRootPath()
gets the full directory
name of your web project on disk.
htmlspecialchars()
calls have a few parameters we don't want to have to
+ Since the htmlspecialchars()
calls have a few parameters we don't want to have to
keep typing in, let's also move that to its own function:
Adding more features
http://localhost/install.php
again.
+ reinstall by visiting http://localhost/install.php
again.
Adding more features
COUNT()
to count
+ a count of comments on the front page. This uses the SQL command COUNT()
to count
the number of rows that would be returned by a query:
Commenting form
addCommentToPost()
for validation and saving.$errors
array.addCommentToPost()
for validation and saving.
+ $errors
array.
<form>
also operate in GET mode,
+ a web server. Some pages containing a <form>
also operate in GET mode,
such as search forms, since these too are used to read information.
method="post"
declaration in the form tag.
+ method="post"
declaration in the form tag.
INSERT
command is missing a value for
+ Adding a comment doesn't work yet, as the INSERT
command is missing a value for
the date of comment creation. Let's fix that now:
Thus, we now set empty values for the GET case (no form submission) in
view-post.php as well as
the already existing POST case. Where we output the user-supplied data, we pass it through
- the PHP function
htmlspecialchars()
via our custom function htmlEscape()
,
+ the PHP function htmlspecialchars()
via our custom function htmlEscape()
,
which prevents any rendering problems if the user has used any HTML characters such as angle
brackets.
@@ -1073,8 +1074,8 @@ Adding a login system
created_at
, which holds the date and time when the user
- was first set up, and is_enabled
, which allows us to turn users on and off. Most
+ immediate need. These are created_at
, which holds the date and time when the user
+ was first set up, and is_enabled
, which allows us to turn users on and off. Most
user systems will find a practical use for these simple features during their lifetime.
Adding a login system
at the start of the tutorial, stored here in vendor/password_compat.
The features it provides are so useful they have been built into PHP 5.5, so if you are running
this version (or later) you can skip adding the new file and the associated
- require_once
. However if you are running an earlier version, or if you are not
+ require_once
. However if you are running an earlier version, or if you are not
sure which version you have, add all of these changes.
@@ -1108,7 +1109,7 @@ Adding a login system
password_hash()
, which takes a password as input and produces what is known as a
+ password_hash()
, which takes a password as input and produces what is known as a
hash. A hash is a mathematical calculation that is strictly one way, which means that
if sometimes steals our database of password hashes, they will find it very difficult indeed
to recreate the passwords they were generated from.
@@ -1141,8 +1142,8 @@ Adding a login system
login()
to sign in the user, and
- then we redirect to the home page using redirectAndExit()
.
+ If the password matches however, then we call login()
to sign in the user, and
+ then we redirect to the home page using redirectAndExit()
.
@@ -1165,8 +1166,8 @@
@@ -1229,14 +1230,14 @@
session_start()
, and then we can just read and write to the
- $_SESSION
array. Easy peasy!
+ session_start()
, and then we can just read and write to the
+ $_SESSION
array. Easy peasy!
Tidy up
-
<div style="border: 1px solid #ff6666; padding: 6px;"> … </div>
+ <div style="border: 1px solid #ff6666; padding: 6px;"> … </div>
style
attribute to specify CSS rules (otherwise known as
- style rules) to the HTML within. Firstly, we have the border
rule, which says
+ The purpose of the style
attribute to specify CSS rules (otherwise known as
+ style rules) to the HTML within. Firstly, we have the border
rule, which says
the content (an error message) should have a red border rendered in an unbroken line, and
- the padding
means that there should be six pixels of gap between the border and
+ the padding
means that there should be six pixels of gap between the border and
an invisible box around the content.
Tidy up
-
<div class="error box"> … </div>
+ <div class="error box"> … </div>
error
and another called
- box
. That's much easier to remember, easier to read, and — since we
+ That applies two rules to the block: one called error
and another called
+ box
. That's much easier to remember, easier to read, and — since we
centralise the definitions in assets/main.css — easier
to change if we have to.
Tidy up
post
and user
, for blog posts and authors respectively. When
- creating a post record, we insert our automatically generated user.id
in
- post.user_id
to store which user has authored it (of course, we only have one
+ post
and user
, for blog posts and authors respectively. When
+ creating a post record, we insert our automatically generated user.id
in
+ post.user_id
to store which user has authored it (of course, we only have one
user in our test data, but in practice we might have several).
post.user_id
, so if we have
+ However, it is possible to insert any number in post.user_id
, so if we have
an undiscovered bug in our code, it might cause a non-existent user primary key to be stored
here. Since we rely on values here always pointing to a user row, if a bad value were to get
in, it might crash our application.
@@ -1301,7 +1302,7 @@ Tidy up
constraints. These allow us to specify that values inserted into a particular column must
exist in another column belonging to another table, and that an error must occur if this
condition is not met. SQLite offers this feature, although it is unusual in that it needs to
- be turned on explicitly, using the PRAGMA
command.
+ be turned on explicitly, using the PRAGMA
command.
Tidy up
user
table does not result
+ Deleting the user
table does not result
in a constraint violation, since we refuse to run the SQL unless the database file is
manually deleted.
Tidy up
order around, so the test user is created before the test postINSERT
that in the script with a dummy hash value, and
- UPDATE
it afterwards with the real hash generated in PHPINSERT
that in the script with a dummy hash value, and
+ UPDATE
it afterwards with the real hash generated in PHP
Tidy up
mod_rewrite
. This module allows the creation of special rules
+ for a module known as mod_rewrite
. This module allows the creation of special rules
for various web addresses, and in our case to refuse to serve ones that are private.
I'll provide a little information about these rules below, but if you don't fully follow them,
don't worry. They are rather out of scope for a beginners' course, and we won't need to
@@ -1365,12 +1366,12 @@
RewriteEngine on
enables the module for the site.
- After that, the RewriteCond
sets up a condition (i.e. when the rule will
- apply) and the RewriteRule
does the action.
+ The first command is simple: RewriteEngine on
enables the module for the site.
+ After that, the RewriteCond
sets up a condition (i.e. when the rule will
+ apply) and the RewriteRule
does the action.
%{REQUEST_URI}
. This is the
+ The condition is looking at a placeholder known as %{REQUEST_URI}
. This is the
URL of the page, relative to the domain. So it will always start with
/ and then it will contain the address, e.g.
/styles/assets/main.css (our stylesheet) or
@@ -1381,14 +1382,14 @@
These ones are in the data, lib,
templates and vendor folders, so
we add those into the second part of this command. The bar character means "or" and the
-
^
means "begins with". This format is known as a
+ ^
means "begins with". This format is known as a
regular expression, and is
a popular and flexible method of testing strings for certain conditions.
RewriteRule
basically says if you get a match, then this is the
- (L
) last rule (i.e. don't process any more rules) and the server should
- (R
) redirect to a 404 page (a special server code for "not found").
+ The RewriteRule
basically says if you get a match, then this is the
+ (L
) last rule (i.e. don't process any more rules) and the server should
+ (R
) redirect to a 404 page (a special server code for "not found").
New post creation
user-form
, so our
+ menu item. There's also a change here to add a generic class user-form
, so our
forms across the whole application can easily acquire a common look-and-feel; see how I've
gone back to existing form comment-form.php to update that too.
New post creation
redirectAndExit()
works; this is in
+ While we are here, let's take a look at how redirectAndExit()
works; this is in
common.php. It starts by reading the domain we are running
- on (such as localhost
), since it is good not to hardwire this into our code.
- It then sends a Location
HTTP header to the browser, which will cause it to
+ on (such as localhost
), since it is good not to hardwire this into our code.
+ It then sends a Location
HTTP header to the browser, which will cause it to
request the specified address. Since the PHP script would happily carry on running at this
point (until it has detected that the browser has disconnected) we also need to forcibly
exit and wait for the redirect.
@@ -1448,9 +1449,9 @@
User-Agent
header)Host
)
- Cookie
)
+ (the User-Agent
header)Host
)
+ Cookie
)
-
Server
)Server
)Content-Type
)Content-Type
)
if ($_POST)
. This contains an array of input values that will be only be present
+ if ($_POST)
. This contains an array of input values that will be only be present
if a user has submitted the form.
$pdo
) for two things, I've moved
+ now we need the database connection (in $pdo
) for two things, I've moved
that line so that it is always executed.
htmlEscape()
function to prevent users
+ As we have done before, this code uses our htmlEscape()
function to prevent users
from entering HTML, which could break our page layout or introduce security problems. It is
perhaps less of a worry here, since at least these users are authenticated, and hence
they might be considered more trustworthy than anonymous commenters.
@@ -1524,16 +1525,16 @@
I've moved the code to read the current post towards the start of the page, since this is
now useful in two situations. The first is when displaying an article for
editing, and the second is when submitting the edit form to save any changes.
- In both cases,
$_GET['post_id']
will be available, and we can look up that row
+ In both cases, $_GET['post_id']
will be available, and we can look up that row
from the post table, and obtain title/body data if it is read successfully.
- Within a POST operation, we can then check $postId
, and if it has a value we
+ Within a POST operation, we can then check $postId
, and if it has a value we
know we are editing an existing article rather than creating a new one. Thus, if we are editing,
- we call the new function editPost()
, which will run the necessary
- UPDATE
command against the database, rather than addPost()
, which
- would run an INSERT
.
+ we call the new function editPost()
, which will run the necessary
+ UPDATE
command against the database, rather than addPost()
, which
+ would run an INSERT
.
@@ -1576,7 +1577,7 @@
- You may have noticed that the class name (read-more
) of the wrapping div was no
+ You may have noticed that the class name (read-more
) of the wrapping div was no
longer accurate, which is why I changed this to something more generic. As it happens, this
section doesn't have any CSS rules specifically attached to it, but it's good to have something
for future styles to hook onto).
@@ -1630,12 +1631,12 @@
-+<input type="submit" name="save" value="Save" />
<input type="submit" name="save" value="Save" />
This is pretty simple. If the button is pressed, the form is submitted, and the submitted
- key save
gets the value of the button, "Save".
+ key save
gets the value of the button, "Save".
@@ -1645,7 +1646,7 @@
-@@ -1657,7 +1658,7 @@<input type="submit" name="delete_1" value="Delete" /> +
<input type="submit" name="delete_1" value="Delete" /> <input type="submit" name="delete_2" value="Delete" /> <input type="submit" name="delete_3" value="Delete" />
-@@ -1668,7 +1669,7 @@<input type="submit" name="delete[1]" value="Delete" /> +
<input type="submit" name="delete[1]" value="Delete" /> <input type="submit" name="delete[2]" value="Delete" /> <input type="submit" name="delete[3]" value="Delete" />
-@@ -1692,7 +1693,7 @@Array +
Array ( [delete] => Array ( @@ -1679,7 +1680,7 @@
This makes things nice and easy: we just look up the name of the element - (i.e.
delete
), and then grab the first key value. + (i.e.delete
), and then grab the first key value.
- While working on the home page, I noticed I'd used the variable name $row
. This
+ While working on the home page, I noticed I'd used the variable name $row
. This
is rather generic — most things read from a database are rows — so I swapped to
a better name for it. This is more readable, and fits in with the common naming convention
of using a plural name for an array and the corresponding singular name for each item.
@@ -1753,9 +1754,9 @@
post_id
) upon which a comment is being added or
+ post_id
) upon which a comment is being added or
deletedadd-comment
and delete-comment
), which
+ add-comment
and delete-comment
), which
can be read by our code to determine what the user wishes to do
We now need to read the action key we set up, so we can decide what feature to call.
- If the user is deleting a comment, the new function deleteComment
in the next set
- of changes is called; although we could just delete by comment.id
, to be sure of
- deleting the right thing we filter additionally by comment.post_id
.
+ If the user is deleting a comment, the new function deleteComment
in the next set
+ of changes is called; although we could just delete by comment.id
, to be sure of
+ deleting the right thing we filter additionally by comment.post_id
.
- Note that we also add an if()
clause to ensure that the delete operation is skipped if
+ Note that we also add an if()
clause to ensure that the delete operation is skipped if
the user is not logged in. Although we do not render delete buttons for anonymous users, it is
still possible for security crackers to fake a form submission that contains a valid deletion
request — this clause prevents that.
@@ -1792,7 +1793,7 @@
Finally, I noticed that the logic to add a comment is contained within a function, but the
- logic to delete one is still in the switch()
block in
+ logic to delete one is still in the switch()
block in
view-post.php. The following change just tidies that up:
-SELECT +
SELECT id, title, created_at, body, (SELECT COUNT(*) FROM comment WHERE comment.post_id = post.id) comment_count FROM @@ -1839,13 +1840,13 @@
Adding some polish
The new part of this is the bracketed expression on the third line. This sort of SQL is known as a sub-query since it is a query contained inside another one. The result of it can be read like a real column, which we explicitly name by adding -comment_count
at the end of it. +comment_count
at the end of it.- This sub-query will count rows in the
comment
table, based on which post they + This sub-query will count rows in thecomment
table, based on which post they belong to. Since the job of the outer query is to list posts, we can filter the comment count - for each row by making a comparison to the outer table,post
. + for each row by making a comparison to the outer table,post
.@@ -1886,13 +1887,13 @@
Adding some polish
Whilst we are looking at the database side of things, I noticed that the -
user.is_enabled
field hasn't yet been used. This was intended mainly for future +user.is_enabled
field hasn't yet been used. This was intended mainly for future enhancements (in particular for a user administration page), but the application can make an initial use of it immediately, by disallowing authenticated features to non-enabled users.- The installer sets the test user to
From 1797f5688576ce7a08020e96e67b8ed72b83f544 Mon Sep 17 00:00:00 2001 From: Jon Hinksis_enabled=1
already, so all we need + The installer sets the test user tois_enabled=1
already, so all we need to do is to adjust the SQL statements that fetch data from the user table.Date: Fri, 12 May 2017 13:13:00 +0100 Subject: [PATCH 14/19] Oops, fix non-inline code blocks --- pages/original.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pages/original.html b/pages/original.html index 5e47af5..2232e6e 100644 --- a/pages/original.html +++ b/pages/original.html @@ -546,7 +546,7 @@ What is a SELECT statement?
- The
@@ -1248,7 +1248,7 @@SELECT
command is used to read from the database. For a single table, it takes a + TheSELECT
command is used to read from the database. For a single table, it takes a format like this:Tidy up
-<div class="error box"> … </div>
+<div class="error box"> … </div>
@@ -1631,7 +1631,7 @@
-+<input type="submit" name="save" value="Save" />
<input type="submit" name="save" value="Save" />
@@ -1646,7 +1646,7 @@
-@@ -1658,7 +1658,7 @@<input type="submit" name="delete_1" value="Delete" /> +
<input type="submit" name="delete_1" value="Delete" /> <input type="submit" name="delete_2" value="Delete" /> <input type="submit" name="delete_3" value="Delete" />
-@@ -1669,7 +1669,7 @@<input type="submit" name="delete[1]" value="Delete" /> +
<input type="submit" name="delete[1]" value="Delete" /> <input type="submit" name="delete[2]" value="Delete" /> <input type="submit" name="delete[3]" value="Delete" />
-Array +
Array ( [delete] => Array ( From 21f879e7e7e4b5f13afd9d982ae4fd87f9eda4c2 Mon Sep 17 00:00:00 2001 From: Jon Hinks
Date: Fri, 12 May 2017 14:49:04 +0100 Subject: [PATCH 15/19] Fix new inline class error --- pages/original.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/original.html b/pages/original.html index 2232e6e..1076182 100644 --- a/pages/original.html +++ b/pages/original.html @@ -546,11 +546,11 @@ What is a SELECT statement?
- The
-SELECT
command is used to read from the database. For a single table, it takes a + TheSELECT
command is used to read from the database. For a single table, it takes a format like this:SELECT +
SELECT (column names in a list) FROM (table) From 800060a24c66d5cf67258f4cd8bb2453d767acd1 Mon Sep 17 00:00:00 2001 From: Jon Hinks
Date: Fri, 12 May 2017 19:57:00 +0100 Subject: [PATCH 16/19] Another non-inline tweak --- pages/original.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/original.html b/pages/original.html index 1076182..2864fcb 100644 --- a/pages/original.html +++ b/pages/original.html @@ -1827,7 +1827,7 @@ Adding some polish
-SELECT +
SELECT id, title, created_at, body, (SELECT COUNT(*) FROM comment WHERE comment.post_id = post.id) comment_count FROM From dc19bc8d35320502cb96b31f3a1169c88769e367 Mon Sep 17 00:00:00 2001 From: Jon Hinks
Date: Thu, 16 Aug 2018 19:06:46 +0100 Subject: [PATCH 17/19] Remove references to libraries (password_compat now gone) --- pages/original.html | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/pages/original.html b/pages/original.html index 2864fcb..04ad51e 100644 --- a/pages/original.html +++ b/pages/original.html @@ -73,7 +73,7 @@
@@ -217,20 +217,13 @@
- The Apache web server
-- PHP, any version after 5.4
+- PHP, any version after 5.5
- SQLite PHP module, for database access
- Any programmer's editor, ideally with syntax highlighting
- One of the other features of this tutorial is that it uses an absolute minimum of additional + 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, and suspect this will be corrected in a second (more - advanced) tutorial. -
-- I have made one exception for this rule, however, for reasons of password security. The login - system requires some cryptographic code, and this sort of thing is quite easy to get wrong. Whilst - this is just a tutorial, if it teaches readers to leave security to the experts (the - library-writers), it has served a valuable purpose. + libraries more than I would normally.
~@@ -254,7 +247,7 @@
Then, run this in your browser, by accessing the URL
@@ -1088,21 +1081,8 @@http://localhost/info.php
. You should see a PHP configuration page. If you do, then check you are running a version later - than 5.4; if it is earlier than this then + 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.Adding a login system
form of protection should a cracker get through our security and steal our database. - -- I was originally of the view that the library should be loaded only if the PHP version - is <5.5, but this feels a bit fussy. I think the note here to say that users - on 5.5+ can omit these changes if they wish is a cleaner. -- -- So, let's make that improvement straight away. To do so, we'll need the library I talked about - at the start of the tutorial, stored here in vendor/password_compat. - The features it provides are so useful they have been built into PHP 5.5, so if you are running - this version (or later) you can skip adding the new file and the associated -
@@ -1115,12 +1095,6 @@require_once
. However if you are running an earlier version, or if you are not - sure which version you have, add all of these changes. + So, let's make that improvement straight away. Here are the changes:Adding a login system
to recreate the passwords they were generated from. -- Whether or not you are using it, by all means do have a read of the source code in - password.php. However, an in-depth exploration of it would be rather - advanced at this stage, so for our purposes we will assume it just works. -
-The next step is to add a login form, and a link from which to access it:
From 33d5b428a6a2519b2da8cc26fc164882aeb1c7d8 Mon Sep 17 00:00:00 2001 From: Jon HinksDate: Sat, 18 Aug 2018 17:13:49 +0100 Subject: [PATCH 18/19] This func is not so new any more... --- pages/original.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/original.html b/pages/original.html index 04ad51e..88f1896 100644 --- a/pages/original.html +++ b/pages/original.html @@ -1088,7 +1088,7 @@ Adding a login system
- So, what does the new code do? It makes use of a new function called + So, what does the new code do? It makes use of a PHP function called
password_hash()
, which takes a password as input and produces what is known as a hash. A hash is a mathematical calculation that is strictly one way, which means that if sometimes steals our database of password hashes, they will find it very difficult indeed From fd23a1e129513617d525452b213c80fc19b94da6 Mon Sep 17 00:00:00 2001 From: euclip <45699492+euclip@users.noreply.github.com> Date: Sat, 2 Mar 2019 19:58:42 +0200 Subject: [PATCH 19/19] Fix minor typo in note about brackets in forms --- pages/original.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/original.html b/pages/original.html index 88f1896..dcde376 100644 --- a/pages/original.html +++ b/pages/original.html @@ -1638,7 +1638,7 @@
- This helps up cheat by doing some of our parsing for us. Here's what happens in that form + This helps us cheat by doing some of our parsing for us. Here's what happens in that form if we press the second Delete button: