PHP CHAPTER 6 2019-01-02T12:25:42+00:00

PHP Chapter 6

Relationships

As you’ve worked through the construction of the Internet Joke Database website, you’ve had opportunities to explore most aspects of Structured Query Language (SQL). From the basic form of a CREATE TABLE query to the two syntaxes of INSERT queries, you probably know many of these commands by heart now.We showed you how to perform basic JOINs using SQL to fetch data from more than one table at a time. A lot of the time, you’ll come across situations where you want to do this—for example, finding information about an author as well as all the jokes they’ve posted, or finding a category and all the jokes that reside inside it. SQL JOIN is one of many solutions to this problem. Although there are performance advantages to using JOIN, unfortunately JOINs don’t work well with object-oriented programming. The relational approach used by databases is generally incompatible with the nested structure of object-oriented programming. In object-oriented programming, objects are stored in a hierarchical structure. An author contains—or, in the correct OOP terminology, encapsulates—a list of their jokes, and a category also encapsulates a list of the jokes within the category. A SELECT query that fetches an author along with all of their jokes can be written like this:

SELECT author.name, joke.id, joke.joketext

FROM author

INNER JOIN joke ON joke.authorId = author.id

WHERE authorId = 123

Using an object-oriented approach, there are various practical ways of achieving this, which we’ll look into shortly. But at a more general level, using OOP, rather than an SQL query, fetching a list of jokes would be expressed like this:

//Find the author with the id `123`

$author = $authors->findById(123);

// Get all the jokes by this author

$jokes = $author->getJokes();

// Print the text of the first joke by that author.

echo $jokes[0]->joketext;

Notice that there’s no SQL here. The data is coming from the database, but it all happens behind the scenes. We could fetch all the information by using the SQL query I provided above, but this doesn’t work well with the DatabaseTable class we’ve used so far. It would be very difficult to design the class in such a way that it would account for every possible set of relationships we may want. So far, we’ve dealt with the relationship between jokes and authors in a relational way. If we wanted to get the information about an author, along with a list of all their jokes, we’d do this:

// Find the author with the ID 123

$author = $this->authors->findById(123);

// Now find all the jokes posted by the author with that I

D $jokesByAuthor = $this->jokes->find(‘authorId’, $authorId);

This runs two separate SELECT queries, and the two DatabaseTable instances are entirely separate. We used a similar approach when inserting a joke into the database:

public function saveEdit() {

  $author = $this->authentication->getUser();

  $joke = $_POST[‘joke’];

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

  $joke[‘authorId’] = $author[‘id’];

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

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

}

This code uses the authentication class to fetch the record that stores the currently logged-in user. It then reads the author’s id in order to provide it when the joke is added: $joke[‘authorId’] = $author[‘id’];Whoever writes this code must know about the underlying structure of the database and that the authors and jokes are stored in a relational way. In object-oriented programming, it’s preferable to hide the underlying implementation, and the code above would be expressed like this:

public function saveEdit() {

   $author = $this->authentication->getUser();

   $joke = $_POST[‘joke’];

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

   $author->addJoke($joke);

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

}

Not a lot has changed, so look closely! Firstly, the line $joke[‘authorId’] = $author[‘id’]; has been removed. Secondly, instead of saving the joke to the $jokesTable object, it’s being passed to the author object: $author->addJoke($joke);What is the advantage of this approach? The person who writes this code doesn’t have to know anything about what happens behind the scenes, or how the relationship is modeled in the database—that there’s an authorId column in the joke table. Instead of modeling the relationships in a relational way, object-oriented programming takes a hierarchical approach, using data structures. Just as the routes in the IjdbRoutes class is a multi-dimensional array, OOP has multi-dimensional data structures stored within objects.

In the example above, the $author->addJoke($joke) method call might be writing the joke data to a database. Alternatively, it might be saving the data to a file. And that file could be in JSON format, XML format or an Excel spreadsheet. The developer who writes this saveEdit method doesn’t need to know anything about the underlying storage mechanism—how data is being stored—but only that the data is being stored somehow, and that it’s being stored inside the author instance.

In object-oriented programming terminology, this is known as implementation hiding, and it has several advantages. Different people can work on different sections of the code. The developer who writes the saveEdit doesn’t have to be familiar with how addJoke actually works. They only need to know that it saves data, and that the data can be retrieved later. When you use the method $pdo->query, you don’t need to know how $pdo actually communicates with the database. You only need to know what the method returns and what arguments it requires. We can imagine what the following lines of code do, knowing how each of them works:

$jokes = $author->getJokes();

echo $joke->getAuthor()->name;

$joke = $_POST[‘joke’];

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

$author->addJoke($joke);

In the middle example, as long as you know there’s a getAuthor method that you can call on the $joke instance, it doesn’t matter how it works. The author name could be hardcoded into the class, or it could go off and fetch it from the database. This is particularly useful, because the storage system can be changed at any time, and the code above doesn’t need to change. The methods getJokes, getAuthor and $joke can be completely rewritten to write to/read from a file, for example, but the code above will still work without any further changes. If the saveEdit contained the INSERT query with all the relevant database fields, we’d need to rewrite the whole method to change the way the data is stored, and we’d need to do this anywhere data was inserted.

This approach to splitting up the logic is loosely known as separation of concerns. The process of saving a joke is different from the process of writing data to the database. Each of these is a different concern. By splitting out the two concerns, you have a lot more flexibility. The addJoke method can be called from anywhere without needing to repeat the logic. The way the addJoke method works can be completely rewritten to work in a different way, but the code that calls it can remain unchanged. This added flexibility is incredibly useful for an increasingly popular development methodology called test-driven development (TDD). In TDD, you’d test the code by writing a version of the DatabaseTable class that worked as a placeholder for the test, rather than needing a working database containing relevant test data. A common problem mid-level developers have when first learning about TDD is that they understand the advantages of it, but their code isn’t written in such a way that makes it easy to test. By considering separation of concerns earlier on in your programming career, you’ll make it easier for yourself moving forward.

Object Relational Mappers

The DatabaseTable class we’ve built step by step so far is a type of library called an object relational mapper (or ORM). There are a lot of ORM implementations available, such as Doctrine, Propel and ReadBeanPHP. These all do essentially the same job as the DatabaseTable class we’ve been building: providing an object-oriented interface for a relational database. They bridge the gap between the relational database’s SQL queries and the PHP code we’re using for everything else on the website.Generally, ORMs deal with Objects. Using our DatabaseTable class to find an author and print their name, we can use this code:

$author = $authors->findById(123);

echo $author[‘name’];

Here, the $author variable is an array with keys for each of the columns in the database. Arrays can’t contain functions1, so implementing an addJoke method on the $author instance isn’t possible. If we want the ability to call methods on the $author instance, such as $author->addJoke($joke) like above, the $author variable needs to be an object rather than an array. The first thing we need to do is create the relevant class to represent authors. Firstly, some properties for each of the columns from the database:

namespace Ijdb\Entity;

class Author {

   public $id;

   public $name;

   public $email;

   public $password;

}

As the properties of an author are unique to the joke website, w’ve put the class in the Ijdb namespace. A class like this, which is designed to map directly to a record in the database, is commonly known as an Entity Class, which is why we’ve used the name Entity in the namespace. We’ll have a different entity class for each of the database tables we need to represent. Create the directory Entity inside the Ijdb folder and save the class inside it with the name Author.phpAlthough the variables don’t need to be declared here, and what we’re doing will work identically whether they are or not, it makes the code easier to read and understand if the variables are included.There’s some repetition here: every time you add a column to the database table, you’ll need to add it to this entity class. Because of this, many ORMs provide a method of generating these entity classes from the database schema, or even creating the database table from the object!

We are not going to show you how to do that, but if you do want to try something similar, you should take a look at the MySQL DESCRIBE query to retrieve a list of columns in a table, or the PHP Reflection library to get a list of properties in a class.

Public Properties

Every time we’ve created a class variable so far, it’s been private, so that the data used is only accessible to methods within the class. The advantage of this is that class variables can be easily added, renamed or removed without potentially breaking any of the code that uses the class. It also prevents developers accidentally breaking the functionality of the class. If the pdo variable were public in the DatabaseTable class, it would be possible to do this:

$this->jokesTable->pdo = 1234;

Any further call to methods on the DatabaseTable class would break:

$this->jokesTable->findById(‘1243’);

The findById method would call $this->pdo->query(‘…’), but because the pdo variable is no longer a PDO instance, it would break! In most cases, private properties are strongly preferred over public ones. However, in the case of entity classes, you should use public properties. The sole purpose of an entity class is to make some data available. It’s no good having a class representing an author if you can’t even read the author’s name! Nine times out of ten—in fact, ninety-nine times out of a hundred—public properties are the wrong solution to any given problem. However, if the responsibility of the class is to represent a data structure, and it’s interchangeable with an array, then public properties are fine.

Methods in Entity Classes

A class is used to store the data about authors instead of an array, because the class can contain methods, and we can do things like this:

// Find the author with the id 1234

$author = $this->authorsTable->findById(‘1234’);

// Find all the jokes by that author

$author->getJokes();

// Add a new joke and associate it with the author

// represented by $author

$author->addJoke($joke);

Let’s take a moment to think about what the getJokes method might look like. Assuming the id property in the $author class is set, it would be possible to do this:

public function getJokes() {

return $this->jokesTable->find(‘authorId’, $this->id);

}

To do this, the author class needs access to the jokesTable instance of the DatabaseTable class. Add the getJokes method, along with a constructor and class variable to store the reference to the jokesTable instance:

<?php

namespace Ijdb\Entity;

class Author

{

   public $id;

   public $name;

   public $email;

  public $password;

   private $jokesTable;

   public function __construct(\Ninja\DatabaseTable

➥ $jokesTable)

 {

        $this->jokesTable = $jokesTable;

}

public function getJokes()

{

      return $this->jokesTable->find(‘authorId’, $this->id);

       }

}

We’re going to amend the DatabaseTable class to return an instance of this class instead of an array. But before we do that, let’s take a look at how the Author class can be used on its own:

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

$author = new \Ijdb\Entity\Author($jokesTable);

$author->id = 123;

$jokes = $author->getJokes();

This would query the database for all the jokes by the author with the ID 123. We can now fetch data that’s related to the author, once we have an Author instance that represents any given author, by setting the id property. Next, we need the addJoke method that takes a joke as an argument, sets the authorId property and then inserts it into the database:

public function addJoke($joke) {

$joke[‘authorId’] = $this->id;

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

}

Let’s use the new class inside the Joke controller’s saveEdit method to save the jokes using $author->addJoke($joke).

public function saveEdit() {

    $author = $this->authentication->getUser();

    $authorObject = new \Ijdb\Entity\Author ($this->jokesTable);

    $authorObject->id = $author[‘id’];

    $authorObject->name = $author[‘name’];

    $authorObject->email = $author[’email’];

     $authorObject->password = $author[‘password’];

    $joke = $_POST[‘joke’];

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

    $authorObject->addJoke($joke);

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

}

You can find this code in Relationships-Author.

Using Entity Classes from the DatabaseTable Class

Because the database returns data as an array, we’ve copied the data from the $author array returned by getUser() to an instance of the newly created Author class. Both the array $author and the object $authorObject will represent the same author. The only difference is that one is an object and the other is an array. Most of the lines of code in the method simply copy data from the array to an object. This is obviously inefficient, and the problem can be avoided if we can construct the Author object outside of the saveEdit method and have getUser return the constructed object like so:

public function saveEdit() {

$authorObject = $this->authentication->getUser();

$joke = $_POST[‘joke’];

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

$authorObject->addJoke($joke);

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

}

Currently, an array is created, because the getUser method in the Authentication class calls the findById method in the DatabaseTable class:

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

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

  $primaryKey . ‘ = :primaryKey’;

$parameters = [

      ‘primaryKey’ => $value

  ];

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

return $query->fetch();

}

Here, $query->fetch() returns an array. Luckily for us, there’s also a fetchObject method, which returns an instance of a specified class—in our case, Author. This will instruct PDO to create an instance of the Author class and set the properties on that, rather than returning a simple array. For example, to have fetchObject() return an Author object, you can use this code:

return $query->fetchObject(‘Author’, [$jokesTable]);

There are two arguments here:

  1. The name of the class to instantiate.
  2. An array of arguments to provide to the constructor when the object is created. Because there’s only a single element in the array, [$jokesTable] looks a little strange. However, as constructors can have multiple arguments, an array is required so you can provide each constructor argument.

Now, rather than writing new Author somewhere in our code, we can have the PDO library create an instance of the Author class in place of returning the data as an array.

$pdo->query(‘SELECT * FROM `author` WHERE id = 123’);

$author = $query->fetchObject(‘Author’, [$jokesTable]);

Because the Author class requires the $jokesTable class as a constructor argument, this must also be provided as an argument when fetchObject is called. However, we can’t amend the DatabaseTable class to use the return line above—firstly, because it doesn’t have access to the $jokesTable variable, and secondly, because we’re using the DatabaseTable class to interact with different database tables. When the findById method is called, it may be on the authorsTable instance, the jokesTable instance, or an instance of the DatabaseTable class that represents some other database table.

We’ll want a different entity class for each table, and they almost certainly won’t have the same constructor arguments. Instead of hardcoding the class name and constructor argument, we can amend the constructor of the DatabaseTable class to take two optional arguments—the name of the class to create, and any arguments to provide to it:

class DatabaseTable {

   private $pdo;

   private $table;

   private $primaryKey;

   private $className;

   private $constructorArgs;

public function __construct(\PDO $pdo, string $table, string $primaryKey, string $className = ‘\stdClass’, array $constructorArgs = []) {

    $this->pdo = $pdo;

   $this->table = $table;

   $this->primaryKey = $primaryKey;

   $this->className = $className;

   $this->constructorArgs = $constructorArgs;

}

Notice that we’ve given default values to each of the new arguments. The stdClass class is an inbuilt PHP empty class that can be used for simple data storage. By specifying this as a default value, if no class name is specified, it will use this generic inbuilt one. The advantage this gives us is that we don’t need to create a unique entity class for every database table, but only those we want to add methods to! The findById method can now be changed to read the new class variables and use those in place of hardcoded ones in the fetchObject method call:

public function findById($value) {

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

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

$parameters = [

     ‘value’ => $value

  ];

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

return $query->fetchObject($this->className, $this->constructorArgs);

}

PDO’s fetchAll method—which we’re using in the DatabaseTable’s find and findAll methods—can also be instructed to return an object by providing \PDO::FETCH_CLASS as the first argument, the class name as the second, and the constructor arguments as the third:

return $result->fetchAll(\PDO::FETCH_CLASS,

$this->className, $this->constructorArgs);

Now that we’ve amended the DatabaseTable class, we can change IjdbRoutes, where the DatabaseTable class is instantiated, and provide the class name and the arguments for the $authorsTable instance:

$this->jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’);

$this->authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’, ‘\Ijdb\Entity\Author’, [$this->jokesTable]);

Now, when the $authorsTable instance is used to retrieve a record, like this:

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

….the $author variable will be an instance of the Author class, and any methods in the class (such as addJoke) will available for us to call. As we’ve made a change to the DatabaseTable class, this will affect every instance of the class. When jokes are retrieved from the database, an object will also be retrieved. As we haven’t specified an entity class for jokes (yet!), they will be an stdClass instance. If you load up the joke list page in your browser, at this point you’ll see this error:

Fatal error: uncaught Error: cannot use object of type 

 stdClass as array in

➥ /home/vagrant/Code/Project/classes/Ijdb/Controllers/Joke.php

➥ on line 21

We’ll fix this later, but first let’s get saveEdit working. Amend the saveEdit method to avoid all the value copying. Change the code from this:

public function saveEdit() {

$author = $this->authentication->getUser();

$authorObject = new \Ijdb\Entity\Author ($this->jokesTable);

$authorObject->id = $author[‘id’];

$authorObject->name = $author[‘name’];

$authorObject->email = $author[’email’];

$authorObject->password = $author[‘password’];

$joke = $_POST[‘joke’];

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

$authorObject->addJoke($joke);

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

}

… to this:

public function saveEdit() {

$author = $this->authentication->getUser();

$joke = $_POST[‘joke’];

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

$author->addJoke($joke);

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

}

Because getUser now returns an instance of Author with all the properties already set, we can use this instance rather than having to manually create the $authorObject instance and set each property individually. Before we can test that this works, we’ll also need to amend the Authentication class to use an object rather than an array. In the isLoggedIn method, replace this line:

if (!empty($user) &&

$user[0][$this->passwordColumn] ===

$_SESSION[‘password’]) {

… with this:

$passwordColumn = $this->passwordColumn;

if (!empty($user) && $user[0]->$passwordColumn

=== $_SESSION[‘password’]) {

This code looks complicated! However, let’s take a moment to understand what’s happening here. The $user[0] variable now stores an instance of the Author class. The Author class has a property called password. To read this property, you could use this:

$user[0]->password

However, if you remember back , you’ll recall that the Authentication class has a class variable, which stores the name of the database column that contains the password. It might not be password in every website you build. Assuming the password column is password, once the variables have been evaluated, all we’re doing here is replacing $user[‘password’] with the object variant $user->password. But this is complicated by the fact that we’re using a variable for the column name. We really need $user[$this->passwordColumn] to be replaced with $user->$this->passwordColumnIt’s possible to use a variable to access a property on an object using a string, as we do with an array:

$columnName = ‘password’;

// Read value stored under key ‘password’ from array

$password = $array[$columnName];

// Read value stored under property ‘password’ from object

$password = $object->$columnName;

You might be wondering why I didn’t use $user[0]->$this->passwordColumn. As PHP evaluates left to right, it would try to look up the contents of the variable $this (an object), then try to read a variable with that name. As a variable name can’t be an object, PHP will give an error. Instead, I read the value into its own variable, $passwordColumn, then used $user[0]->$passwordColumn.

Using Braces

As an alternative to creating a new variable, you can also use braces to tell PHP to evaluate the $this->passwordColumn lookup first:

$user[0]->{$this->passwordColumn};

Do the same with this line in the login method:

public function login($username, $password) {

     $user = $this->users->find($this->usernameColumn, strtolower($username));

if (!empty($user) && password_verify($password, $user[0][$this->passwordColumn])) {

     session_regenerate_id();

      $_SESSION[‘username’] = $username;

     $_SESSION[‘password’] =

     $user[0][$this->passwordColumn];

return true;

}

else {

        return false;

     }

}

The above code changes to this:

public function login($username, $password) {

      $user = $this->users->find($this->usernameColumn, strtolower($username));

 if (!empty($user) && password_verify($password, $user[0]->{$this->passwordColumn})) {

      session_regenerate_id();

      $_SESSION[‘username’] = $username;

      $_SESSION[‘password’] =

     $user[0]->{$this->passwordColumn};

return true;

}

else {

      return false;

     }

}

This code can be found in Relationships-DatabaseTableEntityAfter submitting the form, you’ll see an error on the list page, but you can check the new saveEdit method is working by logging in to the website and adding a joke. When you’re redirected to the list page, you’ll see an error, but you can check that the joke has been added by viewing the contents of the joke table in MySQL Workbench.

Joke Objects

Now we’ll fix the joke list page. At the moment it displays an error, thanks to this code in the controller:

$author = $this->authorsTable->

findById($joke[‘authorId’]);

$jokes[] = [

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

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

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

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

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

    ];

The error occurs because $author and $joke are no longer arrays. This is a simple fix: change the syntax that reads from an array to use the object syntax:

$author = $this->authorsTable->

findById($joke->authorId);

$jokes[] = [

       ‘id’ => $joke->id,

      ‘joketext’ => $joke->joketext,

      ‘jokedate’ => $joke->jokedate,

      ‘name’ => $author->name,

      ’email’ => $author->email

];

You’ll also need to change the method’s return statement to use the object syntax for the $author variable.

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

        ‘title’ => $title,

        ‘variables’ => [

        ‘totalJokes’ => $totalJokes,

        ‘jokes’ => $jokes,

      ‘userId’ => $author->id ?? null

     ]

];

Now also amend the delete method to read the joke’s authorId from the new object:

if ($joke->authorId != $author->id)

This code can be found in Relationships-ObjectsAlthough this solution works, now that we’re using an object-oriented approach, it can be solved in a much nicer way. Currently, each value from either the author or joke table is stored under an equivalent key in the $jokes array. The code for generating the $jokes array looks like this:

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

     ];

}

The $jokes array is used to provide the template access to each joke and its author. The process currently looks like this:

  • query the database and select all the jokes
  • loop over each joke and:

              select the related author

             create a new array containing all the information about the joke and the author

  •   pass this constructed array to the template for display

This is a very long-winded process for something we can make a lot simpler using OOP. At the moment, we can fetch all the jokes by a specific author using

$author->getJokes(). However, we can also model the inverse relationship and do something like this:

echo $joke->getAuthor()->name;

This would let us get the author for any given joke, and this code could even be run from the template. If the $this->jokesTable->findAll(); call returned an array of joke objects, each with their own getAuthor method, this process of creating an array with both sets of data would be unnecessary! Firstly, let’s create the Joke entity class in Ijdb/Entity/Joke.php:

<?php

namespace Ijdb\Entity;

class Joke {
public $id;
public $authorId;
public $jokedate;
public $joketext;
private $authorsTable;

public function __construct(\Ninja\DatabaseTable $authorsTable)

{

        $this->authorsTable = $authorsTable;

}

public function getAuthor()

{

    return $this->authorsTable->

   findById($this->authorId);

     }

}

The Joke class works in the same way as the Author class: it has a constructor that asks for an instance of a DatabaseTable class that contains related data. In this case, it will be passed the DatabaseTable instance, which represents the author database table. The getAuthor method returns the author for the current joke. If $this->authorId is 5, it will return an Author object that represents the author with the ID 5.

Using the Joke Class

To use the new Joke class, we’ll need to update the IjdbRoutes class to provide the authorsTable instance as a constructor argument. The relevant section of code currently looks like this:

$this->jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’, ‘\Ijdb\Entity\Joke’,

[$this->authorsTable]);

$this->authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’, ‘\Ijdb\Entity\Author’, [$this->jokesTable]);

This will pass the authorsTable instance to the jokesTable instance and the authorsTable instance. Think about that for a second. It poses a problem that’s not immediately obvious. If the authorsTable instance constructor requires an instance of jokesTable, and the jokesTable constructor requires an authorsTable instance, we have a catch-22: to create the jokesTable instance, you need an existing authorsTable instance. To create the authorsTable instance, you need an existing jokesTable instance. Both instances require the other instance to exist before they do! If you try the code above, the PDO library will throw an exception—“Cannot call constructor”. Although the message isn’t very clear, it’s because of the problem I just highlighted. This catch-22 occurs sometimes in object-oriented programming. Luckily, in this case it can be fairly easily solved using something called a reference.

References

A reference is special type of variable, a bit like a shortcut in Windows, or a symlink in macOS or Linux. A shortcut on your computer is a special type of file that doesn’t contain any data itself. Instead, it points to another file. When you open the shortcut, it actually opens the file the shortcut is pointing to. References work in a similar way. Instead of a variable containing a specific value, a it can contain a reference to another variable. When you read the value of a variable that stores the reference, it will read the value of the variable being referenced. To create a reference, you prefix the variable you want to create a reference to with an ampersand (&):

$originalVariable = 1;

$reference = &$originalVariable;

$originalVariable = 2;

echo $reference;

The code above will print 2. Without the &, it would print 1! That’s because the variable $reference contains a reference to the $originalVariable variable. Whenever the value of $reference is read, it will actually go off and read the value of the variable $originalVariable as it is at that moment in time. This is important, because it allows us to solve the catch-22 we encountered earlier. By providing references as the constructor arguments for the Joke and Author classes, by the time the authorsTable and jokesTable instances are needed, they’ll have been created:

$this->jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’, ‘\Ijdb\Entity\Joke’,

[&$this->authorsTable]);

$this->authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’, ‘\Ijdb\Entity\Author’, [&$this->jokesTable]);

Now, when the DatabaseTable class creates a Joke or Author object and has to provide it the authorsTable or jokesTable instance, it will read what’s stored in the authorsTable or jokesTable class variables at the time any Author or Joke entity is instantiated.

Simplifying the List Controller Action

The following is now possible inside a controller:

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

echo $joke->getAuthor()->name;

Now that a joke is an object, we can pass the whole object into the template and read the author from there. Remove this code from the list method:

$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

];

}

This code was necessary when using arrays, because for each joke, we needed to fetch the information about the joke, and the information about the author of that particular joke, then combine them into a single data structure. However, now that we’re using objects, this ugly copying code is redundant.Once we’ve removed it, we can also replace this code:

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

… with this:

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

The DatabaseTable class now provides an array of objects rather than arrays. Each Joke object has a method getAuthor, which returns the author of the joke. Rather than fetching the author in the controller, we can now do it in the template jokes.html.php. Let’s update the template to use the new objects rather than the arrays:

Relationships-JokeObject

<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->getAuthor()->email, ENT_QUOTES,

‘UTF-8’

); ?>”>

<?=htmlspecialchars(

$joke->getAuthor()->name,

ENT_QUOTES,

‘UTF-8’

); ?></a> on

<?php

$date = new DateTime($joke->jokedate);

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

?>)

<?php if ($userId == $joke->authorId):

?>

<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>

     <?php endif; ?>

</p>

</blockquote>

<?php endforeach; ?>

For the fields from the joke table, this is fairly simple. We just change the syntax from an array to an object. For example, $joke[‘joketext’] becomes $joke->joketextIt’s slightly more complicated where we want to read information about each joke’s author. Before reading the author’s email, we need to fetch the author instance. To read the author’s email, we previously used $joke[’email’], which now becomes $joke->getAuthor()->email.

This actually fetches the author from within the template! Previously, when writing the controller, we had to anticipate exactly which variables were needed by the template. Now, the controller just provides a list of jokes. The template can now read any of the values it needs, including information about the author. If we added a new column in the database—for example, a joke category—we could amend the template to show this value without needing to change the controller.

Tidying Up

Now that we’ve changed the way the DatabaseTable class works, we’ve broken the “Edit Joke” page. To fix it, open up the template editjoke.html.php and replace the array syntax with object syntax for accessing the joke’s properties:

Relationships-EditJoke

<?php if (empty($joke->id) || $userId == $joke->authorId):?>

<form action=”” method=”post”>

    <input type=”hidden” name=”joke[id]”

    value=”<?=$joke->id ?? ”?>”>

<label for=”joketext”>Type your joke here: </label>

<textarea id=”joketext” name=”joke[joketext]” rows=”3″

cols=”40″><?=$joke->joketext ?? ”?>

</textarea>

   <input type=”submit” name=”submit” value=”Save”>

</form>

<?php else:

    ?>

<p>You may only edit jokes that you posted.</p>

<?php endif; ?>

We’ve now got an almost entirely object-oriented website! All the entities have their own class, and we can add any methods we like to each entity class.

Caching

You might have noticed a potential performance problem with the Joke entity class. The getAuthor method looks like this:

public function getAuthor() {

return $this->authorsTable->findById($this->authorId);

}

Although this works fine, it’s unnecessarily slow. Each time this getAuthor method is called, it will send the same query to the database and retrieve the same result. The following code will send three queries to the database:

echo $joke->getAuthor()->name;

echo $joke->getAuthor()->email;

echo $joke->getAuthor()->password;

Querying the database is considerably slower than just reading a value from a variable. Each time a query is sent to the database, it will slow down the page’s speed slightly. Although each query adds only a tiny overhead, if this is done inside a loop on a page, it can cause a noticeable slowdown. To avoid this problem, you can fetch the author object once, then use the existing instance:

$author = $joke->getAuthor();

echo $author->name;

echo $author->name;

echo $author->password;

By doing this, we avoid sending three queries to the database, because getAuthor is only called once. This method works, but it’s rather crude. You have to remember to implement this technique, and on a larger website you’d have to mentally keep track of all the places you’d need to do this. Instead, it’s better to implement a technique called transparent caching. The term caching refers to storing some data for quicker access later on, and the technique I’m about to show you is called transparent caching because the person using the class doesn’t even need to know it’s happening! To implement caching, add a property to the Joke entity class to store the author between method calls:

class Joke {

// …

public $joketext;

private $authorsTable;

private $author;

// …

Then, in the getAuthor method, we can add logic that will do the following:

  • check to see if the author class variable has a value
  • if it’s empty, fetch the author from the database and store it in the class variable
  • return the value stored in the author variable

public function getAuthor() {

if (empty($this->author)) {

$this->author =  $this->authorsTable->findById($this->authorId);

}

return $this->author;

}

You can find this code in Relationships-CachedWith this simple if statement in place, the database will only be queried the first time the getAuthor method is called on any given joke instance. Now, the following code will only send a single query to the database:

echo $joke->getAuthor()->name;

echo $joke->getAuthor()->email;

echo $joke->getAuthor()->password;

By solving the potential performance issue inside the class, it no longer matters how it’s used externally. There will only ever be a single query for each instance of the class.

Joke Categories

Now that you know how to add relationships between different tables and model them using classes, let’s add a new relationship.At the moment, we have a single list of jokes. As we only have half a dozen jokes on the website, this works fine, but moving forward as more people register for the website and post jokes, the joke list page will keep getting longer!People viewing the website may want to view a specific type of joke. For example, programming jokes, knock-knock jokes, one-liners, puns and so on.

The most obvious way to achieve this is the way we modeled the relationship between jokes and authors: create a category table to list the different categories, then create a categoryId column in the joke table to allow each joke to be placed in a category. However, a joke may fall into more than one category. Before modeling the relationship, let’s add a new form that allows creating new categories and storing them in the database. It’s been a while since we’ve added a new page to the website, and we’ve made a few changes since we last did, so I’ll go through this in detail. Firstly, let’s create the table to store the categories:

CREATE TABLE `ijdb_sample`.`category` (

  `id` INT NOT NULL AUTO_INCREMENT,

`name` VARCHAR(255) NULL,

PRIMARY KEY (`id`));

Either use the SQL code above or use MySQL Workbench to create the category table with two columns: id and name. id is the primary key, so make sure it’s AUTO_INCREMENT, and type should be VARCHARCreate a new controller called Category in Ijdb/Controllers/Category.php. This controller will need access to a DatabaseTable instance that allows interacting with the new category table.

<?php

namespace Ijdb\Controllers;

class Category

{

  private $categoriesTable;

    public function __construct(\Ninja\DatabaseTable $categoriesTable)

{

     $this->categoriesTable = $categoriesTable;

     }

}

Like the controller for jokes, we’ll need several actions: list to display a list of categories, delete to delete a category, edit to display the add/edit form, and saveEdit to handle the form submission. Let’s add the form first. Create the template editcategory.html.php:

<form action=”” method=”post”>

  <input type=”hidden”

     name=”category[id]”

         value=”<?=$category->id ?? ”?>”>

<label for=”categoryname”>Enter category name: </label>

<input type=”text”

      id=”categoryname”

      name=”category[name]”

      value=”<?=$category->

     name ?? ”?>” />

<input type=”submit” name=”submit” value=”Save”>

</form>

You can find this code in Relationships-AddCategoryLike editjoke.html.php, this template will be used for both the edit and add pages, so we’ll pre-fill the text box if the $category variable is set.

Add the edit method to the new Category class:

public function edit() {

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

$category = $this->categoriesTable->findById($_GET[‘id’]);

}

$title = ‘Edit Category’;

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

‘title’ => $title,

‘variables’ => [

‘category’ => $category ?? null

     ]

     ];

}

Open up IjdbRoutes. The first thing to do here is to create the instance of the DatabaseTable class for the category table. Like authorsTable and jokesTable, this can be stored in a class variable and created in the constructor:

class IjdbRoutes implements \Ninja\Routes {

   private $authorsTable;

    private $jokesTable;

   private $categoriesTable;

   private $authentication;

public function __construct() {

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

$this->jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’, ‘\Ijdb\Entity\Joke’, [&$this->authorsTable]);

   $this->authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’,

   ‘\Ijdb\Entity\Author’,

   [&$this->jokesTable]);

$this->categoriesTable = new \Ninja\DatabaseTable($pdo, ‘category’, ‘id’);

//…

Notice that we haven’t specified an entity class for the category table. Unless we need to add some functionality to it, it can use the stdClass class that we set as the default. Next, create an instance of the Category controller and add the route for the edit page. We’ll display this on the URL /category/edit:

Relationships-AddCategory2

$authorController = new \Ijdb\Controllers\Register ($this->authorsTable);

$loginController = new \Ijdb\Controllers\Login ($this->authentication);

$categoryController = new \Ijdb\Controllers\Category ($this->categoriesTable);

$routes = [

// …

‘category/edit’ => [

‘POST’ => [

‘controller’ => $categoryController,

‘action’ => ‘saveEdit’

],

‘GET’ => [

‘controller’ => $categoryController,

‘action’ => ‘edit’

],

‘login’ => true

],

// …

If you’ve followed the steps above, visit http://192.168.10.10/category/edit and you should see the form. We’ve already set up the route, so let’s add the saveEdit method to add a category to the database when the form is submitted:

Relationships-AddCategory-Save

public function saveEdit() {

$category = $_POST[‘category’];

$this->categoriesTable->save($category);

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

}

If you test the form and press Submit, you’ll see an error message, because we haven’t built the list page yet. However, you can check the form is working by selecting all the records from the table in MySQL Workbench. Once a category has been added, you should even be able to edit it by visiting http://192.168.10.10/category/edit?id=1.

At this point, it’s worth momentarily stepping back and considering the benefit of the classes and framework we’ve built. With fairly little effort, we’ve created a form that allows inserting data into the database, loading a record into it and editing. Think back when we were manually writing INSERT and UPDATE queries, and think about how much more code you would have needed to write with that approach!

List Page

We can also add the list page with fairly little code. Firstly, let’s create a template, categories.html.php, that loops through a list of categories and displays an edit/ delete button for each of them:

<h2>Categories</h2>

<a href=”/category/edit”>Add a new category</a>

<?php foreach ($categories as $category): ?>

<blockquote>

<p>

<?=htmlspecialchars($category->name,

ENT_QUOTES, ‘UTF-8’)?>

<a

href=”/category/edit?id=<?=$category->id?>”>

Edit</a>

<form action=”/category/delete” method=”post”> <input type=”hidden”

name=”id”

value=”<?=$category->id?>”>

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

</form>

</p>

</blockquote>

<?php endforeach; ?>

Here’s the controller action in Category.php:

public function list() {

$categories = $this->categoriesTable->findAll(); $title = ‘Joke Categories’;

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

‘title’ => $title,

‘variables’ => [

‘categories’ => $categories

]

];

}

And finally, here’s the route in IjdbRoutes:

‘category/list’ => [

‘GET’ => [

‘controller’ => $categoryController,

‘action’ => ‘list’

],

‘login’ => true

],

You can find this code in Relationships-ListCategories.

The Edit link from the template already works. Add the route and controller action for the Delete button to finish the category management pages:

‘category/delete’ => [

‘POST’ => [

   ‘controller’ => $categoryController,

  ‘action’ => ‘delete’

 ],

   ‘login’ => true

],

public function delete() {

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

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

}

Example: Relationships-DeleteCategory

Assigning Jokes to Categories

Now that categories can be added to the website, let’s add the ability to assign a joke to a category. As we mentioned previously, the simplest approach would be to have a <select> box on the add joke page, which sets a categoryId column in the joke table. However, this approach is the least flexible. It’s possible that a joke will fall into more than one category. Instead, we’re going to model this relationship using a join table—that is, a table with just two columns. In our case, the columns will be jokeId and categoryIdLet’s create the following table using either MySQL Workbench or by running this query:

CREATE TABLE `ijdb_sample`.`joke_category` (

  `jokeId` INT NOT NULL,

  `categoryId` INT NOT NULL,

PRIMARY KEY (`jokeId`, `categoryId`));

Note that both the jokeId and categoryId columns are the primary key. This is to prevent the same joke being added to a category twice. Before continuing, let’s add some categories to the database. Using the form at http://192.168.10.10/category/edit, add the categories “programming jokes” and “one-liners”. Currently we have three jokes, each of which falls into the “programming jokes” category:

How many programmers does it take to screw in a lightbulb? None, it’s a hardware problem.

Why did the programmer quit his job? He didn’t get arrays.

Why was the empty array stuck outside? It didn’t have any keys.

Here are some more jokes that fit in the two categories:

Bugs come in through open Windows. (“programming jokes” and “one-liners” categories)

How do functions break up? They stop calling each other.(“programming jokes” only)

You don’t need any training to be a litter picker, you pick it up on the job. (“one-liners” only)

Venison’s dear, isn’t it? (“one-liners” only)

It’s tricky being a magician. (“one-liners” only)

Don’t add these just yet. We’ll amend the Add Joke page to allow selecting categories and then add the jokes. Instead of using a <select> box, we’ll use a series of checkboxes to allow users to which categories a joke falls into. As the Joke controller will now need to be able to pass a list of categories to the editjoke.html.php template, let’s amend the controller with a new constructor argument and class variable:

class Joke {

private $authorsTable;

private $jokesTable;

private $categoriesTable;

private $authentication;

public function __construct(DatabaseTable $jokesTable, DatabaseTable $authorsTable,

DatabaseTable $categoriesTable,

Authentication $authentication) {

$this->jokesTable = $jokesTable;

$this->authorsTable = $authorsTable;

$this->categoriesTable = $categoriesTable; $this->authentication = $authentication; }

// …

Aesthetic Choices

For consistency, we’ve placed the argument before the Authentication argument. There’s no practical reason for this; it’s an entirely aesthetic choice to put all the DatabaseTable instances together.

Now we should pass in the categoriesTable DatabaseTable instance when the Joke controller is instantiated in IjdbRoutes:

$jokeController = new \Ijdb\Controllers\Joke

($this->jokesTable, $this->authorsTable,

$this->categoriesTable,

$this->authentication);

In the edit method, pass the list of categories to the template:

public function edit() {

   $author = $this->authentication->getUser();

   $categories = $this->categoriesTable->findAll();

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

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

}

   $title = ‘Edit joke’;

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

‘title’ => $title,

‘variables’ => [

      ‘joke’ => $joke ?? null,

     ‘userId’ => $author->id ?? null,

      ‘categories’ => $categories

    ]

   ];

}

}

The next step is to set up the list of categories in the editjoke.html.php template and create a checkbox for each:

<form action=”” method=”post”>

<input type=”hidden” name=”joke[id]”

value=”<?=$joke->id ?? ”?>”>

<label for=”joketext”>Type your joke here:  </label>

<textarea id=”joketext” name=”joke[joketext]” rows=”3″

cols=”40″><?=$joke->joketext ?? ”?>

</textarea>

<p>Select categories for this joke:</p>

     <?php foreach ($categories as $category): ?>

<input type=”checkbox”

name=”category[]”

value=”<?=$category->id?>” />

<label><?=$category->name?></label>

<?php endforeach; ?>

<input type=”submit” name=”submit” value=”Save”>

</form>

The code here should be familiar by now, but I’ll quickly go over the additions we’ve made. <?php foreach ($categories as $category): ?>: loop over each of thecategories. <input type=”checkbox” name=”category[]” value=”<?=$category->id?>” />: this creates a checkbox for each category with the value property set to the ID of the category. name=”category[]”: by setting the name of the checkbox to category[], an array will be created when the form is submitted. For example, if you checked the checkboxes with the values 1 and 3, the variable $_POST[‘category’] would contain an array with the values 1 and 3 ([‘1’, ‘3’]). If you try the code above, the formatting will look strange. Add the following CSS to jokes.css to fix it:

form p {clear: both;}

input[type=”checkbox”] {float: left; clear: left; width: auto; margin-right: 10px;}

input[type=”checkbox”] + label {clear: right;}

You can find this code in Relationships-JokeCategory Now that the categories are listed and selectable on the Add Joke page, we need to change the saveEdit method to handle new data from the form submission.

It’s important to understand what needs to happen here. When the form is submitted, an array of the IDs of the checked categories will be sent as a $_POST variable. Each category ID and the ID of the new joke will then be written to the joke_category table.Before we can add records to the joke_category table, we’ll need a DatabaseTable instance for it. Add the class variable and create the instance in IjdbRoutes:

// …

private $jokeCategoriesTable;

public function __construct() {

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

$this->jokesTable = new \Ninja\DatabaseTable($pdo, ‘joke’, ‘id’, ‘\Ijdb\Entity\Joke’, [&$this->authorsTable]);

$this->authorsTable = new \Ninja\DatabaseTable($pdo, ‘author’, ‘id’,

‘\Ijdb\Entity\Author’,

[&$this->jokesTable]);

$this->categoriesTable = new \Ninja\DatabaseTable($pdo, ‘category’, ‘id’);

$this->jokeCategoriesTable =

new \Ninja\DatabaseTable($pdo,

‘joke_category’, ‘categoryId’);

// …

We could pass the jokeCategoriesTable instance to the Joke controller. However, like we did with the $author->addJoke method, it’s better to implement this using an object-oriented approach, where a joke is added to a category using this code:

$joke->addCategory($categoryId);

To do this, we’ll need a Joke entity instance in the saveEdit method in the Joke controller. The code for the updated saveEdit method will look like this:

public function saveEdit() {

  $author = $this->authentication->getUser();

  $joke = $_POST[‘joke’];

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

  $jokeEntity = $author->addJoke($joke);

foreach ($_POST[‘category’] as $categoryId) {

    $jokeEntity->addCategory($categoryId); }

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

}

The important change to what we have at the moment is the line $jokeEntity = $author->addJoke($joke);.

The addJoke method, which currently does not have a return value, will need to return a Joke entity instance, which represents the joke that has just been added to the database. As a first thought, a simple approach would be to fetch the joke from the jokesTable instance after it’s been created, using the following process:

  • take the data for the new joke from $_POST
  • pass it to the addJoke method in the Author entity class
  • retrieve the newly added joke from the database using a SELECT query (or the findById method)

For example:

public function addJoke($joke) {

$joke[‘authorId’] = $this->id;

// Store the joke in the database

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

// Fetch the new joke as an object

return $this->jokesTable->findById($id);

}

There are two problems with this:

  1. We don’t know what the newly created joke’s id is.
  2. It adds additional overhead. We’re actually making two trips to the database—once to run an INSERT query to send the data about the new joke, and then a SELECT query to fetch that very same information back out of the database immediately afterwards! We already have the information in the $joke variable, so there’s no need to fetch it from the database here.

Although, it would be possible to create the Joke entity instance in the addJoke method, it makes more sense to place this functionality in the DatabaseTable class. Whenever save is called, it can return the relevant entity instance. By placing this logic inside the save method, any time any data is written to any database table, the save method will return an object representing that newly added record. The addJoke method above should be changed to this:

public function addJoke($joke) {

$joke[‘authorId’] = $this->id;

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

}

This will just return the return value of the save method from the DatabaseTable class. That is, whatever the save method returns to the addJoke method will also be returned by the addJoke method. Open up the DatabaseTable class and find 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);

    }

}

This calls either the insert or update method. The first thing we need to do here is create an instance of the relevant entity class. For jokes, it will be the Joke class; for authors, it will be the Author class; and so on. To create the relevant entity, we can use this code:

$entity = new $this->className(…$this->constructorArgs);

This looks complicated. There are lots of variables, and a brand new operator! It’s important to understand what’s happening here, so we’ll show you how this works. Let’s think about what we want to do for the joke class. To create an instance of the Joke entity class, we’d need the following code:

$joke = new \Ijdb\Entity\Joke($authorsTable);

The class name, \Ijdb\Entity\Joke, is stored in the $this->className variable, so the above could be expressed like this:

$joke = new $this->className($authorsTable);

And this will still work in the same way. However, each entity class has different arguments and, potentially, a different number of arguments. The Author entity class, for example, requires the $jokesTabe instance. We already have the list of arguments for the entity class in the $this->constructorArgs variable as an array. The operator, known as the argument unpacking operator or splat operator, allows specifying an array in place of several arguments. For example, consider this code:

$array = [1, 2];

someFunction(…$array);

It’s the same as this:

someFunction(1, 2);

In our use case, this code:

$entity = new $this->className(…$this->constructorArgs);

…. is equivalent to this:

$entity = new  $this->className($this->constructorArgs[0],

$this->constructorArgs[1]);

Once this code has run, the $entity variable will store an object. The type of that object will depend on the class defined in $this->className. For the $jokesTable instance, it will be an instance of the Joke entity class. This line can be placed inside the save method, along with a line to return the newly created entity object:

public function save($record) {

   $entity = new $this->

   className(…$this->constructorArgs);

try {

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

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

}

    $this->insert($record);

}

catch (\PDOException $e) {

$this->update($record);

     }

return $entity;

}

With those lines in place, the save method will instantiate the relevant, empty, entity class. The next stage is writing the data that was sent to the database to the class. This can be done with a simple foreach:

public function save($record) {

   $entity = new $this->

  className(…$this->constructorArgs);

try {

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

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

}

    $this->insert($record);

}

catch (\PDOException $e) {

    $this->update($record);

}

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

  if (!empty($value)) {

   $entity->$key = $value;

    }

   }

return $entity;

}

The important line here is $entity->$key = $value;. Each time foreach iterates, the $key variable is set to the column name—for example joketext—and the $value variable is set to the value being written to that column. By using the $key variable after the object access operator (->), it will write to the property with the name of the column. The if (!empty($value)) check is in place to prevent values that are already set on the entity (such as the primary key) being overwritten with null.

$record = [‘joketext’ => ‘Why did the empty array get stuck outside? It didn\’t have any keys’, ‘authorId’ => 1,

‘jokedate’ => ‘2017-06-22’];

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

   if (!empty($value)) {

          $joke->$key = $value;

      }

}

The code above will have the same result as this:

$joke->joketext = ‘Why did the empty array get stuck outside? It didn\’t have any keys’;

$joke->authorId = 1;

$joke->jokedate = ‘2018-06-22’;

Converting Arrays to Objects

This approach is a common method of converting an array to an object.

With the foreach in place, writing the values to the entity object, the save method will return the object with all the values that were passed as an array to the method. This will work fine for records being updated, as the $record variable will include keys for all the columns in the database table.

A newly created record, however, will not have the primary key set. When a joke is added, we pass the save method values for the joketext, jokedate and authorId columns, not the id. In fact, we couldn’t pass the id even if we wanted to, as we don’t know what it will be before the record has been created in the database. The id primary key is actually created by MySQL inside the database. For INSERT queries, we’ll need to read the value from the database immediately after the record has been added.

Luckily, the PDO library provides a very simple method of doing this. After an INSERT query has been sent to the database, you can call the lastInsertId method on the PDO instance to read the ID of the last record inserted. To implement this, let’s amend the insert method in our DatabaseTable class to return the last insert ID:

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);

return $this->pdo->lastInsertId();

}

Now, the save method can read this value and set the primary key on the created entity object:

public function save($record) {

   $entity = new $this->

className(…$this->constructorArgs);

  try {

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

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

}

  $insertId = $this->insert($record);

  $entity->{$this->primaryKey} = $insertId;

}

catch (\PDOException $e) {

    $this->update($record);

}

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

     $entity->$key = $value;

}

return $entity;

}

WE’ve used the shorthand method with braces to set the primary key, but it’s the same as this:

$insertId = $this->insert($record);

$primaryKey = $this->primaryKey;

$entity->$primaryKey = $insertId;

The save method is now complete. Any time the save method is called, it will return an entity instance representing the record that’s just been saved.

Assigning Categories to Jokes

This functionality is required to enable the joke controller to assign categories to a joke instance. We can amend the saveEdit method in the Joke controller to look like this:

public function saveEdit() {

$author = $this->authentication->getUser();

$joke = $_POST[‘joke’];

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

$jokeEntity = $author->addJoke($joke);

foreach ($_POST[‘category’] as $categoryId) {

$jokeEntity->addCategory($categoryId);

   }

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

}

Now that $author->addJoke($joke); returns a Joke entity object, we can call methods on an entity representing the record that has just been inserted. In this case, $jokeEntity->addCategory($categoryId); can be used to assign a category to the joke that was just added to the database. Of course, for this to work we’ll need to make some changes to the Joke entity class.As the addCategory method will write a record to the new joke_category table, it will need a reference to the jokeCategoriesTable DatabaseTable instance. You know the drill here: add a class variable and constructor argument:

<?php

namespace Ijdb\Entity;

class Joke {

   public $id;

   public $authorId;

   public $jokedate;

   public $joketext;

   private $authorsTable;

   private $author;

  private $jokeCategoriesTable;

public function __construct(\Ninja\DatabaseTable $authorsTable, \Ninja\DatabaseTable $jokeCategoriesTable) {

$this->authorsTable = $authorsTable;

$this->jokeCategoriesTable = $jokeCategoriesTable;

}

// …

Then amend IjdbRoutes to provide the instance as an argument to the constructor of the $jokesTable instance:

$this->jokesTable = new \Ninja\DatabaseTable($pdo,

‘joke’, ‘id’, ‘\Ijdb\Entity\Joke’,

[&$this->authorsTable,

&$this->jokeCategoriesTable]);

By adding &$this->jokeCategoriesTable to the array passed as the fifth argument, each time an instance of \Ijdb\Entity\Joke is created inside the $jokesTable instance, the constructor will be called with the authorsTable and jokeCategoriesTable instances. Next, add the addCategory method to the Joke entity class:

public function addCategory($categoryId) {

$jokeCat = [‘jokeId’ => $this->id,

‘categoryId’ => $categoryId];

$this->jokeCategoriesTable->save($jokeCat);

}

You can find this code in Relationships-AssignCategory

This code is fairly simple. The first line creates an array that represents the record to be added. The jokeId is the id of the joke that we’re adding the category to, and the categoryId comes from the argument. With this code in place, whenever a joke is added to the website it’s assigned to the categories that were checked. Go ahead and add the jokes we supplied earlier, or your own, and verify that the records have been added to the joke_category join table by selecting the records from the table in MySQL Workbench.

Displaying Jokes by Category

Now that we have jokes in the database that are assigned to a category, let’s add a page that allows selecting jokes by category. On the Joke List page, let’s add a list of categories to allow filtering of the jokes. The first part is fairly simple: we need a list of categories as links on the Joke List page. This involves two fairly simple steps:

  Amend the list action to pass a list of categories to the template:

public function list() {

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

$title = ‘Joke list’;

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

$author = $this->authentication->getUser();

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

‘title’ => $title,

‘variables’ => [

‘totalJokes’ => $totalJokes,

‘jokes’ => $jokes,

‘userId’ => $author->id ?? null,

‘categories’ => $this->categoriesTable->findAll()

]

];

}

Loop through the categories in the jokes.html.php template and create a list with links for each one:

<ul class=”categories”>

<?php foreach ($categories as $category): ?>

<li><a href=”/joke/list?category=

<?=$category->id?>”>

<?=$category->name?></a><li>

<?php endforeach; ?>

</ul>

To make it look a little nicer, you can also add a containing div and a div around the list of jokes:

<div class=”jokelist”>

<ul class=”categories”>

<?php foreach ($categories as $category): ?>

<li><a href=”/joke/list?category=

<?=$category->id?>”>

<?=$category->name?></a><li>

<?php endforeach; ?>

</ul>

<div class=”jokes”>

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

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

// …

<?php endforeach; ?>

</div>

Then apply the following CSS:

.jokelist {display: table;}

.categories {display: table-cell; width: 20%; background-color: #333;

                padding: 1em; list-style-type: none;}

.categories a {color: white; text-decoration: none;}

.categories li {margin-bottom: 1em;}

.jokelist .jokes {display: table-cell; padding: 1em;}

You can find this code in Relationships-CategoryList.

That’s the list done, but the links currently don’t do anything. Each link sets a $_GET variable called category to the ID of the category we want to view. If you click on one of the new category links, you’ll see the page you visit is /jokes/ list?category=1 or similar.You’ve likely already worked out what we need to do now. We need to use the new $_GET variable to display a filtered list of jokes if the category variable is set. If we had done a simpler relationship with a categoryId column in the joke table, this would be relatively simple. In the list controller action, we’d just amend the way in which the $jokes variable was set:

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

    $jokes = $this->jokesTable->find(‘category’, $_GET[‘category’]);

}

else {

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

}

However, as we have a many-to-many relationship, it’s not quite so simple. One option is to pass the jokeCategoriesTable to the controller and do something

like this:

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

$jokeCategories = $this->jokeCategoriesTable->

find(‘categoryId’, $_GET[‘categoryId’]);

$jokes = [];

foreach ($jokeCategories as $jokeCategory) {

      $jokes[] = $this->jokesTable-> findById($jokeCategory->jokeId); }

}

else {

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

}

In this example, we’re getting a list of all the records from the joke_cagegory table for the chosen category by its id—for example, 4. This gives us a set of records, each with a categoryId and a jokeIdIn our example, the categoryId will always be 4, because we’ve only selected the records from that category, but each record has a unique jokeId. We then loop through all the records and find the relevant record from the joke table, adding each joke record to the $jokes array.

If you want to go ahead and test this approach for yourself, use the code above in the list method, create the jokeCategoriesTable class variable, constructor argument, and pass the instance into the Joke controller from IjdbRoutes.We haven’t given you the code for this because it’s not a great solution. One of the most difficult parts of programming is placing code in the right place. The logic above is correct. It works, and it was fairly simple to work out. However, it would be better if we could get a list of jokes from a category like this:

$category = $this->categoriesTable->

findById($_GET[‘category’]);

$jokes = $category->getJokes();

Doing so would allow us to get a list of jokes from a category anywhere in the program, not just the list method. You already know how to achieve this: we did the same thing with $joke->getAuthor(). In principle, this is the same. We’ll need a Category entity class that has access to the jokesTable instance, the jokeCategoriesTable instance, and has a method called getJokes:

<?php

  namespace Ijdb\Entity;

use Ninja\DatabaseTable;

class Category

{

  public $id;

  public $name;

  private $jokesTable;

  private $jokeCategoriesTable;

public function __construct(DatabaseTable $jokesTable, DatabaseTable $jokeCategoriesTable)

{

   $this->jokesTable = $jokesTable;

   $this->jokeCategoriesTable = $jokeCategoriesTable;

}

  public function getJokes()

{

    $jokeCategories = $this->jokeCategoriesTable-> find(‘categoryId’, $this->id);

  $jokes = [];

foreach ($jokeCategories as $jokeCategory) {

     $joke = $this->jokesTable-> findById($jokeCategory->jokeId);

if ($joke) {

    $jokes[] = $joke;

    }

}

   return $jokes;

    }

}

Save this in classes/Ijdb/Entity/Category.php. You’ll notice the code in the getJokes method is almost the same as the code I showed you earlier. The only differences are that it uses $this->id instead of $_GET[‘category’] and returns the $jokes array. One minor change I’ve added is a safety precaution: the if ($joke) check ensures that a joke is only added to the $jokes array if it can be retrieved from the database. Amend IjdbRoutes to set the categoriesTable instance to use the new Category entity class and provide the two constructor arguments:

$this->categoriesTable = new \Ninja\DatabaseTable($pdo,  ‘category’, ‘id’, ‘\Ijdb\Entity\Category’, [&$this->jokesTable, &$this->jokeCategoriesTable]);

Finally, use the new getJokes method to retrieve the jokes in the list controller action:

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

   $category = $this->categoriesTable->

   findById($_GET[‘category’]);

   $jokes = $category->getJokes();

}

else {

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

}

You can find this code in Relationships-CategoryList2

Using this approach, any time you need a list of jokes that exist in a category, you can find the category and then use $category->getJokes()If you visit the Joke List page of the website, you’ll be able to click on the category links to filter the jokes.

Editing Jokes

We’ve got most of the functionality for placing jokes in categories, but you’ll notice a problem if you try to edit a joke. In fact, there are two problems. The first, and the most obvious, can be found by editing a joke: try to edit one of the jokes that’s already in a category and you’ll immediately notice that the boxes aren’t checked. To fix this, we need to amend the code that prints the checkboxes:

<p>Select categories for this joke:</p>

<?php foreach ($categories as $category): ?>

<input type=”checkbox” name=”category[]”

        value=”<?=$category->id?>” />

<label><?=$category->name?></label>

<?php endforeach; ?>

To check a checkbox, you add the attribute checked to the input element:

<input type=”checkbox” checked name=”category[]”

value=”<?=$category->id?>” />

This is simple to add with an if statement to display checked if the joke is inside the category. The difficult part is determining if the joke is inside any given category. We’ll also solve this in an object-oriented way. Let’s add a method in the joke entity so we can use this:

if ($joke->hasCategory($category->id))

Add the hasCategory method to the Joke entity class:

public function hasCategory($categoryId) {

    $jokeCategories = $this->jokeCategoriesTable->

     find(‘jokeId’, $this->id);

foreach ($jokeCategories as $jokeCategory) {

          if ($jokeCategory->categoryId == $categoryId) {

        return true;

         }

    }

}

This works by finding all the categories that are associated with a joke, looping through them and checking to see whether one of those matches a given $categoryIdWith that in place, we can use it in the editjoke.html.php template:

<p>Select categories for this joke:</p>

<?php foreach ($categories as $category): ?>

<?php if ($joke &&

$joke->hasCategory($category->id)): ?>

<input type=”checkbox” checked name=”category[]”

value=”<?=$category->id?>” />

<?php else: ?>

<input type=”checkbox” name=”category[]”

value=”<?=$category->id?>” />

<?php endif; ?>

<label><?=$category->name?></label>

<?php endforeach; ?>

If you go to edit a joke, the relevant category boxes will now be checked, solving the first problem. The second problem is a little more subtle. If you edit a joke but don’t change the categories, everything will appear to work. However, if you uncheck a box, the changes won’t be saved! Try editing one of the jokes and unchecking all the category boxes before pressing Save. When you go back to edit the joke, the boxes will still be checked.

The reason for this issue is a common one when dealing with checkboxes in this way. Although we have some logic that says, “If the box is checked, add a record to the joke_category table,” we don’t have anything to remove that record after it’s been added and the checkbox has been unticked. We could use this process:

  • loop through every single category
  • check to see if the corresponding checkbook box wasn’t checked
  • if the box wasn’t checked and there’s a corresponding record, delete the record

We’d need do this check for every category, and it would take a fairly large amount of code to achieve. Instead, a much simpler approach is to delete all the records from the joke_category table that are related to the joke we’re editing, then apply the same logic as before: loop through the checked boxes and insert records for each category that was checked. Admittedly, this isn’t entirely efficient. If the joke is edited and the category checkboxes aren’t changed, this will cause unnecessary deletion and reinsertion of identical data. However, it’s still the simplest approach.

Our DatabaseTable class has a delete method that allows deleting a record by its primary key. However, our table has two primary keys—jokeId and categoryId—so we can’t use it as it currently is. Instead, let’s add a deleteWhere method to the DatabaseTable class that works like the existing find method:

public function deleteWhere($column, $value) {

       $query = ‘DELETE FROM ‘ . $this->table . ‘

WHERE ‘ . $column . ‘ = :value’;

   $parameters = [

        ‘value’ => $value

    ];

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

}

The code here is identical to the find method, except that it sends a DELETE query to the database rather than a SELECT query. For example, $jokesTable->deleteWhere(‘authorId’, 7) would delete all the jokes by the author with the id of 7Add a clearCategories method to the Joke entity class that removes all the related records from the jokeCategories table for a given joke.

public function clearCategories() {

  $this->jokeCategoriesTable->deleteWhere(‘jokeId’,

   $this->id);

}

When $joke->clearCategories() is called, it will remove every record from the joke_category table that represents the joke stored in $joke. Add this call to the saveEdit method in the Joke controller:

public function saveEdit() {

$author = $this->authentication->getUser();

$joke = $_POST[‘joke’];

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

$jokeEntity = $author->addJoke($joke);

$jokeEntity->clearCategories();

foreach ($_POST[‘category’] as $categoryId) {

$jokeEntity->addCategory($categoryId);

}

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

}

You can find this code in Relationships-ChangeCategories.

Test it for yourself by editing a joke and unchecking some categories, then going back and adding them again. If you’ve followed the steps here, you’ll be able to change the categories as you’d expect.

User Roles

We’ve now got a fully functional jokes website where users can register, post jokes, edit/delete their own submissions and view jokes by category. But what happens if someone posts something you want to delete, or you want to fix a spelling mistake in someone else’s joke? At the moment, you can’t do that! There’s a check in place that only allows authors to edit their own jokes. If someone else posts something, there’s currently no way for you to amend it.

The website is also set up so that anyone can add new categories. It would be better if only you (the website owner) were able to do that. This is a very common problem on websites, and it’s generally solved with access levels, where different accounts can perform different tasks. On our website, we would need at minimum the following access levels:

  1. Standard users: can post new jokes and edit/delete jokes they’ve posted.
  2. Administrators: can add/edit/remove categories, post jokes, and edit/delete jokes anyone has posted. They should also be able to turn other users into administrators.

The simplest way to do this is to have a column in the author table that represents the author’s access level. A 1 in the column could represent a normal user and, and a 2 could represent an administrator. It would then be easy to add a check on any page to determine whether or not the logged-in user was an administrator:

$author = $this->authentication->getUser();

if ($author->accessLevel == 2) {

    // They’re an administrator

}

else {

      // Otherwise, they’re not

}

This method is very simple to understand, and we could even very easily abstract it to if ($author->isAdmin()) to improve the readability. This implementation of access levels is fine for small websites with only a few users, or when there’s only one administrator.

However, on larger, real-world websites, you often need to give users different levels of access. For example, we may want someone to be able to add categories, but not able to grant other people administrator access, or worse, revoke your administrator privileges so they have complete control of the website! A more flexible approach is to give each user individual permissions for each action. For example, we could set a user up to be able to edit a category but not add an administrator. For this website, we’ve already considered these permissions:

  • edit other people’s jokes
  • delete other people’s jokes
  • add categories
  • edit categories
  • remove categories
  • edit user access levels

Before we model this in the database, let’s think about how we’d check these in the existing code. Our Author entity class could have a method called hasPermission that takes a single argument and returns true or false, depending on whether or not the user has a specific permission. We could assign numbers to each of the permissions above, so that the following code could be used to check whether they have permission to edit other people’s jokes:

if ($author->hasPermission(1))

2 could represent deleting other users’ jokes, 3 could be whether members are allowed to add categories, and so on. This is roughly what we want to do, but looking at the line of code above, it’s really not clear what’s happening. If you saw the code $author->hasPermission(6), you’d have to go away and look up what 6 meant.

To make the code much easier to read, each value can be stored inside a constant—which, like a variable, is a label given to a value. The difference, however, is that a constant always has the same value. The value is set once at the beginning of the program and can’t be changed. In object-oriented programming, constants are defined in classes like so:

<?php

namespace Ijdb\Entity;

class Author {

const EDIT_JOKES = 1;

const DELETE_JOKES = 2;

const LIST_CATEGORIES = 3;

const EDIT_CATEGORIES = 4;

const REMOVE_CATEGORIES = 5;

const EDIT_USER_ACCESS = 6;

Constant Conventions

By convention, constants are written in uppercase, with words separated by underscores. Although it’s possible to use lowercase letters, it’s almost universal in every programming language that constants are written like this!

We’ve defined the constants in the Author entity class, since the permissions are related to authors. We’ll also define a method called hasPermission in the class:

<?php

namespace Ijdb\Entity;

class Author {

const EDIT_JOKES = 1;

const DELETE_JOKES = 2;

const LIST_CATEGORIES = 3;

const EDIT_CATEGORIES = 4;

const REMOVE_CATEGORIES = 5;

const EDIT_USER_ACCESS = 6;

// …

public function hasPermission($permission) {

// …

}

Before we write the code for this method, I’m going to show you how it will be used:

$author = $this->authentication->getUser();

if ($author->hasPermission (\Ijdb\Entity\Author::LIST_CATEGORIES)) {

// …

}

Notice that the constant is prefixed with the namespace and class name, then a ::. The :: operator is used to access constants in a given class. When accessing a constant, you don’t need an instance to be created, as each instance would have the same value for the constant anyway.

There are two different places we’ll need to implement this. The first is page-level access. As we did with the login check, a check can be done in the router to stop people even viewing a page if they don’t have the correct permissions. This will need to be done in EntryPoint, but because each website you build might have a different way of handling these checks, we’ll add a new method to IjdbRoutes called checkPermissions:

public function checkPermission($permission): bool {

$user = $this->authentication->getUser();

if ($user && $user->hasPermission($permission)) {

return true;

} else {

return false;

          }

}

This fetches the current logged-in user and checks to see if they have a specific permission. As this is something that will need to be provided by any website you build, amend the Routes interface to include the checkPermission method:

<?php

namespace Ninja;

interface Routes

{

public function getRoutes(): array;

public function getAuthentication(): \Ninja\Authentication;

public function checkPermission($permission): bool;

}

To implement this in the EntryPointy class, we’ll add an extra entry to the $routes array to specify which permissions are required for accessing each page. Let’s start with the category permissions:

‘category/edit’ => [

   ‘POST’ => [

        ‘controller’ => $categoryController,

        ‘action’ => ‘saveEdit’

],

 ‘GET’ => [

     ‘controller’ => $categoryController,

     ‘action’ => ‘edit’

],

  ‘login’ => true,

  ‘permissions’ => \Ijdb\Entity\Author::EDIT_CATEGORIES

],

‘category/delete’ => [

  ‘POST’ => [

       ‘controller’ => $categoryController,

       ‘action’ => ‘delete’

],

     ‘login’ => true,

    ‘permissions’ => \Ijdb\Entity\Author::REMOVE_CATEGORIES

],

  ‘category/list’ => [

          ‘GET’ => [

                  ‘controller’ => $categoryController,

                   ‘action’ => ‘list’

],

  ‘login’ => true,

   ‘permissions’ => \Ijdb\Entity\Author::LIST_CATEGORIES

],

We’ve added the relevant permission requirements to each page using an extra key in the array. This only defines an extra value in the array. We’ll need to have the EntryPoint class call the checkPermission method that we just added to determine whether the logged-in user is allowed to view the page. The process here is fairly straightforward. When you visit a page—for example, /category/edit—the EntryPoint class will read the value stored in the permissions key for that route, then call the new checkPermission method to determine whether the user who is logged in and viewing the page has that permission. This will work in the same way as the login check:

if (isset($routes[$this->route][‘login’]) &&

!$authentication->isLoggedIn()) {

header(‘location: /login/error’);

}

else if (isset($routes[$this->route][‘permissions’])

&&

!$this->routes->checkPermission

($$routes[$this->route][‘permissions’])) {

header(‘location: /login/error’);

}

else {

// …

Firstly, the login check is performed, then the permissions check. If the route contains a permissions key, the value stored under that key—for example, \Ijdb\Entity\Author::REMOVE_CATEGORIES—is passed to the checkPermission method, which determines whether the logged-in user has the required permission. Since we haven’t written the code for the hasPermission method in the Author entity class yet, any permission check will always return false. Go ahead and try viewing the category list by visiting http://192.168.10.10/category/list.

You’ll see the error page that says “You are not logged in”. At this point, you might want to add a new template and route that displays a more accurate error message, but by now you should be very familiar with adding pages, so I’m not going to walk you through it!mYou can find this code in Relationships-PermissionsCheck.

Creating a Form to Assign Permissions

Before we can implement the hasPermission method, the website needs a page that allows assigning permissions to any given user. We’ll need two pages—one that lists all the authors, so we can select the one we want to give permissions to, and a second that contains a form with checkboxes for each permission. Add the following routes:

‘author/permissions’ => [

‘GET’ => [

‘controller’ => $authorController,

‘action’ => ‘permissions’

],

‘POST’ => [

‘controller’ => $authorController,

‘action’ => ‘savePermissions’

],

‘login’ => true

],

‘author/list’ => [

‘GET’ => [

‘controller’ => $authorController,

‘action’ => ‘list’

],

‘login’ => true

],

For now, we only have a login check on here. If we add a permissions check right now, we won’t be able to use the form to set the permissions, because your account won’t have the required permission to view the page! Rather than adding a new controller, we’ll use the Register controller that already exists and is used for handling changes to users’ accounts.

Author List

Let’s do the list first. Add a method in the Register controller called list that fetches a list of all the registered users and passes them to the template:

public function list() {

$authors = $this->authorsTable->findAll();

return [‘template’ => ‘authorlist.html.php’, ‘title’ => ‘Author List’, ‘variables’ => [

‘authors’ => $authors

    ]

   ];

}

And here’s the template authorlist.html.php for listing the users:

<h2>User List</h2>

<table>

<thead>

<th>Name</th>

<th>Email</th>

<th>Edit</th>

</thead>

<tbody>

<?php foreach ($authors as $author): ?> <tr>

<td><?=$author->name;?></td>

<td><?=$author->email;?></td>

<td>

<a href=”/author/permissions?id=<?=$author->id;?>”> Edit Permissions</a></td>

</tr>

<?php endforeach; ?>

</tbody>

</table>

If you visit http://192.168.10.10/author/list, you’ll see the list of registered authors, each with a link for editing their permissions. The Edit Permissions link goes to the /author/permissions page, and passes it the id of the author whose permissions we want to change.

Edit Author Permissions

There’s nothing on the Edit Permissions page yet, because we haven’t created it. It’s quite a simple page: it will display a checkbox for each permission in the system, and it will be checked if the author currently has that permission. The template could look like this:

<input type=”checkbox” value=”1″ <?php

if ($author->hasPermission(EDIT_JOKES)) {
echo ‘checked’;
} ?> Edit Jokes
<
<input type=”checkbox” value=”2″ <?php

if ($author->hasPermission(DELETE_JOKES)) {

echo ‘checked’;

?> Delete Jokes

<input type=”checkbox” value=”3″ <?php

if($author->hasPermission(LIST_CATEGORIES)) {
echo ‘checked’;
} ?> Add Categories
// etc.

This would work, but it requires storing the information about the permissions in two different places—the constants in the Author entity class, and the template. We also need to write out all the HTML and PHP for each checkbox. Like most cases when we find repetition like this, there’s a much easier way! It’s actually possible to read information about the variables, methods and constants that are contained inside a class using a tool called Reflection.We can actually get a list of constants, and their values, from the class! PHP makes this fairly simple. To reflect the Author entity class and read all its properties, you can use the following code:

$reflected = new \ReflectionClass(‘\Ijdb\Entity\Author’);

$constants = $reflected->getConstants();

The $constants array will contain information about all the constants that are defined within the class. If you use var_dump to print out the contents of the $constants variable, you’ll see an array with the constant names as the key and the constant values as the value:

array (size=6)

‘EDIT_JOKES’ => int 1

‘DELETE_JOKES’ => int 2

‘LIST_CATEGORIES’ => int 3

‘EDIT_CATEGORIES’ => int 4

‘REMOVE_CATEGORIES’ => int 5

‘EDIT_USER_ACCESS’ => int 6

Reflection

Reflection can be a very powerful tool, and I’ve only scratched the surface of what you can do. For more information on Reflection, see the PHP manual page.

By passing this array to the template, we can actually generate the list of checkboxes for permissions from the constants inside the template. Add this permissions method to the Register controller.:

public function permissions() {

  $author = $this->authorsTable->findById($_GET[‘id’]);

  $reflected = new \ReflectionClass(‘\Ijdb\Entity\Author’);

  $constants = $reflected->getConstants();

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

  ‘title’ => ‘Edit Permissions’,

  ‘variables’ => [

          ‘author’ => $author,

         ‘permissions’ => $constants

  ]

  ];

}

And here’s the corresponding template permissions.html.php:

<h2>Edit <?=$author->name?>’s  Permissions</h2>

<form action=”” method=”post”>

<?php foreach ($permissions as $name => $value): ?>

<div>

<input name=”permissions[]”

        type=”checkbox”

     value=”<?=$value?>”

<?php if ($author->hasPermission($value)):

   echo ‘checked’; endif; ?> />

<label><?=$name?>

</div>

<?php endforeach; ?>

<input type=”submit” value=”Submit” />

</form>

Like we did with categories, I’ve made the checkbox list an array. If you go to edit one of the user’s permissions, you’ll see a checkbox for each of the constants in the Author class! This makes future developments a lot easier. Once the constant has been added to the Author entity class, it will automatically appear on the list without us having to manually go and edit the template each time we want to add a new permission to the website. You can find this code in Relationships-EditPermissions

Setting Permissions

The next stage is storing the user’s permissions once you press save. Each user will have a set of permissions, and there are many different ways to represent this in the database. We could do it in the same way we did with categories: create a user_permission table with two columns authorId and permission. Then we could write a record for each permission. A user with the id of 4 and the permissions EDIT_JOKES, LIST_CATEGORIES and REMOVE_CATEGORIES would have the following records:

authorId | permission

                4|         1

               4|          3

              4 |          5

You already know how to implement this. You’ll need to create the table, the relevant DatabaseTable instance, and write the savePermissions method to create a record in the user_permission table for each checked box. Finally, the hasPermission method in the Author entity class would look something like this:

public function hasPermission($permission) {

$permissions = $this->userPermissionsTable->

find(‘authorId’, $this->id);

foreach ($permissions as $permission) {

     if ($permission->permission == $permission) {

         return true;

       }

       }

}

Before you implement this, I’m going to show you an alternative approach.

A Different Approach

Imagine if we set up the author table with a column for each permission:

CREATE TABLE `author` (

`id` INT(11) NOT NULL AUTO_INCREMENT,

`name` VARCHAR(255) DEFAULT NULL,

`email` VARCHAR(255) DEFAULT NULL,

`password` VARCHAR(255) DEFAULT NULL,

`editJoke` TINYINT(1) NOT NULL DEFAULT 0,

`deleteJokes` TINYINT(1) NOT NULL DEFAULT 0,

`addCatgories` TINYINT(1) NOT NULL DEFAULT 0,

`removeCategories` TINYINT(1) NOT NULL DEFAULT 0,

`editUserAccess` TINYINT(1) NOT NULL DEFAULT 0,

PRIMARY KEY (`id`)

) ENGINE=InnoDB CHARSET=utf8;

Each column is TINYINT(1), which means it can store a single bit. It can either be a 1 or 0, and I’ve set it to default to 0We could then set any author’s permissions using an UPDATE statement. For the user we mentioned earlier with the id of 4, this could be sent to the database as:

UPDATE `author` SET `editJokes` = 1, `listCategories` = 1,  `removeCategories` = 1 WHERE `id` = 4

It would be possible to name our checkboxes with the names of the columns and use them in the same way as any other field. The advantage of this approach is that it’s simpler. To find out if a user has a permission, you can just use this:

// Can this author edit jokes?

if ($author->editJokes == 1)

Or this:

// Can this author remove categories ?

 if ($author->removeCategories == 1)

This is a nicer approach than a join table, as the same check using a join table would require querying the database for all the user’s permissions, looping through the records, and checking to see whether the permission we’re looking for has a corresponding record. The downside to this approach is that every time we add a permission to the website, we’d need to add a column to the table. In principle, though, this is a lot simpler. It avoids a database table, and checking whether a user has a permission requires significantly less code and fewer database queries. Each permission is a simple 1 or 0 in the database. If you examined a user’s record in MySQL Workbench, you might see something like 0 1 0 0 1 0 0 in the permissions columns.

A Crash Course in Binary

This sequence of ones and zeros looks a lot like binary. In fact, every number stored in a database is actually stored as a series of ones and zeros behind the scenes. If you have an INT column and it stores the number 6 for a particular record, it will actually store the binary value 0110 on the hard disk. Everything stored on a computer is binary! Each binary number is a bit like a database record. Each one or zero is in a specific column, and that column represents a particular value. In fact, with normal decimal numbers, when you see the value 2395, you know that the 3 (since it’s in the third column from the right) represents 300The above number can actually be expressed like this:

2 x 1000 +

3 x 100 +

9 x 10

5 x 1

You’re so familiar with this process that it’s second nature, and you don’t need to think about it. Binary works in the same way. The only differences are that only the digits one and zero are available, and the column numbers are different. In both binary and decimal, numbers are created right to left. As an extra digit is added, it’s added to the left-hand side. To add 300 to 27, you add a 3 to the left. The same thing is true of binary: digits further to the right have a higher value.

In the case of the binary number 0110, the third column from the right represents 4, and the second column from the right represents 2To translate the value of the binary number 0110 into decimal, you can do the same kind of calculation as long as you know the values of each column. In decimal, each column is multiplied by ten each time you move to the left. In binary, each column is multiplied by two. To work out the total of 0110, we can do the same calculation:

0 x 8 +

1 x 4 +

1 x 2 +

0 x 1

If you calculate this, you’ll get 6. Each column is called a bit, and in this example we can describe the 8 bit as not being set (because it’s set to zero) and the 4 bit as being set (because it’s set to one). See if you can convert the following binary numbers to decimal2:

  • 1000
  • 0020
  • 1010

Be Bit-Wise

You’re probably wondering why any of this matters and what it has to do with user permissions.In short, binary has nothing to do with permissions. But neither do if statements or checkboxes. All three are tools we can use to solve the problem of permissions. What’s useful is that you can use PHP (and almost every programming language) to inquire whether any given integer has a 1 or 0 set for any of the bits that make up the number.

Bitwise Permissions

Rather than using a different database column to store each one or zero, we could use a single binary number to store those ones and zeros in a single column. By assigning a column to a permission, a binary number can represent which permissions any user has.

EDIT_USER_ACCESS | REMOVE_CATEGORIES | EDIT_CATEGORIES |
32 | 16 | 8
|
LIST_CATEGORIES | DELETE_JOKES | EDIT_JOKES |

4 | 2 | 1|

The binary number 000001, which has a 1 in the EDIT_JOKES column, would represent a user with EDIT_JOKES permissions.

111111 would represent a user that had all the permissions, and 011111 would represent a user that had every permission apart from being able to edit the permissions of other users (EDIT_USER_ACCESS). This process is identical to using multiple columns in a database, each with a one or zero. We’re just using a binary number to represent the same data. Rather than one column per bit, we can store multiple bits in a single INT column. Let’s convert the binary numbers to decimal: 000001 becomes 1, 111111 becomes 63 and 011111 becomes 31. We can easily store these numbers as integers in a database! If someone has the permissions value 63, we know they have all the permissions that are available.

Back to PHP

The difficult part is extracting the individual permissions. What if we want to know whether the user has the EDIT_CATEGORIES permission? On the chart above, I’ve assigned the 8 bit to mean EDIT_CATEGORIES. If a user has the permissions value 13, it’s not clear whether the 8 bit is set. In a database with multiple columns, we can use SELECT * FROM author WHERE id = 4 AND editCategories = 1 to determine whether the editCategories column is set to 1 for a specific user—in this case, the user with the id of 4Most programming languages, including PHP and MySQL, support something called bitwise operations. These allow you to inquire whether a specific bit is set in any integer. Using a single permissions column, the query above can be expressed as follows:

SELECT * FROM author WHERE id = 4 AND 8 & permissions

The clever part here is the part AND 8 & permissions. This uses the bitwise and (&) operator to inquire whether the 8 bit is set in the number stored in the permissions column for that record. PHP also provides the bitwise and operator. You’ve already seen that the number 6 is represented as 0110, which means that the bits 4 and 2 are set. The bitwise & operator can be used to determine whether a bit is set in a specific number, and it’s used like this:

if (6 & 2) {

}

This says, “Is the bit 2 set in the number 6?”, and it will evaluate to true. If you wanted to check bit 1, however, it would return false, because the 1 bit is not set in the binary representation of 6 (0110):

if (6 & 1) {

}

Binary operations like this are actually fairly common in PHP! When you set the error_reporting variable in PHP to E_WARNING | E_NOTICE, what you’re doing is setting the bits that represent warnings and notices. PHP will then internally check which bits are set when it encounters an error. Internally, PHP will do something like this:

if (E_NOTICE & ini_get(‘error_reporting’)) {

        display_notice($notice);

}

We can apply this to permissions. Imagine the author table had a column called permissions: it’s possible to determine whether an author has the permission EDIT_CATEGORIES by using this code:

if ($author->permissions & 8) {

}

This code has the same problem I mentioned earlier: it’s not clear exactly what’s happening here to anyone looking at the code. Again, we could represent the bits as constants:

const EDIT_JOKES = 1;

const DELETE_JOKES = 2;

const LIST_CATEGORIES = 4;

const EDIT_CATEGORIES = 8;

const REMOVE_CATEGORIES = 16;

const EDIT_USER_ACCESS = 32;

And we could write the permissions check like so:

//  Does the author have the EDIT_CATEGORIES permission?

if ($author->permissions & EDIT_CATEGORIES) {

}

// Does the author have the DELETE_JOKES permission?

if ($author->permissions & DELETE_JOKES) {

}

You don’t even need to understand the underlying binary to understand what’s happening here, and the individual numbers don’t even matter!

Storing Bitwise Permissions in the Database

Let’s implement this on the website. Amend the author table by adding a column called permissions, and set it to INT(64) so we can store a maximum of 64 different permissions. Change the constant values in the Author entity class as I have above. We don’t need to make any changes to the Edit Permissions form page, but we need to add the savePermissions method and have it store the binary permissions in the database. Before we do that, let’s consider what will happen when the form is submitted. If you checked the boxes labeled EDIT_JOKES and REMOVE_CATEGORIES, the variable $_POST[‘permissions’] would be an array containing the numbers 1 and 16

([1, 16]).

We want to convert those to the binary representation, where the 1 bit and 16 bits are set. It sounds difficult but, as you’ll see very shortly, it’s not! The number we need to generate is 010001, where bits 16 and 1 are set. Use your new binary knowledge to calculate the decimal version of this value, and you’ll work out that this binary number represents 17. All we need to do is add the numbers together! Just to prove the theory, let’s imagine that the boxes for EDIT_JOKESDELETE_JOKES, LIST_CATEGORIES and EDIT_USER_ACCESS are ticked. When the form is submitted, we’d get the array [1, 2, 4, 32]The binary representation of those permissions is 100111. If you work out what that is in decimal, you’ll get 39. Add together the values from the array 1 + 2 + 4 + 32 and you’ll also get 39!

All we need to do to store the numbers in the database is add together each element in the $_POST[‘permissions’] array. PHP even includes a function called array_sum that can do exactly that. The savePermissions method in the Register controller can be written like this:

public function savePermissions() {

$author = [

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

‘permissions’ => array_sum($_POST[‘permissions’] ?? [])

];

$this->authorsTable->save($author);

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

}

The line ‘permissions’ => array_sum($_POST[‘permissions’] ?? []) is used to add all the values from the $_POST[‘permissions’] array. However, if no boxes are ticked, $_POST[‘permissions’] won’t be set. The ?? operator is used to provide the array_sum operator with an empty array if there’s nothing in the $_POST[‘permissions’] variable. That’s it! The savePermissions method is converting the checked boxes into a number, and that number’s binary representation is how we’re modeling the permissions of each user.

Join Table or Bitwise

When using the join table approach for categories, to cater for boxes that were unchecked, we specifically had to delete all the records and reinsert them each time the form was submitted. As the permissions column is a single number, if no boxes are checked, array_sum will return 0 and the 0 will be inserted into the database, avoiding the need to specifically handle unchecked boxes. The final piece is the hasPermission method in the Author entity class. Add the $permission class variable. Then, to check if a user has a permission, we need a single line of code:

 Relationships-BinaryPermissions

public function hasPermission($permission) {

   return $this->permissions & $permission;

}

There are several advantages to this approach over a join table. The first is performance: we don’t need to query the database for the user’s permissions. Secondly, this is considerably less code for both saving the form and checking whether the permission is set.

The downsides to this approach are that it can be more difficult to understand if you’re not familiar with the bitwise operator. However, bitwise operators are fairly common in PHP. They’re used by the PDO library and for the various php.ini configuration settings such as error_reporting, so it’s a good idea to have a basic understanding of what they do. The second downside is that you’re limited to 64 bits, because that’s all a CPU can process. However, if you find yourself needing more, you can group the permissions into different columns, such as jokePermissions and adminPermissionsWhether you choose to implement user roles as bitwise, like I have here, or whether you use a join table, is up to you. There are pros and cons to each approach. Personally, I prefer the shorter code and fewer database operations, which bitwise operators offer.

Cleaning Up

There’s a little tidying up left to do. Firstly, we need to add the permissions to the routes. Make sure you have granted your user account the permission EDIT_USER_ACCESS before making these changes, or you won’t be able to change anyone’s permissions! We already amended the routes for LIST_CATEGORIES, EDIT_CATEGORIES and REMOVE_CATEGORIES.

You can test this works by visiting http://192.168.10.10/category/list and giving and revoking your LIST_CATEGORIES permission. Let’s do the same for EDIT_USER_ACCESS. Set the permissions key in the IjdbRoutes $routes array for the author list and permissions pages:

$routes = [

  // …

‘author/permissions’ => [

    ‘GET’ => [

         ‘controller’ => $authorController,

        ‘action’ => ‘permissions’

],

  ‘POST’ => [

       ‘controller’ => $authorController,

        ‘action’ => ‘savePermissions’

],

   ‘login’ => true,

     ‘permissions’ => \Ijdb\Entity\Author::EDIT_USER_ACCESS

],

   ‘author/list’ => [

  ‘GET’ => [

        ‘controller’ => $authorController,

         ‘action’ => ‘list’

],

      ‘login’ => true,

      ‘permissions’ => \Ijdb\Entity\Author::EDIT_USER_ACCESS

],

This will prevent anyone who doesn’t have the EDIT_USER_ACCESS permission from changing the permissions of other users.

Editing Others’ Jokes

The final two permissions are EDIT_JOKES and DELETE_JOKES, which determine whether the logged-in user can edit or delete a joke someone else has posted. We can’t do this with the $routes array, because the check isn’t done there. The edit link and delete button are hidden in the template, and there are checks inside the joke controller. Firstly, let’s make the edit link and delete button appear on the list page for all jokes if you have the EDIT_JOKES or DELETE_JOKES permissions. The relevant section of jokes.html.php looks like this:

<?php if ($userId == $joke->authorId) {

?>

<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>

<?php

} ?>

Now that we have different permissions for edit and delete, we’ll need two separate if statements—one for the delete button and one for the edit link. However, we can’t do this with the $userId alone. Instead of passing in the $userId variable to the template, change the list method in the Joke controller to pass in the entire $author object that represents the logged-in user:

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

    ‘title’ => $title,

   ‘variables’ => [

   ‘totalJokes’ => $totalJokes,

    ‘jokes’ => $jokes,

    ‘user’ => $author, //previously ‘userId’ => $author->id

    ‘categories’ => $this->categoriesTable->findAll()

        ]

];

The check in the template can now be amended so that the button and link are only visible to the person who posted the joke, or someone with the relevant permission:

<?php if ($user): ?>

      <?php if ($user->id == $joke->authorId ||

$user->hasPermission(\Ijdb\Entity\Author::EDIT_JOKES)):  ?>

<a href=”/joke/edit?id=<?=$joke->id?>”> Edit</a>

<?php endif; ?>

<?php if ($user->id == $joke->authorId ||

$user->hasPermission(\Ijdb\Entity\Author::DELETE_JOKES)):

?>

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

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

         value=”<?=$joke->id?>”>

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

</form>

<?php endif; ?>

<?php endif; ?>

This is a lot more complicated, as there are now three if statements.We’ve added if ($user) around the entire block, because the $user variable may be empty if no one is logged in. The following two use a logical or to determine whether the person who’s viewing the page is the same person who posted the joke, or if they have the relevant permission.

Editing the jokes.html.php template makes the buttons appear, but if you have the EDIT_JOKES permission and attempt to edit a joke you didn’t post, you’ll see the error “You may only edit jokes that you posted”. That’s because we added a specific check in the editjoke.html.php template, and the delete method in the Joke controller. Change delete to include the permissions check:

public function delete() {

$author = $this->authentication->getUser();

$joke = $this->jokesTable->findById($_POST[‘id’]); if ($joke->authorId != $author->id && !$author->hasPermission(\Ijdb\Entity\Author::DELETE_JOKES))

 {

    return;

}

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

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

}

And, like the list method, pass the entire author object to the template in the edit method, and adjust the template to include the permissions check. controllers/joke.php:

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

‘title’ => $title,

‘variables’ => [

‘joke’ => $joke ?? null,

‘user’ => $author,

‘categories’ => $categories

]

];

In editjoke.html.php, this code:

<?php if (empty($joke->id) || $userId == $joke->authorId):  ?>

…becomes this:

<?php if (empty($joke->id) || $user->id == $joke->authorId || 

 $user->hasPermission(\Ijdb\Entity\Author::EDIT_JOKES)): ?>

That’s it! All the permission checks are now in place.

Phew!

We showed you how to think in a more object-oriented way, and how to handle relationships between objects in an OOP way rather than a relational way. You learned how to represent many-to-many relationships using both join tables and bitwise operations. We added permissions to the existing site, but moving forward, you can think about user permissions as you go and create them while you’re writing the original code.

Content Formatting with Regular Expressions

We’re almost there! We’ve designed a database to store jokes, organized them into categories, and tracked their authors. We’ve learned how to create a web page that displays this library of jokes to site visitors. We’ve even developed a set of web pages that a site administrator can use to manage the joke library without kno wing anything about databases.In so doing, we’ve built a site that frees the resident webmaster from continually having to plug new content into tired HTML page templates, and from maintaining an unmanageable mass of HTML files. The HTML is now kept completely separate from the data it displays. If you want to redesign the site, you simply have to make the changes to the HTML contained in the PHP templates that you’ve constructed. A change to one file (for example, modifying the footer) is immediately reflected in the page layouts of all pages in the site. Only one task still requires knowledge of HTML: content formatting.

On any but the simplest of websites, it will be necessary to allow content (in our case, jokes) to include some sort of formatting. In a simple case, this might merely be the ability to break text into paragraphs. Often, however, content providers will expect facilities such as bold or italic text, hyperlinks, and so on. As it stands, we’ve stripped out any formatting from text entered by users using the htmlspecialchars function. If, instead, we just echo out the raw content pulled from the database, we can enable administrators to include formatting in the form of HTML code in the joke text:

<?php echo $joke->joketext; ?>

Following this simple change, a site administrator could include HTML tags that would have their usual effect on the joke text when inserted into a page. But is this really what we want? Left unchecked, content providers can do a lot of damage by including HTML code in the content they add to your site’s database. Particularly if your system will be enabling nontechnical users to submit content, you’ll find that invalid, obsolete, and otherwise inappropriate code will gradually infest the pristine website you set out to build. With one stray tag, a well-meaning user could tear apart the layout of your site.

You’ll learn about several PHP functions that you haven’t seen before, which are used for finding and replacing patterns of text in your site’s content. We’ll show you how to use these capabilities to provide a simpler markup language for your users that’s better suited to content formatting. By the time we’ve finished, we’ll have completed a content management system that anyone with a web browser can use—no knowledge of HTML required.

Regular Expressions

To implement our own markup language, we’ll have to write some PHP code to spot our custom tags in the text of jokes and then replace them with their HTML equivalents. For tackling this sort of task, PHP includes extensive support for regular expressions. A regular expression is a short piece of code that describes a pattern of text that may occur in content like our jokes. We use regular expressions to search for and replace patterns of text. They’re available in many programming languages and environments, and are especially prevalent in web development languages like PHP.

The popularity of regular expressions has everything to do with how useful they are, and absolutely nothing to do with how easy they are to use—because they’re not at all easy. In fact, to most people who encounter them for the first time, regular expressions look like what might eventuate if you fell asleep with your face on the keyboard. Here, for example, is a relatively simple (yes, really!) regular expression that will match any string that might be a valid email address:

/^[\w\.\-]+@([\w\-]+\.)+[a-z]+$/i

By the end of this section, you’ll actually be able to make sense of that. The language of a regular expression is cryptic enough that, once you master it, you may feel as if you’re able to weave magical incantations with the code you write. To begin with, let’s start with some very simple regular expressions. This is a regular expression that searches for the text “PHP” (without the quotes):

/PHP/

Fairly simple, right? It’s the text you want to search for, surrounded by a pair of matching delimiters. Traditionally, slashes (/) are used as regular expression delimiters, but another common choice is the hash character (#). You can actually use any character as a delimiter except letters, numbers, or backslashes (\).

Escaping Delimiters

To include a forward slash as part of a regular expression that uses forward slashes as delimiters, you must escape it with a preceding backslash (\/); otherwise, it will be interpreted as marking the end of the pattern. The same goes for other delimiter characters: if you use hash characters as delimiters, you’ll need to escape any hashes within the expression with backslashes (\#).

To use a regular expression, you must be familiar with the regular expression functions available in PHP. preg_match is the most basic, and can be used to determine whether or not a regular expression is matched by a particular text string. Consider this code:

<?php

$text = ‘PHP rules!’;

if (preg_match(‘/PHP/’, $text)) {

       echo ‘$text contains the string “PHP”.’; }

else {

        ‘$text does not contain the string “PHP”.’;

}

In this example, the regular expression finds a match, because the string stored in the variable $text contains “PHP”. This example will therefore output the message shown below.

The regular expression finds a match

Use of Single Quotes Above

Notice that the single quotes around the strings in the code prevent PHP from filling in the value of the variable $text.

By default, regular expressions are case-sensitive. That is, lowercase characters in the expression only match lowercase characters in the string, and uppercase characters only match uppercase characters. If you want to perform a case-insensitive search instead, you can use a pattern modifier to make the regular expression ignore case. Pattern modifiers are single-character flags following the ending delimiter of an expression. The modifier for performing a case-insensitive match is i. So while /PHP/ will only match strings that contain “PHP”, /PHP/i will match strings that contain “PHP”, “php”, or even “pHp”. Here’s an example to illustrate this:

<?php

$text = ‘What is Php?’;

  if (preg_match(‘/PHP/i’, $text)) {

               echo ‘$text contains the string “PHP”.’;

} else {

           echo ‘$text does not contain the string “PHP”.’;

}

Again, as shown below, this outputs the same message, despite the string actually containing “Php”.

 

No need to be picky …

Regular expressions are almost a programming language unto themselves. A dazzling variety of characters have a special significance when they appear in a regular expression. Using these special characters, you can describe in great detail the pattern of characters that a PHP function like preg_match will search for. To show you what I mean, let’s look at a slightly more complex regular expression:

/^PH.*/

The caret (^) is placed at the beginning of an expression and indicates that the pattern much match the start of the string. The expression above will only match strings that start withPHThe dot (.) means “any single character”. The expression /PH./ would match PHP, PHA, PHx and any other string that started with PH and one more letter. The asterisk (*) is a modifier for the dot, and it means “zero or more of the preceding character”. The expression P* would match PPPPPPP but not PHP.

.* matches any character zero or more times.

Therefore, the pattern /^PH.*/ matches not only the string “PH”, but “PHP”, “PHX”, “PHP: Hypertext Preprocessor”, and any other string beginning with “PH”. When you first encounter it, regular expression syntax can be downright confusing and difficult to remember, so if you intend to make extensive use of it, a good reference might come in handy. Regular expressions are a complex and extensive mini-language. We’ll introduce the individual characters as we need them. The PHP Manual includes a very thorough regular expression reference,, and interactive tools such as regex101.com are incredibly useful visual learning tools.

String Replacement with Regular Expressions

As you may recall, we’re aiming to make it easier for non-HTML- savvy users to add formatting to the jokes on our website. For example, if a user puts asterisks around a word in the text of a joke—such as ‘Knock *knock*…’—we’d like to display the joke with HTML emphasis tags around that word: Knock <em>knock</em>…’.

We can detect the presence of plain-text formatting such as this in a joke’s text using preg_match with the regular expression syntax we’ve just learned. However, what we need to do is pinpoint that formatting and replace it with appropriate HTML tags. To achieve this, we need to look at another regular expression function offered by PHP: preg_replacepreg_replace, like preg_match, accepts a regular expression and a string of text, and attempts to match the regular expression in the string. In addition, preg_replace takes another string of text and replaces every match of the regular expression with that string. The syntax for preg_replace is as follows:

$newString = preg_replace($regExp, $replaceWith, $oldString);

Here, $regExp is the regular expression, and replaceWith is the string that will replace matches in $oldString. The function returns the new string with all the replacements made. In that code, this newly generated string is stored in $newStringWe’re now ready to build our joke formatting function.

Emphasized Text

We could use a relevant preg_replace method everywhere it’s required in our templates. However, since this is going to be useful in multiple places, and any website we build, we’ll create a class for it and place it in our Ninja namespace: 

namespace Ninja;

class Markdown {

      private $string;

     public function __construct($markDown) {

     $this->string = $markDown;

    }

public function toHtml() {

   // convert $this->string to HTML

return $html;

     }

}

The plain-text formatting syntax we’ll support is a simplified form of Markdown, created by John Gruber.

Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain-text format, then convert it to structurally valid XHTML (or HTML). — the Markdown home page 

Since this class will convert Markdown to HTML, it’s named MarkDownThis first action is to use the htmlentities function to convert any HTML code present in the text into text, by removing any characters that are understood by browsers (<, >, &, ). We want to avoid any HTML code appearing in the output, except that which is generated from plain-text formatting.

Let’s start with formatting that will create bold and italic text. In Markdown, you can emphasize text by surrounding it with a pair of asterisks (*), or a pair of underscores (_). Obviously, we’ll replace any such pair with <em> and </em> tags.To achieve this, we’ll use two regular expressions: one that handles a pair of asterisks, and one that handles a pair of underscores.Let’s start with the underscores:

/_[^_]+_/

Breaking this down:

/: we choose our usual slash character to begin (and therefore delimit) our regular expression.

_: there’s nothing special about underscores in regular expressions, so this will simply match an underscore character in the text.

[^_]: square brackets are used to match a sequence of one or more characters that are placed between the opening bracket [ and closing bracket ]. The caret (^), when placed inside square brackets, acts as a logical not. The expression [^_] will match any character that is not an underscore.

+: the plus character indicates one or more characters that match the preceding expression. [^_]+ can be read as one or more characters that are not an underscore.

_: the second underscore, which marks the end of the italicized text.

/: the end of the regular expression.

In English, the expression /_[^_]+_/ could be translated as: “Find an underscore, followed by one or more characters that aren’t an underscore, and stop at the following underscore.” Now, it’s easy enough to feed this regular expression to preg_replace, but we have a problem:

$text = preg_replace(‘/_[^_]+_/’, ‘<em>emphasized text</em>’, $text);

The second argument we pass to preg_replace needs to be the text that we want to replace each match with. The problem is, we have no idea what the text that goes between the <em> and </em> tags should be. It’s part of the text that’s being matched by our regular expression! Thankfully, another feature of preg_replace comes to our rescue. If you surround a portion of the regular expression with parentheses, you can capture the corresponding portion of the matched text and use it in the replacement string. To do this, you’ll use the code $n, where n is 1 for the first parenthesized portion of the regular expression, 2 for the second, and so on, up to 99 for the 99th. Consider this example:

$text = ‘banana’;

$text = preg_replace(‘/(.*)(nana)/’, ‘$2$1’, $text);

echo $text; // outputs ‘nanaba’

So $1 is replaced with the text matched by the first grouped portion of the regular expression ((.*)—zero or more non-newline characters), which is ba in this case. $2 is replaced by nana, which is the text matched by the second grouped portion of the regular expression ((nana)). The replacement string ‘$2$1’, therefore, produces ‘nanaba’We can use the same principle to create our emphasized text, adding parentheses to our regular expression:

/_([^_]+)_/

These parentheses have no effect on how the expression works at all, but they create a group of matched characters that we can reuse in our replacement string:

$text = preg_replace(‘/_([^_]+)_/’, ‘<em>$1</em>’, $text);

The pattern to match and replace pairs of asterisks looks much the same, except we need to escape the asterisks with backslashes, since the asterisk character normally has a special meaning in regular expressions:

$text = preg_replace(‘/\*([^\*]+)\*/’, ‘<em>$1</em>’, $text);

That takes care of emphasized text, but Markdown also supports creating strong emphasis (<strong> tags) by surrounding text with a pair of double asterisks or underscores (**strong emphasis** or __strong emphasis__). Here’s the regular expression to match pairs of double underscores:

/__(.+?)__/s

The double underscores at the start and end are straightforward enough, but what’s going on inside the parentheses? Previously, in our single-underscore pattern, we used [^_]+ to match a series of one or more characters, none of which could be underscores. That works fine when the end of the emphasized text is marked by a single underscore. But when the end is a double underscore, we want to allow for the emphasized text to contain single underscores (for example, __text_with_strong_emphasis__). “No underscores allowed,” therefore, won’t cut it: we must come up with some other way to match the emphasized text. You might be tempted to use .+ (one or more characters, any kind), giving us a regular expression like this:

/__(.+)__/s

The problem with this pattern is that the + is greedy: it will cause this portion of the regular expression to gobble up as many characters as it can. Consider this joke, for example:

__Knock-knock.__ Who’s there? __Boo.__ Boo who? __Aw, don’t  cry about it!__

When presented with this text, the regular expression above will see just a single match, beginning with two underscores at the start of the joke and ending with two underscores at the end. The rest of the text in between (including all the other double underscores) will be gobbled up by the greedy .+ as the text to be emphasized! To fix this problem, we can ask the + to be non-greedy by adding a question mark after it. Instead of matching as many characters as possible, .+? will match as few characters as possible while still matching the rest of the pattern, ensuring we’ll match each piece of emphasized text (and the double-underscores that surround it) individually. This gets us to our final regular expression:

/__(.+?)__/s

Using the same technique, we can also come up with a regular expression for double-asterisks. This is how the finished code for applying strong emphasis ends up looking:

$text = preg_replace(‘/__(.+?)__/s’, ‘<strong>$1</strong>’, $text);

$text = preg_replace(‘/\*\*(.+?)\*\*/s’, ‘<strong>$1</strong>’, $text);

One last point: we must avoid converting pairs of single asterisks and underscores into <em> and </em> tags until after we’ve converted the pairs of double asterisks and underscores in the text into <strong> and </strong> tags. Our toHtml function, therefore, will apply strong emphasis first, then regular emphasis:

namespace Ninja;

class Markdown {

    private $string;

public function __construct($markDown) {

        $this->string = $markDown; }

public function toHtml() {

    // convert $this->string to HTML

       $text = htmlspecialchars($this->string, ENT_QUOTES, ‘UTF-8’);

// strong (bold)

$text = preg_replace(‘/__(.+?)__/s’,

      ‘<strong>$1</strong>’, $text);

$text = preg_replace(‘/\*\*(.+?)\*\*/s’,

        ‘<strong>$1</strong>’, $text);

// emphasis (italic)

$text = preg_replace(‘/_([^_]+)_/’,

      ‘<em>$1</em>’, $text);

$text = preg_replace(‘/\*([^\*]+)\*/’,

      ‘<em>$1</em>’, $text);

     return $text;

    }

}

Paragraphs

While we could choose characters to mark the start and end of paragraphs, just as we did for emphasized text, a simpler approach makes more sense. Since your users will type the content into a form field that allows them to create paragraphs using the Enter key, we’ll take a single newline to indicate a line break (<br />) and a double newline to indicate a new paragraph (</p><p>). As we explained earlier, you can represent a newline character in a regular expression as \n. Other whitespace characters you can write this way include a carriage return (\r) and a tab space (\t).

Exactly which characters are inserted into text when the user hits Enter depends on the user’s operating system. In general, Windows computers represent a line break as a carriage return followed by a newline (\r\n), whereas Mac computers used to represent it as a single carriage return character (\r). These days, Macs and Linux computers use a single newline character (\n) to indicate a new line.To deal with these different line-break styles, any of which may be submitted by the browser, we must do some conversion:

// Convert Windows (\r\n) to Unix (\n)

$text = preg_replace(‘/\r\n/’, “\n”, $text);

// Convert Macintosh (\r) to Unix (\n)

$text = preg_replace(‘/\r/’, “\n”, $text);

Avoid Using Double-Quoted String with Regular Expressions

All the regular expressions we’ve seen so far  have been expressed as single-quoted PHP strings. The automatic variable substitution provided by PHP strings is sometimes more convenient, but they can cause headaches when used with regular expressions. Double-quoted PHP strings and regular expressions share a number of special character escape codes. “\n” is a PHP string containing a newline character. Likewise, /\n/ is a regular expression that will match any string containing a newline character. We can represent this regular expression as a single-quoted PHP string (‘/\n/’) and all is well, because the code \n has no special meaning in a single-quoted PHP string.

If we were to use a double-quoted string to represent this regular expression, we’d have to write “/\\n/”—with a double-backslash. The double-backslash tells PHP to include an actual backslash in the string, rather than combining it with the n that follows it to represent a newline character. This string will therefore generate the desired regular expression, /\n/Because of the added complexity it introduces, it’s best to avoid using double-quoted strings when writing regular expressions. Note, however, that I have used double quotes for the replacement strings (“\n”) passed as the second parameter to preg_replace. In this case, we actually do want to create a string containing a newline character, so a double-quoted string does the job perfectly.

With our line breaks all converted to newline characters, we can convert them to paragraph breaks (when they occur in pairs) and line breaks (when they occur alone):

// Paragraphs

$text = ‘<p>’ . preg_replace(‘/\n\n/’, ‘</p><p>’, $text) . ‘</p>’;

// Line breaks

$text = preg_replace(‘/\n/’, ‘<br>’, $text);

Note the addition of <p> and </p> tags surrounding the joke text. Because our jokes may contain paragraph breaks, we must make sure the joke text is output within the context of a paragraph to begin with. This code does the trick: the line breaks in the text will now become the natural line- and paragraph-breaks expected by the user, removing the requirement to learn anything new to create this simple formatting. It turns out, however, that there’s a simpler way to achieve the same result in this case: there’s no need to use regular expressions at all! PHP’s str_replace function works a lot like preg_replace, except that it only searches for strings instead of regular expression patterns:

$newString = str_replace($searchFor, $replaceWith, $oldString);

We can therefore rewrite our line-breaking code as follows:

// Convert Windows (\r\n) to Unix (\n)

$text = str_replace(“\r\n”, “\n”, $text);

// Convert Macintosh (\r) to Unix (\n)

$text = str_replace(“\r”, “\n”, $text);

// Paragraphs

$text = ‘<p>’ . str_replace(“\n\n”, ‘</p>

        <p>’, $text) . ‘</p>’; // Line breaks

$text = str_replace(“\n”, ‘<br>’, $text);

str_replace is much more efficient than preg_replace, because there’s no need for it to apply the complex rules that govern regular expressions. Whenever str_replace (or str_ireplace, if you need a case-insensitive search) can do the job, you should use it instead of preg_replace.

Hyperlinks

While supporting the inclusion of hyperlinks in the text of jokes may seem unnecessary, such a feature makes plenty of sense in other applications. Here’s what a hyperlink looks like in Markdown:

[linked text](link URL)

Simple, right? You put the text of the link in square brackets, and follow it with the URL for the link in parentheses. As it turns out, you’ve already learned everything you need to match and replace links like this with HTML links. If you’re feeling up to the challenge, you should stop reading right here and try to tackle the problem yourself! First, we need a regular expression that will match links of this form. The regular expression is as follows:

/\[([^\]]+)]\((.+)\)/i

This is a rather complicated regular expression. You can see how regular expressions have gained a reputation for being indecipherable! Squint at it for a little while, and see if you can figure out how it works. Try writing out the expression on regex101.com and it will display the regular expression in its groups with some useful highlighting. You can try typing in various strings to see which match.

Let us break it down for you:

/: as with all our regular expressions, we choose to mark its beginning with a slash.

\[: this matches the opening square bracket ([). Since square brackets have a special meaning in regular expressions, we must escape it with a backslash to have it interpreted literally.

([^\]]+): first of all, this portion of the regular expression is surrounded with parentheses, so the matching text will be available to us as $1 when we write the replacement string. Inside the parentheses, we’re after the linked text. Because the end of the linked text is marked with a closing square bracket (]), we can describe it as one or more characters, none of which is a closing square bracket ([^\]]+).

]\(: this will match the closing square bracket that ends the linked text, followed by the opening parenthesis that signals the start of the link URL. The parenthesis needs to be escaped with a backslash to prevent it from having its usual grouping effect. (The square bracket doesn’t need to be escaped with a backslash, because there’s no unescaped opening square bracket currently in play.)

(.+): as URLs can contain (almost) any character, anything typed inside the markdown parentheses will be matched by .+ and stored inside the group $2 in the replacement string.

\): this escaped parenthesis matches the closing parenthesis ()) at the end of the link URL.

/i: we mark the end of the regular expression with a slash, followed by the case-insensitivity flag, i.

We can therefore convert links with the following PHP code:

$text = preg_replace(

‘/\[([^\]]+)]\(([-a-z0-9._~:\/?#@!$&\'()*+,;=%]+)\)/i’,

‘<a href=”$2″>$1</a>’, $text);

As you can see, $1 is used in the replacement string to substitute the captured link text, and $2 is used for the captured URL. Additionally, because we’re expressing our regular expression as a single-quoted PHP string, you have to escape the single quote that appears in the list of acceptable characters with a backslash.

Putting It All Together

Here’s how our finished class for converting Markdown to HTML looks:

<?php

namespace Ninja;

class Markdown

{

    private $string;

   public function __construct($markDown)

{

               $this->string = $markDown;

}

public function toHtml()

{

// convert $this->string to HTML

             $text = htmlspecialchars($this->string, ENT_QUOTES, ‘UTF-8’);

// strong (bold)

$text = preg_replace(‘/__(.+?)__/s’,

       ‘<strong>$1</strong>’, $text);

$text = preg_replace(‘/\*\*(.+?)\*\*/s’,

      ‘<strong>$1</strong>’, $text);

// emphasis (italic)

$text = preg_replace(‘/_([^_]+)_/’,

      ‘<em>$1</em>’, $text);

$text = preg_replace(‘/\*([^\*]+)\*/’, ‘<em>$1</em>’, $text);

// Convert Windows (\r\n) to Unix (\n)

$text = str_replace(“\r\n”, “\n”, $text);

// Convert Macintosh (\r) to Unix (\n)

$text = str_replace(“\r”, “\n”, $text);

// Paragraphs

$text = ‘<p>’ . str_replace(“\n\n”,

     ‘</p><p>’, $text) . ‘</p>’;

// Line breaks

$text = str_replace(“\n”, ‘<br>’, $text);

// [linked text](link URL)

    $text = preg_replace(

‘/\[([^\]]+)]\(([-a-z0-9._~:\/?#@!$&\'()*+,;=%]+)\)/i’,

  ‘<a href=”$2″>$1</a>’,

        $text

  );

                 return $text;

          }

}

We can then use this class in our template that outputs the joke text, jokes.html.php:

<div class=”jokelist”>

<ul class=”categories”>

       <?php foreach ($categories as $category): ?>

       <li><a href=”/joke/list?category=

       <?=$category->id?>”><

       ?=$category->name?></a><li>

       <?php endforeach; ?>

</ul>

<div class=”jokes”>

<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->getAuthor()->email,

         ENT_QUOTES,

         ‘UTF-8’

); ?>”>

     <?=htmlspecialchars(

       $joke->getAuthor()->name,

      ENT_QUOTES,

     ‘UTF-8’

       ); ?></a> on

<?php

$date = new DateTime($joke->jokedate);

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

?>)

<?php if ($user): ?>

      <?php if ($user->id == $joke->authorId ||

$user->hasPermission(\Ijdb\Entity\Author::EDIT_JOKES)):    ?>

       <a href=”/joke/edit?id=<?=$joke->id?>”> Edit</a>

<?php endif; ?>

    <?php if ($user->id == $joke->authorId ||

$user->hasPermission(\Ijdb\Entity\Author::DELETE_JOKES)):    ?>

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

      <input type=”hidden” name=”id” value=”<?=$joke->id?>”>

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

    </form>

<?php endif; ?>

   <?php endif; ?>

       </p>

</blockquote>

<?php endforeach; ?>

</div>

The line we’re interested in is this:

<?=htmlspecialchars($joke->joketext,

ENT_QUOTES, ‘UTF-8’)?>

However, each joke is already wrapped in a <p> tag. This can be removed:

<div class=”jokelist”>

<ul class=”categories”>

    <?php foreach($categories as $category): ?>

    <li><a href=”/joke/list?category=

    <?=$category->id?>”>

    <?=$category->name?></a><li>

    <?php endforeach; ?>

</ul>

<div class=”jokes”>

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

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

<blockquote>

       <!– Remove the opening tag <p> –>

      <?=htmlspecialchars($joke->joketext,

           ENT_QUOTES, ‘UTF-8’)?>

                            <!— … –>

<?php endif; ?>

     <!– Remove the closing tag </p> –>

</blockquote>

<?php endforeach; ?>

</div>

Now, replace the line that shows the joke text with this:

<?php

$markdown = new \Ninja\Markdown($joke->joketext);

echo $markdown->toHtml();

?>

This will pass the contents of joketext to the markdown class as a constructor argument and call the toHtml method to convert the text to HTML. This is a lot untidier than the original method, as it requires two lines. As with most things in PHP, there is a way to express this using shorter syntax:

<?=(new\Ninja\Markdown($joke->joketext))->toHtml()?>

This code can be found in Formatting-MarkdownWith these changes made, take your new plain-text formatting for a spin! Edit a few of your jokes to contain Markdown syntax and verify that the formatting is correctly displayed.

Why Using Markdown is Cool

What’s nice about adopting a formatting syntax like Markdown for your own website is that there’s often plenty of open-source code out there to help you deal with it. Your newfound regular expression skills will serve you well in your career as a web developer, but if you want to support Markdown formatting on your site, the easiest way to do it would be to not write all the code to handle Markdown formatting yourself! Commonly used Markdown libraries include ParseDown and cebe/markdown.

Sorting, Limiting and Offsets

We’ve spent a lot of time writing PHP code and, thanks to the DatabaseTable class, it’s been quite some time since you learned about any new SQL. However, there’s a few final MySQL features I’d like to show you before you get your Ninja title.

Sorting

MySQL supports asking for retrieved records in a specific order. At the moment, the Joke List page displays jokes in the order they were posted. It would be better if it showed the newest first. A SELECT query can contain an ORDER BY clause that specifies the column that the data is sorted by. For our jokes table, SELECT * FROM `joke` ORDER BY `jokedate` would order the jokes by the date they were posted. You can also specify a modifier of ASC (ascending-counting up) or DESC (descending-counting down).

SELECT * FROM `joke` ORDER BY `jokedate` DESC

This query would select all the jokes and order them by date in descending order, newest first. Let’s implement this on the website. All our SQL queries are generated by the DatabaseTable class, so we’ll need to amend that to include an ORDER BY clause. At the moment, the findAll method looks like this:

public function findAll() {

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

  $this->table);

  return $result->fetchAll(\PDO::FETCH_CLASS,

  $this->className, $this->constructorArgs);

}

Let’s add an optional argument for ORDER BY:

public function findAll($orderBy = null) {

  $query = ‘SELECT * FROM ‘ . $this->table;

  if ($orderBy != null) {

       $query .= ‘ ORDER BY ‘ . $orderBy;

}

  $result = $this->query($query);

  return $result->fetchAll(\PDO::FETCH_CLASS,

   $this->className, $this->constructorArgs);

}

The SELECT query is now built up in the same way we built the INSERT and UPDATE queries. When a value for $orderBy is supplied, it’s appended to the query along with the ORDER BY clause. By making the argument optional, all of our existing code will still work without modification. We can provide a value for the $orderby argument only where it’s needed. To sort the Joke List page by date descending, amend the Joke controller’s list method to supply the argument to the findAll method:

public function list() {

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

     $category = $this->categoriesTable->

     findById($_GET[‘category’]);

     $jokes = $category->getJokes();

}

else {

     $jokes = $this->jokesTable->findAll(‘jokedate DESC’);

}

// …

At the moment, the main Joke List page is sorted newest first. However, if you click on one of the categories, they’re listed oldest first. You might consider adding the same optional argument to the find method:

public function find($column, $value, $orderBy = null) {

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

   WHERE ‘ . $column . ‘ = :value’;

  $parameters = [

      ‘value’ => $value

  ];

if ($orderBy != null) {

        $query .= ‘ ORDER BY ‘ . $orderBy;

}

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

     return $query->fetchAll(\PDO::FETCH_CLASS,

       $this->className, $this->constructorArgs);   

}

classsbox

Although this will be useful, it’s not going to solve the problem. The list of jokes is generated in the Category entity class:

classsboxstart

public function getJokes() {

     $jokeCategories = $this->jokeCategoriesTable-> find(‘categoryId’, $this->id);

   $jokes = [];

foreach ($jokeCategories as $jokeCategory) {

         $joke = $this->jokesTable-> findById($jokeCategory->jokeId);

if ($joke) {

     $jokes[] = $joke;

          }

     }

   return $jokes;

}

Because the find method is called on the DatabaseTable instance that represents the joke_category table, we can’t easily sort by date. There are a few ways to solve this. We could add a date column to the joke_category table for sorting purposes. We could also use an SQL JOIN, but that would be difficult to implement into our OOP DatabaseTable class. Instead, we can do the sort in PHP itself, using the usort function. The usort function takes two arguments: an array to be sorted, and the name of a function that compares two values. The example given in the PHP manual is this:

<?php

function cmp($a, $b)

{

if ($a == $b) {

           return 0;

     }

      return ($a < $b) ? -1 : 1;

}

$a = [3, 2, 5, 6, 1];

usort($a, “cmp”);

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

         echo “$key: $value\n”;

}

The code above outputs this:

0: 1
1: 2
2: 3
3: 5
4: 6

The array has been sorted smallest to largest. The cmp function is called with two values from the array, and returns 1 if the first should be placed after the second, and -1 if the first should be placed before the second. The important part is this line:

return ($a < $b) ? -1 : 1;

The syntax here looks strange if you haven’t come across it before. You actually know what’s happening here, but you’ve not seen it expressed in this way. The code here is a shorthand (or ternary) if statement, and it’s identical in execution to this:

if ($a < $b) {

    return -1;

} else {

     return 1;

}

The comparison function can take arguments that are objects, and we can build a comparison function into our Category class like so:

public function getJokes() {

      $jokeCategories = $this->jokeCategoriesTable-> find(‘categoryId’, $this->id);

    $jokes = [];

foreach ($jokeCategories as $jokeCategory) {

     $joke = $this->jokesTable->

    findById($jokeCategory->jokeId);

if ($joke) {

    $jokes[] = $joke;

    }

}

       usort($jokes, [$this, ‘sortJokes’]);

return $jokes;

}

private function sortJokes($a, $b) {

    $aDate = new \DateTime($a->jokedate);

    $bDate = new \DateTime($b->jokedate);

if ($aDate->getTimestamp() == $bDate->getTimestamp()) {

      return 0;

   }

return $aDate->getTimestamp() > $bDate->getTimestamp() ? -1 : 1;

}

You can find this code in Formatting-Usort

There’s a lot going on here, so we’ll go through it line by line. Firstly, the $jokes array is sorted using the usort function: usort($jokes, [$this, ‘sortJokes’]);. To call a method in a class, rather than just a function, you can use an array containing the object you want to call the method on (in our case, the same instance, $this) and the name of the method to be called (sortJokes). The sortJokes method starts by converting the dates from each of the $a and $b objects into \DateTime instances for easier comparison. The getTimestamp method returns a Unix timestamp—the number of seconds between the January 1 1970 and the date being represented. Using timestamps allows us to compare the dates as integers.

The if statement checks to see if the dates have the same timestamp. If so, it returns 0, indicating that neither should be moved before or after the other in the sorted list. If the dates are different, either 1 or -1 is returned to sort the dates. Notice I’ve used $a > $b, which will sort the array in the opposite order to the example, and put the larger timestamps (later dates) first. There’s a slight performance overhead in using usort instead of ORDER BY and having the database perform the sort, but unless you’re dealing with thousands of records, the difference between the two will be milliseconds at worst!

Pagination with LIMIT and OFFSET

Now that you know how to sort the records, we can think a little about scalability. You’ve probably got fewer than a dozen jokes in your database at the moment. What will happen after the website has been online a few months and is starting to get popular? You might get users coming on and posting hundreds of jokes a day. It won’t take long before the Joke List page takes a very long time to load because it’s displaying hundreds or thousands of jokes. The performance alone will put users off, but nobody is going to sit and read through a page of two thousand jokes.

A common approach is using pagination to display a sensible number—for example, ten jokes per page—and allow clicking a link to move between pages. Before you continue, add at least 21 jokes to your database so we can test this correctly. Alternatively, for testing purposes, change 10 in the following sections to 2 to display two jokes per page.

What If I Don’t Know any Jokes?

Don’t worry if you can’t think of any jokes. Just add some test data like “joke one”, “joke two”, “joke three”, etc.

Our first task is to display just the first ten jokes. Using SQL, this is incredibly easy. The LIMIT clause can be appended to any SELECT query to restrict the number of records returned:

SELECT * FROM `joke` ORDER BY `jokedate DESC` LIMIT 10

We’ll need to build this into findAll and find methods of the DatabaseTable class as optional parameters, as we did with the $orderBy variable:

public function find($column, $value, $orderBy = null, $limit = null) {

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

   ‘ WHERE ‘ . $column . ‘ = :value’;

  $parameters = [

       ‘value’ => $value

    ];

if ($orderBy != null) {

    $query .= ‘ ORDER BY ‘ . $orderBy;

    }

if ($limit != null) {

      $query .= ‘ LIMIT ‘ . $limit;

    }

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

      return $query->fetchAll(\PDO::FETCH_CLASS,

     $this->className, $this->constructorArgs);

}

public function findAll($orderBy = null, $limit = null) {

    $query = ‘SELECT * FROM ‘ . $this->table;

if ($orderBy != null) {

      $query .= ‘ ORDER BY ‘ . $orderBy;

   }

if ($limit != null) {

     $query .= ‘ LIMIT ‘ . $limit;

    }

$result = $this->query($query);

   return $result->fetchAll(\PDO::FETCH_CLASS,

    $this->className, $this->constructorArgs);

}

Then, to limit to ten jokes, open up the Joke controller class and provide the value 10 for the new $limit argument:

$jokes = $this->jokesTable->findAll(‘jokedate DESC’, 10);

Also supply the new limit in the Category entity class:

$jokeCategories = $this->jokeCategoriesTable->find(‘categoryId’, $this->id, null, 10);

You’ll notice we’ve supplied null for the $orderBy argument. Even though the argument is optional, to provide a value for $limit, a value for all the earlier arguments must be provided. With that in place, you’ll see only ten jokes on the Joke List page. The problem now is how we’ll view the rest of the jokes! The solution is to have different pages that can be accessed by a $_GET variable: /joke/list?page=1 or /joke/list?page=2 to select which page to show. Page 1 will show jokes 1–10, page 2 will show jokes 11–20, and so on. Before doing anything using the page $_GET variable, let’s create the links in the template. We can easily use a for loop to display a set of links to different pages:

for ($i = 1; $i <= 10; $i++) {

       echo ‘<a href=”/joke/list?page=’ . $i . ‘”>’ .

       $i ‘</a>’;

}

The problem is, we need to know how many pages there will be. It’s actually very easy to work out. If we’re displaying ten jokes per page, the number of pages is the number of jokes in the database divided by ten, and then rounded up.With 21 jokes in the system, 21/10 is 2.1, and if we round up, it gives 3 pages. PHP’s ceil function can be used to round up any decimal number. The template already has access to the $totalJokes variable, so we can display the pages at the end of jokes.html.php:

// …

<?php endif; ?>

</blockquote>

<?php endforeach; ?>

Select page:

<?php

// Calculate the number of pages

$numPages = ceil($totalJokes/10);

// Display a link for each page

for ($i = 1; $i <= $numPages; $i++):

?>

<a href=”/joke/list?page=<?=$i?>”>

<?=$i?></a>

<?php endfor; ?>

</div>

If you click the links, the $_GET variable will be set. It’s now just a matter of using it to display different sets of jokes. The SQL clause OFFSET can be used with LIMIT to do exactly what we want:

SELECT * FROM `joke` ORDER BY `jokedate` LIMIT 10 OFFSET 10

This query will return 10 jokes, but instead of returning the first ten jokes, it will display ten jokes starting from joke 10. We’ll need to turn page numbers into offsets. Page 1, will be OFFSET 0, page 2 will be OFFSET 10, page 3 will be OFFSET 20 and so on. This is a simple calculation: $offset = ($_GET[‘page’]-1)*10.As we did with limit, let’s add OFFSET as an optional argument for the findAll and find methods:

public function findAll($orderBy = null, $limit = null, $offset = null) {

     $query = ‘SELECT * FROM ‘ . $this->table;

if ($orderBy != null) {

            $query .= ‘ ORDER BY ‘ . $orderBy;

}

if ($limit != null) {

          $query .= ‘ LIMIT ‘ . $limit;

}

if ($offset != null) {

           $query .= ‘ OFFSET ‘ . $offset;

}

$result = $this->query($query);

     return $result->fetchAll(\PDO::FETCH_CLASS,

           $this->className, $this->constructorArgs);

}

public function find($column, $value,

$orderBy = null, $limit = null, $offset = null) {

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

      WHERE ‘ . $column . ‘ = :value’;

$parameters = [

      ‘value’ => $value

      ];

if ($orderBy != null) {

       $query .= ‘ ORDER BY ‘ . $orderBy;

}

if ($limit != null) {

       $query .= ‘ LIMIT ‘ . $limit;

}

if ($offset != null) {

          $query .= ‘ OFFSET ‘ . $offset;

}

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

       return $query->fetchAll(\PDO::FETCH_CLASS,

      $this->className, $this->constructorArgs);

}

Then supply the offset in the list method in the Joke controller:

$page = $_GET[‘page’] ?? 1;

$offset = ($page-1)*10;

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

     $category = $this->categoriesTable->

     findById($_GET[‘category’]);

     $jokes = $category->getJokes();

   }

else {

       $jokes = $this->jokesTable->findAll(‘jokedate DESC’, 10, $offset);

}

   $title = ‘Joke List’;

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

   $author = $this->authentication->getUser();

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

        ‘title’ => $title,

        ‘variables’ => [

                ‘totalJokes’ => $totalJokes,

                ‘jokes’ => $jokes,

                ‘user’ => $author,

                ‘categories’ => $this->categoriesTable->findAll()

              ]

      ];

}

Now, if you click between the different page links, you’ll see ten different jokes on each page. This new pagination doesn’t work for the lists within categories, and we’ll fix that shortly. But at the moment, the links aren’t very user friendly. Let’s change the styling of the link that represents the current page. We can pass the number of the current page to the template:

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

          ‘title’ => $title,

          ‘variables’ => [

          ‘totalJokes’ => $totalJokes,

           ‘jokes’ => $jokes,

          ‘user’ => $author,

          ‘categories’ => $this->categoriesTable->findAll(),

          ‘currentPage’ => $page

       ]

];

Then add a CSS class to the page link if it’s the current page:

Select page:

<?php

$numPages = ceil($totalJokes/10);

for ($i = 1; $i <= $numPages; $i++):

      if ($i == $currentPage):

?>

    <a class=”currentpage”

            href=”/joke/list?page=<?=$i?>”>

           <?=$i?></a>

<?php else: ?>

       <a href=”/joke/list?page=<?=$i?>”>

       <?=$i?></a>

<?php endif; ?>

<?php endfor; ?>

</div>

We’ve added a CSS class currentpage to the link if the link being printed is the current page being viewed. Add some CSS to jokes.css to make the link stand out. You could change the color, make it bold, underlined or however you like. We’ve chosen to surround the number with square brackets:

.currentpage:before {

content: “[“;

}

.currentpage:after {

content: “]”;

}

You can find this code in Formatting-Pagination

Pagination in Categories

We have a small bug in the code at the moment. If you click on one of the categories, it won’t supply the correct offset value. To fix this, we can add an $offset argument to the Category entity’s getJokes method. While you’re there, to improve flexibility you may as well supply $limit as an argument as well, instead of hardcoding it in the method:

public function getJokes($limit = null, $offset = null) {

      $jokeCategories = $this->jokeCategoriesTable->find(‘categoryId’,

      $this->id, null, $limit, $offset);

    $jokes = [];

foreach ($jokeCategories as $jokeCategory) {

       $joke = $this->jokesTable->findById($jokeCategory->jokeId);

       if ($joke) {

                       $jokes[] = $joke;

         }

}

usort($jokes, [$this, ‘sortJokes’]);

return $jokes;

}

Then provide the values when the method is called in the list method:

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

$category =$this->categoriesTable->findById($_GET[‘category’]);

$jokes = $category->getJokes(10, $offset);

}

With that done, the pagination will work … kind of. You can manually enter the $_GET variables in the URL—for example, http://192.168.10.10/joke/ list?category=1&page=1. However, the links we created don’t work. There are two problems:

  1. The page links don’t include the category variable.
  2. The number of page links displayed is based on the total number of jokes in the database, not the number of jokes in the selected category.

Let’s fix these one at a time. The easiest task is providing the category in the link. In the list method, we’ll need to pass the category to the template:

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

‘title’ => $title,

‘variables’ => [

    ‘totalJokes’ => $totalJokes,

    ‘jokes’ => $jokes,

    ‘user’ => $author,

    ‘categories’ => $this->categoriesTable->findAll(), ‘currentPage’ => $page,

    ‘category’ => $_GET[‘category’] ?? null

      ]

];

Then amend the links in the template to provide the category variable if required:

Select page:

<?php

$numPages = ceil($totalJokes/10);

for ($i = 1; $i <= $numPages; $i++):

        if ($i == $currentPage):

?>

     <a class=”currentpage” href=”/joke/list?page=<?=$i?>

          <?=!empty($categoryId) ? ‘&category=’ . $categoryId : ” ?>”>

          <?=$i?></a>

<?php else: ?>

     <a href=”/joke/list?page=<?=$i?>

     <?=!empty($categoryId) ?

     ‘&category=’ . $categoryId : ” ?>”>

     <?=$i?></a>

<?php endif; ?>

<?php endfor; ?>

</div>

We’ve used the shorthand if, which we displayed earlier to append &category=$categoryId to the link if it’s set. We’ve fixed the first problem, but the number of page links being displayed is still calculated based on the number of jokes in the entire table, rather than just a category. At the moment, the total method in the DatabaseTable class returns the total number of records in a given table. To count a subset of the records, it will need a WHERE clause. We can implement it in the same way as the find method:

public function total($field = null, $value = null) {

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

   $parameters = [];

if (!empty($field)) {

       $sql .= ‘ WHERE `’ . $field . ‘` = :value’;

       $parameters = [‘value’ => $value];

      }

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

$row = $query->fetch();

return $row[0];

}

The total method now supports doing something like echo $this->jokesTable->total(‘authorId’, 4);, which would give us the total number of jokes by the author with the id of 4We can’t do the same to count the number of jokes in a category, as there’s no categoryId column in the joke table. We need to call the total method on the jokeCategoriesTable instance: $this->jokeCategoriesTable->total(‘categoryId’, 2);, which would count the number of jokes in the category with the id of 2Instead of implementing this in the list method, let’s add a new method to the Category entity class that returns the number of jokes in that particular category: $totalJokes = $category->getNumJokes();:

public function getNumJokes() {

return $this->jokeCategoriesTable->total(‘categoryId’, $this->id);

}

This can then be called from the list method in the Joke controller:

public function list() {

  $page = $_GET[‘page’] ?? 1;

  $offset = ($page-1)*10;

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

     $category = $this->categoriesTable->findById($_GET[‘category’]);

     $jokes = $category->getJokes(10, $offset);

     $totalJokes = $category->getNumJokes();

}

else {

       $jokes = $this->jokesTable->findAll(‘jokedate DESC’, 10, $offset);

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

}

$title = ‘Joke List’;

// …

You can find this code in Final-Website.

Notice that I’ve moved the original $totalJokes variable into the else branch of the if statement. When a category is selected, $totalJokes is the total number of jokes in the selected category. When no category has been chosen, $totalJokes stores the total number of jokes in the database.

Achievement Unlocked

We showed you some additional tools that will be useful when you develop your next website. You have a basic understanding of regular expressions, along with the SQL features of LIMIT and OFFSET, and you know how to combine them to paginate data sets. You now have all the tools you need to build a real website. You know how to think about writing code, and you know how to separate out project specific code from the code you can use in future projects. You also have an understanding of the concepts behind PHP frameworks. You can jump into Symfony, Zend or Laravel, and although the code will be different, all the concepts you’ve learned  will be familiar.

What Next?

You have all the tools you need to build a fully functional PHP website and put it live on the web. Go ahead and publish your first website. It’s a great feeling! With programming, there’s always more to learn. There are different techniques and approaches you can try out, and a lot of different tools that will help you develop more efficiently and reduce bugs. You’re never “done”, you never complete the game. You just keep playing. Each time you learn something new, be it a new tool, a new technique, or even a new language, it will extend what you knew before and you’ll wonder how you ever coped without it. Things also change constantly, and it’s difficult to keep up. Don’t be disappointed: learning is fun, and as long as you don’t fall into the trap of thinking you know everything, you’ll go a long way.

Before taking the next few steps, we recommend getting at least two or three projects finished to ensure you’re comfortable with everything. It won’t sink in right away, and as you go forward, you’ll find yourself solving different sets of problems. It will take you a few attempts to get everything clear in your mind. Once you’ve done that, you can move on to the next few steps. What are those steps?

  1. Composer. Composer is a package management tool that’s used by almost all PHP projects these days. If you want to use someone else’s code in your project, you’ll need to know how to use Composer.
  2. Take a look at some PHP frameworks to see how other people do things.
  3. PHPUnit. Test-driven development has really taken off in PHP over the last few years, and for good reason. Once you start using TDD, it’s difficult to go back. Everything seems so much tidier and easier. Rather than having to load up your website, fill in your form, then check the record was inserted into a database, you can just run a script that does all that for you!
  4. Git. Git is a vital tool for software developers. You may have come across the website GitHub, which allows sharing code and collaborating with other developers. To use the site, you’ll need to understand git. But at its most basic, it’s an incredible tool. No more copy/pasting code after making a change, or commenting out large sections. Just delete it, and git will keep track of any changes you make!

With that said, there’s little else I need to add. However you proceed from this point, rest assured you’re starting out with a solid grounding in the essentials and a good understanding of the tools and techniques used by modern PHP websites. That’s more than can be said for many developers working today. Take that advantage and use it. Most importantly, go out there and write some code!

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.