PHP CHAPTER 4 2019-01-02T12:23:52+00:00

PHP Chapter 4

Objects and Classes

We showed you how to write generic, reusable functions that could be used to manipulate any database table. In this chapter, we’ll move those functions into a class, to avoid some of the repetition that’s needed when they’re used. One of the biggest problems with functions is that all the information they need to execute has to be sent to them in arguments. In the case of the delete function we wrote, there are four pieces of information:

  • the $pdo database instance
  • the name of the table to delete from
  • the name of the primary key field
  • the value to delete

The same is true of all the functions—findById, findAll, update, insert and save. Each of the functions we created needs to be passed at least the $pdo database instance and the name of the table. All of them except findAll and insert also need to know the name of the column that represents the primary key.For example, the save function is used like this:

if (isset($_POST[‘joke’])) {

$joke = $_POST[‘joke’];

$joke[‘jokedate’] = new DateTime();

$joke[‘authorId’] = 1;

save($pdo, ‘joke’, ‘id’, $joke);

// …

Each time one of the functions is called, it must be passed the $pdo instance. With up to four arguments for each function, it can be difficult to remember the order they need to be provided in. A good method for avoiding this problem is putting the functions inside a class.

Time for Class

As each class needs a name, and ours will deal with functions that have something to do with database tables, we’ll call ours DatabaseTableLike variables, classes can contain any sequence of alphanumeric characters. However, special characters like , +, { or a space aren’t allowed. By convention, classes in PHP use CamelCase, starting with an uppercase letter followed by lowercase letters until the start of the next word. PHP allows for the class to be called databasetable, DATABASETABLE, or some other similar variation, but it’s a good idea to follow the naming convention used by almost all PHP programmers. You can think of a class as a collection of functions and data (variables). Each class will contain a set of functions and some data that the functions can access. Our DatabaseTable class needs to contain all the functions we created for interacting with the database, along with any functions that those functions need to call. As a first step, move all the database functions into a class wrapper:

<?php

class DatabaseTable

{

private function query($pdo, $sql, $parameters = [])

{

   $query = $pdo->prepare($sql);

   $query->execute($parameters);

    return $query;

}

public function total($pdo, $table)

{

$query = $this->query($pdo, ‘SELECT COUNT(*) FROM

    `’ . $table . ‘`’);

   $row = $query->fetch();

return $row[0];

}

public function findById($pdo, $table, $primaryKey, $value)

{

     $query = ‘SELECT * FROM `’ . $table . ‘` WHERE

          `’ . $primaryKey . ‘` = :value’;

$parameters = [

     ‘value’ => $value

];

    $query = $this->query($pdo, $query, $parameters);

return $query->fetch();

}

private function insert($pdo, $table, $fields)

{

     $query = ‘INSERT INTO `’ . $table . ‘` (‘;

foreach ($fields as $key => $value) {

     $query .= ‘`’ . $key . ‘`,’;

}

  $query = rtrim($query, ‘,’);

  $query .= ‘) VALUES (‘;

foreach ($fields as $key => $value) {

     $query .= ‘:’ . $key . ‘,’;

}

  $query = rtrim($query, ‘,’);

  $query .= ‘)’;

  $fields = $this->processDates($fields);

   $this->query($pdo, $query, $fields);

}

private function update($pdo, $table, $primaryKey, $fields)

{

   $query = ‘ UPDATE `’ . $table .’` SET ‘;

foreach ($fields as $key => $value) {

      $query .= ‘`’ . $key . ‘` = :’ . $key . ‘,’;

}

$query = rtrim($query, ‘,’);

$query .= ‘ WHERE `’ . $primaryKey . ‘` = :primaryKey’;

// Set the :primaryKey variable

    $fields[‘primaryKey’] = $fields[‘id’];

       $fields = $this->processDates($fields);

     $this->query($pdo, $query, $fields);

}

public function delete($pdo, $table, $primaryKey, $id)

{

      $parameters = [‘:id’ => $id];

$this->query($pdo, ‘DELETE FROM `’ . $table . ‘` WHERE

      `’ . $primaryKey . ‘` = :id’, $parameters);

}

public function findAll($pdo, $table)

{

        $result = query($pdo, ‘SELECT * FROM `’ . $table . ‘`’);

return $result->fetchAll();

}

private function processDates($fields)

{

foreach ($fields as $key => $value) {

      if ($value instanceof DateTime) {

           $fields[$key] = $value->format(‘Y-m-d’);

        }

}

return $fields;

}

public function save($pdo, $table, $primaryKey, $record)

{

    try {

      if ($record[$primaryKey] == ”) {

          $record[$primaryKey] = null;

    }

    $this->insert($pdo, $table, $record);

} catch (PDOException $e) {

    $this->update($pdo, $table, $primaryKey, $record);

            }   

      }

}

Like templates and include files, it’s good practice to store classes outside the public directory. Create a new directory called classes inside your Project directory and save the code above as DatabaseTable.php.

Naming Your Class Files

It’s good practice to name your class files exactly the same as your classes. The class DatabaseTable would be placed in DatabaseTable.php, a class called User would be stored in User.php and so on. Although it doesn’t matter at the moment, later on I’ll introduce something called an autoloader, and it will be difficult to use without this convention.

Methods

A function that exists inside a class is called a method. Although many developers—and the PHP language itself—use the word function to describe subroutines in classes, the correct term is method, which we’ll be using throughout. However, the difference between a function and a method is that a method is inside a class, while a function isn’t. Anything you can do with a function (arguments, return values, calling other functions) can also be done with a method.

If you examine the code above, you’ll see we’ve made two changes beyond just pasting the functions into the class. The first change I’ve made is that when functions from within the class are called, they’re prefixed with $this->. Instead of $result = query($pdo,…, the updated code has $result = $this->query($pdo,….

You can think of this as “this class”. We can’t just use query() anymore, because now that the query() function is inside the class, it can’t be called like a global function, as it’s outside of global scope. Any method inside a class can only be called on a variable. In the same way we use $pdo->prepare(), the query() method is now called on an object. From within the class, the current object is referenced as $this. The $this variable is created automatically inside any method and will always exist without being declared.

Public vs Private

The second change I’ve made when converting functions to methods is that each is prefixed with either public or private. These are known as visibility, allowing the programmer to determine where the method can be called from. Methods marked private can only be called from other methods inside the class, whereas methods marked public can be called from both inside and outside the class. You’ve already seen some public methods on the PDO instance we’ve been using throughout. When you call $pdo->prepare(), you’re calling a public method called prepare. If the method was marked private, this wouldn’t be possible.

What’s the point of private methods then? Take a look at the methods I’ve marked as private in the DatabaseTable class: query and processDates. The reason these are private is that, on their own, they aren’t very useful. Nobody using the DatabaseTable class should ever need to call the query method directly. The query method is only there to provide some shared functionality for the other methods in the class—save, findById, and so on. The same is true of the processDates function. At first glance, it seems a little pointless. However, it’s actually a very useful tool. Once a method such as query is private, you can completely rewrite the way it works, and you can guarantee the only place it’s being called from is another method within the same class. When you’re working in large teams or sharing your code online, knowing exactly where a method is called from is useful. You can release a new version of the class without the query method, and you can be guaranteed it’s not being used anywhere else. Someone else can use the new version and their code won’t be broken. If it was public and you changed the code, you’d have no idea if the query function was being called from anywhere else, and you’d need to be wary of changing the way it worked in case it broke someone else’s code.

Objects

You can think of a class as a recipe. On its own, it’s just a series of instructions. To make something from it that’s actually useful—something you can eat—you need to follow the instructions. A class on its own isn’t very useful: it’s just a series of instructions. There’s no way to call a method from within the class without creating an object. An object is an instance of a class. Creating an instance of the DatabaseTable class is done the same way as the pdo instance we’ve been using throughout : only the name of our class, DatabaseTable, is used:

$databaseTable = new DatabaseTable();

The new keyword creates an object from the defined class, which can then be used. Without this step, none of the functions defined in the class can be used. At the moment, this feels like an extra step that doesn’t achieve anything special. But as we’ll see later on, this is a very powerful tool for programmers, allowing us to create different instances that represent different database tables. Once the object is created, the methods can be called on the variable in the same way we call $pdo->prepare() on the $pdo object:

$databaseTable = new DatabaseTable();

$jokes = $databaseTable->findAll($pdo, ‘joke’);

Any of the public methods can be called in this way. However, if you try to call one of the private methods, you’ll get an error.

Class Variables

We mentioned that the goal of using objects and classes was to reduce repeated code. However, so far we’ve actually made the code longer. With the DatabaseTable class, each time we want to use one of the methods, we need to call it on an object:

$databaseTable = new DatabaseTable();

$jokes = $databaseTable->findAll($pdo, ‘joke’);

$databaseTable->save($pdo, ‘joke’, ‘id’, $_POST[‘joke’]);

In this case, we’ve increased rather than reduced the amount of code that’s required. Each time one of the methods in the class is called, it needs the same information—at a minimum, the database connection and the name of the table that’s being interacted with. Rather than supply these values every time a method is called, it’s possible to supply them once, to the class, and have the values used within the methods. Every class can have variables that are available to be used within any method. To declare a variable that will be used inside the class, you need to declare the variable within the class. It’s convention to define variables at the top of the class, before any methods. To declare a variable, you must make it visible and give it a name. For example:

class MyClass {

public $myVariable;

}

Once the variable has been declared, you can use it when you create an instance of the class. Like methods, variables can be written to and read from using the arrow (->) operator:

$myInstance = new MyClass();

$myInstance->myVariable = ‘A value’;

echo $myInstance->myVariable; // prints “A value”

An important distinction between class variables and normal variables is that they’re bound to a specific instance. In practice, all this means is that each instance can have a different value for the same variable. For example:

$myInstance = new MyClass();

$myInstance->myVariable = ‘A value’;

$myInstance2 = new MyClass();

$myInstance2->myVariable = ‘Another value’;

echo $myInstance->myVariable;

echo $myInstance2->myVariable;

This will print “A value” and then “Another value”, because each instance of the class has its own value for the myVariable variable. Later on, this will be very useful for our DatabaseTable class, but for now, let’s just add the variables for the $pdo instance, the table name and the primary key to the class:

class DatabaseTable {

    public $pdo;

    public $table;

    public $primaryKey;   

// …

}

A class is more than just a collection of functions. You can think of it as a blueprint that can be used to create objects. Each object or instance of the class can store its own values for these variables. For example, when you create the $pdo variable for your database connection, the $pdo variable stores the connection information—the database server address, username, password, etc. You don’t need to send this information every time you call prepare or execute; the information is stored inside the $pdo object. The same can be done with the DatabaseTable class. Once the variables have been declared, they can be written to on each instance:

$databaseTable = new DatabaseTable();

$databaseTable->pdo = $pdo;

$databaseTable->table = ‘joke’;

$databaseTable->prmaryKey = ‘id’;

Now that the variables are set, they can be used instead of the arguments inside any of the methods in the class. For example, the findAll and query methods can be rewritten to use the class variables, instead of having the database connection and table name passed in explicitly:

private function query($sql, $parameters = []) {

      $query = $this->pdo->prepare($sql);

      $query->execute($parameters);

return $query;

}

public function findAll() {

     $result = $this->query(‘SELECT *

                       FROM ‘ . $this->table);

return $result->fetchAll();

}

The variables in the class are accessed the same way as the functions using the $this variable. Now, when the findAll() function is called, it doesn’t need any arguments, because the $pdo connection and the name of the table are read from the class variables:

$jokesTable = new DatabaseTable();

$jokesTable->pdo = $pdo;

$jokesTable->table = ‘joke’;

$jokes = $databaseTable->findAll();

Let’s go ahead and make this change to all the methods. Anywhere $pdo, $table or $primaryKey was used as an argument, the argument can be removed and replaced with a reference to the class variable. Here’s what the total method looks like now:

public function total() {

   $query = $this->query(‘SELECT COUNT(*)

          FROM `’ . $this->table . ‘`’);

$row = $query->fetch();

return $row[0];

}

The save method:

public function save($record) {

    try {

         if ($record[$this->primaryKey] ==  ‘ ‘)  {

                 $record[$this->primaryKey] = null;

     }

     $this->insert($record);

}

catch (PDOException $e) {

   $this->update($record);

      }

}

The update method:

private function update($fields) {

      $query = ‘ UPDATE `’ . $this->table .’` SET ‘;

foreach ($fields as $key => $value) {

       $query .= ‘`’ . $key . ‘` = :’ . $key . ‘,’;

}

$query = rtrim($query, ‘,’);

$query .= ‘ WHERE `’ . $this->primaryKey . ‘` = :primaryKey’;

// Set the :primaryKey variable

$fields[‘primaryKey’] = $fields[‘id’];

$fields = $this->processDates($fields);

   $this->query($query, $fields);

}

The insert method:

private function insert($fields) {

       $query = ‘INSERT INTO `’ . $this->table . ‘` (‘;

foreach ($fields as $key => $value) {

    $query .= ‘`’ . $key . ‘`,’;

}

$query = rtrim($query, ‘,’);

$query .= ‘) VALUES (‘;

foreach ($fields as $key => $value) {

     $query .= ‘:’ . $key . ‘,’;

     }

$query = rtrim($query, ‘,’);

$query .= ‘)’;

   $fields = $this->processDates($fields);

   $this->query($query, $fields);

 }

The findById method:

public function findById($value) {

$query = ‘SELECT * FROM `’ . $this->table . ‘` WHERE `’ . $this->primaryKey . ‘` = :value’;

$parameters = [

    ‘value’ => $value

  ];

$query = $this->query($query, $parameters);

return $query->fetch();

}

The delete method:

public function delete($id ) {

      $parameters = [‘:id’ => $id];

$this->query(‘DELETE FROM `’ . $this->table .

     ‘` WHERE `’ . $this->primaryKey . ‘` = :id’, $parameters);

}

The processDates method remains unchanged, as it doesn’t require any of the class variables. Now, to interact with the database, the common variables only need to be set once:

$jokesTable = new DatabaseTable();

$jokesTable->pdo = $pdo;

$jokesTable->table = ‘joke’;

$jokesTable->primaryKey = ‘id’;

And then the methods can be used without repeating all the arguments:

// Find the joke with the ID `123`

$joke123 = $jokesTable->findById(123);

// Find All the jokes

$jokes = $jokesTable->findAll();

foreach ($jokes as $joke) {

      // …

}

// Delete the joke with the ID `33`

$jokesTable->delete(33);

$newJoke = [

    ‘authorId’ => 1,

    ‘jokedate’ => new DateTime(),

    ‘joketext’ => ‘A man threw some cheese and milk at me. How dairy!’

];

$jokesTable->save($newJoke);

This reduces the number of arguments needed by each method, and makes it easier for someone using the methods to follow. They don’t have to remember the order of all the arguments—for example, whether the table name is the first or second argument. This is a huge improvement, but there are some potential problems. What happens if the variables aren’t set before the findAll() method is called? What happens if the $pdo variable is set to a string rather than an object?

$jokesTable = new DatabaseTable();

$jokes = $databaseTable->findAll();

If you run this you’ll get an error, because the findAll method is expecting the $pdo and table variables to be set to valid values. Luckily, there’s a way of preventing this from happening.

Constructors

As the author of a class, you get to tell anyone who uses it how it works. (If you want to get technical, this is called the Application Programming Interface or API). You can make sure that any required variables are set before any functions are run. There’s a special function you can add to the class called a constructor. This is a function that is automatically run whenever an instance of the class is created. To add a constructor to a class, you simply add a function called __construct().

Magic Methods

That’s two underscores in front of the word construct. If you use just one, it won’t work! In PHP, any method prefixed by two underscores is a magic method. These are generally called automatically in different cases. As the language evolves, more of these magic methods may be added, so it’s a good idea to avoid giving your own methods names beginning with two underscores. A complete list of the available magic methods can be found in the PHP manual.

This is a method like any other, but it’s called automatically. For example:

class MyClass {

public function __construct() {

    echo ‘construct called’;

    }

}

$myclass1 = new MyClass();

$myclass2 = new MyClass();

Once a function with the name __construct() is created, each time you create a new instance of the class the function is called. The code above will output the following:

construct called

construct called

Even though we’ve never directly called the function using $myclass1->__construct(), you can see it’s been called because the string construct called has been printed. You’ll also have noticed it’s been called twice. This is because each time an instance of the class is created, the constructor is called. Like any other function, the constructor can also take arguments. For example:

class MyClass {

    public function __construct($argument1) {

             echo $argument1;

      }

}

When you create an instance of the class, the arguments can be provided:

$myclass1 = new MyClass(‘one’);

$myclass2 = new MyClass(‘two’);

If you try to create an instance of the class that needs a constructor argument and doesn’t have a default defined, but you fail to pass in an argument, you’ll see an error. Let’s add a constructor to the DatabaseTable class that sets the $pdo, $table and $primaryKey variables:

class DatabaseTable {

   public $pdo;

   public $table;

   public $primaryKey;

public function __construct($pdo, $table, $primaryKey) {

   $this->pdo = $pdo;

   $this->table = $table;

   $this->primaryKey = $primaryKey;

    }

// …

}

You’ll come across constructors like this frequently if you start using objects and classes regularly, so it’s important to understand what’s happening here.

Placing Your Constructor Methods

It’s common practice to put the constructor at the top of the class, after variables but before any other methods.

There are two different variables with the same names, which can be confusing at first. The first version is the argument, which is defined in the line public function __construct($pdo, $table, $primaryKEy) {. When you create a function argument, the variable is only available inside that specific function and isn’t available to be used in other functions in the class. When the constructor is called, the $pdo instance is sent to it, but we want to make the $pdo instance available to every function in the class. The only way to make a variable available to every function inside the class is to make it a class variable like we did above. Instead of setting the class variable from outside the class—for example, $jokesTable->pdo = $pdo;—we want to set it from within the constructor inside the class.

Like before, the $this variable represents the current instance, and $this->pdo = $pdo; is doing the same thing as $jokesTable->pdo = $pdo;, only from inside the class. Both $jokesTable and $this reference the same object, and making changes to one will be reflected in the other. You can think of this like the English language. Although you’ll always refer to yourself as “I”, your friends will use your name. Regardless of whether you’re referring to yourself, or someone is referring to you by your name, it’s always the same person—you—who’s being referred to. The same thing happens here. $this references the current instance from inside the class, like “I” in English. However, $jokesTable refers to the same instance using its name from outside the class.

Using either $jokesTable->pdo = $pdo from outside the class, or $this->pdo = $pdo; from inside the class, the $pdo class variable will be set and then be available inside any methods when they’re called on that instance. By using a constructor, when the instance is created, two variables must be supplied:

$jokesTable = new DatabaseTable($pdo, ‘jokes’, ‘id’);

If you tried to create an instance of the DatabaseTable class without passing it two arguments, it would give you an error, because the two arguments are required for the code to work. This kind of check ensures the code is robust. It also helps anyone who uses the class, because they’ll see an error as soon as they do something wrong.

Type Hinting

If we’re trying to make the class foolproof, there’s still a problem. What happens if the person using your DatabaseTable class gets the order of the arguments wrong? Consider these two examples:

$jokesTable = new DatabaseTable(‘jokes’, $pdo, ‘id’);

$jokesTable = new DatabaseTable($pdo, ‘jokes’, ‘id’);

The first could easily be written instead of the second. This is an innocent mistake, and an easy one to make by accident. An error won’t be seen until one of the functions in the class is called. For example:

$jokesTable = new DatabaseTable(‘jokes’, $pdo, ‘id’);

$jokes = $jokes->findAll();

The code above will result in the error “Call to function prepare on non-object”, because the findAll function contains the line $result = $this->query(‘SELECT * FROM ‘ . $this->table);, and the query function has the line $query = $this->pdo->prepare($sql);Because the order of the constructor arguments is wrong, the $pdo variable will actually be set to the string joke. The string joke does not have a function called prepare, which is what causes the error. The error “Call to function prepare on non-object” doesn’t make it clear what went wrong, and it would be difficult for the person who made the mistake to figure it out without looking in depth at your class and examining it line by line.

To help them out, it’s better to ensure that the arguments are the correct type. PHP is loosely typed, meaning that a variable can be any type—such as a string, a number, an array, or an object. Even so, you can enforce types when you create a function. This is particularly useful for constructors, where getting the arguments in the wrong order appears to work. For example, take the following code:

$jokesTable = new DatabaseTable(‘jokes’, $pdo);

This won’t actually cause any errors. The person running this line of code won’t know that it’s wrong. It’s possible to use if statements to check the type of each argument, but PHP also provides a nice feature called “type hinting”. Type hinting allows you to specify the type of an argument. The type can be a class name, or one of the basic types, such as string, array or integer.

Type Hinting Compatibility

Type hinting for basic types (numbers strings, arrays—anything that isn’t an object) was only introduced in PHP 7. It’s possible your web host is still on PHP 5, so be careful when using this feature!

To provide a type hint for an argument, prefix the variable name with the type that the variable should be. For our database class this will be:

public function __construct(PDO $pdo, string $table, string $primaryKey) {

This tells PHP to check the types of each argument when they’re provided. If the object is constructed with the wrong types now—as in $jokesTable = new DatabaseTable(‘jokes’, $pdo, ‘id’);, for example—PHP will check to see whether the type of each argument matches the hint. If it doesn’t, it will produce an error. The error that’s printed in this case is this:

Uncaught TypeError: Argument 1 passed to DatabaseTable::__construct() must be an instance of PDO, string given

This error explains much more clearly what the problem is than “Call to function prepare on non-object”, and it prevents the rest of the script from even running. As soon as a mistake is detected, the script is halted so you can fix it. This is a lot better than only getting a vague error message at the point you’re trying to call one of the methods on the object! By using type hinting on constructors like this, you can ensure that the class variables are set to the types you’re expecting. This way, when the code $this->pdo->prepare is run inside one of the methods in the class, $this->pdo must be set to an instance of $pdo and have a prepare method. There’s no way for the $this->pdo variable to be set to a string, a number, or even not set to anything. This is known as defensive programming, and it’s a very useful way of preventing bugs. By stopping variables being set to the wrong type, you can rule out the possibility of many potential bugs.

Private Variables

The class variables and constructor in the DatabaseTable class now look like this:

class DatabaseTable {

   public $pdo;

   public $table;

   public $primaryKey;

public function __construct(PDO $pdo, string $table,

    string $primaryKey) {

          $this->pdo = $pdo;

         $this->table = $table;

         $this->primaryKey = $primaryKey;

     }

// …

}

When an instance of the class is created, it must be passed three arguments, and those three arguments must be of specific types (a $pdo instance, a string and a string). It’s now impossible to construct the class without providing the correct parameters:

$jokesTable = new DatabaseTable($pdo, ‘jokes’, ‘id’);

Any other combination, such as new DatabaseTable($pdo, ‘jokes’);, or new DatabaseTable(‘jokes’, $pdo, ‘id’); or new DatabaseTable(); will display an error. Once one of the methods is called (for example, $jokesTable->findAll();), all of the class variables must have been set to the correct type, which should stop the $pdo variable being set to anything but a real database connection, a PDO instance. However, the code still has a weak point. There’s still a way of making it so the $pdo variable in the class is not a PDO instance. That’s because the variable $pdo is public. Like public functions, this means that the variables are accessible from outside the class, and it means the following code is possible:

// Correctly create the instance with a database connection

$jokesTable = new DatabaseTable($pdo, ‘jokes’, ‘id’);

$jokesTable->pdo = ‘a string’;

$jokes = $jokesTable->findAll();

Although the constructor is ensuring that a valid database connection is being set when the object is created, the code above has overwritten the $pdo class variable between the constructor being executed and the findAll method being called. The $pdo variable in the $jokesTable object has been set to “a string”. When the findAll() method runs, $this->pdo->prepare() will throw an error, because $this->pdo is a string, not an object with a prepare method. Public class variables like these cause problems because they allow the variable to be overwritten from anywhere. Instead, it’s good practice to make class variables private to prevent these issues:

class DatabaseTable {

  private $pdo;

   private $table;

   private $primaryKey;

public function __construct(PDO $pdo, string $table, string $primaryKey) {

   $this->pdo = $pdo;

   $this->table = $table;

   $this->primaryKey = $primaryKey;

   }

// …

}

When the variables are private, like private functions, they can’t be accessed from outside the class (for either reading or writing). By combining type hints, constructors and private properties, several conditions have been imposed on the class:

  1. It’s impossible to create an instance of the DatabaseTable class without passing it a $pdo instance.
  2. The first argument must be a valid PDO instance.
  3. There’s no way to change the $pdo variable after it’s been set.

As a result of these conditions, when any of the functions (such as findAll() or save()) are called, the $pdo, $table and $primaryKey variables must be set, and must be of the correct type. When $this->pdo->prepare() is called, it won’t cause an error, because there’s no way that findAll() can be called unless the variables are correctly set. This type of defensive programming can take a little more thinking about—for example, what needs to be public and what needs to be private?—but in all but the most simple projects, it’s worth it! By eliminating the conditions for a bug to exist, you can save yourself a lot of bug-tracking time later on.

Using the DatabaseTable Class

The final version of the DatabaseTable class looks like this:

<?php

class DatabaseTable

{

  private $pdo;

   private $table;

   private $primaryKey;

public function __construct(PDO $pdo, string $table, string $primaryKey)

{

   $this->pdo = $pdo;

   $this->table = $table;

   $this->primaryKey = $primaryKey;

}

private function query($sql, $parameters = [])

{

   $query = $this->pdo->prepare($sql);

   $query->execute($parameters);

   return $query;

}

public function total()

{

    $query = $this->query(‘SELECT COUNT(*) FROM `’ . $this->table . ‘`’);

   $row = $query->fetch();

return $row[0];

}

public function findById($value)

{

  $query = ‘SELECT * FROM `’ . $this->table . ‘` WHERE `’ .

   $this->primaryKey . ‘` = :value’;

$parameters = [

         ‘value’ => $value

  ];

     $query = $this->query($query, $parameters);

return $query->fetch();

}

private function insert($fields)

{

     $query = ‘INSERT INTO `’ . $this->table . ‘` (‘;

foreach ($fields as $key => $value) {

    $query .= ‘`’ . $key . ‘`,’;

     }

  $query = rtrim($query, ‘,’);

  $query .= ‘) VALUES (‘;

foreach ($fields as $key => $value) {

          $query .= ‘:’ . $key . ‘,’;

}

$query = rtrim($query, ‘,’);

$query .= ‘)’;

$fields = $this->processDates($fields);

$this->query($query, $fields);

}

private function update($fields)

{

    $query = ‘ UPDATE `’ . $this->table .’` SET ‘;

foreach ($fields as $key => $value) {

       $query .= ‘`’ . $key . ‘` = :’ . $key . ‘,’;

}

$query = rtrim($query, ‘,’);

$query .= ‘ WHERE `’ . $this->primaryKey . ‘` = :primaryKey’;

// Set the :primaryKey variable

  $fields[‘primaryKey’] = $fields[‘id’];

   $fields = $this->processDates($fields);

    $this->query($query, $fields);

}

public function delete($id)

{

    $parameters = [‘:id’ => $id];

     $this->query(‘DELETE FROM `’ . $this->table . ‘` WHERE `’ . $this->primaryKey . ‘` = :id’,     $parameters);

}

public function findAll()

{

    $result = $this->query(‘SELECT * FROM ‘ .

     $this->table);

return $result->fetchAll();

}

private function processDates($fields)

{

foreach ($fields as $key => $value) {

    if ($value instanceof DateTime) {

        $fields[$key] = $value->format(‘Y-m-d’);

          }

    }

    return $fields;

}

public function save($record)

{

    try {

          if ($record[$this->primaryKey] == ”) {

                $record[$this->primaryKey] = null;

}

$this->insert($record);

} catch (PDOException $e) {

   $this->update($record);

                 }

            }

}

Let’s save this in its own file, DatabaseTable.php. Remember to put the <?php tag at the top of the file.

Omitting the Closing Tag from Your Files

Whenever you create a PHP file, you need to remember to put the PHP code inside PHP tags. However, the closing tag is optional, and it’s actually better to omit it if the file only contains PHP code. This is because, if there are any whitespace characters (blank lines, tabs or spaces) at the end of the file after the closing PHP tag ?>, they’ll be sent to the browser, which isn’t what you want to happen. Instead, it’s better to prevent this from happening by omitting the ?> tag entirely. By leaving out the closing PHP tag, the whitespace will be interpreted on the server by PHP, and ignored, rather than being sent as part of the HTML code to the browser.

One of the most useful features of using classes is that, once a class has been written, it can be used as many times as you like. And each time you use it, by creating an instance, that instance can store different values of the class variables. For example, it’s possible to use the DatabaseTable class to interact with the joke table and the author table. Because each instance has its own version of the variables, we can have one version of the class where $table is set to joke and one version where $table is set to author:

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, id’);

// Find the joke with the ID 123

$joke = $jokesTable->findById(123);

// Find the author with the ID 34

$author = $authorsTable->findById(34);

Because the class variable $table is different, a different table will be used for each of the instances. When $author = $authorsTable->findById(34) is called, $this->table is equal to author, so the query that runs will be SELECT * FROM author …, whereas when $joke = $jokesTable->findById(123); is called, $this->table is set to joke, so the query that runs is SELET * FROM joke …This means the DatabaseTable class can now be used to insert, update or find records from any table in the database by constructing an instance with the table name in the constructor!

Updating the Controller to Use the Class

Now that we have the complete DatabaseTable class, let’s use it in the controllers. Firstly, delete includes/DatabaseFunctions.php. All our functions are now stored inside the class in classes/DatabaseTable.phpSecondly, let’s update public/jokes.php to use the new class:

<?php

try {

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

include __DIR__ . ‘/../classes/DatabaseTable.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$result = $jokesTable->findAll();

$jokes = [];

foreach ($result as $joke) {

        $author = $authorsTable->findById($joke[‘authorId’]);

$jokes[] = [

    ‘id’ => $joke[‘id’],

    ‘joketext’ => $joke[‘joketext’],

    ‘jokedate’ => $joke[‘jokedate’],

    ‘name’ => $author[‘name’],

    ’email’ => $author[’email’]

    ];

}

  $title = ‘Joke list’;

  $totalJokes = $jokesTable->total();

  ob_start();

include __DIR__ . ‘/../templates/jokes.html.php’;

  $output = ob_get_clean();

} catch (PDOException $e) {

     $title = ‘An error has occurred’;

   $output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘ . $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

This controller is better. We no longer have to provide the table name and $pdo instance to each of the functions—total, findById and findAll. The functions can each be called on either the $jokesTable variable or the $authorsTable variable to run the relevant query on either table. Let’s do the same thing with our other controllers. Here’s the updated deletejoke.php:

<?php

try {

    include __DIR__ . ‘/../includes/DatabaseConnection.php’;

     include __DIR__ . ‘/../classes/DatabaseTable.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$jokesTable->delete($_POST[‘id’]);

    header(‘location: jokes.php’);

} catch (PDOException $e) {

   $title = ‘An error has occurred’;

   $output = ‘Unable to connect to the database server: ‘ .

   $e->getMessage() . ‘ in ‘ .

   $e->getFile() . ‘:’ . $e->getLine();

  }

include __DIR__ . ‘/../templates/layout.html.php’;

 And editjoke.php:

<?php

try {

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

include __DIR__ . ‘/../classes/DatabaseTable.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

if (isset($_POST[‘joke’])) {

    $joke = $_POST[‘joke’];

    $joke[‘jokedate’] = new DateTime();

    $joke[‘authorId’] = 1;

    $jokesTable->save($joke);

  header(‘location: jokes.php’);

} else {

   if (isset($_GET[‘id’])) {

      $joke = $jokesTable->findById($_GET[‘id’]);

}

  $title = ‘Edit joke’;

  ob_start();

include __DIR__ . ‘/../templates/editjoke.html.php’;

  $output = ob_get_clean();

    }

} catch (PDOException $e) {

   $title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

   . $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

This example can befoound in OOP-DatabaseTable Now that you’re familiar with objects and classes, and you know that repeated code is a very bad thing for a programmer, it’s time to start tidying up these controller scripts. While making the last few changes, you would have found yourself making similar changes in multiple locations. The DRY (Don’t Repeat Yourself) principle states that it’s bad practice to have repeated code.

DRY

Carefully examine the different controllers. What is actually different about them? Each of the controllers follows this basic pattern:

<?php

try {

  /*

    – include some required files

  */

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

include __DIR__ . ‘/../classes/DatabasetabaseTable.php’;

/*

    – create one or more database table instances

*/

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

/*

     – Do something that’s unique to this particular page and create the $title and $output variables

*/

} catch (PDOException $e) { /*

    –  Handle errors if they occur

*/

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘

in ‘ . $e->getFile() . ‘:’ . $e->getLine();

}

/*

   – Load the template file

*/

include __DIR__ . ‘/../templates/layout.html.php’;

Using this approach, if you wanted to rename the DatabaseConnection.php file, you’d have to go through each controller to use the new name. Similarly, if you wanted to change the layout file, you’d need to edit each controller separately. All that really changes for each controller is the middle section that creates the $output and $title variables for the layout to use. Rather than having different files for each controller, it’s possible to write a single controller that handles each action as a method. That way, we can have one file that handles all the parts that are common to each page, and methods in a class that handle the individual parts.

Creating a Controller Class

The first thing we could do is move the code for each controller into a method in a class. Firstly create a class called JokeControllerAs this is a special type of class, we won’t store it in the classes directory. Instead, create a new directory called controllers and save this as JokeController:

class JokeController {

}

Before moving the relevant code into methods, let’s consider what variables this class needs. Any variables required by the various actions will need to be class variables so that they can be defined once and used in any of the methods. In this case, there are only two variables that are common to the controllers: $authorsTable and $jokesTable. Add these two variables to the class:

class JokeController {

   private $authorsTable;

   private $jokesTable;

}

Like the DatabaseTable class, it’s good practice to make these variables private so they can only be changed from within the class. Also, like the DatabaseTable class, you’ll need a constructor so that the two variables can be set when the class is instantiated:

class JokeController {

     private $authorsTable;

     private $jokesTable;

public function __construct(DatabaseTable $jokesTable,

   DatabaseTable $authorsTable) {

     $this->jokesTable = $jokesTable;

      $this->authorsTable = $authorsTable;

     }

}

Add the listJokes method first. Copy/paste the relevant section from jokes.php, but remember to use the class variables $jokesTable and $authorsTable, rather than including the existing code from jokes.php which creates them in the same block. We’ll create the instances once and pass them into the controller:

public function list() {

  $result = $this->jokesTable->findAll();

  $jokes = [];

foreach ($result as $joke) {

    $author =

     $this->authorsTable->findById($joke[‘authorId’]);

$jokes[] = [

     ‘id’ => $joke[‘id’],

    ‘joketext’ => $joke[‘joketext’],

    ‘jokedate’ => $joke[‘jokedate’],

    ‘name’ => $author[‘name’],

    ’email’ => $author[’email’]

  ];

}

  $title = ‘Joke list’;

  $totalJokes = $this->jokesTable->total();

  ob_start();

include __DIR__ . ‘/../templates/jokes.html.php’;

  $output = ob_get_clean();

}

Before getting this working, let’s add the other methods for the corresponding editjoke and deletejoke pages, along with the home page from index.php:

public function home() {

    $title = ‘Internet Joke Database’;

   ob_start();

include __DIR__ . ‘/../templates/home.html.php’;

    $output = ob_get_clean();

}

public function delete() {

    $this->jokesTable->delete($_POST[‘id’]);

header(‘location: jokes.php’);

}

public function edit() {

    if (isset($_POST[‘joke’])) {

        $joke = $_POST[‘joke’];

        $joke[‘jokedate’] = new DateTime();

        $joke[‘authorId’] = 1;

        $this->jokesTable->save($joke);

header(‘location: jokes.php’);

}

else {

   if (isset($_GET[‘id’])) {

      $joke = $this->jokesTable->findById($_GET[‘id’]);

  }

$title = ‘Edit joke’;

ob_start();

include __DIR__ . ‘/../templates/editjoke.html.php’;

$output = ob_get_clean();

      }

}

If you examine the controller code closely, you might notice that, regardless of how we eventually use this class, it’s not going to be very useful. That’s because the $title and $output variables can never be used in layout.html.php. Once either the home, edit or list methods are run, the $title and $output variables, along with their contents, are lost. To make those variables available to the code that calls the methods, we’ll use the return keyword. We already used return in the DatabaseTable class. Each time a method was run, it was able to send some data back to the place it was called from. The findAll method returns an array of all the records in the table. It would be possible to return the $output variable using return $output, but when layout.html.php is loaded, it will need both the $output and the $title variables. Like the findAll method, the individual controller methods can return arrays:

public function home() {

   $title = ‘Internet Joke Database’;

   ob_start();

include __DIR__ . ‘/../templates/home.html.php’;

   $output = ob_get_clean();

  return [‘output’ => $output, ‘title’ => $title];

  }

public function list() {

   $result = $this->jokesTable->findAll();

    $jokes = [];

foreach ($result as $joke) {

  $author =

  $this->authorsTable->findById($joke[‘authorId’]);

$jokes[] = [

     ‘id’ => $joke[‘id’],

     ‘joketext’ => $joke[‘joketext’],

     ‘jokedate’ => $joke[‘jokedate’],

     ‘name’ => $author[‘name’],

     ’email’ => $author[’email’]

  ];

}

$title = ‘Joke list’;

  $totalJokes = $this->jokesTable->total();

ob_start();

include __DIR__ . ‘/../templates/jokes.html.php’;

  $output = ob_get_clean();

return [‘output’ => $output, ‘title’ => $title];

}

public function edit() {

   if (isset($_POST[‘joke’])) {

      $joke = $_POST[‘joke’];

      $joke[‘jokedate’] = new DateTime();

      $joke[‘authorId’] = 1;

      $this->jokesTable->save($joke);

header(‘location: jokes.php’);

}

else {

   if (isset($_GET[‘id’])) {

      $joke = $this->jokesTable->findById($_GET[‘id’]);

}

  $title = ‘Edit joke’;

ob_start();

include __DIR__ . ‘/../templates/editjoke.html.php’;

  $output = ob_get_clean();

return [‘output’ => $output, ‘title’ => $title];

   }

}

The return value of each of the functions is an array that contains the output and title variables. Now, when one of the methods is called, it will return the output and title strings, which can then be used. Importantly, because each method returns data in the same format (an array with output and title keys), no matter which of the methods is called, we’ll have an array with two variables. Until now, we’ve had each different page using its own file: index.php, jokes.php, editjoke.php, and deletejoke.php.

Single Entry Point

With the controller complete, we can now write a single file to handle any page. Importantly, this single file can contain all the code that was previously repeated in each of the files. As a starting point, here’s a very crude way of using the new class:

<?php

try {

    include __DIR__ . ‘/../includes/DatabaseConnection.php’;

    include __DIR__ . ‘/../classes/DatabaseTable.php’;

    include __DIR__ . ‘/../controllers/JokeController.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new JokeController($jokesTable, $authorsTable);

if (isset($_GET[‘edit’])) {

    $page = $jokeController->edit();

} elseif (isset($_GET[‘delete’])) {

    $page = $jokeController->delete();

} elseif (isset($_GET[‘list’])) {

    $page = $jokeController->list();

} else {

    $page = $jokeController->home();

}

$title = $page[‘title’];

$output = $page[‘output’];

} catch (PDOException $e) {

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

. $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

You can find this example in OOP-EntryPoint Save this over the top of index.php in the public directory and visit the home page at http://http://192.168.10.10/. If everything is correct, the page will display as expected. While you have the public directory open, delete jokes.php, editjoke.php and deletejoke.php. We’ve moved the relevant code from these files into JokeController, so they’re no longer needed.The new index.php page follows the same structure as each of our controllers. A lot of this code looks familiar, but let’s go through the new lines individually.

$jokeController = new JokeController($jokesTable, $authorsTable);

This creates an instance of the JokeController class that we just wrote. When the constructor is called, it’s passed the instances of DatabaseTable$jokesTable, and $authorsTable.

if (isset($_GET[‘edit’])) {

    $page = $jokeController->edit();

}

else if (isset($_GET[‘delete’])) {

    $page = $jokeController->delete();

}

else if (isset($_GET[‘list’])) {

     $page = $jokeController->list();

}

else {

     $page = $jokeController->home();

}

This if … else if block is the clever part. These if statements examine the $_GET variables to determine which of the methods in the JokeController class is called. Because of the else clause, at least one of these blocks is guaranteed to get executed. Regardless of how this page is accessed, the $page variable will be created, and will contain two values—the page title, in the title key, and the page contents, stored under the output key. The final part creates the $title and $output variables for use in the template by reading them out of the newly created $page array.

$title = $page[‘title’];

$output = $page[‘output’];

To check all this works, open up the home page in your browser at http://192.168.10.10/Unfortunately, if you click any of the links—for example the Jokes List link—you’ll see an error. That’s because there are no longer individual pages that represent each page of the website. Now, everything is diverted through index.php. To access any of the pages on the website, you’ll have to type index.php followed by a relevant URL variable. To access the “Joke List” page, you’ll have to visit http://192.168.10.10/ index.php?listThis is called a single entry point or front controller.

We’ll have to go through and change all links to the old pages to go via index.php, but before we do that, let’s do some tidying up. We already called the new index.php crude, because it’s not very efficient. Every time you want to add a page to the website, you’ll need to do two things:

  1. add the method in JokeController
  2. add the relevant else if block in index.php

You’ve probably already noticed that the GET variable name maps exactly to the name of the function:

  • when $_GET[‘edit’] is set, the edit function is called
  • when $_GET[‘list’] is set, the list function is called

  This seems a bit redundant. PHP allows some cool stuff. For example, you can do this:

$function = ‘edit’;

$jokeController->$function();

This will evaluate $function to edit and actually call $jokeController->edit(). We can utilize this feature to read the GET variable and call the method with that name. Commonly, a function in a controller is called an action. We could use the GET variable action to call the relevant function on the controller. index.php?action=edit would call the edit function, index.php?action=delete would call delete, and so on. The code for this is remarkably simple:

<?php

try {

    include __DIR__ . ‘/../includes/DatabaseConnection.php’;

    include __DIR__ . ‘/../classes/DatabaseTable.php’;

    include __DIR__ . ‘/../controllers/JokeController.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new JokeController($jokesTable, $authorsTable);

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

$title = $page[‘title’];

$output = $page[‘output’];

} catch (PDOException $e) {

   $title = ‘An error has occurred’;

   $output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

   . $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

The whole if … else block that selects the relevant action has been replaced with two lines:

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

The first line utilizes the confusingly named “null coalescing operator”. This reads the GET variable called action. If it’s set, action is read from the GET variable, and if it’s not, $action is set to “home”. The second line calls the relevant method on the $jokeController object. If you open up your browser and visit index.php?action=listjokes, you’ll see the list of jokes. If you visit index.php without an action set, you’ll see the home page. The advantage of this approach is that, to add a new page to the website, all we need to do is add a method to the JokeController class and link to index.php, supplying the relevant action variable. Now that the URL structure of the website has changed completely, we’ll need to go through each page and update any links, form actions, or redirects. Firstly, layout.html.php:

<!doctype html>

<html>

<head>

   <meta charset=”utf-8″>

   <link rel=”stylesheet” href=”jokes.css”> <title><?=$title?></title>

</head>

<body>

<nav>

  <header>

       <h1>Internet Joke Database</h1>

  </header>

<ul>

   <li><a href=”index.php”> Home</a></li>

   <li><a href=”index.php?action=list”> Jokes List</a></li>

   <li><a href=”index.php?action=edit”> Add a new Joke</a></li>

</ul>

</nav>

<main>

    <?=$output?>

</main>

<footer>

   &copy; IJDB 2017

</footer>

</body>

</html>

Now open jokes.html.php, change the “Edit” link and form action for deleting:

<p><?=$totalJokes?> jokes have been submitted to the Internet Joke Database.</p>

    <?php foreach ($jokes as $joke): ?>

<blockquote>

<p>

     <?=htmlspecialchars($joke[‘joketext’], ENT_QUOTES, ‘UTF-8’)?>

(by <a  href=”mailto:<?=htmlspecialchars($joke[’email’], ENT_QUOTES, ‘UTF-8’); ?>”>

<?=htmlspecialchars($joke[‘name’], ENT_QUOTES, ‘UTF-8’);  ?>

</a> on <?php

$date = new DateTime($joke[‘jokedate’]);

echo $date->format(‘jS F Y’);

?>)

<a href=”index.php?action=edit&id=<?=$joke[‘id’]?>”> Edit</a>

<form action=”index.php?action=delete” method=”post”>

<input type=”hidden” name=”id”

value=”<?=$joke[‘id’]?>”>

<input type=”submit” value=”Delete”>

</form>

</p>

</blockquote>

<?php endforeach; ?>

 Finally, change the two redirects in JokeController from header(‘location: jokes.php’); to header(‘location: index.php?action=list’);You can find this example in OOP-EntryPoint2

Keeping it DRY

You’re nearly done! A large proportion of your PHP code is now neatly organized into methods inside classes, and you can quickly add new pages to the website by simply creating a new method inside JokeController. Before we continue, let’s quickly remove some of the remaining repeated code. If you examine JokeController, most of the methods perform the same set of steps. The edit method contains this code:

ob_start();

include __DIR__ . ‘/../templates/editjoke.html.php’;

$output = ob_get_clean();

return [‘output’ => $output, ‘title’ => $title];

The home method contains this code:

ob_start();

include __DIR__ . ‘/../templates/home.html.php’;

$output = ob_get_clean();

return [‘output’ => $output, ‘title’ => $title];

The list method contains this code:

ob_start();

include __DIR__ . ‘/../templates/jokes.html.php’;

$output = ob_get_clean();

return [‘output’ => $output, ‘title’ => $title];

These blocks of code are all very similar. Some of the lines are identical. As always, whenever you see repeated code like this, it’s worth considering how it can be removed. Rather than having each action include this block of code, it would be simpler to have the action provide a file name—such as home.html.php—and then have it loaded from within index.phpTo make that change, firstly open up index.php and change it to this:

<?php

try {

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

include __DIR__ . ‘/../classes/DatabaseTable.php’;

include __DIR__ . ‘/../controllers/JokeController.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new JokeController($jokesTable, $authorsTable);

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

$title = $page[‘title’];

ob_start();

include __DIR__ . ‘/../templates/’ . $page[‘template’];

$output = ob_get_clean();

} catch (PDOException $e) {

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

. $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

We’ve moved the three repeated lines from the individual methods into index.php. index.php now expects the $page variable to provide a template key. Let’s amend each of the controller actions to provide it. The controller actions will no longer provide the $output variable, but instead just a filename for index.php to include. The home method:

public function home() {

$title = ‘Internet Joke Database’;

return [‘template’ => ‘home.html.php’, ‘title’ =>

$title];

}

The list method:

public function list() {

$result = $this->jokesTable->findAll();

$jokes = [];

foreach ($result as $joke) {

    $author = $this->authorsTable->

         findById($joke[‘authorId’]);

$jokes[] = [

  ‘id’ => $joke[‘id’],

  ‘joketext’ => $joke[‘joketext’],

  ‘jokedate’ => $joke[‘jokedate’],

  ‘name’ => $author[‘name’],

  ’email’ => $author[’email’]

  ];

}

$title = ‘Joke list’;

$totalJokes = $this->jokesTable->total();

return [‘template’ => ‘jokes.html.php’, ‘ title’ => $title];

}

The edit method:

public function edit() {

   if (isset($_POST[‘joke’])) {

     $joke = $_POST[‘joke’];

     $joke[‘jokedate’] = new DateTime();

     $joke[‘authorId’] = 1;

    $this->jokesTable->save($joke);

header(‘location: index.php?action=list’);

}

else {

 if (isset($_GET[‘id’])) {

    $joke = $this->jokesTable->findById($_GET[‘id’]);

    }

$title = ‘Edit joke’;

return [‘template’ => ‘editjoke.html.php’,

‘title’ => $title];

    } 

}

Each action now provides the name of a template that gets loaded in index.php. We’ve saved ourselves from needing to repeat the output buffer and include lines. However, if you try the code above, only the home page will work. If you try viewing the list of jokes, you’ll get an error:

Notice: Undefined variable: totalJokes in

➥ /home/vagrant/Code/Project/templates/jokes.html.php on line 2

The reason for this error is that jokes.html.php is now being included from index.php and index.php does not include the variable $totalJokesWe need a way to get the $totalJokes and $jokes variables into index.phpOn first glance, you might think to do it in the return statement, the same way that we did with the title, output, and later template variables:

return [‘template’ => ‘jokes.html.php’,

‘title’ => $title,

‘totalJokes’ => $totalJokes,

‘jokes’ => $jokes];

And then recreate the variables in index.php:

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

$title = $page[‘title’];

$totalJokes = $page[‘totalJokes’];

$jokes = $page[‘jokes’];

ob_start();

include __DIR__ . ‘/../templates/’ . $page[‘template’];

$output = ob_get_clean();

If you try this, the jokes list page will work as expected. However, as soon as you navigate to another page, you’ll get errors. For example, the “Edit Joke” page requires a variable called joke, and it doesn’t provide the variables for totalJokes or jokesA very messy solution would be to have each method in the controller return every single variable that’s needed, but leave the array values blank when they’re not needed. The edit return statement would then end up looking like this:

return [‘template’ => ‘jokes.html.php’,

‘title’ => $title,

‘totalJokes’ => ”,

‘jokes’ => ”,

‘joke’ => $joke];

This is obviously not a viable solution. Each time we add a template that requires a variable with a new name, we’d need to amend every single controller method to provide an empty string for that variable and then amend index.php to set it!

Template Variables

Instead, we’ll solve the problem in the same way we did for the return statement. Each method will supply an array of variables. The list return statement will now look like this:

return [‘template’ => ‘jokes.html.php’, ‘title’ => $title,

 ‘variables’ => [

   ‘totalJokes’ => $totalJokes,

   ‘jokes’ => $jokes

    ]

];

This is called a multi-dimensional array: there’s an array inside an array. In this case, the variables key maps to a second array that contains the keys totalJokes and jokesAlthough the code is slightly more difficult to read, the advantage of this approach is that each controller method can provide a different array in the variables key. The editJoke page can use this return statement:

return [‘template’ => ‘editjoke.html.php’,

  ‘title’ => $title,

  ‘variables’ => [

  ‘joke’ => $joke ?? null

    ]

];

In the code above, the joke array key is mapped to $joke ?? null. You were probably expecting to see ‘joke’ => $joke. However, because the joke variable may or may not have been set by the code above, the joke key is set either to the contents of the joke variable or to null. null is an empty valueBoth the list and edit controller actions now consistently return an array with the keys template, title and variablesWe can now use the variables array in index.php. The simplest way to achieve this would be to create a variable called $variables inside index.php, in the same way we did with $title:

$title = $page[‘title’];

$variables = $page[‘variables’];

ob_start();

include __DIR__ . ‘/../templates/’ . $page[‘template’];

$output = ob_get_clean();

Each template (such as jokes.html.php) now has access to the $variables array, and could read values from it—for example, by replacing this:

<p><?=$totalJokes?> jokes have been submitted to the Internet Joke Database.</p>

….with this:

<p><?=$variables[‘totalJokes’]?> jokes have been submitted to the Internet Joke Database.</p>

This solution works, but it means opening up and changing every template file. A simpler alternative is to create the variables that are required. Luckily, PHP provides a method of doing exactly that. The extract function can be used to create variables from an array:

$array [‘hello’ => ‘world’];

extract($array);

echo $hello; // prints “world”

A variable is created for any key in the array, and its value is set to the corresponding value. We can use extract to create the relevant template variables in index.php:

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

$title = $page[‘title’];

if (isset($page[‘variables’])) {

extract($page[‘variables’]);

}

ob_start();

include __DIR__ . ‘/../templates/’ . $page[‘template’];

$output = ob_get_clean();

You can find this example in OOP-EntryPoint3If $page[‘variables’] is an array that’s come from the list method, variables called totalJokes and jokes will be created. If it was created by the edit method, a single variable named joke will be created.We’ve surrounded the extract call with if (isset($page[‘variables’])), because some methods, like the home method, may not need to provide any methods to the template.

Be Careful With Extract

Everything is working perfectly, and we’ve managed to remove the repeated code from the controller’s methods. Unfortunately, we’re not quite done yet. One of the biggest problems with extract is that it creates variables in the current scope. Take another look at this block of code:

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

$title = $page[‘title’];

if (isset($page[‘variables’])) {

extract($page[‘variables’]);

}

ob_start();

include __DIR__ . ‘/../templates/’ . $page[‘template’];

What would happen if the array $page[‘variables’] contained the keys page and title? The $title and $page variables would be overwritten! It’s likely the overwritten $page variable would not be an array with a key called template that contains the name of a template file. If the return statement from a controller action happened to include a key called page in the variables array, it would prevent that controller action from loading a template. It is possible to tell the extract function not to overwrite variables, but then if the template is expecting a variable called $page, it’s going to be given the wrong information. A very simple solution to this is moving the code that loads the template into its own function. Amend index.php to this:

OOP-EntryPoint-Template

<?php

function loadTemplate($templateFileName, $variables = [ ])

{

   extract($variables);

   ob_start();

include __DIR__ . ‘/../templates/’ . $templateFileName;

 return ob_get_clean();

}

try {

    include __DIR__ . ‘/../includes/DatabaseConnection.php’;

    include __DIR__ . ‘/../classes/DatabaseTable.php’;

    include __DIR__ . ‘/../controllers/JokeController.php’;

  $jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

  $authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

  $jokeController = new JokeController($jokesTable, $authorsTable);

  $action = $_GET[‘action’] ?? ‘home’;

  $page = $jokeController->$action();

  $title = $page[‘title’];

if (isset($page[‘variables’])) {

    $output = loadTemplate($page[‘template’]

       , $page[‘variables’]);

} else {

     $output = loadTemplate($page[‘template’]);

}

} catch (PDOException $e) {

   $title = ‘An error has occurred’;

  $output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

   . $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

If the variables array does contain keys called page or title, placing the code for loading a template in its own function (loadTemplate) means the existing variables won’t be overwritten, because they don’t exist inside the function’s scope. We showed you how to use object-oriented programming to break up the code further and reduce repetition. We also began to add a clearer structure to the controller code. 

Creating an Extensible Framework

Now that you’re able to write a controller with methods, and call those methods from index.php, the next step is to add the rest of the pages for managing the website. Currently, we can add jokes to the database through index.php by specifying an action URL parameter. However, a real website will need to do considerably more than handle basic database operations for a single table. The next extension of the website will be allowing users to register as authors and post their own jokes. However, before we do that we’ll show you how to write a modern, flexible framework to build upon. By the end of this section, you’ll have the foundation for building any website, and you’ll have a good understanding of the techniques and concepts used by professional PHP developers. We’re not going to add any new features. Instead, We are going to show you how the code can be organized so that it can be reused on each website you build.

We ended with this code in index.php:

<?php

function loadTemplate($templateFileName, $variables = [])

{

    extract($variables);

  ob_start();

include __DIR__ . ‘/../templates/’ . $templateFileName;

  return ob_get_clean();

}

try {

     include __DIR__ . ‘/../includes/DatabaseConnection.php’;

     include __DIR__ . ‘/../classes/DatabaseTable.php’;

     include __DIR__ . ‘/../controllers/JokeController.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new JokeController($jokesTable, $authorsTable);

$action = $_GET[‘action’] ?? ‘home’;

$page = $jokeController->$action();

$title = $page[‘title’];

if (isset($page[‘variables’])) {

       $output = loadTemplate($page[‘template’],

                $page[‘variables’]);

} else {

     $output = loadTemplate($page[‘template’]);

}

} catch (PDOException $e) {

  $title = ‘An error has occurred’;

      $output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

    . $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

This lets us call any of the functions in the JokeController class by specifying the action URL parameter—for example, by linking to or visiting index.php?action=list.

Search Engines

Before we make any structural changes to the code, we need to do a little housekeeping. In PHP, functions are not case sensitive. list is treated exactly the same way as LIST. Due to case insensitivity, visiting index.php?action=list will display the page, but so will index.php?action=LIST or index.php?action=List. This may seem like a good thing, as people will be able to mistype the URL and still see the correct page. However, this feature can also cause problems for search engines. Some search engines will see these two URLs as two entirely different pages, even if they display the exact same content. Both index.php?action=LIST and index.php?action=list will appear in search results. You might think “Great, more of my pages will appear in search results!” But search engines generally dislike “duplicate content”, either ranking it lower or ignoring it altogether. Would you prefer one result on the first page or two results potentially much further down in the search pages?

There are several ways to fix this. You can tell search engines to ignore certain pages. Or you can tell them which is the “canonical” (primary) version. But this can be difficult to manage on larger sites, and it’s usually simpler to enforce strict URLs. A common way to do this is forcing all URLs to lowercase. Forcing URLs to lowercase is possible using a simple piece of PHP code, which detects whether or not the user entered a lowercase URL:

<?php

$action = $_GET[‘action’];

if ($action == strtolower($action)) {

     $jokeController->$action();

} else {

     echo ‘Sorry that page does not exist.’;

}

This code compares $action to a lowercase version of $action. The strtolower function converts any string to lowercase: LISTJOKES, listJokes or listjokes all become listjokes. By comparing the original $action to the lowercase version, it’s possible to work out whether or not someone came to the page using a lowercase value for $action. If they didn’t, an error is displayed. Visitors and search engines will only see the content on the lowercase version of the URL.

While doing this will prevent duplicate content and protect your search engine ranking, it’s not very helpful for users who accidentally mistype the URL. Luckily, we can get the best of both worlds by redirecting non-lowercase pages to their lowercase equivalents. We’ve already used the header function to redirect people to different pages. We can also use the header function to send all uppercase URLs to their lowercase equivalents:

<?php

 $action = $_GET[‘action’];

if ($action == strtolower($action)) {

    $jokeController->$action();

} else {

     header(‘location: index.php?action=’ . strtolower($action));

}

Now anyone who visits index.php?action=LISTJOKES or index.php?action=listJokes will be redirected to index.php?action=listjokes. However, there’s one more thing we need to do. There are two types of redirection, temporary and permanent. To tell search engines not to list the page, you need to tell them the redirection is permanent. This is done with an “HTTP response code”. You’ve probably come across at least one of these while browsing the web. The code 404 means “Not found”. Each time a page is sent to the browser, a response code is sent along with it to tell the browser and search engines how to treat the page. To tell the browser that a redirect is permanent, you need to send the code 301. PHP has a function called http_response_code that can be used to send the 301 response code along with the redirect:

<?php

$action = $_GET[‘action’];

if ($action == strtolower($action)) {

     $jokeController->$action();

} else {

    http_response_code(301);

    header(‘location: index.php?action=’ . strtolower($action));

}

HTTP Response Codes

There are many different HTTP response codes you can use. 404 is particularly useful when you display an error message, as this will stop the page appearing in search results and prevent the page from going into the browser’s history. You don’t generally want “Sorry, the product you requested is no longer available” appearing in search engines. You can even change the HTTP response code to 404 to get search engines to unlist pages that are no longer relevant. A complete list of response codes and their meaning can be found on the W3.org website.

Make Things Generic

In PHP and any other programming language, if you can make a piece of code generic and able to cope with different use cases, it’s generally considered better, because it’s more flexible. If you can reuse existing code, it saves you from needing to write similar code repeatedly. This is the approach we took with the DatabaseTable class. Rather than writing similar code elsewhere in our application, the DatabaseTable class can be used any time we need to interact with the database. The index.php code we already have allows any function to be called in the JokeController class by specifying the action URL parameter. Using a single file to handle any controller action is an improvement over having a unique file for each action, because it avoids us repeating code. The complete index.php, including the redirect that we just added, now looks like this:

 CMS-Redirect

function loadTemplate($templateFileName, $variables = []) {

     extract($variables);

     ob_start();

include __DIR__ . ‘/../templates/’ . $templateFileName;

return ob_get_clean();

}

try {

     include __DIR__ . ‘/../includes/DatabaseConnection.php’;

     include __DIR__ . ‘/../classes/DatabaseTable.php’;

     include __DIR__ .

    ‘/../classes/controllers/JokeController.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new JokeController($jokesTable, $authorsTable);

$action = $_GET[‘action’] ?? ‘home’;

if ($action == strtolower($action)) {

     $page = $jokeController->$action();

}

else {

   http_response_code(301);

   header(‘location: index.php?action=’ .

    strtolower($action));

}

$title = $page[‘title’];

if (isset($page[‘variables’])) {

    $output = loadTemplate($page[‘template’],

                      $page[‘variables’]);

}

else {

    $output = loadTemplate($page[‘template’]);

      }

}

catch (PDOException $e) {

    $title = ‘An error has occurred’;

   $output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

   . $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

Thinking Ahead: User Registration

The next step for us will be to allow someone to register as a user so they can post a joke. To do this, we’ll need a new page on the website. Although it would be possible to keep adding methods to the JokeController class, on any nontrivial website this would result in a very large class that contained the code for every single page on the website. Instead, we’ll create a new controller called RegisterController with some methods to handle user registration. This helps keep the code manageable, by keeping anything to do with jokes in JokeController and any page related to user registration in RegisterControllerWith the index.php above, we’d need to write a new PHP script to utilize RegisterController, such as register.php, that looked like this:

<?php

try {

    include __DIR__ . ‘/../includes/DatabaseConnection.php’;

    include __DIR__ . ‘/../classes/DatabaseTable.php’;

    include __DIR__ . ‘/../controllers/RegisterController.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$registerController = new RegisterController($authorsTable);

$action = $_GET[‘action’] ?? ‘home’;

if ($action == strtolower($action)) {

     $page = $registerController->$action();

} else {

   http_response_code(301);

   header(‘location: index.php?action=’ .

   strtolower($action));

}

$title = $page[‘title’];

   if (isset($page[‘variables’])) {

      $output = loadTemplate($page[‘template’],

        $page[‘variables’]);

      } else {

$output = loadTemplate($page[‘template’]);

}

} catch (PDOException $e) {

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

. $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

Most of this code is identical to index.php. The only differences are:

  1. include ‘JokeController.php’; becomes include ‘RegisterController.php’;
  2. $jokeController = new JokeController($jokesTable, $authorsTable); becomes $registerController = new RegisterController($authorsTable);
  3. $jokeController->$action(); becomes $registerController->$action();

It would be better if a single index.php could work with any controller in the same way it works with any action, avoiding the need for different PHP files for loading each controller. To achieve that, we’d need to make it so that the same index.php can be used for any controller. The third change is very easy to fix, so let’s remove that difference first. The only reason this needs changing is because of the different variable names $jokesController and $registerController. If the same variable names were used throughout—such as $controller—then this change wouldn’t be needed. $controller = new RegisterController($authorsTable); or $controller = new JokeController($jokesTable, $authorsTable); could both work with $controller->$action();.

The solution to 1. will be simple to implement. As we saw when loading templates, the include statement can be used to include files using a string stored in a variable. Making this change to include the correct file is fairly simple:

$controllerName = ucfirst($_GET[‘controller’]) .

‘Controller’;

include __DIR__ . ‘/../controllers/’ . $controllerName .

‘.php’;

As you already know, variables can be built up from other variables, including $_GET variables. Using the same process we used to define $action, it’s also possible to specify a URL parameter for controller, like so: index.php?controller=jokes&action=listJokes. We could use this to load JokesController and call the action listJokes.

To build the class name, I’ve used ucfirst to make the first letter of the controller from the URL uppercase to match the filename. joke as supplied in the URL becomes Joke. The string Controller.php is then appended to give the complete class name. Using the URL parameter controller=register would mean the file RegisterController.php is included. Now the file that contains the controller will be included by specifying the controller name in the URL, so that it’s available in $_GET[‘controller’]That fixes problem 1. and partially fixes problem 3. The new code looks like this:

include __DIR__ . ‘/../controllers/’ . $_GET[‘controller’] . ‘.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

$controller = new RegisterController($authorsTable);

$action = $_GET[‘action’] ?? ‘home’;

if ($action == strtolower($action)) {

$page = $controller->$action();

}

For change 2. we need to be able to substitute the line $controller = new JokeController($jokesTable, $authorsTable); with a line that creates the relevant controller (for now, either JokeController or RegisterController). Just as a variable can be used in place of a method name, a variable can also be used in place of a class name, so it’s possible to substitute the class name with a variable from $_GET:

$controllerObject = new $controllerName($jokesTable, $authorsTable);

new $controllerName() will create an instance of the class name provided in the $_GET variable controller. Visiting index.php?controller=joke&action=list will now load the controller with the name JokeController. We can also apply the same logic as before to do the following select a default controller (“joke”) if no $_GET[‘controller’] variable is set:

$controllerName = $_GET[‘controller’] ?? ‘joke’;

redirect to the lowercase URL if required:

if ($action == strtolower($action) &&

$controllerName == strtolower($controllerName)) {

}

else {

// redirect to lowercase version

}

Now we can take the name from $controllerName and get the class name by making the first letter uppercase (the ucfirst function does this for any string) and then appending the string Controller:

$className = ucfirst($controllerName) . ‘Controller’;

Finally, include the relevant file and create the controller instance:

include __DIR__ . ‘/../controllers/’ . $className . ‘.php’;

$controller = new $className($jokesTable, $authorsTable);

The complete block of code looks like this:

$action = $_GET[‘action’] ?? ‘home’;

$controllerName = $_GET[‘controller’] ?? ‘joke’;

if ($action == strtolower($action) &&

   $controllerName == strtolower($controllerName)) {

       $className = ucfirst($controllerName) . ‘Controller’;

       include __DIR__ . ‘/../controllers/’ . $className . ‘.php’;

$controller = new $className($jokesTable, $authorsTable);

 $page = $controller->$action();

}

else {

   http_response_code(301);

   header(‘location: index.php?controller=’ .

   strtolower($controllerName) . ‘&action=’ .

   strtolower($action));

}

If we add this to index.php and visit one of the joke pages—via index.php controller=joke&action=list, for example—it will work as intended. However, you may have spotted a potential problem. When the controller is created, its constructor is called and passed the $jokesTable and $authorsTable objects. What if different controllers require different objects to work? For example, the RegisterController class will only require $authorsTable.

Dependencies

Different controllers will inevitably require different dependencies. The JokeController we built requires the $jokesTable and $authorsTable objects, but not all controllers will require those same objects. An object that’s required by another object is called a dependency. For example, JokeController is dependent on the $jokesTable instance, as without it, it won’t work correctly. To identify a dependency in a piece of code, look for a function call on another object. For example, the delete method in the controller depends on the jokesTable variable, and that variable must contain a DatabaseTable instance. Without a DatabaseTable instance, the delete method below can’t work. It’s dependent on functionality from another class.

public function delete() {

$this->jokesTable->delete($_POST[‘id’]);

header(‘Location: .’);

exit();

}

The method being called jokesTable->delete() is in another class. If the DatabaseTable class didn’t exist, this delete function would fail. We can say that the JokeController class is dependent on the DatabaseTable class. Likewise, we can say that the DatabaseTable class has a dependency on the PDO class, because it can’t function without it.

We’re going to add a second controller, RegisterController, that deals with allowing new authors to register so they can post jokes. To begin with, we’ll create a single form for registration. When submitted, information will need to be inserted into the author table. There’s no reason this controller will ever need to interact with the $jokesTable object, so a RegisterController object would be instantiated like this:

$controller = new RegisterController($authorsTable);

Other controllers might need other database tables—for example, a categories table for categorizing the jokes or objects that don’t even deal with database access, to validate data that’s been entered. We face a problem here. We can use a variable in place of the class name like so:

$controllerName = $_GET[‘controller’];

$controller = new $controllerName($authorsTable);

But there’s no easy way to determine what dependencies the required controller needs.This is the most complicated topic, and something even very experienced developers struggle with! Different people have come up with some potential solutions, and there are many approaches you can take. However, many are considered “bad practice” and should be avoided.  It’s a very good idea to pass dependencies into the constructor of classes that need them. This stops an object being able to exist without having the dependencies set. The problem we have is that different controllers will require different dependencies. The JokesController class constructor looks like this:

public function __construct(

DatabaseTable $jokesTable, DatabaseTable $authorsTable) {

   $this->jokesTable = $jokesTable;

    $this->authorsTable = $authorsTable;

}

And when we write the code for the RegisterController class, the constructor will look like this:

public function __construct(DatabaseTable $authorsTable) {

   $this->authorsTable = $authorsTable;

}

The JokesController class has dependencies on two DatabaseTable objects, onefor authors and one for jokes. The RegisterController class only has a dependency on one, $authorsTable. If we’re trying to automate creation of the controllers, it presents a problem: if the constructors are different, how can the objects be automatically created?

One method of fixing this would be to ensure that all controllers have the same constructor. They all require access to all the possible DatabaseTable objects. This works, but it’s messy. It results in controllers with dependencies on everything that any controller may ever need. One major downside to this approach is that, when a new database table is added, all the controllers’ constructors must be changed. We could overcome this by passing an array of all the possible dependencies and picking out the ones we need. This is essentially something known as a “Service Locator”, and it’s a common approach, although it’s been widely considered bad practice over the last few years. The technical term for what we’re doing is dependency injection. It sounds complicated, but it’s just a fancy term for passing dependencies into constructors. You’ve been doing it all along without even knowing! The simplest way of solving the problem of different constructors needing different arguments is a series of if statements. This way, each controller can be created with the correct dependencies:

$action = $_GET[‘action’] ?? ‘home’;

$controllerName = $_GET[‘controller’] ?? ‘joke’;

if ($controllerName === ‘joke’) {

$controller = new JokeController($jokesTable,  $authorsTable);

}

else if ($controllerName === ‘register’) {

$controller = new RegisterController($authorsTable);

}

$page = $controller->$action();

Now the controller is selected by the variable supplied in $_GET[‘controller’]and the method defined in $_GET[‘action’] is called on the controller object. This approach is very flexible. It allows us to call any method in any controller by specifying the class name in the controller URL variable and method name in the action URL variable. Although this adds some flexibility, it also opens up several security issues. Someone can alter the URL and run any method in any class. Depending on what our controllers are doing, this may cause a problem. Instead, it’s more secure, and only a little extra code, to specify a single URL variable that triggers a specific controller action. This single URL variable is called a route:

$route = $_GET[‘route’] ?? ‘joke/home’; // If no route  variable is set, use ‘home’

if ($route === ‘joke/list’) {

    include __DIR__ .

    ‘/../classes/controllers/JokeController.php’; $controller = new JokeController($jokesTable,

        $authorsTable);

  $page = $controller->list();

}

else if ($route === ‘joke/home’) {

   include __DIR__ .

   ‘/../classes/controllers/JokeController.php’; $controller = new JokeController($jokesTable,

              $authorsTable);

$page = $controller->home();

}

else if ($route === ‘register’) {

include __DIR__ .

‘/../classes/controllers/RegisterController.php’;

$controller = new RegisterController($authorsTable);

$page = $controller->showForm();

}

Although this is slightly more code and we have some repetition, it’s considerably more secure. Someone can only instantiate a controller and call a method if it’s in this list. In this case, the repetition is preferable to the potential security hole of letting anyone call any method. We’ve used joke/list and joke/edit, with the joke/ prefix, because each page needs a unique identifier. In future, we may want to create a page that lists or allows editing authors, which can then be stored on the URLs author/list, author/edit, etc.

The complete index.php now looks like this:

CMS-Controller

<?php

function loadTemplate($templateFileName, $variables = [])

{

  extract($variables);

  ob_start();

include __DIR__ . ‘/../templates/’ . $templateFileName;

  return ob_get_clean();

}

try {

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

include __DIR__ . ‘/../classes/DatabaseTable.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

//if no route variable is set, use ‘joke/home’

$route = $_GET[‘route’] ?? ‘joke/home’;

  if ($route == strtolower($route)) {

     if ($route === ‘joke/list’) {

  include __DIR__ .

  ‘/../classes/controllers/JokeController.php’;

  $controller = new JokeController($jokesTable, $authorsTable);

        $page = $controller->list();

} elseif ($route === ‘joke/home’) {

   include __DIR__ .

‘/../classes/controllers/JokeController.php’;

        $controller = new JokeController($jokesTable,

  $authorsTable);

$page = $controller->home();  

} elseif ($route === ‘joke/edit’) {

   include __DIR__ .

  ‘/../classes/controllers/JokeController.php’;

  $controller = new JokeController($jokesTable, $authorsTable);

        $page = $controller->edit();

} elseif ($route === ‘joke/delete’) {

  include __DIR__ .

  ‘/../classes/controllers/JokeController.php’;

   $controller = new JokeController($jokesTable,

       $authorsTable);

     $page = $controller->delete();

} elseif ($route === ‘register’) {

  include __DIR__ .

   ‘/../classes/controllers/RegisterController.php’;

    $controller = new RegisterController($authorsTable);

          $page = $controller->showForm();

  }

} else {

  http_response_code(301);

   header(‘location: index.php?route=’ . strtolower($route));

}

  $title = $page[‘title’];

if (isset($page[‘variables’])) {

    $output = loadTemplate($page[‘template’],

   $page[‘variables’]);

} else {

$output = loadTemplate($page[‘template’]);

}

} catch (PDOException $e) {

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

. $e->getFile() . ‘:’ . $e->getLine();

}

include __DIR__ . ‘/../templates/layout.html.php’;

Note that we’ve also amended the if statement that checks case to use the new $route variable. Ideally, we were looking to be able to use any controller without editing index.php. But for simplicity’s sake, we’ll stick with this approach. Now that the routes have changed, we’ll also need to amend layout.html.php to use the new routes in the menu:

<li><a href=”index.php?route=joke/list”>Jokes List</a></li>

<li><a href=”index.php?route=joke/edit”>Add a new Joke</a></li>

Before we go ahead and change all the links throughout the website, I want to introduce an approach called URL Rewriting, which is another reason for using a single route variable instead of separate controller and action variables.

URL Rewriting

A lot of websites are written in PHP, including Facebook and Wikipedia. If you visit one of these sites, you’ll see that the URLs don’t look like the ones we’ve been using on the joke website. The URL for SitePoint’s Wikipedia page is https://en.wikipedia.org/wiki/ SitePoint, and its Facebook page URL is https://www.facebook.com/ sitepoint/Using the structure we’ve looked at so far, you’d probably expect to see something like https://www.facebook.com/index.php?controller=page&id=sitepoint or https://en.wikipedia.org/ index.php?route=wiki/sitepoint.

Most PHP websites don’t actually show you the PHP filename in the URL. Many years ago, search engines preferred this approach. These days, search engines don’t care about URL structureURL structure, and friendly URLs are used more for aesthetic reasons. As most websites use this approach, it’s useful to know how to do it. URL Rewriting is a tool for forwarding one URL to another. You can configure your web server so that when someone visits /jokes/list, it actually runs index.php?route=jokes/list, or even when someone visits contact.php it instead it runs index.php?route=contact.

Importantly, the original URL is still shown in the browser’s address bar. URL Rewriting is a long and complex topic. You can set up all kinds of wonderful and impressive rules. However, almost all modern PHP websites use the same rule: if a file requested doesn’t exist, load index.phpIn fact, the Homestead Improved box we’re using is already set up to do this. Visit http://192.168.10.10/I-dont-exist.php or any whimsical filename you can think of and you’ll see the website home page rather than an error page. If you create the file I-dont-exist.php and visit the page in the browser, it will be loaded. Otherwise, all requests are sent to index.php.

NGINX

If you need to configure an NGINX server for URL rewriting, the guide on the NGINX website is the first place to look for examplesHowever, for most setups you’ll just need the configuration directive:

location / {

try_files $uri $uri/ /index.php;

}

For Apache servers, the same can be achieved by creating a file called .htaccess in the public (or, more likely for Apache, public_html or httpdocs) directory with the following contents:

conf

RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^.*$ /index.php [NC,L,QSA]

If a file doesn’t exist, it will load index.php rather than display and error. 

You know just enough about URL rewriting to make use of it on the site. Rather than using a $_GET variable to determine the route, you can use the URL that the person used to connect to the website. PHP supplies this information in the variable $_SERVER[‘REQUEST_URI’]Open up index.php and replace this:

$route = $_GET[‘route’] ?? ‘joke/home’;

… with this:

$route = ltrim(strtok($_SERVER[‘REQUEST_URI’], ‘?’), ‘/’);

The ltrim function removes the leading /. If you visit http://192.168.10.10/ joke/list, $_SERVER[‘REQUEST_URI’] will store the string /joke/list. By trimming any leading slashes, we can match the request URI to our existing routes. Because the $_SERVER[‘REQUEST_URI’] contains the complete URL, if the URL contains $_GET variables, the entire URL string is included in the variable. We don’t want these in our routes. The following code will return the entire string up to the first question mark, or the entire string if there’s no question mark:

strtok($_SERVER[‘REQUEST_URI’], ‘?’)

Now, visit http://192.168.10.10/joke/list and you’ll see the joke list on the prettier URL … with one problem! One issue with URL rewriting in this manner is that the browser will see the / in joke/list as a directory separator.

<link rel=”stylesheet” href=”jokes.css”>

When the browser sees the line above, it will look for jokes.css at http://192.168.10.10/joke/jokes.css. As that file doesn’t exist, the stylesheet won’t be applied. There are two possible fixes:

  1. The HTML <base> tag. Although it’s a viable solution, it introduces a few issues that are not worth covering here.
  2. Make all URLs relative to the domain. To do this, just prefix all links with a /.

The second option is more work but causes fewer issues. Let’s open up layout.html and change this:

<link rel=”stylesheet” href=”jokes.css”>

… to this:

<link rel=”stylesheet” href=”/jokes.css”>

By prefixing a link in an HTML document with a forward slash /, it tells the browser to look for the file from the top level of the website. If we refresh the page, we’ll see the styles now display correctly. If we just visit http://192.168.10.10/ without specifying a file name, we’ll see an error. That’s because we don’t have a route set up for an empty string. In index.php, we replace else if ($route === ‘joke/home’) { with else if ($route === ”) {. If we refresh the home page, it should display as expected. Finally, let’s amend each link on the website to use the new prettier format. layout.html.php:

<ul>

<li><a href=”/”>Home</a></li>

<li><a href=”/joke/list”>Jokes List

</a></li>

<li><a href=”/joke/edit”>Add a new Joke </a></li>

</ul>

The edit link and delete form action in jokes.html.php:

<a href=”/joke/edit?id=<?=$joke[‘id’]?>”>Edit </a>

<form action=”/joke/delete” method=”post”>

   <input type=”hidden” name=”id” value=”<?=$joke[‘id’]?>”>

<input type=”submit” value=”Delete”>

</form>

And finally the two redirects in JokeController.php:

header(‘location: /joke/list’);

You can find this example in CMS-Controller-RewriteNow you know why we’re making these structural changes before our website has too many pages!

Tidying Up

You’ve probably noticed that index.php is getting a bit long and unwieldy. Before we get into creating RegisterController.php, let’s tidy up index.php a little.

Make it OOP

One of the primary causes of overly complex code is nested if statements. With any long piece of code, it’s possible to break it up into a single class with a set of functions. This can be done by identifying unique tasks within the code. Looking at the code, we can see the following distinct tasks: 

  • instantiating the controller and calling the relevant action based on $route
  • the loadTemplate function
  • redirecting to a lowercase version of the URL if required
  • loading the relevant template file and setting its variables

  Let’s take each of these and make it a function inside a class called EntryPoint, inside the classes directory.This class will take a single variable, $route, representing the route to load. It will then store the route in a class variable so that each function can use it:

<?php

class EntryPoint

{

private $route;

public function __construct($route)

{

    $this->route = $route;

      }

}

The next step is checking that the route is the correct case and redirecting to the lowercase version if it’s not:

private function checkUrl() {

   if ($this->route !== strtolower($this->route))

               { http_response_code(301);

            header(‘location: ‘ . strtolower($this->route));

       }

}

We’ve amended the logic here slightly so that the if statement checks to see if the supplied route is the same as the lowercase version. This function can be called directly from the constructor:

public function __construct($route) {

$this->route = $route;

$this->checkUrl();

}

By putting the function call in the controller, this class won’t even get constructed if either $controller or $action are not lowercase. The existing loadTemplate function can be copied directly into the class. We’ve made it private, as we’re going to call it from another method in the same class:

private function loadTemplate($templateFileName, $variables = []) {

extract($variables);

ob_start();

include __DIR__ . ‘/../templates/’ . $templateFileName;

return ob_get_clean();

}

There are two parts left: loading/instantiating the controller and handling the $page variable to generate the layout. Firstly, let’s copy/paste the entire if statement that checks the route and move it to its own function called callAction. The task for this method is calling the relevant controller action and returning the $page variable:

private function callAction() {

include __DIR__ . ‘/../classes/DatabaseTable.php’;

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

if ($this->route === ‘joke/list’) {

   include __DIR__ .

   ‘/../classes/controllers/JokeController.php’;

$controller = new JokeController($jokesTable,$authorsTable);

   $page = $controller->list();

}

else if ($this->route === ”) {

  include __DIR__ .

  ‘/../classes/controllers/JokeController.php’;

$controller = new JokeController($jokesTable, $authorsTable);

$page = $controller->home();

}

else if ($this->route === ‘joke/edit’) {

  include __DIR__ .

 ‘/../classes/controllers/JokeController.php’;

$controller = new JokeController($jokesTable, $authorsTable);

   $page = $controller->edit();

}

else if ($this->route === ‘joke/delete’) {

   include __DIR__ .

   ‘/../classes/controllers/JokeController.php’;

$controller = new JokeController($jokesTable, $authorsTable);

$page = $controller->delete();

}

else if ($this->route === ‘register’) {

  include __DIR__ .

  ‘/../classes/controllers/RegisterController.php’;

$controller = new RegisterController($authorsTable);

$page = $controller->showForm();

   }

return $page;

}

We’ve made this function a method private. Once again, we’ll only be calling it from within the class. Because the new DatabaseTable lines are the only parts of our code that need the $pdo database connection or DatabaseTable class, I’ve moved the include statement for DatabaseConnection.php and DatabaseTable.php here. In fact, if DatabaseConnection.php wasn’t included here, the line $jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’); would fail, as there’s no $pdo variable in this scope. Finally, we can place the remaining code for loading the relevant template and supplying it variables into a method called run. This will be the only public method in the class:

public function run() {

$page = $this->callAction();

$title = $page[‘title’];

if (isset($page[‘variables’])) {

$output = $this->loadTemplate($page[‘template’], $page[‘variables’]);

}

else {

$output = $this->loadTemplate($page[‘template’]);

}

include __DIR__ . ‘/../templates/layout.html.php’;

}

There’s nothing new here, but we’ve moved each task into its own method. The callAction method deals entirely with the controllers and returns the $page variable. Using the new EntryPoint class, index.php can now be rewritten like this:

<?php

try {

include __DIR__ . ‘/../classes/EntryPoint.php’;

$route = ltrim(strtok($_SERVER[‘REQUEST_URI’], ‘?’), ‘/’);

$entryPoint = new EntryPoint($route);

$entryPoint->run();

} catch (PDOException $e) {

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

. $e->getFile() . ‘:’ . $e->getLine();

include __DIR__ . ‘/../templates/layout.html.php’;

}

The complete EntryPoint.php looks like this:

CMS-EntryPoint-Class

<?php

class EntryPoint

{

   private $route;

  public function __construct($route)

{

   $this->route = $route;

   $this->checkUrl();

}

private function checkUrl()

{

   if ($this->route !== strtolower($this->route)) {

      http_response_code(301);

      header(‘location: ‘ . strtolower($this->route));

    }

}

private function loadTemplate($templateFileName, $variables = [])

{

  extract($variables);

  ob_start();

include __DIR__ . ‘/../templates/’ . $templateFileName;

  return ob_get_clean();

  }

private function callAction()

{

include __DIR__ . ‘/../classes/DatabaseTable.php’;

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

  $jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

  $authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

if ($this->route === ‘joke/list’) {

  include __DIR__ . ‘/../classes/controllers/JokeController.php’;

$controller = new JokeController($jokesTable, $authorsTable);

$page = $controller->list();

} elseif ($this->route === ”) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

   $controller = new JokeController($jokesTable, $authorsTable);

      $page = $controller->home();

} elseif ($this->route === ‘joke/edit’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

   $controller = new JokeController($jokesTable, $authorsTable);

    $page = $controller->edit();

} elseif ($this->route === ‘joke/delete’) { 

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable, $authorsTable);

     $page = $controller->delete();

} elseif ($this->route === ‘register’) {

include __DIR__ . ‘/../classes/controllers/RegisterController.php’;

    $controller = new RegisterController($authorsTable) ;

               $page = $controller->showForm();

}

return $page;

}

public function run()

{

$page = $this->callAction();

$title = $page[‘title’];

if (isset($page[‘variables’])) {

     $output = $this->loadTemplate($page[‘template’],

       $page[‘variables’]);

} else {

    $output = $this->loadTemplate($page[‘template’]);

}

include __DIR__ . ‘/../templates/layout.html.php’;

    }

}

We just made some very substantial changes. Although none of the code is new, and it doesn’t produce any different output, the structure is completely different. Take a look through the completed EntryPoint.php to see how it works. Each task is now in its own method, rather than being nested inside a series of if statements. Our index.php and EntryPoint.php can now be used to load any controller class, and call any method on it, by specifying the appropriate route. A controller can be easily added by creating a class in the controllers directory and adding the logic for creating the controller and calling the relevant action in the callAction method. As every single page on the website is now going to be loaded using the EntryPoint class, it’s worth taking some time to make sure it’s correct. Before we add another controller, let’s consider how code can be reused on a much larger scale than we’ve looked at until now.

Reusing Code on Different Websites

Now that we’ve tidied up index.php, it’s worth considering what we’ve achieved by doing so. We’ve broken up the code into more easily manageable chunks, and the code is easier to read. If there’s a problem with the redirect check, you know to look in the checkURL method. If a template isn’t loading correctly, you know to look in the loadTemplate method. And if a URL isn’t displaying the page it should, you know to look in the callAction method. Even though we haven’t finished our Internet Joke Database website yet, it’s worth thinking about your next website. You could make a website for people to post jokes. You likely have a real project in mind that you’re planning to build using the knowledge you learn.

How much of the code we’ve written so far can be used without modification on your next website? We specifically built the DatabaseTable class so that it can work with any database table. Not only can it work with tables that exist for the joke website, joke and author, it could work with tables called customer, product and order for a shopping website, or account and message on a social media website, or indeed any database table on any website.

Generic or Project Specific?

Besides the DatabaseTable class, how much of the code we’ve written so far would be useful on another website? The templates probably wouldn’t. Another website would likely have completely different HTML, its forms would have completely different fields, and the website would deal with a different topic. The controllers would be different. The code in JokeController is very specific to the joke website we’re building. It’s unlikely the code in the controller will be useful without changing it.

However, the code we just wrote in the EntryPoint class that loads controllers and template files would be useful on another website. The templates and controllers loaded would be different, but the code to load those files would be the same. There are two types of code files in any given website:

  1. project-specific files containing code that’s only relevant to that particular website
  2. generic, reusable files containing code that can be used to build any website

The more code we can make generic, the bigger the foundation we have to work from when we start a new website. If we can use a lot of code from our previous website on our next website, we’ll save ourselves a lot of time. Rather than having to write code that’s similar to the EntryPoint and DatabaseTable classes for our next website, we could save a lot of time by using the code we already have. This foundation is called a framework—a set of generic code (usually classes) that can be quickly built on to build any website. It doesn’t contain any code that’s specific to one particular project.

It’s important to make a distinction between framework code and project-specific code. If we can successfully separate them, we can reuse our framework code in every website we build, saving significant upfront development time. If we have framework code mixed with project-specific code, we’ll find ourselves writing very similar code for every website we build.

When we first start out, it can be difficult to recognize which parts of the code belong specifically to that project, and which can be used across different projects. As a rule of thumb, processes are generic but data is specific. For example, adding a joke to the database is specific to the joke site, but adding to the database is a generic process that’s needed on most websites. We showed how to separate out the generic add to database process from the project-specific process of adding a joke. Anything related to jokes is in JokeController.php, but all the code related to adding to the database is stored in DatabaseTable.php.

By identifying the process and separating it from the project-specific data being worked with, we can repeat the same process of adding to the database with any data on any website by reusing the DatabaseTable class. The first step to making something generic is usually placing it inside a class. This helps us break up the problem into smaller parts. Once we’ve broken the problem up into individual methods, we can then see which are generic and which are project specificA dead giveaway that something is project specific is a hardcoded value or variable name that alludes to something for that project only.

Let’s apply that to the new EntryPoint class. The methods loadTemplate, checkUrl and run don’t contain any references to jokes, authors or anything that’s specific the joke website. Indeed, we’ll need a way of loading controllers and templates on future websites; they just won’t be dealing with jokes and authors. However, the callAction method contains several references to jokes and authors. If we wanted to reuse this class on a different website—for example, an online shop—we’d need to rewrite the entire method. It won’t have controllers or database tables dealing with jokes authors; it will have controllers and database tables for products, customers, and orders.

Let’s imagine we do have two websites—the joke website and an online shop. We’ve copy/pasted the file EntryPoint.php and changed callAction to suit each website. If there’s a bug in the checkUrl or run methods, we’d need to fix the bug in two places, while being careful not to change any code specific to that website. Instead, if the file contained only the generic framework code, we could overwrite the old EntryPoint.php with the new one everywhere it’s being used, and fix the bug without worrying about undoing changes that were made specifically for one project.

Making EntryPoint Generic

The answer, then, is to remove all references to project-specific concepts from otherwise generic classes. This process is more of an art than a science, and even experienced developers can struggle to work out where to draw the line between generic and project-specific code. However, I’m going to show you a fairly simple step-by-step process that can be used to remove a project-specific method from a class, turning it into a framework class.

1. Identify the method you want to remove.

In this case, it’s the callAction method:

private function callAction() {

include __DIR__ . ‘/../classes/DatabaseTable.php’;

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

if ($this->route === ‘joke/list’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable,$authorsTable);

              $page = $controller->list();

}

else if ($this->route === ”) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

    $controller = new JokeController($jokesTable, $authorsTable);

           $page = $controller->home();

}

else if ($this->route === ‘joke/edit’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable, $authorsTable);

          $page = $controller->edit();

}

else if ($this->route === ‘joke/delete’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

       $controller = new JokeController($jokesTable,$authorsTable);

                       $page = $controller->delete();

}

else if ($this->route === ‘register’) {

include __DIR__ .  ‘/../classes/controllers/RegisterController.php’;

       $controller = new RegisterController($authorsTable);

              $page = $controller->showForm();

      }

return $page;

}

2. Move the method to its own class and make it public.

Create a class called IjdbRoutes in classes/IjdbRoutes.php:

<?php

class IjdbRoutes

{

public function callAction()

{

include __DIR__ . ‘/../classes/DatabaseTable.php’;

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

if ($this->route === ‘joke/list’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

         $controller = new JokeController($jokesTable,$authorsTable);

                            $page = $controller->list();

} elseif ($this->route === ”) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable, $authorsTable);

             $page = $controller->home();

} elseif ($this->route === ‘joke/edit’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

       $controller = new JokeController($jokesTable, $authorsTable);

            $page = $controller->edit();

} elseif ($this->route === ‘joke/delete’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

       $controller = new JokeController($jokesTable, $authorsTable);

              $page = $controller->delete();

} elseif ($this->route === ‘register’) {

include __DIR__ . ‘/../classes/controllers/RegisterController.php’;

     $controller = new RegisterController($authorsTable);

                           $page = $controller->showForm();     

}

return $page;

      }

}

3. Replace any referenced class variables with arguments.

Replace $this->route with $route and add the $route as arguments for the method.

<?php

class IjdbRoutes

{

public function callAction($route)

{

include __DIR__ . ‘/../classes/DatabaseTable.php’;

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

if ($route === ‘joke/list’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable, $authorsTable);

           $page = $controller->list();

} elseif ($route === ”) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable, $authorsTable);

           $page = $controller->home();

} elseif ($route === ‘joke/edit’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

     $controller = new JokeController($jokesTable, $authorsTable);

            $page = $controller->edit();

} elseif ($route === ‘joke/delete’) {

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

            $controller = new JokeController($jokesTable, $authorsTable);

                 $page = $controller->delete();

} elseif ($route === ‘register’) {

include __DIR__ . ‘/../classes/controllers/RegisterController.php’;

     $controller = new RegisterController($authorsTable);

        $page = $controller->showForm();

}

return $page;

     }

}

4. Remove the method from the original class.

Remove callAction from the EntryPoint class.

5. Add a new constructor argument/class variable to the original class.

Add $controllerArguments as a constructor argument/class variable to EntryPoint:

class EntryPoint {

private $route;

private $routes;

public function __construct($route, $routes) { $this->route = $route;

$this->routes = $routes;

$this->checkUrl();

}

// …

We’ll use the $routes variable to contain an instance of IjdbRoutes.

6. Pass in an instance of the new class when the original class is created.

In index.php, replace $entryPoint = new EntryPoint($route); with

$entryPoint = new EntryPoint($route, new IjdbRoutes());, and remember to include the IjdbRoutes.php file in index.phpThe new index.php looks like this:

<?php

try {

include __DIR__ . ‘/../classes/EntryPoint.php’;

include __DIR__ . ‘/../classes/IjdbRoutes.php’;

$route = ltrim(strtok($_SERVER[‘REQUEST_URI’], ‘?’), ‘/’);

$entryPoint = new EntryPoint($route, new IjdbActions());

$entryPoint->run();

} catch (PDOException $e) {

$title = ‘An error has occurred’;

$output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

. $e->getFile() . ‘:’ . $e->getLine();

include __DIR__ . ‘/../templates/layout.html.php’;

}

7. Change the method call to reference the new object and pass in any required variables.

In EntryPoint, change $page = $this->callAction(); to $page = $this->routes->callAction($this->route);You can find this code in CMS-EntryPoint-Framework With that complete, we now have a generic EntryPoint.php. There are no longer any references to jokes, authors or anything specific to one particular website. In future, to create a website for an online shop, we can write the relevant ShopActions class with a callAction method to handle the arguments for that specific website:

class ShopActions {

public function callAction($route) {

  //load the controller and call the relevant action

// …

return $controller->$action();

    }

}

By splitting out the framework code from the project-specific code, it’s possible to use the same EntryPoint class in any project. We can pass it the actions it’s going to use when it’s constructed. The index.php for the shop website would contain this line:

$entryPoint = new EntryPoint($controller, $action, new ShopActions());

Autoloading and Namespaces

One line of code we’re repeating often is the include line to include a relevant class each time a class is required. Any time we use one of the classes we’ve created, it must be referenced with an include statement. This can get tricky to manage, as you need to ensure the class file has been included before you use the class. On top of that, if you accidentally issue the include statement twice for the same class, you’ll see an error. Our IjdbRoutes class has to include the DatabaseTable and controller classes, and index.php has to include the EntryPoint and Ijdb classes. Some pages may require some classes to be loaded, whereas others may require other classes to be loaded. A very inefficient but easy-to-organize method of managing loading classes would be to include every single class at the top of the index.php file, so that any class that might be needed has already been loaded. Using this method, we’ll never have to write an include statement for a class outside index.php.

A major drawback of this approach is that each time you add a new class to the project, you’ll have to open up index.php and add the relevant include statement. This is time consuming, and will use an unnecessary amount of memory on the server, as all classes would be loaded whether they’re needed or not. We’ve advised placing classes in their own files throughout , as well as naming files to match identically the name of the classes they contain. The class DatabaseTable is inside the file DatabaseTable.php, JokeController is stored in JokeController.php, and EntryPoint is stored in a file called EntryPoint.phpOne of the reasons we’ve advised structuring files this way is that it’s considered good practice. It helps someone reading the code to find the classes that are referenced. If they want to look at the code for JokeController, they know to look in JokeController.php.

One other advantage of a standardized file structure is that PHP contains a feature called autoloading. Autoloading is used to automatically load PHP files that store classes. As long as your file names are consistent with the class names, it’s easy to write an autoloader to load the relevant PHP file. Once we’ve written an autoloader, we’ll never need to write an include line for a class anywhere in the project. When we use the statement new ClassName(), if the class ClassName doesn’t exist (because it hasn’t been included), PHP can trigger an autoloader that can then load the file ClassName.php, and the rest of the script will continue as normal without us ever needing to manually write the line include ‘ClassName.php’;An autoloader is a function that takes a class name as an argument and then includes the file that contains the corresponding class. The function can be as simple as this:

function autoloader($className) {

$file = __DIR__ . ‘/../classes/’ . $className . ‘.php’; include $file;

}

It would be possible to use this function manually to save a little time:

autoloader(‘DatabaseTable’);

autoloader(‘EntryPoint’);

This would include both DatabaseTable.php and EntryPoint.php. However, it’s possible to instruct PHP to call this function automatically whenever it can’t find a class that’s referenced:

spl_autoload_register(‘autoloader’);

The function spl_autoload_register is built into PHP and allows us to tell PHP to call the function with the name we’ve given if it comes across a class that hasn’t yet been included. The autoloader function will be called automatically when a class is used for the first time:

function autoloader($className) {

$file = __DIR__ . ‘/../classes/’ . $className .

‘.php’; include $file;

}

spl_autoload_register(‘autoloader’);

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$controller = new EntryPoint($jokesTable);

Now files will automatically be included the first time the class stored in them is used. new DatabaseTable will trigger the autoloader with DatabaseTable, as the $className argument and DatabaseTable.php will be included.

Case Sensitivity

PHP classes are not case sensitive, but file names usually are. This can cause a problem with autoloaders. The first time a class is used it will be included, and new DatabaseTable will load DatabaseTable.php. However, new databasetable will cause an error, because the filename is case sensitive and databasetable.php doesn’t exist. So a problem is caused in a situation like this:

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

$authorstable = new databasetable($pdo, ‘author’, ‘id’);

The code above will work as intended, because the first time DatabaseTable is loaded with the correct case, the file is successfully included and PHP’s case insensitivity allows both objects to be constructed. However, if we reverse the order of arguments—because the autoloader is triggered with a lowercase name—we’ll get an error:

$authorstable = new databasetable($pdo, ‘author’, ‘id’);

$jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

An alternative is to make all file names lowercase and have the autoloader convert the class name to lowercase before loading the file. Although this is a more robust approach and arguably a better technical implementation, it goes against PHP community conventions, and will cause problems if we want to share our code with other people in the future.

Implement an Autoloader

Let’s implement an autoloader. To keep things organized, let’s create autoload.php and save it in the includes directory:

<?php

function autoloader($className)

{

$file = __DIR__ . ‘/../classes/’ . $className . ‘.php’; include $file;

}

spl_autoload_register(‘autoloader’);

Now we can amend index.php to include the autoloader, but remove the include lines that explicitly include EntryPoint.php and IjdbRoutes.php:

<?php

try {

       include __DIR__ . ‘/../includes/autoload.php’;

       $route = ltrim(strtok($_SERVER[‘REQUEST_URI’], ‘?’), ‘/’);

       $entryPoint = new EntryPoint($route, new IjdbRoutes());

       $entryPoint->run();

} catch (PDOException $e) {

    $title = ‘An error has occurred’;

    $output = ‘Database error: ‘ . $e->getMessage() . ‘ in ‘

    . $e->getFile() . ‘:’ . $e->getLine();

include __DIR__ . ‘/../templates/layout.html.php’;

}

We can also remove the include line for DatabaseTable from IjdbRoutes.php:

<?php

class IjdbRoutes {

public function callAction($route) {

      include __DIR__ . ‘/../includes/DatabaseConnection.php’;

                  $jokesTable = new DatabaseTable($pdo, ‘joke’, ‘id’);

                 $authorsTable = new DatabaseTable($pdo, ‘author’, ‘id’);

if ($route === ‘joke/list’) {

You can find this code in CMS-EntryPoint-AutoloadNotice that DatabaseConnection.php is still included manually, because it doesn’t include a class. It sets up the $pdo variable, which is used by both DatabaseTable objects. Autoloaders can only be used to load classes, and that’s one of the reasons it’s a good idea to structure as much of our code as possible inside classes.

Redecorating

If we peruse the rest of the code for include statements, we’ll see that the autoloader works for all classes that are framework classes, but doesn’t work for JokeController or RegisterController. The controller is still loaded in IjdbRoutes.php on several lines:

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

We might try removing this line and having the autoloader load it automatically. If we do, we’ll see an error. That error occurs because, when PHP encounters new JokeController and triggers the autoloader, it attempts to load JokeController.php from the classes directory, rather than from the classes/ controllers directory where the file is actually stored. Earlier we mentioned the difference between framework code—the code you might want to use on every website you build—and project-specific code that exists only for one particular website. It’s a good idea to keep these separated in different directories, so that you can easily copy/paste framework files between websites without copying files that are specific to a single project.

Let’s name our framework  Ninja. Move all the framework code into a directory called Ninja inside the classes directory. We should move EntryPoint.php and DatabaseTable.php, as these are our two generic framework files. Similarly, let’s create a new directory inside the classes directory called Ijdb. This is where we’ll keep all the code that’s specific to the joke site and can’t be reused on future websites. We’ll move IjdbRoutes.php into the Ijdb directory and move the controllers directory inside as well. While we’re moving things around, for consistency, let’s give the Controllers directory an uppercase C and ensure the Ninja and Ijdb directories all start with an uppercase letter. When we’re finished, EntryPoint.php and DatabaseTable.php should be located inside classes/Ninja/, while JokeController should be stored inside classes/Ijdb/Controllers and IjdbRoutes.php inside classes/IjdbDon’t try loading the website! As we’ve moved all the files around, everything is broken! Because all of our classes are now one level deeper inside the file structure, we’ll need to amend any include lines in the files we just moved around.Firstly, let’s open up EntryPoint.php and change this:

include __DIR__ . ‘/../templates/’ . $templateFileName;

… to this:

include __DIR__ . ‘/../../templates/’ . $templateFileName;

We’ve added the extra ../ before templates/ so it looks up an additional directory. Now we should change this:

include __DIR__ . ‘/../templates/layout.html.php’;

… to this:

include __DIR__ . ‘/../../templates/layout.html.php’;

Now, inside IjdbRoutes.php, we’ll need to change this:

include __DIR__ . ‘/../includes/DatabaseConnection.php’;

… to this:

include __DIR__ . ‘/../../includes/DatabaseConnection.php’;

If we do try loading a page at this point, we’ll see some errors. That’s because the autoloader is now looking in the wrong place. To solve this, we could add some logic to the autoloader that looks at the name of the class and loads the file from the correct location, or store an array of class names mapped to file names. Instead, we’re going to use a new tool: namespaces.

Namespaces

Each of our classes has a unique name. However, are they truly unique? If we download some code someone else has written, it may well contain a class named DatabaseTable or EntryPoint. In a world with thousands of PHP developers, these names aren’t unique. Modern PHP is great: we can find code online that does almost anything we can dream of—creating graphs and charts, turning web pages into PDFs, manipulating images and videos, connecting to Twitter streams, controlling services on a Raspberry Pi. The list is almost endless. What if we found a great-looking library we wanted to use but it included a class named DatabaseTable? This name clash would present a problem. When we run the line new DatabaseTable, PHP has to know which class should be used. One feature that has revolutionized PHP and made it much easier to share code online is namespaces.

Before namespaces came along, PHP developers would name their classes with a prefix. For example, we might name our classes Ninja_EntryPointNinja_DatabaseTable and Ijdb_JokeControllerThat way, when we wanted to use SuperLibary_DatabaseTable, it wouldn’t clash with Ninja_DatabaseTable, and we could use both DatabaseTable classes on the same website. Namespaces provide a simpler method of solving the same problem. Every class we write can (and should!) be placed within a namespace. You can think of namespaces a bit like folders on your computer. Inside any given folder on your computer, each file has to have a unique name. For example, our public directory can only contain one file named index.php, but a different directory could also contain a different file also named index.phpLet’s move our framework files into the Ninja namespace. At the top of EntryPoint.php and DatabaseTable.php, add the following code:

namespace Ninja;

The first few lines of DatabaseTable.php now look like this:

<?php

namespace Ninja;

class DatabaseTable {

   private $pdo;

   private $table;

// …

Now add the namespace Ijdb to IjdbRoutes.php:

<?php

namespace Ijdb;

class IjdbRoutes {

    public function callAction($route) {

// …

Before giving the final class (JokeController) a namespace, I’ll show you how to use the classes now that they have a namespace. index.php has the code new EntryPoint and new IjdbRoutes. Now that the classes are inside namespaces, this won’t work. We’ll need to specify the namespace when instantiating the class by using a backslash (\), followed by the namespace, another backslash, and then the class. This line:

$entryPoint = new EntryPoint($route, new IjdbRoutes());

… will become this:

$entryPoint = new \Ninja\EntryPoint($routes, new \Ijdb\IjdbRoutes());

Similarly, in IjdbRoutes.php, we need to change new DatabaseTable to new \Ninja\DatabaseTable:

$jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’);

At this point, we might be inclined to add the namespace Ijdb to JokeController. We’ll be giving JokeController a namespace containing Ijdb, but we’ll give it the namespace Ijdb\ControllersThe backslash (\) in the namespace represents a sub-namespace—a namespace within a namespace. This isn’t strictly necessary, but it’s a good idea to keep related code together. In this case, we’ll place all controllers inside the Ijdb\ Controllers namespace and the Ijdb/Controllers directory.

While we’re changing the JokeController.php file to include the namespace, we’ll rename the class (and file) to Joke. That way, the class is \Ijdb\ Controllers\Joke rather than \Ijdb\Controllers\JokeController, and the word “Controller” isn’t repeated unnecessarily in the full class name. This parallel between directory structures and namespaces is important. It allows us to write an autoloader than can use both namespaces and class names to locate the file it needs to load. The combined namespace and class name now represent the folder structure, making it easy to autoload the classes.

This convention is known as PSR-4 (PSR stands for PHP Standards Recommendations), and it’s used by almost all modern PHP projects. Each class should be contained inside a file that directly maps to its namespace and class name. The full class name including namespace should exactly match the directory and file name, including case sensitivity.

Autoloading with PSR-4

By using PSR-4, it’s simple to convert a class name in a namespace to a file path. Let’s replace autoload.php with this PSR-4 version:

<?php

function autoloader($className)

{

     $fileName = str_replace(‘\\’, ‘/’, $className) . ‘.php’;

     $file = __DIR__ . ‘/../classes/’ . $fileName;

      include $file;

}

spl_autoload_register(‘autoloader’);

When the autoloader is triggered with a class inside the namespace, it’s passed the entire class name including the namespace. For example, when EntryPoint is loaded, the autoloader is given the class name Ninja\EntryPoint.

The line str_replace(‘\\’, ‘/’, $className) . ‘.php’; replaces

backslashes with forward slashes to represent the file in the file system. Ninja\ EntryPoint becomes Ninja/EntryPoint.php, referencing the file. With the autoloader in place, we can now remove the include lines from IjdbRoutes.php, which load JokeController and RegisterController:

include __DIR__ . ‘/../classes/controllers/JokeController.php’;

Then we should change the reference to the controllers to use the full class name:

$controller = new \Ijdb\Controllers\Joke($jokesTable, $authorsTable);

Nearly there! We’re close to having the site up and running again using the new file and namespace structure. However, if we try to load one of the pages, we’ll see this error:

Uncaught TypeError: Argument 1 passed to
 Ninja\DatabaseTable::__construct() must be an instance of Ninja\PDO, instance of PDO given

To fix it, we can open up DatabaseTable.php and change the type hint in the constructor from PDO to \PDO. We saw this error because namespaces are relative. If you provide a reference to a class name, in a type hint or following the new keyword, PHP will look for a class with that name in the current namespace. We also need to replace DateTime with \DateTime and PDOException with \PDOException in this file. Because the DatabaseTable class is inside the Ninja prefix, without the backslash prefix PHP will to load the class \Ninja\PDO rather than the inbuilt PHP class PDOThe PDO class is in something called the global namespace—that is, a class that exists at the very top level, effectively not inside a namespace. To reference a class in the global namespace, we must prefix it with a backslash. Save DatabaseTable.php and refresh the page. There’s just one more error to fix:

Fatal error: Uncaught TypeError: Argument 1 passed to

 Ijdb\Controllers\Joke::__construct() must be an instance of

Ijdb\Controllers\DatabaseTable, instance of Ninja\DatabaseTable given

This is the same problem as above. Because there’s no namespace specified, PHP looks for a class called DatabaseTable in the current namespace. As the controller is in the Ijdb namespace, PHP will try to load the class Ijdb\ DatabaseTableWe could fix this the same way as above—by providing the class name with the namespace (which PHP refers to as a Fully qualified class name): \Ninja\ DatabaseTable. But a neater solution is importing the class DatabaseTable into the current namespace. We can do this with the use keyword after the namespace declaration:

CMS-EntryPoint-Namespaces

<?php

namespace Ijdb\Controllers;

use \Ninja\DatabaseTable;

class Joke {

      private $authorsTable;

// …

If we’ve done everything correctly, we should be able to refresh the website and see everything working once again. We’ve made a lot of changes here, but only to the structure of the code. Most of the code is the same as before; we’ve just moved it around. To recap, we’ve done the following:

  • split our code up into classes, recognizing the code that’s specific to the joke website and the code that can be used on any future website
  • organized all our classes in either the Ijdb directory, for project-specific files, or the Ninja directory for our framework files
  • given all our classes namespaces
  • removed all include statements for classes by implementing a PSR-4 compatible autoloader

A Note on Composer

Most modern PHP applications use a tool called Composer to handle all autoloading. It’s also used to quickly and easily download and install third-party libraries. If you follow the PSR-4 convention, your classes are good to go when you want to start using it, and you can use composer’s autoloader as a drop-in replacement for the autoload.php we just wrote. When you do start using Composer, just add this code to your composer.json file:

{

     “autoload”: {

     “psr-4”: {

             “Ninja\\”: “classes/Ninja”,

             “Ijdb\\”: “classes/Ijdb”

      }

     }

}

And the REST

The current iteration of our router uses a very simplistic approach. Each route is a string from the URL that maps to a controller and calls a specific action. If we continue using this approach, we’ll quickly find ourselves repeating logic in the controllers. Our edit joke form contains the following logic: if the form is submitted, process the submission, otherwise display the formThis logic will be required for any form on the website. Similarly, we can envisage other features in the future requiring similar logic in the controller. For example: display the page if the user is logged in, otherwise display the login formWhen the edit joke form is submitted, it uses the POST method. Any other request to pages on the website will use the GET method. PHP can detect whether the page was requested using GET or POST. The variable $_SERVER[‘REQUEST_METHOD’] is created by PHP and will contain either the string GET or the string POST, depending on how the page was requested by the browser. We can use this to determine if the form was submitted, and call a different controller action if the form was submitted:

else if ($route === ‘joke/edit’ &&

$_SERVER[‘REQUEST_METHOD’] === ‘GET’) {

     $controller = new \Ijdb\Controllers\Joke($jokesTable, $authorsTable);

         $page = $controller->edit();

}

else if ($route === ‘joke/edit’ && $_SERVER[‘REQUEST_METHOD’] === ‘POST’) {

    $controller = new \Ijdb\Controllers\Joke($jokesTable, $authorsTable);

           $page = $controller->editSubmit();

}

This approach works, but it’s rather long-winded. Instead, we can use nested arrays to create a data structure that represents all of the routes in the application:

$routes = [

‘joke/edit’ => [

     ‘POST’ => [

          ‘controller’ => $jokeController,

           ‘action’ => ‘saveEdit’

   ],

   ‘GET’ => [

     ‘controller’ => $jokeController,

   ‘action’ => ‘edit’

    ]

],

‘home’ => [

    ‘GET’ => [

   ‘controller’ => $jokeController,

  ‘action’ => ‘home’

  ]

]

// …

This may look fairly strange, but this kind of multidimensional data structure is used frequently in programming, so it’s a good tool to learn. Looking at the code for the $routes array, the downside of this approach is obvious: it requires writing out exactly which controller and action to call for every single page on the website. There are ways around this by using wildcards, but I’ll leave that as an exercise for the reader. The $routes variable is a standard array. It’s possible to extract the nested arrays. If we wanted to get the controller and action for the POST method for the route joke/editwe could do it like this:

// First read the route

$route = $routes[‘joke/edit’];

// Now read the value stored in the `POST` key:

$postRoute = $route[‘POST’];

// Finally, read the controller and action:

$controller = $postRoute[‘controller’];

$action = $postRoute[‘action’];

We are effectively “digging down” into the array, choosing which branch of the data structure to follow—a bit like the file/folder structure on your computer. This can also be expressed in a much shorter way by chaining the square brackets for looking up each value in the arrays:

$controller = $routes[‘joke/edit’][‘POST’][‘controller’];

$action = $routes[‘joke/edit’][‘POST’][‘action’];

By using variables in place of strings, it’s possible to substitute the hardcoded values with values from the $_SERVER variables REQUEST_URI and REQUEST_METHOD:

$route = $_SERVER[‘REQUEST_URI’];

$method = $_SERVER[‘REQUEST_METHOD’];

$controller = $route[$route][$method][‘controller’];

$action = $route[$route][$method][‘action’];

$controller->$action();

This approach of having the same URL perform different actions depending on the request method is loosely known as Representational State Transfer (REST).

REST Methods

Although REST typically supports the methods PUT and DELETE along with GET and POST, because web browsers only support GET and POST, PHP developers tend to use POST in place of both PUT and DELETE requests. Some PHP developers have found superficial ways of mimicking PUT and DELETE, but most developers just stick to using POST for writing data and GET for reading.

Let’s go ahead and implement a router using the REST approach on our site. To begin with, change the callAction method in the IjdbRoutes class to include both the $routes array for the jokes website and the code for selecting the relevant route:

<?php

namespace Ijdb;

class IjdbRoutes

{

     public function callAction($route)

{

     include __DIR__ . ‘/../../includes/DatabaseConnection.php’;

$jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new \Ijdb\Controllers\Joke($jokesTable, $authorsTable);

$routes = [

   ‘joke/edit’ => [

         ‘POST’ => [

                   ‘controller’ => $jokeController,

                  ‘action’ => ‘saveEdit’

  ],

   ‘GET’ => [

        ‘controller’ => $jokeController,

       ‘action’ => ‘edit’

    ]

],

    ‘joke/delete’ => [

        ‘POST’ => [

          ‘controller’ => $jokeController,

         ‘action’ => ‘delete’

      ]

],

   ‘joke/list’ => [

         ‘GET’ => [

             ‘controller’ => $jokeController,

            ‘action’ => ‘list’

      ]

],

  ” => [

      ‘GET’ => [

           ‘controller’ => $jokeController,

          ‘action’ => ‘home’

              ]

      ]

];

$method = $_SERVER[‘REQUEST_METHOD’];

$controller = $routes[$route][$method][‘controller’];

$action = $routes[$route][$method][‘action’];

    return $controller->$action();

     }

}

Now split the edit method in Controllers/Joke.php into a method for displaying the form and another for the handling the submission:

public function saveEdit() {

  $joke = $_POST[‘joke’];

  $joke[‘jokedate’] = new \DateTime();

  $joke[‘authorId’] = 1;

  $this->jokesTable->save($joke);

header(‘location: /joke/list’);

}

public function edit() {

    if (isset($_GET[‘id’])) {

       $joke = $this->jokesTable->findById($_GET[‘id’]);

}

$title = ‘Edit joke’;

     return [‘template’ => ‘editjoke.html.php’,

           ‘title’ => $title,

           ‘variables’ => [

             ‘joke’ => $joke ?? null

           ]

      ];

}

Once again, it’s worth taking a look at the code for the new IjdbRoutes class. On the next website, we’ll need to repeat the logic for selecting the correct controller and calling the action. This logic will be identical on any site we build:

$method = $_SERVER[‘REQUEST_METHOD’];

$controller = $routes[$route][$method][‘controller’];

$action = $routes[$route][$method][‘action’];

return $controller->$action();

Instead, we’ll just use the IjdbRoute class to provide the $routes array. We’ll rename the callAction method getRoutes, remove the argument, and have it return the array rather than accessing it:

<?php

    namespace Ijdb;

class IjdbRoutes

{

         public function getRoutes()

      {

               include __DIR__ . ‘/../../includes/DatabaseConnection.php’;

$jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’);

$authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’);

$jokeController = new \Ijdb\Controllers\Joke($jokesTable, $authorsTable);

    $routes = [

      ‘joke/edit’ => [

                   ‘POST’ => [

                        ‘controller’ => $jokeController,

                        ‘action’ => ‘saveEdit’

   ],

     ‘GET’ => [

           ‘controller’ => $jokeController,

           ‘action’ => ‘edit’

        ]

],

  ‘joke/delete’ => [

      ‘POST’ => [

          ‘controller’ => $jokeController,

         ‘action’ => ‘delete’

        ]

],

  ‘joke/list’ => [

       ‘GET’ => [

            ‘controller’ => $jokeController,

          ‘action’ => ‘list’

        ]

],

   ” => [

       ‘GET’ => [

           ‘controller’ => $jokeController,

          ‘action’ => ‘home’

          ]

      ]

];

return $routes;

      }

}

Now we’ll amend EntryPoint to use both $method and $route. We could hard code the $route and $method variables in the run method by reading from the server here:

public function run() {

$method = $_SERVER[‘REQUEST_METHOD’];

$route = $_SERVER[‘REQUEST_URI’];

// …

The problem with this approach is that it’s not very flexible. If we ever want to use the EntryPoint class in an application that isn’t web based, it won’t work, because these server variables won’t be set. Instead, let’s create a class variable and amend the constructor to take the method along with the route:

class EntryPoint {

    private $route;

    private $method;

    private $routes;

public function __construct($route, $method, $routes) {

   $this->route = $route;

   $this->routes = $routes;

   $this->method = $method;

   $this->checkUrl();

}

Next, we amend the run method to make use of both class variables:

public function run() {

    $routes = $this->routes->getRoutes();

   $controller = $routes[$this->route]

        [$this->method][‘controller’];

  $action = $routes[$this->route]

      [$this->method][‘action’];

  $page = $controller->$action();

  $title = $page[‘title’];

if (isset($page[‘variables’])) {

         $output = $this->loadTemplate($page[‘template’], $page[‘variables’]);

}

else {

      $output = $this->loadTemplate($page[‘template’]);

}

include __DIR__ . ‘/../../templates/layout.html.php’;

}

Then we supply the method in index.php:

$entryPoint = new \Ninja\EntryPoint($route,

$_SERVER[‘REQUEST_METHOD’], new \Ijdb\IjdbRoutes());

You can find this code in CMS-EntryPoint-Namespaces-RouterAvoiding hardcoding like this is a very good habit to get into. The trend in PHP (and software development in general) is towards test-driven development (TDD), and hardcoded values like $_SERVER[‘REQUEST_METHOD’] make testing difficult. 

Enforcing Dependency Structure with Interfaces

When we created the DatabaseTable class, we wrote the constructor so that it would check the types of its arguments:

public function __construct(PDO $pdo, string $table, string $primaryKey) {

Using this approach, it’s impossible to construct an instance of the DatabaseTable class without supplying an instance of PDO as the first argument.The EntryPoint class has a dependency on IjdbRoutes, and it calls the getRoutes method on it:

$routes = $this->routes->getRoutes();

However, what happens if the $this->routes variable is not an instance of IjdbJokes, or it’s an object that doesn’t have a getRoutes method?As we did with DatabaseTable, we can enforce the types using hints in the constructor:

public function __construct(string $route, string $method, \Ijdb\IjdbRoutes $routes) {

With the type hints in place, it’s impossible to construct the EntryPoint class without supplying an instance of IjdbRoutes as the third argument. But this breaks our flexibility! What happens when we build the online shop and we want to use a class called \Shop\Routes? Ideally, we want the flexibility of allowing each website to supply a different set of routes, but we also want the robustness that type checking gives us. This can be achieved using something called an interface. An interface can be used to describe what methods a class should contain, but doesn’t contain any actual logic. Classes can then implement the interface. An interface for the routes would look like this:

<?php

namespace Ninja;

interface Routes

{

   public function getRoutes();

}

You’ll notice that it looks a little like a class. It has a namespace, a name and a method. However, the difference between an interface and a class is that it only contains the method header (the first line), and doesn’t contain any logic. Let’s save the interface in the Ninja directory as Routes.php. Like classes, interface files can be loaded by the autoloader. We can now type hint the interface in EntryPoint:

public function __construct(string $route, string $method, \Ninja\Routes $routes) {

Currently, this will prevent us from passing an instance of Ijdb\IjdbRoutes into EntryPoint’s constructor. However, we can make IjdbRoutes implement the interface:

<?php

namespace Ijdb;

class IjdbRoutes implements \Ninja\Routes {

You can find this codd in CMS-EntryPoint-InterfaceThis has two effects:

  1. The class IjdbRoutes must contain the methods described in the interface. If not, an error is displayed.
  2. The IjdbRoutes class can now be type hinted using the interface.

Now, when we build the online shop or any other website, we can make a new version of the routes class by implementing the interface: namespace Shop;

class Routes implements \Ninja\Routes {

   public function getRoutes() {

        // Return routes for the online shop

        }

}

Interfaces are very useful for the kind of generic framework we’ve built. By providing a set of interfaces, each website can provide classes that implement the interfaces, guaranteeing that the framework code and project-specific code fit together. You can connect your TV to a Blu-ray player, a satellite TV receiver, a games console, or even a computer, because they all use HDMI. The makers of the TV don’t know what’s going to be connected to it, but anything that follows the HDMI standard will work with the TV. Similarly, anything that uses the Ninja\Routes interface will work with the Ninja framework. Interfaces, when used correctly, are a very powerful tool for bridging framework and project-specific code.

Your Own Framework

Writing a framework is a rite of passage for a PHP developer. Everyone does it, and we’ve just written one! So you learned: 

  • the difference between framework code and project-specific code 
  • how to differentiate them by use of directory structures and namespaces
  • how to write an autoloade
  •  the basics of interfaces and REST 
  • routing and URL rewriting

Although we haven’t added any functionality in this chapter, the knowledge covered here will put you on a very firm footing when working with modern PHP applications and third-party code from fellow developers.

This Is A Custom Widget

This Sliding Bar can be switched on or off in theme options, and can take any widget you throw at it or even fill it with your custom HTML Code. Its perfect for grabbing the attention of your viewers. Choose between 1, 2, 3 or 4 columns, set the background color, widget divider color, activate transparency, a top border or fully disable it on desktop and mobile.

This Is A Custom Widget

This Sliding Bar can be switched on or off in theme options, and can take any widget you throw at it or even fill it with your custom HTML Code. Its perfect for grabbing the attention of your viewers. Choose between 1, 2, 3 or 4 columns, set the background color, widget divider color, activate transparency, a top border or fully disable it on desktop and mobile.