PHP and MySQL is one of the most common stacks for web development, even in 2014. With it such a common combination powering so many websites, it is quite shocking that the most common online examples of using it are completely horrible and outdated by at least a decade. Try searching for "php mysql tutorial" and see what you find - it ain't pretty.
In this tutorial I will try and show a simple, yet forward thinking and secure way to use MySQL in PHP. I will explain the reasoning behind each step and compare it with the less desired alternative. This tutorial does not cover installing MySQL on your web server - it focuses on using it in PHP. Perhaps we will cover that in a future tutorial, but that part is usually pretty straightforward.
If it works, don't fix it
If they're so horrible, how come those examples are still so common?
because they work. Yes, the code shown on sites like W3Schools and others of its ilk, does work. It's insecure, uses outdated methods, does not protect from change and promotes bad habits, but it does work. Let's look at an example:
<?php
$username="username";
$password="password";
$database="your_database";
$field1-name=$_POST['Value1'];
$field2-name=$_POST['Value2'];
$field3-name=$_POST['Value3'];
$field4-name=$_POST['Value4'];
$field5-name=$_POST['Value5'];
mysql_connect(localhost,$username,$password);
@mysql_select_db($database) or die( "Unable to select database");
$query = "INSERT INTO tablename VALUES('','$field1-name','$field2-name',
'$field3-name','$field4-name','$field5-name')";
mysql_query($query);
mysql_close();
?>
What are the problems here?
- User input is fed directly into query without escaping, making this code vulnerable to SQL injection attacks (or even accidental). We always want to escape user input in database queries, or better yet - use prepared statements (more on that below), so we don't end up with a Bobby Tables situation.
mysql_query()
function used directly in the code. While this might seem fine for a simple tutorial, those tutorials target inexperienced developers and instill bad habits. You want to abstract database access in case you want to change your database or adapter (PHP extension used to communicate with the database).mysql_query()
specifically has been deprecated as of PHP 5.5. It's interesting to note that W3Schools updated their tutorial to use the MySQLi extension, but most tutorials still use the mysql_
functions to demonstrate usage.
- The error suppression operator is used (@), and a failure on selecting the database kills the script with a generic message (which many of you probably seen in the past on actual production websites). No proper error handling is introduced. How do you debug this script if you're a complete newbie to PHP and MySQL?
- A database connection is created manually with the credentials in the same script running the query. We would want to abstract creating the connection, so we have a central location to manage it, and likely also keep the credentials in a separate file - especially important if you serve your files directly from the document root directory (web visible). You shouldn't do that anyway, but that is outside the scope of this tutorial (perhaps in a follow up?) - but at least you should protect your database credentials. As a side note, there is no error handling on connecting to the database - which is a much more common error than not being able to select one after connecting.
Doing it the right way
Let's break the process step-by-step, and see how we can improve on the example above.
Choosing an extension
Much of the functionality of PHP is delivered by extensions - plug-in files that are loaded when you start your web server (or PHP process on some configuration). Database access is delivered through extensions, and there are multiple options to choose from.
To keep a long story short, you can go with either the MySQLi or PDO extension and do well. The MySQLi extension supports slightly more use-cases than PDO, while PDO has a uniform API for other databases (such as PostgreSQL and MsSQL), which is nice if you ever need to switch. You can view
a more detailed comparison of the various extensions over at the official PHP site.
For the purposes of this tutorial we use the MySQLi extension.
A note about MySQLi:PHP presents two ways to use MySQLi - procedural (functions) or object-oriented (using the MySQLi set of classes). Both are identical syntax and usage wise, except for the way you call the functions (as standalone or on a MySQLi object). To make things simpler for people who have not yet ventured into object oriented programming (OOP), I use the procedural version. If you do use OOP, it should not be a problem to use this tutorial as a guideline and apply it on the OOP style.
Connecting to the database
The first order of business is connecting to the database. With MySQLi, the function that does that is
mysqli_connect()
:
// Try and connect to the database
$connection = mysqli_connect('localhost',$username,$password,$dbname);
// If connection was not successful, handle the error
if($connection === false) {
// Handle error - notify administrator, log to a file, show an error screen, etc.
}
Explanation:
- First we connect to the databasewith the
mysqli_connect()
which needs the host name, database username and password as a minimum to create a connection. We also pass the database name as the 4th parameter to select that database for the queries we will run. You canpass additional parameters - port and socket, if your database requires that level of configuration (the defaults work on most installations. You can also change it in your PHP configuration).
- Then we check if the connection was successful. There are many reasons why the connection could fail - common ones include wrong credentials ( / misconfigured database) and high database usage load which causes the database to refuse new connections. How you handle this error is up to you - common approaches include showing a custom error page to the user, and notifying the administrator (via Email or otherwise) about the error.
Something is missing here though - where do the database credentials come from? As I mentioned previously, it's not good practice to keep those in the database connection script, especially if the PHP files are inside the document root (which means they will be accessible directly from the web). A server error (malfunction or intentional) could cause those files to be displayed as text, revealing your database credentials to everyone viewing it at the time.
If you do not put your PHP scripts inside the document root, but rather include them from outside of it (commonly used with
bootstrapping), then this is not a must. I still like to keep my configuration settings in a separate file, which I can refer to any time I need to make overall changes to my PHP application.
Let's create a simple configuration file, which looks like this:
[database]
username = root
password = 1234
dbname = mydb
This is the
ini
configuration format, for which PHP has very good support out of the box. Save it in a file called
config.ini
and place it outside of the document root, so it is not accessible from the web. We will now modify our code to load the configuration and use it to create the database connection:
// Load configuration as an array. Use the actual location of your configuration file
$config = parse_ini_file('../config.ini');
// Try and connect to the database
$connection = mysqli_connect('localhost',$config['username'],$config['password'],$config['dbname']);
// If connection was not successful, handle the error
if($connection === false) {
// Handle error - notify administrator, log to a file, show an error screen, etc.
}
We've abstracted the credentials from the connection. But now we're going to go one step higher, and put the connection process inside a function, which we can later use any time we want to access the database. Note that unless you close the connection to the database (as the initial example showed),
you only need to connect to the database once during the lifetime of the script.
function db_connect() {
// Define connection as a static variable, to avoid connecting more than once
static $connection;
// Try and connect to the database, if a connection has not been established yet
if(!isset($connection)) {
// Load configuration as an array. Use the actual location of your configuration file
$config = parse_ini_file('../config.ini');
$connection = mysqli_connect('localhost',$config['username'],$config['password'],$config['dbname']);
}
// If connection was not successful, handle the error
if($connection === false) {
// Handle error - notify administrator, log to a file, show an error screen, etc.
return mysqli_connect_error();
}
return $connection;
}
This our database connection function. I prefixed it with
db_
, which I will do as well for the rest of the database functions later. If we were using OOP, we could have an even cleaner looking interface by creating a database class which has the operations as the method names (
connect()
,
query()
, etc.). An OOP version of this tutorial has been included at the end of the tutorial.
I added one small wrinkle in there - defining $connection as a static variable, which means its state will be kept between function calls to
db_connect()
. This allows to connect just once to the database, and then just return the connection on future function calls. In the old
mysql_connect()
, the connection would be kept globally by MySQL. We emulate that behavior here, to avoid establishing a connection every time we need to query the database.
Querying the database
Now that we have a connection, we can start querying the database. The simplest way to go about it is to use the
mysqli_query()
function.
// Connect to the database
$connection = db_connect();
// Query the database
$result = mysqli_query($connection,$query);
Pretty straightforward. The value of
$result
will be
false
if the query failed, otherwise its value depends on the type of query we ran. For SELECT, SHOW, DESCRIBE or EXPLAIN queries, it will return a
mysqli_result
object, and for all other queries it will return
true
. As before, let's wrap it up in a function:
function db_query($query) {
// Connect to the database
$connection = db_connect();
// Query the database
$result = mysqli_query($connection,$query);
return $result;
}
Now let's take a look at an example query:
// An insertion query. $result will be `true` if successful
$result = db_query("INSERT INTO `users` (`name`,`email`) VALUES ('John Doe','john.doe@gmail.com')");
if($result === false) {
// Handle failure - log the error, notify administrator, etc.
} else {
// We successfully inserted a row into the database
}
If $result is
false
, we would like to have the error message returned by the database. For that purpose we'll create a function that gets it for us:
function db_error() {
$connection = db_connect();
return mysqli_error($connection);
}
Then we could use it in the code above:
if($result === false) {
$error = db_error();
// Send the error to an administrator, log to a file, etc.
}
Selecting rows
As mentioned above, running a SELECT query (and a couple of other similar types) will return a
mysqli_result
object. To get the row data itself from the database, we need an additional step that fetches that data. We'll use
mysqli_fetch_assoc
which retrieves rows from a
mysqli_result
object as an associated array - the array indexes will be the column names from the database.
// A select query. $result will be a `mysqli_result` object if successful
$result = db_query("SELECT `name`,`email` FROM `users` WHERE id=5");
if($result === false) {
// Handle failure - log the error, notify administrator, etc.
} else {
// Fetch all the rows in an array
$rows = array();
while ($row = mysqli_fetch_assoc($result)) {
$rows[] = $row;
}
}
Now we have an array containing the rows returned from the database. If no rows were returned - the array would be empty. Since fetching rows is a very common use case, we'll wrap it up in its own function:
function db_select($query) {
$rows = array();
$result = db_query($query);
// If query failed, return `false`
if($result === false) {
return false;
}
// If query was successful, retrieve all the rows into an array
while ($row = mysqli_fetch_assoc($result)) {
$rows[] = $row;
}
return $rows;
}
Querying the database now is dead simple:
$rows = db_select("SELECT `name`,`email` FROM `users` WHERE id=5");
if($rows === false) {
$error = db_error();
// Handle error - inform administrator, log to file, show error page, etc.
}
If
db_select()
returned
false
, we need to handle the error. Otherwise we can use the returned database data in our application.
Escaping dynamic values and user input
In the previous two example queries, I manually placed the values I passed to the database in the query - the user name and email in the INSERT query, and the user id in the SELECT query. If we replace those values with variables that contain unknown values, and especially user input, we need to escape it properly. There are a few characters - notably quotation marks and carriage returns - that will break our query, and in the case of user input allow an attacker to compromise our database (an attack referred to as SQL injection).
For that purpose, we'll use the
mysqli_real_escape_string()
function. Since it needs a database connection, we'll go ahead and wrap it in its own function. In addition, since we only need to escape strings, we might as well quote the value at the same time:
function db_quote($value) {
$connection = db_connect();
return "'" . mysqli_real_escape_string($connection,$value) . "'";
}
If we are not sure of the type of value we pass to the database, it's always best to treat it as a string, escape and quote it. Let's look at a common example - form submission. We'll use our previous INSERT query with user input:
// Quote and escape form submitted values
$name = db_quote($_POST['username']);
$email = db_quote($_POST['email']);
// Insert the values into the database
$result = db_query("INSERT INTO `users` (`name`,`email`) VALUES (" . $name . "," . $email . ")");
And there you have it - a safe way to use user input in your queries. As mentioned earlier in the tutorial, an alternative to escaping / quoting values is using prepared statements. MySQLi use of prepared is somewhat verbose and requires more effort to abstract properly to functions like I did here. If you are interested, check out this
nice introduction to prepared statements in MySQLi.
Object oriented version
I personally use OOP exclusively when I program in PHP. Since this tutorial is aimed at beginners, I did not want the requirement of OOP proficiency to be able to understand it. In case you come from a background with a different language and have experience with OOP, here is how the above procedural code would look:
class Db {
// The database connection
protected static $connection;
/**
* Connect to the database
*
* @return bool false on failure / mysqli MySQLi object instance on success
*/
public function connect() {
// Try and connect to the database
if(!isset(self::$connection)) {
// Load configuration as an array. Use the actual location of your configuration file
$config = parse_ini_file('./config.ini');
self::$connection = new mysqli('localhost',$config['username'],$config['password'],$config['dbname']);
}
// If connection was not successful, handle the error
if(self::$connection === false) {
// Handle error - notify administrator, log to a file, show an error screen, etc.
return false;
}
return self::$connection;
}
/**
* Query the database
*
* @param $query The query string
* @return mixed The result of the mysqli::query() function
*/
public function query($query) {
// Connect to the database
$connection = $this -> connect();
// Query the database
$result = $connection -> query($query);
return $result;
}
/**
* Fetch rows from the database (SELECT query)
*
* @param $query The query string
* @return bool False on failure / array Database rows on success
*/
public function select($query) {
$rows = array();
$result = $this -> query($query);
if($result === false) {
return false;
}
while ($row = $result -> fetch_assoc()) {
$rows[] = $row;
}
return $rows;
}
/**
* Fetch the last error from the database
*
* @return string Database error message
*/
public function error() {
$connection = $this -> connect();
return $connection -> error;
}
/**
* Quote and escape value for use in a database query
*
* @param string $value The value to be quoted and escaped
* @return string The quoted and escaped string
*/
public function quote($value) {
$connection = $this -> connect();
return "'" . $connection -> real_escape_string($value) . "'";
}
}
Usage is identical to before, with OOP syntax instead of function calls. Our previous example queries would look like this:
// Our database object
$db = new Db();
// Quote and escape form submitted values
$name = $db -> quote($_POST['username']);
$email = $db -> quote($_POST['email']);
// Insert the values into the database
$result = $db -> query("INSERT INTO `users` (`name`,`email`) VALUES (" . $name . "," . $email . ")");
A select query:
$db = new Db();
$rows = $db -> select("SELECT `name`,`email` FROM `users` WHERE id=5");
And there you have it.
Source files and contributing
All the code found in this tutorial is
hosted on GitHub under an MIT license for your convenience. If you have suggestions for improvement or other feedback, use the comments below or submit a pull request. Hope you found it useful!
Post a Comment