Chapter 2
IN THIS CHAPTER
Getting your directory structure set up
Creating the app database and adding the required tables
Understanding PHP sessions
Creating your app’s startup files
Making your coding life easier by taking a modular approach
Every great developer you know got there by solving problems they were unqualified to solve until they actually did it.
— PATRICK MCKENZIE
A well-built web app begins with a solid foundation. Sure, when you’ve got a great idea for a web app, it’s always tempting to work on the visible front end first, even if it’s just cobbling together a quick proof-of-concept page. However, party pooper that I am, I’m going to gently suggest that it’s a good idea to nail down at least some of the more fundamental work off the top. Not only does that give you some major items to check off your app to-do list, but having a solid foundation under your feet will help you immeasurably when it comes time to code the rest of your app.
This chapter is all about laying down that solid foundation. First I show you how to figure out your app’s directory structure. I then talk about constructing the database and tables. From there, I crank up the text editor and talk about some useful PHP techniques such as defining constants, setting up and securing sessions, and including code from one PHP file in another. Finally, I show you how to code the startup files your app needs for both its back end and its front end. Along the way, you see a practical example of every technique as I build out the foundation code for my own FootPower! app.
Start by opening the “Employees Only” door and heading into the back room of the web app. Your back-end work begins by setting up some directories and subdirectories to store your app’s files. Doing this now offers two benefits:
Organization: Even a small app can end up with quite a few files, from PHP scripts to HTML code to external CSS and JavaScript files. If you add your directories on-the-fly as they’re needed, it’s a certainty they’ll end up a bit of a mess, with files scattered hither and thither (as my old grandmother used to say). It’s better to come up with a sensible directory structure now and stick with it throughout the development cycle.
Okay, I hear you saying, “Organization I can get on board with, but what’s all this about security?” Good question. Here’s the answer:
So your directory structure begins by creating one directory and two subdirectories:
public
to reinforce that only files that should be publicly accessible go in this subdirectory.private
to remind me that this is where I put files that should not have public access.After you’ve created the public
subdirectory, you need to tell the web server that this location is the new web root. If you set up the XAMPP web development environment as I describe in Book 1, Chapter 2, then you change the web root by editing a document named httpd.conf
, the location of which depends on your operating system:
httpd.conf
in the following folder:
c:/xampp/apache/conf
httpd.conf
here:
/Applications/XAMPP/xamppfiles/etc
Open httpd.conf
in a text editor and then scroll to or search for the line that begins with DocumentRoot
. For example, here's the Mac version of the line:
DocumentRoot: "/Applications/XAMPP/xamppfiles/htdocs"
Edit this line to point to your app’s web root subdirectory. For example, if you added your main app folder to htdocs
, add a slash (/
), the app folder name, and then /public
. Here's the web root path that I’m using for my FootPower! app:
DocumentRoot: "/Applications/XAMPP/xamppfiles/htdocs/footpower/public"
By default, the web server denies permission to the entire server filesystem, with one exception: the web root. Therefore, you must now tell the server that it’s okay for remote users to access the new web root. To do that, first look for the line in httpd.conf
that begins with <Directory
, followed by the path to the old web root. For example, here's the Mac version of the line:
<Directory "/Applications/XAMPP/xamppfiles/htdocs">
Edit this line to point to your app’s web root, as in this example:
<Directory "/Applications/XAMPP/xamppfiles/htdocs/footpower/public">
Save the file and restart the web server to put the new configuration into effect.
<?php
echo "Hello World from the web root!";
?>
Now surf to localhost
and make sure you see the correct output, as shown in Figure 2-1.
Your final chore for setting up the public
directory is to add the subdirectories you'll use to store various file types. Here are my suggestions:
Subdirectory |
What It Stores |
|
Files that are used in all your web app’s pages, including the top part of each page (the opening tags and the head section) and common page elements such as a header, sidebar, and footer |
|
Your web app's CSS files |
|
Files that handle Ajax requests from the front end |
|
Image files used in your web app |
|
Your web app's JavaScript files |
To give you a kind of road map to where we’re going, Figure 2-2 shows the final file structure of my FootPower! app’s public
directory.
Getting the private
subdirectory configured is much easier because you only have to create the subdirectories you need to organize your app's back-end files. Here are my suggestions:
Subdirectory |
What It Stores |
|
Files that contain the code for your web app’s classes |
|
Files that are used in other back-end files |
|
Log files, such as the error log |
Figure 2-3 shows the final file structure of my FootPower! app's private
directory.
You already know your web app's data requirements, so now it’s time to load phpMyAdmin on your development server (http://localhost/phpMyAdmin
), and then use it to create your MySQL data stores. I go through this in detail in Book 5, Chapter 2, so I only list the general steps here:
utf8_general_ci
collation.If your app needs to support user accounts, create a table to hold the account data.
At a minimum, this table will have an ID field, a username field, and a password field.
If your app needs to save user-generated data, create a table to hold the data.
This table should have an ID field as well as user ID field that, for each user, contains the same ID from the user table you created in Step 2.
If your app is configured so that the user creates one main item and then many subitems, create a table to hold the subitems.
To be clear, the table you created in Step 3 holds each user's main item, and this new table holds the subitems. This table should have an ID field, a field that points to the ID of the main item, and a field for each tidbit of data you want to store.
An example might make this clearer, so I’ll go through the data structures for my FootPower! app. First, here’s the users
table:
Field Name |
Type |
Other Settings |
user_id |
INT |
PRIMARY KEY, AUTO_INCREMENT |
username |
VARCHAR(150) |
UNIQUE, NOT NULL |
password |
VARCHAR(255) |
NOT NULL |
distance_unit |
VARCHAR(10) |
DEFAULT 'miles' |
verification_code |
VARCHAR(32) |
NOT NULL |
verified |
TINYINT |
DEFAULT 0 |
creation_date |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
Each registered user gets an activity log in which to record her activities, so next up is the logs
table:
Field Name |
Type |
Other Settings |
log_id |
INT |
PRIMARY KEY, AUTO_INCREMENT |
user_id |
INT |
NOT NULL |
creation_date |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
Note that both the users
and logs
tables have a common user_id
field. This enables me to link each log to the user who owns it.
For now, go ahead and add one record to this table, where user_id
equals 1 and creation_date
is today's date (in YYYY-MM-DD format).
Finally, each log records the user’s foot-propelled activities, so I’ll store this data in the activities
table:
Field Name |
Type |
Other Settings |
activity_id |
INT |
PRIMARY KEY, AUTO_INCREMENT |
log_id |
INT |
NOT NULL |
type |
VARCHAR(25) |
NOT NULL |
date |
DATE |
NOT NULL |
distance |
DECIMAL(10,6) |
|
duration |
TIME |
Note that both the logs
and activities
tables have a common log_id
field. This will enable me to link each activity to the log in which it belongs.
The back end of a web app consists of both the MySQL data and the PHP code that manipulates that data and returns information to the app's front end. You can get some of the PHP code written now, and you can add the rest as you build the app.
It’s a rare web app that doesn’t have one or more variables that are used throughout the back-end code, but where the value of those variables must never change. For example, when you’re managing server data, your PHP files are constantly logging into the MySQL database, which requires credentials such as a username and password. That username and password are the same throughout your code, but your code will fail if, somehow, these values get changed.
define(name, value)
name
: The name of the constant. By convention, constant names are all uppercase and don't begin with a dollar sign ($
).value
: The value of the constant. The value must be an integer, floating point number, string, or Boolean.Here's an example:
define("GREETING", "Hello Constant World!")
It’s good web app practice to gather all your constants and put them in a separate file, which you can then include in any other PHP file that requires one or more of the constants. (I talk about how you include a PHP file in another PHP file later in this chapter.) For example, here’s a PHP file that defines the database credentials for my FootPower! app:
<?php
define('HOST', 'localhost');
define('USER', 'root');
define('PASSWORD', '');
define('DATABASE', 'footpower');
?>
I’ve named this file constants.php
and added it to the app’s private/common/
directory.
One of the biggest web app challenges is keeping track of certain bits of information as the user moves from page to page within the app. For example, when someone first surfs to the app's home page, your PHP code might store the current date and time in a variable, with the goal of, say, tracking how long that person spends using the app. A worthy goal, to be sure, but when the user moves on to another page in the app, your saved date and time gets destroyed.
Similarly, suppose the user’s first name is stored in the database and you use the first name to personalize each page. Does that mean every time the user accesses a different page in your app, your code must query the database just to get the name?
You start a session by invoking the session_start()
function:
session_start();
Once you’ve done that, the session remains active until the user closes the browser window. Your web server also specifies a maximum lifetime for a session, usually 1,440 seconds (24 minutes). You can check this by running echo phpinfo()
and looking for the session.gc_maxlifetime
value. You can work around this timeout in one of two ways:
session_start()
function to each page, which refreshes the session.session_status()
function, which returns the constant PHP_SESSION_NONE
if the user doesn't have a current session.How does a session help you keep track of information about a user? By offering an array called $_SESSION
, which you can populate with whatever values you want to track:
$_SESSION['start_time'] = time();
$_SESSION['user_first_name'] = 'Biff';
$_SESSION['logged_in'] = 1;
A PHP session is a vital link between your users and your app because it enables you to store data that make each user’s experience easier, more efficient, and more seamless. However, because sessions are such a powerful tool, the world’s dark-side hackers have come up with a number of ingenious ways to hijack user sessions and thereby gain access to session data.
A full tutorial on protecting your users from would-be session-stealers would require an entire book, but there’s a relatively simple technique you can use to thwart all but the most tenacious villains. The technique involves a value called a token, which is a random collection of numbers and letters, usually 32 characters long. How does a token serve to keep a session secure? It’s a three-step process:
$_SESSION
array.input
field (that is, an <input>
tag where the type
attribute is set to hidden
) and set the value
of that field to the session's token value.$_SESSION
array. If they’re identical, it means the form submission is secure (that is, the form was submitted by the session user) and you can safely proceed; if they’re different, however, it almost certainly means that an attacker was trying to pull a fast one and your code should stop processing the form data.openssl_random_pseudo_bytes(length)
length
: An integer that specifies the number of random bytes you want returnedThe openssl_random_pseudo_bytes()
function returns a string of random bytes, but byte values aren't much good to us. We need to convert the binary string to a hexadecimal string, and that’s the job of PHP’s bin2hex()
function:
bin2hex(str)
str
: The binary string you want to convertFor example, 16 bytes will convert to 32 hex characters, so you can use something like the following expression to generate a token:
bin2hex(openssl_random_pseudo_bytes(16));
This creates a value similar to the following:
387f90ce4b3d8f9bd7e4b38068c9fce3
For your session, you'd store the result in the $_SESSION
array, like so:
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
It’s also good practice to generate a fresh token after a certain period of time has elapsed, say 15 minutes. To handle this, when the session starts you use the $_SESSION
array to store the current time plus the expiration time:
$_SESSION['token_expires'] = time() + 900;
PHP's time()
function returns the number of seconds since January 1, 1970, so adding 900
sets the expiration time to 15 minutes in the future. Your web app would then use each session refresh to check whether the token has expired:
if (time() > $_SESSION['token_expires']){
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
$_SESSION['token_expires'] = time() + 900;
}
Most web apps are multi-page affairs, which means your app consists of multiple PHP files, each of which performs a specific task, such as creating data, retrieving data, or logging in a user. Depending on the structure of your app, each of these PHP files will include some or all of the following:
You don't want to copy and paste all this code into each PHP file because if the code changes, then you have to update every instance of the code. Instead, place each chunk of common code in its own PHP file and save those files in a subdirectory. Earlier in this chapter, I explain that you should create two common
subdirectories for such files, one in the public
directory and one in the private
directory. To get a common file's code into another PHP file, use PHP’s include_once
statement:
include_once file;
file
: The path and name of the file with the code you want to includeFor example, here's a PHP file that defines some constants that hold the database credentials for my FootPower! app:
<?php
define('HOST', 'localhost');
define('USER', 'root');
define('PASSWORD', '');
define('DATABASE', 'footpower');
?>
I’ve stored this code in a file named constants.php
in the private/common/
subdirectory, so I'd use the following statement to include it from the web root folder:
include_once '../private/common/constants.php';
The double dots (..
) stand for “go up one directory,” so here they take the script up to the app’s filesystem root, and from there the statement adds the path to constants.php
.
All web apps perform a number of chores at the beginning of any task. On the back end, these initialization chores include starting a user session and connecting to the database, and on the front end the startup includes outputting the app's common HTML (especially the <head>
section) and including the app’s common components, such as a header and footer.
Rather than repeating the code for these startup chores in every file, you should create two files — one for the back end initialization and one for the front end’s common code — and then include the files as you begin each web app task. The next two sections provide the details.
When performing any task, a typical web app must first run through a number of back-end chores, including the following:
You should store this file in your web app’s private/common/
directory. For FootPower!, I created an initialization file named /private/common/initialization.php
:
<?php
// Make sure we see all the errors and warnings
error_reporting(E_ALL | E_STRICT);
// Start a session
session_start();
// Have we not created a token for this session,
// or has the token expired?
if (!isset($_SESSION['token']) || time() > $_SESSION['token_expires']){
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(16));
$_SESSION['token_expires'] = time() + 900;
$_SESSION['log_id'] = 1;
}
// Include the app constants
include_once 'constants.php';
// Connect to the database
$mysqli = new MySQLi(HOST, USER, PASSWORD, DATABASE);
// Check for an error
if($mysqli->connect_error) {
echo 'Connection Failed!
Error #' . $mysqli->connect_errno
. ': ' . $mysqli->connect_error;
exit(0);
}
?>
This code cranks up the error reporting to 11 for the purposes of debugging, starts a new session, creates a session token (if needed), includes the constants file (which contains the database credentials), and then connects to the database and creates a MySQLi
object. Note, too, that I set $_SESSION['log_id'
]
to 1
, but this is temporary. In Book 7, Chapter 4, you see that this value gets set to the user's log ID value when the user signs in to the app.
error_reporting(E_ALL | E_STRICT)
with these statements:
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '../private/logs/error_log');
These statements configure PHP to not display errors onscreen, but to log them to a file, the name and path of which is specified in the final statement.
Each page of your web app has a common structure. For example, the top part of each page includes the following elements:
DOCTYPE
and the <html>
taghead
element, including the <meta>
tags, page title, CSS <link>
tags, and JavaScript <script>
tagsready
event<body>
tag<header>
, <nav>
, and <main>
tagsHere's an example, which I’m going to name public/common/top.php
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FootPower! | <?php echo $page_title ?></title>
<link href="css/styles.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
});
</script>
</head>
<body>
<header role="banner">
</header>
<main role="main">
<article role="contentinfo">
<header class="article-header" role="banner">
<div class="header-title">
<h1><?php echo $page_title ?></h1>
</div>
</header>
In this code, note that the page title is given by the following inline PHP:
<?php echo $page_title ?>
<?php
$page_title = 'Home';
include_once 'common/top.php';
?>
Note that this same title also gets inserted in the page header
element, within the <h1>
tag.
Most web apps also include a sidebar — defined by an <aside>
tag — that includes info common to all pages, such as a description of the app, instructions for using the app, the latest app news, or a newsletter sign-up form. For this sidebar, create a separate file called, say, publiccommonsidebar.php
and include your code:
<aside role="complementary">
Your sidebar text and tags go here
</aside>
Finally, you need a file to handle the common elements that appear at the bottom of each page, including the </main>
closing tag, a footer, and the </body>
and </html>
closing tags. For this code, create a separate file called, say, publiccommonottom.php
and add your code:
</main>
<footer role="contentinfo">
Copyright <?php echo date('Y'); ?> Your Name
</footer>
<script src="js/data.js"></script>
<script src="js/user.js"></script>
</body>
</html>
With the initialization files in place, it’s time to build the skeleton for the app’s home page. At the moment, this page is nothing but PHP:
<?php
include_once '../private/common/initialization.php';
$page_title = 'Home';
include_once 'common/top.php';
?>
Main app content goes here
<?php
include_once 'common/sidebar.php';
include_once 'common/bottom.php';
?>
Save this file as index.php
in the web root directory.
18.222.138.230