Skill Level: Intermediate
UPDATE: This part covers PHP classes. I will create a short article and cover the general idea of classes soon.
This is Part 2 of 4 of this series. Part 3 will be launched soon which will cover error handling!
In the last section, we created our HTML markup, our CSS and jQuery. This section will be mainly focused on PHP (our server side or back-end language) and MySql which will power our database. At this point, our form should be looking pretty good, but we need to allow our users to register and submit their information to the database as well as be able to login once they have registered.
What we Need to do
- Create our database. Name it login_ajax, and give it a table called `users`. Give it 6 columns. `id`, `name`, `username`, `hashed_password`, `pass_token`, `email`. The column `id` should be set to auto increment primary key.
- Create our database connection using PDO.
- Create our active record. We need to create 2 classes. DbObject and User. DbObject will hold our active record functions.
- Create our controller that takes our data from the form and passes it to our classes. This will be the job of login.php which you should already have created.
- Create our functions to submit data to the database, and allow us to search for users by username.
Step 1: Connect to Database
Let's get our database connection using PDO. We need to create our constants for our database information. Then we will pass that data into our $dsn variable along with our database driver which will be mysql. We will see our default PDO options and pass it all into our $pdo object. If there is an error in our connection, we will tell PDO to throw an error.
<?php
define("DB_HOST", "localhost");
define("DB_PASS", "");
define("DB_NAME", "login_ajax");
define("DB_USER", "root");
define("DB_CHARSET", "utf8mb4");
$dsn = "mysql:host=" . DB_HOST . ";" . "dbname=" . DB_NAME . ";" . "charset=" . DB_CHARSET . "";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
}
catch (\PDOException $e) {
throw new \PDO($e->getMessage(), (int)$e->getCode());
}
?>
If you want to see if your connection is successful, just create a simple if statement and echo something out if $pdo == true. It will probably show up in the top left corner of your screen.
Step 2: Create Our DbObject Class
Our next step is we need to create our database class which will be called DbObject. Our classes always start with a capital letter and we name our file the same as our class. If our class is DbObject, we call our file DbObject.php. This is where we begin to create our active record pattern. The active record is a programming design pattern intended to help make code more modular and reusable. To create this pattern, we name the properties in our classes the same as what our columns are called in our database. This is how we get the "active record". Let's start creating our class and I'll show you as we go along.
<?php
class DbObject {
static protected $con; //Set our connection variable
static protected $table_name; //Set our table name variable
static protected $columns = ""; //set our columns variable
public function __construct($args = []) { //create an empty array as our argument
}
public function testing() {
echo "Our class is working!";
}
}
?>
To check if our class is working, go to our index.php page and at the top of the page, let's create a new object and call our testing() method. We use the arrow notation to call our non static methods.
$test = new DbObject;
$test->testing();
If you got the text to show up that we echo'd out in our method, your class is working! If it's working, go ahead and delete that object as well as the testing method.
We need to create several methods in our class.
- attributes() // Dynamically set a value to our database columns based upon our $columns variable.
- create() // Dynamic method to send data to the database.
- findBySql() // This method will take our queries as arguments as well as an empty array that we will use to pass in data for our prepared statements.
- instantiate() // We will pass in our records from our findBySql method and create a new object and foreach loop that will loop through our records and check if the record exists as a property of our class. If it does, we set a value to it.
- findByUsername() // Our method for us to find users by a username. We will be able to pass in a username into the method as an argument and select users based on their username
- setDb() // We will use this method to call our database credentials within our class.
Let's get to it!
Underneath our variables that we created in our DbObject class, we will first create our DbObject. This will hold our PDO credentials.
<?php
class DbObject {
static protected $con;
static protected $table_name;
static protected $columns = "";
private static function setDb() {
define("DB_HOST", "localhost");
define("DB_PASS", "");
define("DB_NAME", "login_ajax");
define("DB_USER", "root");
define("DB_CHARSET", "utf8mb4");
$dsn = "mysql:host=" . DB_HOST . ";" . "dbname=" . DB_NAME . ";" . "charset=" . DB_CHARSET . "";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
}
catch (\PDOException $e) {
throw new \PDO($e->getMessage(), (int)$e->getCode());
}
self::$con = $pdo;
}
}
If you went through the article on PHP classes, you should remember some things about the visibility. For our setDb()
method, we set it to private since we only need it to be called by the DbObject class. If we wanted another class to be able to call the method, but prevent it from being called outside the class, we would set it to protected, but in this case we will leave it as private.
The next method we need to create is our attributes()
method. This will create an associate array that will set the array keys to the values in our $columns
array.
public static function attributes() {
$attributes = []; //Set our empty array
foreach(static::$columns as $column) { //Loop through our columns as set them to our $column variable
if($columns == "id") { continue; } // If column is equal to 'id' we skip it
$attributes[$column] = $this->$column; // Creates indexes in our array based on our column names and sets the value to the value of the property with the same name as the column. Example: $attributes['name'] = $this->name or $this->email;
}
return $attributes;
}
Our $attributes
variable will produce an array like this once we start sending in our data.
Array
(
[name] => my name
[email] => myemail@gmail .com
[username] => test123
[hashed_password] => $2y$12$0B2ZFBr6F3mSko0FEx7wWuBurkhoApgDaz8V9OHj6R/v7HO60Cfz6
)
Now that we are able to call our attributes()
method and create our array, we need to make our create()
method which will allow us to pass in data to our database. We will first call our setDb()
method and then set our $attributes()
method to a variable called $attributes. Since we are going to make this more secure, we are going to use prepared statements. With PDO, we will create named parameters which is a great feature of PDO. We will essentially be passing an array to the PDO execute function which will look like this.
$stmt->execute([":name" => "myname", ":email" => "test@gmail .com"]);
In order to create this array, we will make an array that will hold our named parameters, or placeholders for our execute function.
public function create() {
self::setDb(); // Call our database method
$attributes = $this->attributes(); // Call our attributes method and set it to a variable.
$attribute_pairs = []; // Create an empty array that will hold our key/value pairs.
foreach($attributes as $k => $val) { // loop through each of our attributes and set our named parameters to our array.
$attribute_pairs[] = ":$k";
}
}
Our output for our $attribute_pairs array would look like this.
Array
(
[0] => :name
[1] => :email
[2] => :username
[3] => :hashed_password
)
Now we need to make our query. Assuming the DRY(Do Not Repeat Yourself) principles, we are making this query dynamic so we can use it to submit data to other tables as well without having to make another method. One method to rule them all, and in the darkness...ok, we won't get into my Lord of the Rings obsession.
If this part doesn't make a ton of sense, don't worry, I'll explain what's going on as much as possible.
public function create() {
self::setDb();
$attributes = $this->attributes();
$attribute_pairs = [];
foreach($attributes as $k => $val) {
$attribute_pairs[] = ":$k";
}
$sql = "INSERT INTO " . static::$table_name . "("; //Insert into table
$sql.= join(', ', array_keys($attributes)); //Comma separate each $attribue array key.
$sql.= ") VALUES (";
$sql.= join(',', array_values($attribute_pairs)); //Comma separate each $attribute_pair array value;
$sql.= ")";
$result = self::$con->prepare($sql)->execute($attributes); //Pass our query into the prepared statement and send our $attributes array into the execute() function.
if($result) {
$this->id = self::$con->lastInsertId();
}
return $result;
}
What we're going to do is create a Users class soon which will extend our DbObject class. The Users class is the class we will be calling to create our users in the database. Since the Users class will extend our DbObject class, our create()
method will take the values of our static parameters in our Users class. By doing that, if we were to echo out our $sql
, it would look like this.
INSERT INTO users(name, email, username, hashed_password) VALUES (:name,:email,:username,:hashed_password)
You can see how we have our named parameters as our values. They are basically acting as placeholders which is how we create prepared statements. When our execute gets called, these named parameters get set to a value which gets input into the database.
Now we need to pass in our data to our create()
method so we can register users. Go to your user.php and find the public function __construct($args = [])
. Notice that we passed in a blank array into our User class. In our login.php, we are going to create a new User object and pass in an array. Our array indexes need to match our variables and the columns in our $columns
array.
<?php
include "DbObject.php";
include "User.php";
if(isset($_POST['formData']) && isset($_POST['action'])) { // If the formData and action are sent from ajax, run following code.
$data = $_POST['formData']; // Set our POST data to a variable
$action = $_POST['action']; // Set our action to a variable
$username = $data['username']; // Set our username to a variable
$password = $data['password']; // Set our password to a variable
if($action == "register") { // If our action is "register" run following code.
$args = []; // Create empty array that will be passed into our User class
$args['name'] = $data['name']; // Append to the array for each value we want to send to the database. name, email, username, password. These also have to match the name of the parameters in the User class.
$args['email'] = $data['email'];
$args['username'] = $data['username'];
$args['password'] = $data['password'];
$user = new User($args); // Create a new User object and pass in our $args array. This will assign the array values to the parameters in our class.
$user->create(); // Run our create method which will send the data to the database.
print_r($user); // Return our $user object to view in the console. Remove when you no longer need it for reference.
}
}
?>
If you look back at the user.php, you will see how this works. We have taken the values form our form and created an array called $args
which will be passed into our class. The array if printed out looks like this.
Array
(
[name] => Richard Hardin
[email] => richard@thecodecrypt .com
[username] => thecodecrypt
[password] => testing
)
Now in the users.php, we have this code at the top of our class. We have set some public parameters and in our constructor, we have assigned some of those values to the array that is passed into it.
public $id;
public $name;
public $email;
public $username;
public $password;
public $hashed_password;
static public $table_name = "users";
static public $columns = ["name", "email", "username", "hashed_password"];
public function __construct($args = []) {
$this->name = $args['name']; // Assigning the `name` array index to our $name parameter
$this->email = $args['email']; // Assigning the `email` array index to our $email parameter
$this->username = $args['username']; // Assigning the `username` array index to our $username parameter
$this->password = $args['password']; // Assigning the `password` array index to our $password parameter
}
Notice also our static public $table_name = "users";
. This is setting the table name in our database that we want to send our data too. When we call anything from the User class, data will go to the users table. Since our User class extends the DbObject class, it automatically inherits the methods in that class since it's the parent class. So we can call static methods without having to write the method in the User class since it's already in the DbObject class, but it will take the values from the class we are instantiating which is the User class. In our login.php, we created a new User object and called our create()
method. Since it's a dynamic method, our $table_name
get's passed into the query and the values in our $columns
become our $attributes
which get's passed into the query as well.
As of now, you should be able to fill in the fields on the registration form and send your data to the database! Check to make sure all columns are being populated in your DB. If something isn't getting inserted properly, make sure you have your $columns and $table_name set to the right values and make sure the values you're taking from the form match the properties in our User class e.g., public $name, public $email, public $username, public $password;
If you have questions about how this process works, feel free to comment below. Part 3 will cover the login portion of the project as well as displaying input validation and error handling. See you there!