Make your own blog
Tidy up
We've got a nice set of features working, but there are a good number of improvements and refactoring changes that can be made. Doing these periodically can make for easier and happier development, which in turn can improve your development efficiency.
The first change is two-fold: pages were replicating some information in the header, and various items (the top menu bar and system messages) were using inline style rules rather than using class rules that need only be written once.
 
		 
	- assets/main.css assets/main.css
- index.php index.php
- install.php install.php
- login.php login.php
- templates/comment-form.php templates/comment-form.php
- templates/head.php templates/head.php
- templates/title.php templates/title.php
- view-post.php view-post.php
 
				1
					2
					3
					4
					5
					6
					7
					8
					9
					10
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					23
					24
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					35
					36
					/* Success/error message boxes */
					.box {
						border: 1px dotted silver;
						border-radius: 5px;
						padding: 4px;
					}
					.error {
						background-color: #ff6666;
					}
					.success {
						background-color: #88ff88;
					}
					.install-password {
						font-size: 1.2em;
					}
					.top-menu {
						border: 1px dotted silver;
						min-height: 18px;
						padding: 4px;
						margin-bottom: 4px;
					}
					.menu-options {
						float: right;
					}
					h1, h2, h3 {
						margin-top: 0;
						margin-bottom: 8px;
					}
					body {
						font-family: sans-serif;
					}
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					35
					36
					37
					<html>
						<head>
							<title>A blog application</title>
							<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
						</head>
						<body>
							<?php require 'templates/title.php' ?>
							<?php if ($notFound): ?>
								<div style="border: 1px solid #ff6666; padding: 6px;">
									Error: cannot find the requested blog post
								</div>
							<?php endif ?>
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					35
					36
					37
					<html>
						<head>
							<title>A blog application</title>
							<?php require 'templates/head.php' ?>
						</head>
						<body>
							<?php require 'templates/title.php' ?>
							<?php if ($notFound): ?>
								<div class="error box">
									Error: cannot find the requested blog post
								</div>
							<?php endif ?>
					52
					53
					54
					55
					56
					57
					58
					59
					60
					61
					62
					63
					64
					65
					66
					67
					68
					69
					70
					71
					78
					79
					80
					81
					82
					83
					84
					<html>
						<head>
							<title>Blog installer</title>
							<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
							<style type="text/css">
								.box {
									border: 1px dotted silver;
									border-radius: 5px;
									padding: 4px;
								}
								.error {
									background-color: #ff6666;
								}
								.success {
									background-color: #88ff88;
								}
							</style>
						</head>
						<body>
							<?php if ($attempted): ?>
										<?php // Report the new password ?>
										The new '<?php echo htmlEscape($username) ?>' password is
										<span style="font-size: 1.2em;"><?php echo htmlEscape($password) ?></span>
										(copy it to clipboard if you wish).
									</div>
					52
					53
					54
					55
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					56
					57
					58
					78
					79
					80
					81
					82
					83
					84
					<html>
						<head>
							<title>Blog installer</title>
							<?php require 'templates/head.php' ?>
						</head>
						<body>
							<?php if ($attempted): ?>
										<?php // Report the new password ?>
										The new '<?php echo htmlEscape($username) ?>' password is
										<span class="install-password"><?php echo htmlEscape($password) ?></span>
										(copy it to clipboard if you wish).
									</div>
					35
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
							<title>
								A blog application | Login
							</title>
							<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
						</head>
						<body>
							<?php require 'templates/title.php' ?>
							<?php // If we have a username, then the user got something wrong, so let's have an error ?>
							<?php if ($username): ?>
								<div style="border: 1px solid #ff6666; padding: 6px;">
									The username or password is incorrect, try again
								</div>
							<?php endif ?>
					35
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
							<title>
								A blog application | Login
							</title>
							<?php require 'templates/head.php' ?>
						</head>
						<body>
							<?php require 'templates/title.php' ?>
							<?php // If we have a username, then the user got something wrong, so let's have an error ?>
							<?php if ($username): ?>
								<div class="error box">
									The username or password is incorrect, try again
								</div>
							<?php endif ?>
					10
					11
					12
					13
					14
					15
					16
					<?php // Report any errors in a bullet-point list ?>
					<?php if ($errors): ?>
						<div style="border: 1px solid #ff6666; padding: 6px;">
							<ul>
								<?php foreach ($errors as $error): ?>
									<li><?php echo $error ?></li>
					10
					11
					12
					13
					14
					15
					16
					<?php // Report any errors in a bullet-point list ?>
					<?php if ($errors): ?>
						<div class="error box">
							<ul>
								<?php foreach ($errors as $error): ?>
									<li><?php echo $error ?></li>
					1
					2
					<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
					<link rel="stylesheet" type="text/css" href="assets/main.css" />
					1
					2
					3
					4
					5
					6
					7
					 
					 
					8
					9
					10
					<div style="float: right;">
						<?php if (isLoggedIn()): ?>
						Hello <?php echo htmlEscape(getAuthUser()) ?>.
							<a href="logout.php">Log out</a>
						<?php else: ?>
							<a href="login.php">Log in</a>
						<?php endif ?>
					</div>
					<a href="index.php">
					1
					2
					3
					4
					5
					6
					7
					8
					9
					10
					11
					12
					<div class="top-menu">
						<div class="menu-options">
							<?php if (isLoggedIn()): ?>
								Hello <?php echo htmlEscape(getAuthUser()) ?>.
								<a href="logout.php">Log out</a>
							<?php else: ?>
								<a href="login.php">Log in</a>
							<?php endif ?>
						</div>
					</div>
					<a href="index.php">
					62
					63
					64
					65
					66
					67
					68
								A blog application |
								<?php echo htmlEscape($row['title']) ?>
							</title>
							<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
						</head>
						<body>
							<?php require 'templates/title.php' ?>
					62
					63
					64
					65
					66
					67
					68
								A blog application |
								<?php echo htmlEscape($row['title']) ?>
							</title>
							<?php require 'templates/head.php' ?>
						</head>
						<body>
							<?php require 'templates/title.php' ?>
					Let's take a better look at the second change. Some of the original code was written like this:
	<div style="border: 1px solid #ff6666; padding: 6px;"> … </div>
	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
	an invisible box around the content.
However, having to write that for every error message takes a bit of effort, and it's a pain if we decide that error messages should have a magenta border and not a red one. Thus, it makes life easier if we write this instead:
	<div class="error box"> … </div>
	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.
While we are dealing with CSS changes, let's add a few more. Here we add some rules for article synopses, titles and dates on the home page, and comments on individual blog posts.
 
		 
	- assets/main.css assets/main.css
- index.php index.php
- templates/comment-form.php templates/comment-form.php
- view-post.php view-post.php
 
				33
					34
					35
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					36
					body {
						font-family: sans-serif;
					}
					33
					34
					35
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
					49
					50
					51
					52
					53
					54
					55
					56
					57
					58
					59
					60
					61
					62
					63
					64
					65
					66
					67
					68
					69
					70
					71
					body {
						font-family: sans-serif;
					}
					.post-synopsis {
						padding-bottom: 8px;
						border-bottom: 1px dotted silver;
						margin-bottom: 20px;
					}
					.post-synopsis h2, .post h2 {
						color: darkblue;
					}
					.post .date, .post-synopsis .meta {
						color: white;
						background-color: grey;
						border-radius: 7px;
						padding: 2px;
						display: inline;
						font-size: 0.95em;
					}
					.comment .comment-meta {
						font-size: 0.85em;
						color: grey;
						border-top: 1px dotted silver;
						padding-top: 8px;
					}
					.comment-body p {
						margin: 8px 4px;
					}
					.comment-list {
						border-bottom: 1px dotted silver;
						margin-bottom: 12px;
					}
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
					49
					50
					51
					52
					53
					54
					55
					56
					 
					 
					 
					 
					57
					58
					59
								</div>
							<?php endif ?>
							<?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?>
								<h2>
									<?php echo htmlEscape($row['title']) ?>
								</h2>
								<div>
									<?php echo convertSqlDate($row['created_at']) ?>
									(<?php echo countCommentsForPost($row['id']) ?> comments)
								</div>
								<p>
									<?php echo htmlEscape($row['body']) ?>
								</p>
								<p>
									<a
										href="view-post.php?post_id=<?php echo $row['id'] ?>"
									>Read more...</a>
								</p>
							<?php endwhile ?>
						</body>
					</html>
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
					49
					50
					51
					52
					53
					54
					55
					56
					57
					58
					59
					60
					61
					62
					63
								</div>
							<?php endif ?>
							<div class="post-list">
								<?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?>
									<div class="post-synopsis">
										<h2>
											<?php echo htmlEscape($row['title']) ?>
										</h2>
										<div class="meta">
											<?php echo convertSqlDate($row['created_at']) ?>
											(<?php echo countCommentsForPost($row['id']) ?> comments)
										</div>
										<p>
											<?php echo htmlEscape($row['body']) ?>
										</p>
										<div class="read-more">
											<a
												href="view-post.php?post_id=<?php echo $row['id'] ?>"
											>Read more...</a>
										</div>
									</div>
								<?php endwhile ?>
							</div>
						</body>
					</html>
					5
					6
					7
					8
					9
					10
					11
					12
					13
					 */
					?>
					<?php // We'll use a rule-off for now, to separate page sections ?>
					<hr />
					<?php // Report any errors in a bullet-point list ?>
					<?php if ($errors): ?>
						<div class="error box">
					5
					6
					7
					 
					 
					 
					8
					9
					10
					 */
					?>
					<?php // Report any errors in a bullet-point list ?>
					<?php if ($errors): ?>
						<div class="error box">
					67
					68
					69
					70
					71
					72
					73
					74
					 
					 
					 
					 
					 
					75
					76
					77
					78
					79
					 
					80
					81
					82
					83
					84
					85
					86
					87
					88
					89
					 
					 
					 
					90
					91
					92
					93
					94
					95
					96
					97
					98
					99
						<body>
							<?php require 'templates/title.php' ?>
							<h2>
								<?php echo htmlEscape($row['title']) ?>
							</h2>
							<div>
								<?php echo convertSqlDate($row['created_at']) ?>
							</div>
							<?php // This is already escaped, so doesn't need further escaping ?>
							<?php echo convertNewlinesToParagraphs($row['body']) ?>
							<h3><?php echo countCommentsForPost($postId) ?> comments</h3>
							<?php foreach (getCommentsForPost($postId) as $comment): ?>
								<?php // For now, we'll use a horizontal rule-off to split it up a bit ?>
								<hr />
								<div class="comment">
									<div class="comment-meta">
										Comment from
										<?php echo htmlEscape($comment['name']) ?>
										on
										<?php echo convertSqlDate($comment['created_at']) ?>
									</div>
									<div class="comment-body">
										<?php // This is already escaped ?>
										<?php echo convertNewlinesToParagraphs($comment['text']) ?>
									</div>
								</div>
							<?php endforeach ?>
							<?php require 'templates/comment-form.php' ?>
						</body>
					67
					68
					69
					70
					71
					72
					73
					74
					75
					76
					77
					78
					79
					80
					 
					 
					81
					82
					83
					84
					85
					86
					87
					88
					89
					90
					91
					92
					93
					94
					95
					96
					97
					98
					99
					 
					 
					 
					 
					100
					101
					102
						<body>
							<?php require 'templates/title.php' ?>
							<div class="post">
								<h2>
									<?php echo htmlEscape($row['title']) ?>
								</h2>
								<div class="date">
									<?php echo convertSqlDate($row['created_at']) ?>
								</div>
								<?php // This is already escaped, so doesn't need further escaping ?>
								<?php echo convertNewlinesToParagraphs($row['body']) ?>
							</div>
							<div class="comment-list">
								<h3><?php echo countCommentsForPost($postId) ?> comments</h3>
								<?php foreach (getCommentsForPost($postId) as $comment): ?>
									<div class="comment">
										<div class="comment-meta">
											Comment from
											<?php echo htmlEscape($comment['name']) ?>
											on
											<?php echo convertSqlDate($comment['created_at']) ?>
										</div>
										<div class="comment-body">
											<?php // This is already escaped ?>
											<?php echo convertNewlinesToParagraphs($comment['text']) ?>
										</div>
									</div>
								<?php endforeach ?>
							</div>
							<?php require 'templates/comment-form.php' ?>
						</body>
					You may have noticed that some function calls that need to access the database create their own connection rather than use one that we've already created. For low-volume systems this might not matter a great deal, but programmers hate this sort of inefficiency, and where it is trivial to fix, we should.
 
		 
	- index.php index.php
- lib/common.php lib/common.php
- view-post.php view-post.php
 
				45
					46
					47
					48
					49
					50
					51
										<div class="meta">
											<?php echo convertSqlDate($row['created_at']) ?>
											(<?php echo countCommentsForPost($row['id']) ?> comments)
										</div>
										<p>
											<?php echo htmlEscape($row['body']) ?>
					45
					46
					47
					48
					49
					50
					51
										<div class="meta">
											<?php echo convertSqlDate($row['created_at']) ?>
											(<?php echo countCommentsForPost($pdo, $row['id']) ?> comments)
										</div>
										<p>
											<?php echo htmlEscape($row['body']) ?>
					94
					95
					96
					 
					97
					98
					99
					100
					101
					102
					103
					104
					105
					119
					120
					121
					 
					122
					 
					123
					124
					125
					126
					127
					128
					129
					/**
					 * Returns the number of comments for the specified post
					 *
					 * @param integer $postId
					 * @return integer
					 */
					function countCommentsForPost($postId)
					{
						$pdo = getPDO();
						$sql = "
							SELECT
								COUNT(*) c
					/**
					 * Returns all the comments for the specified post
					 *
					 * @param integer $postId
					 */
					function getCommentsForPost($postId)
					{
						$pdo = getPDO();
						$sql = "
							SELECT
								id, name, text, created_at, website
					94
					95
					96
					97
					98
					99
					100
					101
					102
					 
					103
					104
					105
					119
					120
					121
					122
					123
					124
					125
					126
					127
					 
					128
					129
					130
					/**
					 * Returns the number of comments for the specified post
					 *
					 * @param PDO $pdo
					 * @param integer $postId
					 * @return integer
					 */
					function countCommentsForPost(PDO $pdo, $postId)
					{
						$sql = "
							SELECT
								COUNT(*) c
					/**
					 * Returns all the comments for the specified post
					 *
					 * @param PDO $pdo
					 * @param integer $postId
					 * return array
					 */
					function getCommentsForPost(PDO $pdo, $postId)
					{
						$sql = "
							SELECT
								id, name, text, created_at, website
					80
					81
					82
					83
					84
					85
					86
					87
					88
							</div>
							<div class="comment-list">
								<h3><?php echo countCommentsForPost($postId) ?> comments</h3>
								<?php foreach (getCommentsForPost($postId) as $comment): ?>
									<div class="comment">
										<div class="comment-meta">
											Comment from
					80
					81
					82
					83
					84
					85
					86
					87
					88
							</div>
							<div class="comment-list">
								<h3><?php echo countCommentsForPost($pdo, $postId) ?> comments</h3>
								<?php foreach (getCommentsForPost($pdo, $postId) as $comment): ?>
									<div class="comment">
										<div class="comment-meta">
											Comment from
					Now we improve the appearance of the comment form. It's worth opening up an article page prior to making the improvements, so you can refresh it after the CSS is in place. This will allow you to see the change quickly.
 
		 
	- assets/main.css assets/main.css
- templates/comment-form.php templates/comment-form.php
 
				10
					11
					12
					 
					 
					 
					 
					 
					 
					 
					 
					13
					14
					15
					76
					77
					78
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					79
					.success {
						background-color: #88ff88;
					}
					.install-password {
						font-size: 1.2em;
					.comment-list {
						border-bottom: 1px dotted silver;
						margin-bottom: 12px;
					}
					10
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					23
					76
					77
					78
					79
					80
					81
					82
					83
					84
					85
					86
					87
					88
					89
					90
					91
					92
					93
					94
					95
					96
					97
					98
					.success {
						background-color: #88ff88;
					}
					.box ul {
						margin: 4px;
						padding-left: 14px;
					}
					.box ul li {
						margin-bottom: 2px;
					}
					.install-password {
						font-size: 1.2em;
					.comment-list {
						border-bottom: 1px dotted silver;
						margin-bottom: 12px;
					}
					.comment-margin {
						margin-bottom: 8px;
					}
					.comment-form input,
					.comment-form textarea {
						margin: 4px;
					}
					.comment-form label {
						font-size: 0.95em;
						margin: 7px;
						width: 100px;
						width: 7em;
						color: grey;
						float: left;
						text-align: right;
					}
					7
					8
					9
					10
					11
					12
					13
					18
					19
					20
					21
					22
					23
					24
					25
					29
					30
					31
					32
					33
					34
					35
					36
					40
					41
					42
					43
					44
					45
					46
					47
					51
					52
					53
					54
					55
					56
					 
					 
					57
					<?php // Report any errors in a bullet-point list ?>
					<?php if ($errors): ?>
						<div class="error box">
							<ul>
								<?php foreach ($errors as $error): ?>
									<li><?php echo $error ?></li>
					<h3>Add your comment</h3>
					<form method="post">
						<p>
							<label for="comment-name">
								Name:
							</label>
								name="comment-name"
								value="<?php echo htmlEscape($commentData['name']) ?>"
							/>
						</p>
						<p>
							<label for="comment-website">
								Website:
							</label>
								name="comment-website"
								value="<?php echo htmlEscape($commentData['website']) ?>"
							/>
						</p>
						<p>
							<label for="comment-text">
								Comment:
							</label>
								rows="8"
								cols="70"
							><?php echo htmlEscape($commentData['text']) ?></textarea>
						</p>
						<input type="submit" value="Submit comment" />
					</form>
					7
					8
					9
					10
					11
					12
					13
					18
					19
					20
					21
					22
					23
					24
					25
					29
					30
					31
					32
					33
					34
					35
					36
					40
					41
					42
					43
					44
					45
					46
					47
					51
					52
					53
					54
					55
					56
					57
					58
					59
					<?php // Report any errors in a bullet-point list ?>
					<?php if ($errors): ?>
						<div class="error box comment-margin">
							<ul>
								<?php foreach ($errors as $error): ?>
									<li><?php echo $error ?></li>
					<h3>Add your comment</h3>
					<form method="post" class="comment-form">
						<div>
							<label for="comment-name">
								Name:
							</label>
								name="comment-name"
								value="<?php echo htmlEscape($commentData['name']) ?>"
							/>
						</div>
						<div>
							<label for="comment-website">
								Website:
							</label>
								name="comment-website"
								value="<?php echo htmlEscape($commentData['website']) ?>"
							/>
						</div>
						<div>
							<label for="comment-text">
								Comment:
							</label>
								rows="8"
								cols="70"
							><?php echo htmlEscape($commentData['text']) ?></textarea>
						</div>
						<div>
							<input type="submit" value="Submit comment" />
						</div>
					</form>
					
	We now turn our attention to an improvement to the database. This consists of the tables
	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).
	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.
	To protect against that scenario, many database systems will allow the use of foreign key
	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.
So, let's make these changes:
 
		 
	- data/init.sql data/init.sql
- lib/common.php lib/common.php
- lib/install.php lib/install.php
 
				2
					3
					4
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					5
					6
					7
					36
					37
					38
					39
					 
					40
					41
					42
					91
					92
					93
					94
					 
					95
					96
					97
					122
					123
					124
					125
					126
					127
					128
					129
					130
					131
					132
					 * Database creation script
					 */
					DROP TABLE IF EXISTS post;
					CREATE TABLE post (
						body VARCHAR NOT NULL,
						user_id INTEGER NOT NULL,
						created_at VARCHAR NOT NULL,
						updated_at VARCHAR
					);
					INSERT INTO
						created_at VARCHAR NOT NULL,
						name VARCHAR NOT NULL,
						website VARCHAR,
						text VARCHAR NOT NULL
					);
					INSERT INTO
							"This is a comment from Jonny"
						)
					;
					CREATE TABLE user (
						id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
						username VARCHAR NOT NULL,
						password VARCHAR NOT NULL,
						created_at VARCHAR NOT NULL,
						is_enabled BOOLEAN NOT NULL DEFAULT true
					);
					2
					3
					4
					5
					6
					7
					8
					9
					10
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					23
					24
					25
					26
					27
					28
					29
					30
					31
					32
					33
					36
					37
					38
					39
					40
					41
					42
					43
					91
					92
					93
					94
					95
					96
					97
					98
					122
					123
					124
					 * Database creation script
					 */
					/* Foreign key constraints need to be explicitly enabled in SQLite */
					PRAGMA foreign_keys = ON;
					DROP TABLE IF EXISTS user;
					CREATE TABLE user (
						id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
						username VARCHAR NOT NULL,
						password VARCHAR NOT NULL,
						created_at VARCHAR NOT NULL,
						is_enabled BOOLEAN NOT NULL DEFAULT true
					);
					/* This will become user = 1. I'm creating this just to satisfy constraints here.
					   The password will be properly hashed in the installer */
					INSERT INTO
						user
						(
							username, password, created_at, is_enabled
						)
						VALUES
						(
							"admin", "unhashed-password", datetime('now', '-3 months'), 0
						)
					;
					DROP TABLE IF EXISTS post;
					CREATE TABLE post (
						body VARCHAR NOT NULL,
						user_id INTEGER NOT NULL,
						created_at VARCHAR NOT NULL,
						updated_at VARCHAR,
						FOREIGN KEY (user_id) REFERENCES user(id)
					);
					INSERT INTO
						created_at VARCHAR NOT NULL,
						name VARCHAR NOT NULL,
						website VARCHAR,
						text VARCHAR NOT NULL,
						FOREIGN KEY (post_id) REFERENCES post(id)
					);
					INSERT INTO
							"This is a comment from Jonny"
						)
					;
					37
					38
					39
					40
					 
					 
					 
					 
					 
					 
					 
					 
					 
					41
					42
					43
					 */
					function getPDO()
					{
						return new PDO(getDsn());
					}
					/**
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
					49
					50
					51
					52
					 */
					function getPDO()
					{
						$pdo = new PDO(getDsn());
						// Foreign key constraints need to be enabled manually in SQLite
						$result = $pdo->query('PRAGMA foreign_keys = ON');
						if ($result === false)
						{
							throw new Exception('Could not turn on foreign key constraints');
						}
						return $pdo;
					}
					/**
					76
					77
					78
					79
					80
					81
					82
					100
					101
					102
					103
					104
					105
					106
					107
					108
					109
					110
					111
					112
					113
					114
					115
					116
					135
					136
					137
					138
					139
					140
					141
					}
					/**
					 * Creates a new user in the database
					 *
					 * @param PDO $pdo
					 * @param string $username
						// Insert the credentials into the database
						$sql = "
							INSERT INTO
								user
								(username, password, created_at)
								VALUES (
									:username, :password, :created_at
								)
						";
						$stmt = $pdo->prepare($sql);
						if ($stmt === false)
						{
							$error = 'Could not prepare the user creation';
						}
						if (!$error)
							);
							if ($result === false)
							{
								$error = 'Could not run the user creation';
							}
						}
					76
					77
					78
					79
					80
					81
					82
					100
					101
					102
					103
					104
					105
					106
					107
					108
					109
					110
					111
					112
					113
					114
					115
					116
					135
					136
					137
					138
					139
					140
					141
					}
					/**
					 * Updates the admin user in the database
					 *
					 * @param PDO $pdo
					 * @param string $username
						// Insert the credentials into the database
						$sql = "
							UPDATE
								user
							SET
								password = :password, created_at = :created_at, is_enabled = 1
							WHERE
								username = :username
						";
						$stmt = $pdo->prepare($sql);
						if ($stmt === false)
						{
							$error = 'Could not prepare the user update';
						}
						if (!$error)
							);
							if ($result === false)
							{
								$error = 'Could not run the user password update';
							}
						}
					Here's what's new:
- Since we want posts to reference users, we need a user first. Thus, I've swapped the order around, so the test user is created before the test post
- 
		We now need the test user to be created immediately in order to satisfy the foreign key
		constraint, so we INSERTthat in the script with a dummy hash value, andUPDATEit afterwards with the real hash generated in PHP
- Now when we make database connections in common.php, the first thing we do is to switch on foreign key constraints
As usual: delete your database file, re-run the installer, and check all the changes appear to work.
The last change is related to security. Since all of our files are in the web server's public directory, a user who knows our directory structure would be able to download files that we didn't intend to make accessible. Since SQLite uses a single file to store its data, and since this file is often stored in a web-accessible location, it is of particular importance to lock this down.
 
		 
	- .htaccess .htaccess
 
				1
					2
					3
					4
					RewriteEngine on
					RewriteCond %{REQUEST_URI} ^/(data|lib|templates|vendor)/
					RewriteRule ^ - [L,R=404]
					 Download
						Download