© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
J. Lengstorf et al.PHP 8 for Absolute Beginnershttps://doi.org/10.1007/978-1-4842-8205-2_6

6. Databases, MVC, and Data Objects

Jason Lengstorf1  , Thomas Blom Hansen2 and Steve Prettyman3
(1)
No 392, Portland, OR, USA
(2)
Kirke Saaby, Denmark
(3)
Palm Bay, FL, USA
 

Objectives

After completing this chapter, you will be able to
  • Understand how to create MySQL/MariaDB databases

  • Manipulate data in MySQL/MariaDB tables

  • Design a database table structure

  • Use PHP for secure interaction with MySQL/MariaDB databases

  • Organize PHP scripts with a model-view-controller approach

  • Create and use PHP Data Objects

Modern websites are incredibly powerful, and much of this power derives from their ability to store information. Storing information creates highly customizable interactions between software and the user. These interactions can range from entry-based blogs and commenting systems to high-powered banking applications that handle sensitive transactions securely.

This chapter covers the basics of MySQL/MariaDB, a powerful, open source database platform. We will also explore an object-oriented approach to accessing data within PHP projects.

Note

In 2009, Sun Microsystems purchased MySQL. Within a brief period of time, Oracle purchased Sun Microsystems. There was a concern that MySQL would not continue to be open source. Some of the developers of MySQL banded together to create a free and open source database system, MariaDB, which would be highly compatible with MySQL. While over the years, each product has added additional features not replicated in the other product, the basic commands and routines used to access and manipulate databases have remained the same. Many WAMP, MAMP, and LAMP stack software packages actually include MariaDB instead of MySQL (even though there is still a free version of MySQL available). Some of these products even use commands and references which indicate the database used is MySQL when it is actually MariaDB. However, since both products use virtually the same commands to access and manipulate databases, the code shown in this book and many others will work with either database system.

The Basics of MySQL/MariaDB Data Storage

MySQL/MariaDB is a relational database management system that lets us store data in multiple tables. Each table contains a set of named columns, and each row consists of a data entry into the table. Tables will often contain information about other table entries . This allows tables to be linked together using a value stored in both tables.

When comparing Tables 6-1 and 6-2, the value in one table (artist_id in the artist table) can be associated with the same value (artist_id in album table) in another table. artist_id in the artist table is the primary key for that table as it uniquely identifies each row. artist_id in the album table is the secondary key because it is not used to uniquely identify the rows in the album table (album_id uniquely identifies the rows; it is the primary key). However, it is used to link to another table.
Table 6-1

The Artist Table

artist_id

artist_name

1

Bon Iver

2

Feist

Table 6-2

The Album Table

album_id

artist_id

album_name

1

1

For Emma, Forever Ago

2

1

Blood Bank - EP3

3

2

Let It Die

4

2

The Reminder

The artist table includes two columns. The first column, artist_id, stores a unique numerical identifier for each artist. The second column, artist_name, stores the artist’s name.

The album table stores a unique identifier for each album in the album_id column and the album name in – you guessed it – the album_name column. The album table includes a third column, artist_id, that relates the artist and album tables. This column stores the unique artist identifier that corresponds to the artist who recorded the album.

At first glance, it might seem like a silly way of storing data. Why keep an abstract, incomprehensible number, instead of simply writing the artist’s name for every album? Table 6-3 gives an example of why this is not a good idea.
Table 6-3

The Badly Designed Album Table

album_id

artist

album_name

1

Bon Iver

For Emma, Forever Ago

2

Bon Iver

Blood Bank - EP3

3

Feist

Let It Die

4

fiest

The Reminder

Did you notice the spelling error for album_id? Because the artist’s name is spelled out separately for every album, it is possible to store different names for the same artist. In a tiny table with four entries, like the one preceding, it is easy to spot and correct errors. But tables are rarely that small in the real world. Imagine building a database for a music store. We would have to keep track of thousands of albums.

This problem also demonstrates the need to design databases in third-normal form. Without going into too much detail, a database is in third-normal form if data redundancy is reduced, and records can easily be uniquely identified. By creating an artist table, we eliminate the need to duplicate the artist’s name in the album table. We also eliminate the possibility that two artists might have the same name. Using an artist id keeps each artist name unique, even if the spelling of the name is the same. If we did not use an id, to uniquely identify an album row, we would need to include both the album id and the artist’s name (hopefully spelled correctly).

By designing tables that store one piece of data once, and only once, you design a robust database with data integrity. Joe Celko, a prominent figure in the SQL community, has aptly coined the slogan “One simple fact, in one place, one time.” Memorize that slogan and let your database tables follow this one rule.

Manipulating Data with SQL

We can manipulate the data in a MySQL/MariaDB table via the Structured Query Language (SQL) . SQL is a language which manages data in a relational database system. A relational database system (such as MySQL/MariaDB) formats data into tables and builds relationships between the tables, using primary and secondary keys. In this section, we will discover the basics of SQL, including the ability to create a database, create tables, insert data, update data, and display data.

phpMyAdmin is provided in many WAMP, LAMP, and MAMP stacks to easily create and maintain databases. In addition, other tools, such as MySQL Workbench ( www.mysql.com/products/workbench/ ), provide additional ways to design, develop, and secure MySQL/MariaDB databases. We will start by looking at phpMyAdmin because it is included in the XAMPP stack. But first, we must activate both Apache and MySQL from the XAMPP control panel.

A window shows the modules of X A M P P control panel version 3.3.0, compiled on April 6th 2021. The modules are service, module, P I D(s), Port(s) and Actions.

Figure 6-1

The XAMPP control panel

To start both Apache and MySQL, simply click the start button to the right of each. Once they have properly started, each will be highlighted in green, and the start button will change to a stop button. If there are problems, the program will be highlighted in red, with error messages displayed in the view box. If this occurs, copy the error message into a browser to discover what others have done to fix the problem.

With MySQL and Apache running, open a browser and navigate to http://localhost/. The XAMPP Dashboard will display. On the menu of the dashboard, select phpMyAdmin to open the phpMyAdmin control panel.

An image of the P h p my admin application shows the server's general settings, appearance settings and database server.

Figure 6-2

The phpMyAdmin control panel

We are now ready to create our first MySQL/MariaDB database!

Developing a Database for the Poll

The best way to get a feel for database-driven web pages is to create one for testing. Over the next pages, we will create a database-driven site poll. It is a simple example of database-driven development , but it is quite sufficient to demonstrate the essential principles. The simplest possible site poll will present one question to which site visitors can reply yes or no. All replies from users will be displayed, so every site user can see how others replied.

As simple as the example is, it will use many of the skills we have gained so far, and it will teach us how to integrate database-driven data in PHP projects.

The site poll relies on a database table to store the poll question and the poll replies. PHP will connect to MySQL/MariaDB and retrieve the relevant data, so it can be displayed in a browser. Using PHP, we will output an HTML form allowing site visitors to interact with the site poll. Whenever a visitor submits the form, PHP will then submit the answer and update the MySQL/MariaDB database table accordingly. Let’s begin by creating a database with a table and some poll data.

Building a Database Using CREATE

SQL uses the keyword CREATE to build a database or a database table. The CREATE statement must include the CREATE keyword and a keyword to indicate if we want to build a database or table. Logically, we must first create the database before we place any tables within it. Thus, we will use the keyword DATABASE along with the CREATE keyword to build a database. Of course, we also need to provide a name for our database.
CREATE DATABASE playground
To execute an SQL statement, we can select the SQL tab in the phpMyAdmin control panel (Figure 6-3). Once selected, a text field will be displayed to enter in any valid SQL commands. To execute the SQL, click the Go button beneath the text field.

An image of the P h p my admin application shows the server S Q L query to create a database.

Figure 6-3

The SQL tab in phpMyAdmin

We can also create a database by selecting the Databases tab.

An image of the P h p my admin application shows the server databases table to create a database.

Figure 6-4

The Databases tab in phpMyAdmin

Just enter the database name and click CREATE. We could also select the type of collation to use with the database. However, the default is acceptable.

Note

A collation is a set of rules that specify which characters in the character set come first. We know that a comes before b, but how about the character 7? Should that come before or after alphabetical characters? What about special characters such as #”#€%&? A collation explicitly states how characters should be ordered.

The CREATE TABLE Statement

As mentioned earlier, since MySQL/MariaDB is a relational database, it stores data in tables. Naturally, after creating the database, we need to create a table. We need a little more SQL knowledge to accomplish this. The general syntax for creating a table is as follows:
CREATE TABLE table_name (
    column_name datatype [any constraints or default values],
    column_name datatype [any constraints or default values]
)

Again, we use the CREATE statement to build a table, in a similar format to building the database. We will declare names for our attributes (columns) and the data type for each column or attribute. We can always add or delete columns or attributes at any time. The CREATE statement can also declare constraints or default values.

Secure Programming

When creating columns within databases, special care and consideration should be given to the data types, constraints, and default values. A secure database will restrict values entered that do not meet these parameters. If the program using the database is dependent on a value existing within a field, default values should be provided. Remember, the program might reject an invalid value before attempting to update the table. It might be acceptable to use the default value when this occurs. However, there may be cases in which the value is required in order for enough information to exist within the record created. In this case, the database column should be set to require the information. If it is not provided, the database management system will reject the attempt to enter the data.

Let’s access the playground database by clicking its name in the left column of the phpMyAdmin control panel. Click the SQL tab at the top of the screen, and we’re ready to create our first table.
CREATE TABLE poll (
    poll_id INT NOT NULL AUTO_INCREMENT,
    poll_question TEXT,
    yes INT DEFAULT 0,
    no INT DEFAULT 0,
    PRIMARY KEY (poll_id)
)
Notice the format of the CREATE statement. It is very vital that the proper parentheses and commas are included between each statement. The last line does not require a comma. Once we have entered the SQL into the text field, we can click Go to execute it. This will create the new table.

An image of the P h p my admin application showing the database S Q L to create a table Poll.

Figure 6-5

Create a new table in the playground database

We can also create a table using the default window after selecting the database from the left-hand column. Once the table name is entered, we must also select the number of columns to create. After clicking the button, the next screen will display boxes to enter in the attributes for each column. The information entered is the same as what was shown in the preceding program code.

Let’s explore the new table we created by selecting the poll table from the panel at the left side of phpMyAdmin. Next, we will select the Structure tab from the menu.

An image of the P h p my admin application showing the Structure head comes under the poll table.

Figure 6-6

Poll table structure

The poll table has four attributes or columns: poll_id, poll_question, yes, and no. Each attribute has a type (data type). The fields of a table can only hold data of the correct type. For example, we can only store integers in poll_id, yes, and no as indicated by the int type. The number 11 is the default size of the integer. This can be adjusted with SQL code or by making changes when clicking the Change keyword to the right of the column. We will use the default settings. The type setting for poll_question restricts entries to text only. Finally, the yes and no attributes are created with a default value of 0. None of the other attributes has default values.

Understanding PRIMARY KEY

The poll_id attribute includes a visual key to the right of the name. Also, it is listed at the bottom of the screen with a PRIMARY keyname . This indicates poll_id is now the primary key of poll entities. When an attribute is declared as the primary key, it must hold a unique value. So, however many rows of data the poll table will eventually contain, there can be no two identical poll_id values.

If we have a row of data with a poll_id of 1. Then if we attempt to insert another row of data also with a poll_id of 1, the new row will display an error message. A primary key is used to unambiguously identify one row of data. As an example, think about how many John Smiths there are. We could not use name as a unique identifier. However, we could give each a unique id to determine which John Smith we want to explore.

We can actually create tables in MySQL/MariaDB without a primary key, but such tables are special cases. Most of the time, you will want to create tables with a primary key, because data isn’t really useful if you can’t identify entries uniquely.

We now know that the poll table is created in such a way that a primary key poll_id must have a value which is unique. The poll_id attribute is declared as NOT NULL, meaning that a null value (null is basically the same as no entry at all) will not be accepted for poll_id. The poll_id attribute must invariably hold an integer value. The poll_id attribute cannot be left empty.

Understanding AUTO_INCREMENT

What about the auto-increment setting for poll id? It is a simple but powerful tool: The first row of data in the poll table will get a poll_id of 1. The next row will automatically get a poll_id of 2. The next row will get a poll _ id of 3, and so forth. The value of poll_id will automatically increment! A great way to make sure each row is identified by a unique value.

The INSERT Statement

With our table created, we’re ready to start storing data. Every new entry into the poll table will be stored as a separate row. For the sake of simplicity, we can start with inserting a single row of data.
INSERT INTO poll (
    poll_question
) VALUES (
    "Is PHP hard fun?"
)

This SQL statement will insert a new row of data into the table called poll. It will declare a value for the poll_question column or attribute. More specifically, the poll_question column will get a value of Is PHP hard fun? Remember how the poll table has a total of four attributes or columns? The remaining columns poll_id, yes, and no will simply be created with default values. poll_id will get a value of 1, while yes and no will both get a value of 0.

We can execute the SQL statement in the same manner as we did when creating the table. First, we select the playground database in the phpMyAdmin control panel. Then, we click the SQL tab and enter the preceding SQL statement. Finally, we click Go to execute the entered SQL statement. The system will then indicate that one row has been inserted.

Alternatively, we can select the database from the control panel. Then click insert in the menu provided. Text fields will be presented to allow us to enter data. We can then enter the “Is PHP hard fun?” statement in the pool_question text box. Then click the “Go” button at the bottom of the screen. This will generate an SQL statement similar to the one presented earlier. The system will also include the default values not previously shown. To execute the generated statement, click the “Go” button at the bottom of the screen.

To insert values for more than one column (attribute), the syntax of the INSERT statement will slightly vary.
    INSERT INTO `poll` (`poll_id`, `poll_question`, `yes`, `no`) VALUES (NULL, '"Is PHP hard fun?" ', '0', '0');)

As shown, we can specify the attribute (such as ‘yes’) in the first set of parentheses and then its related setting (‘0’) in the second set of parentheses. The positions must be the same. ‘yes’ is in the third position, and its value is also in the third position. For any attribute listed, there must be an associated value. Remember that any attributes not listed will be given a default value if one was defined in the table. Notice that in this example, poll_id is set to NULL. This setting is required for any auto numerated attributes. The NULL setting (remember, we set the restrictions to not allow NULL) will be rejected which prompts the system to generate a new number by incrementing the previous number. There are other formats that can be used to insert data. However, we will use this format because we can see clearly which value is associated with which attribute.

The SELECT Statement

Once we have inserted a row of data into the poll table, it would be nice to see the new row displayed. To retrieve data from database tables, we will use the SQL SELECT statement.
SELECT column_name, column_name FROM table_name
The SELECT keyword is used to retrieve data. The FROM keyword specifies the table in the database to retrieve the information. The SELECT statement returns a temporary table populated with any retrieved data. The temporary table will have exactly the properties indicated immediately after the SELECT keyword.
SELECT poll_id, poll_question, yes, no FROM poll
We can then paste this statement into the SQL text box, as previously mentioned, to produce the required results.

An image of P h p my admin application showing S Q L Query results for the Poll table.

Figure 6-7

Poll table with one row inserted

There is one row of data in the table. It has a poll_id of 1 and a poll_question. The yes and no columns are 1 and 0, respectively.

Secure Programming

Alternatively, we could have used the * symbol to request all fields be displayed.
SELECT * FROM poll

While this format is certainly quick and easy to create, it can cause possible vulnerabilities. Remember that a temporary table will be created with all attributes returned by the SELECT statement. This table might be accessible to hackers. Only expose those columns that are necessary for the completion of the task at hand. We can use the * symbol for quickly testing that the data was stored. But we don’t want to use it in the final product.

It is not much to look at in its present state, but this is all the data required to have a site poll displayed on a website. The website will display the poll question. Site visitors can then post their responses through an HTML form. Possible options would be yes or no. All responses from site visitors will then be stored in the yes or no fields. So, with a tiny bit of math, we could calculate the relative responses and display a message such as the following: 79% of all site visitors think PHP is hard and fun to learn.

Exercise: Using phpMyAdmin, execute code to accomplish the following. You can accomplish this task without coding, but this exercise is designed to help you practice your SQL coding skills. Start by creating a database named Students. Within that database, create a student table. Within the table, create the following attributes (fields): StudentID, Name, Address, City, State, Zip code, and Major. Populate at least three rows of information. Now display the information using the SELECT statement.

The UPDATE Statement

As you can probably determine, the yes or no values in the poll table will change every time a site visitor submits a response. We need one more SQL statement to make these changes. Let’s assume a site user just agrees that PHP is hard to learn. We would need to generate an SQL statement to increase the stored value for the yes property by a value of 1.
UPDATE poll SET yes = yes + 1
WHERE poll_id = 1
We can execute this statement in the same matter as previously discussed.

An image of the P h p my admin application showing S Q L Query from the browse page under Poll table.

Figure 6-8

Result after update to poll table

We can see that the yes property of the first row of data in the poll is incremented to 1. If we run the same SQL statement again, yes will be incremented to a value of 2.

Note how the WHERE clause limits which rows will be affected by the update. Only the row with a poll_id of 1 will be affected. Any other rows in the table will not be updated, because of the WHERE clause.

Secure Programming

An UPDATE statement without a WHERE clause would update the yes attribute of all rows in the poll table. In this case, there is just one row, so the WHERE clause isn’t absolutely necessary. However, most tables you will work with will have much more than just one row, so it’s vital that the row(s) be explicitly declared. Otherwise, attributes might be updated that should not have, which will corrupt the data in the database.

Now that we have gained some basic SQL knowledge, it is time to code for our database-driven site poll.

Coding a Database-Driven Site Poll

Obviously, our program will connect to a database from PHP, and additional PHP scripts will display the content for the site. PHP is a very forgiving language, and we can approach this task in many ways. But some ways are more scalable than others. Some that seem easy at first can transform code into a completely disorganized, tangled, spaghetti mess. Let’s use a tried-and-tested approach to code architecture that can be scaled to accommodate complex projects. While this is overkill for our simple site poll, it gives us an opportunity to introduce the model-view-controller (MVC) design technique .

Separating Concerns with MVC

The model-view-controller (MVC) design pattern is a common approach to organizing scripts consistently. Using a consistent approach to organizing your scripts can help you develop and debug faster and more efficiently.

The MVC framework is built upon the basic principles as defined by model-view-controller. Once you are familiar with the basic concepts, there are many MVC frameworks available to logically organize your PHP projects. Some of these include CodeIgniter, CakePHP, and Yii. These frameworks will aid in designing and developing more complex web applications.

At its most basic, MVC separates coding concerns into, you guessed it, three categories: models, views, and controllers. A model is a piece of code that represents data. Your models should also hold most logic involved in the system you’re building. A view is a piece of code that shows information visually. The information to be displayed by the view is received from a model. A controller is a piece of code that retrieves input from users and sends commands to relevant model(s). In short, MVC separates user interactions from visual representation from system logic and data. This provides the ability to divide large projects in which an HTML expert can create the views, a database expert can create the models, and a PHP programmer can create the controllers. We can also include multiple views which allow the code to be used both on the Web and in applications, without any major changes.

Note

You can read much more about MVC at http://en.wikipedia.org/wiki/Model-view-controller .

We have already seen examples of separating code into model, view, and controller. Remember how we made a template for HTML pages? We worked with a view that held a bare-bones HTML page skeleton. You can find it in the gallery we started building in Chapter 4. The view is located in ch4/templates/page.php.

In the same project, we created a model related to that view: the ch4/classes/Page_Data.class.php, which declares a number of methods and properties related to the content of the HTML pages.

The model and view were hooked up through a controller. In ch4/index.php, we assigned values to the model and made the model available to the view. It displayed a well-formed HTML5 page. Thus, index.php was our controller.

In this book, the aim is to use a simple implementation of MVC. Most other MVC implementations you will come across are likely to be much more elaborate. You can easily find many MVC examples that are not meant for beginning programmers. Once you understand the basic MVC principles and have gained some experience working with those principles in a simple context, you will find it much easier to understand the more elaborate implementations.

Exercise: Now that we have discovered that we have used MVC for our previous projects, return to the last project in Chapter 4. Change the folder names to indicate the model, view, and controller for the project. Make the necessary coding changes (it should not be very much) to reference the proper files within the new folders.

Planning the Logic

Let’s keep the poll simple. We will create an index.php to output a valid HTML5 page that will show the poll. The index will be a front controller.

A front controller is a design pattern very often seen in MVC web applications. A front controller is a single “entrance door” to a web application. We have already used a front controller in previous projects. Remember how index.php has been the only script loaded directly in your browser? It’s a front controller.

Note

The front controller design pattern is well documented online. You could start your own research at http://en.wikipedia.org/wiki/Front_Controller_pattern .

As in the previous projects, index.php will output a valid HTML5 page, and it will load the poll controller. The poll controller should return the poll as HTML, so it can be displayed on index.php. Note how every one view has its own model and its own controller (Figure 6-7).

A flowchart of the distribution of responsibilities starts from the front(page) controller to Models-Views-Controllers.

Figure 6-9

Distribution of responsibilities

Notice the poll model, the poll controller, and the poll view. These three work together to display a functional poll. We can also see that the page has its own model, view, and controller. The front controller is the page controller.

Creating the Poll Project

Let’s organize our project logically using the basic MVC structure. We will create a new folder, ch6, in our executable folder (in XAMPP it’s the htdocs folder) and another folder, poll, inside this folder. We will also create three folders inside of the poll folder: models, views, and controllers. We can now copy the Privatepage.php program from the template folder in ch4 to the views folder under poll (rename it page.php). We can also copy the Private_Page_Data.class.php program from the ch4 classes folder into the models folder (rename it Page_Data.class.php). Of course, we could also download the files for this chapter from the publisher’s website.

Now let’s create our first version of the index.php program to test everything. It will be placed directly under the poll folder .
<?php
//complete code for index.php
$nav = "";
$info = "";
include_once "models/Page_Data.class.php";
$pageData = new Page_Data();
$pageData->setTitle(“PHP/MySQL site poll example");
$pageData->setContent("<h1>Everything works so far!</h1>");
$pageData->appendContent("<div>...and content goes here.</div>");
require "views/page.php";
echo $page;
?>
Listing 6-1

index.php

This index program is just a slight modification from the original privateindex program seen previously. Some content has been removed, and the folder names and the values for title and content have changed! Let’s test it.

A image of admin webpage having main heading as, everything works so far exclamation mark and subheading saying , and the content goes here.

Figure 6-10

Output of new index.php program

Our view has now changed. We are now looking at this code with a model-view-controller perspective. We can see how the created HTML page is a combination of a view merged with a model. We can see how the front controller hooks up the model and the view and outputs a well-formed HTML5 page to the browser for the user to see.

Making a Poll Controller

With a nearly blank page created with its own model and view and a front controller setup, we can prepare a file for displaying our poll example in the browser. Sticking to the MVC approach, we will eventually need a poll model, a poll view, and a poll controller. Let’s create a bare-bones poll controller and load that from the front controller (index.php). Let’s create a new file, poll.php, in the controllers folder .
<?php
//complete code listing for controllers/poll.php
$info = "<h1>Poll will show here soon</h1>";
?>
Listing 6-2

poll.php

Next, we will load the poll controller from the front controller (which we will now name pollindex.php).
<?php
//complete code for index.php
$nav = "";
$info = "";
include_once "models/Page_Data.class.php";
$pageData = new Page_Data();
$pageData->setTitle(“PHP/MySQL site poll example");
include_once "controllers/poll.php”;
$pageData->setContent($info);
$pageData->appendContent("<div>...and content goes here.</div>");
require "views/page.php";
echo $page;
?>
Listing 6-3

pollindex.php

This format should look familiar, as it is very similar to the previous index examples in this textbook.

A image of admin webpage saying, Poll will show here soon.

Figure 6-11

Output from pollindex.php

Now our front controller and poll controller are working together to create a dynamic web page for our poll! Let’s work on our poll model next.

Making a Poll Model

With a preliminary poll controller in place, we can develop a preliminary poll model .
<?php
//complete code for models/Poll.class.php
class Poll {
    private string $poll_question = "Default Question";
    private int $yes = 0;
    private int $no = 0;
    public function getPollData() {
        return $this;
    }
    public function getPollQuestion() : string {
        return $this->poll_question;
    }
    public function getYes() : int {
        return $this->yes;
    }
    public function getNo() : int {
        return $this->no;
    }
}
?>
Listing 6-4

Poll.class.php

The Poll class defines a blueprint for all Poll objects. The Poll class includes a method, getPollData(), which returns $this. What does the return accomplish? The simple answer is that it returns a pointer (address in memory) to the object for whatever code calls this method. What object, there is not one in this program? Before this method is called, an instance of this class (which we will see soon) must be created. Once it is created, then the getPollData() method can be called. At that point, an object with the contents of this class will exist in its own space in memory. The return statement passes a reference (pointer) to the object to the code that has called the function. This will allow the calling code to directly access and use the object and its contents.

The $pollData object has properties and get methods for poll_question, yes, and no. The $pollData object represents all the content required to show a poll. In other words, the $pollData models poll data.

Making a Poll View

We can now create a poll view to look at our data (contained in the model we just created).
<?php
//complete code for views/poll-html.php
$info = "
<aside id='poll'>
<h1>Poll results</h1>
<ul>
<li>" . $pollData->getYes() . " said yes</li>
<li>" . $pollData->getNo() . " said no</li>
</ul>
</aside>";
?>
Listing 6-5

poll-html.php

This view uses the get methods to retrieve the results of the poll (currently defaulting to zero values) and places them into an unordered list.

Hooking Up Poll View with Poll Model

With a preliminary poll model and poll view created, we can update the poll controller to hook up model and view and, finally, show something in the browser.
<?php
//complete code listing for controllers/poll.php
include_once "models/Poll.class.php";
$poll = new Poll();
$pollData = $poll->getPollData();
include_once "views/poll-html.php";
?>
Listing 6-6

newpoll.php

The Poll class model is included into the controller, and an instance ($poll) is created. The getPollData method is called which creates the reference to the $poll object named $pollData. We say that $pollData is now a pointer which points to the object. We can now access anything in the object using $pollData. Finally, the poll-html.php view is attached which displays the poll results. That’s it! We have an MVC poll.

An application output (newpollindex.php) shows poll results, 0 for YES and NO.

Figure 6-12

Output from newpollindex.php

Perhaps you’re dying to ask a question such as, Why should I create three different files to show a simple <ul> element? It would be a completely justified question to ask. If all you wanted was to show a <ul> element with a few hard-coded values, the MVC approach would be complete overkill. The best approach would probably be to write a short HTML program.

The point here is to introduce the MVC design pattern with a very simple example, so there is no overly complex code hiding the basic principles of MVC from your eyes. With the MVC approach, you are perfectly set up for creating a database-driven web application. The MVC architecture is hardly necessary for something this simple, but it can really solve some challenges you will come across with projects of greater complexity, such as the blogging system we will start making in the next chapter.

MVC encapsulates views from models and from controllers. That means we can change a view without changing anything else. Imagine we didn’t want a <ul> element for the poll. We could simply change the HTML tags used in views/poll-html.php and trust the rest of the code to run correctly. We can easily change a view without changing anything else.

Similarly, we could change the content and still trust our code to run as expected. It would be a simple task to set the no property to 9. We would only need to change a tiny bit of code. This poll application is built with self-contained elements.

Connecting to MySQL/MariaDB from PHP

The MVC architecture will make it a fairly straightforward task to connect the database and use database-driven data for the poll. Once we have established such a connection, we can retrieve data from our database and view it with HTML. That is the essence of an MVC database-driven website.

PHP Data Objects (PDO)

PHP provides several ways to access MySQL/MariaDB databases. In this book, we will exclusively use PHP Data Objects (PDO) . It is a very safe and efficient way of connecting to a database from PHP. PDO supports multiple databases and provides a uniform set of methods for handling most database interactions. This is a great advantage for applications that have to support multiple database types, such as PostgreSQL, Firebird, or Oracle.

Opening a Connection

It is time to connect to our database. Remember that models contain all code that retrieves data. Thus, we will create a model program to connect to our database, and soon, a program to retrieve our database information. The following code assumes that the database does not require a userid and password for access. However, in production, it is essential that the database be secured with login credentials. This requires only a simple change to update the code with the proper userid and password.
<?php
$dbInfo = "mysql:host=localhost;dbname=playground";
$dbUser = "root";
$dbPassword = "";
try {
    //create a database connection with a PDO object
    $db = new PDO( $dbInfo, $dbUser, $dbPassword );
    }
    catch (PDOException $e) {
        $error_message = $e->getMessage();
        $pageData->setContent ("<h1>Connection failed!</h1><p>$e</p>");
        exit();
    }
?>
Listing 6-7

database.php

The preceding code uses the try/catch blocks introduced previously to handle any code which might throw exceptions. When attempting to open the database, we might not be successful. Thus, this line of code is placed in the try block. If an exception is raised, the catch block will execute, displaying the exception message, our own message and exiting the program. If all works correctly, we can proceed with attempting to retrieve the data. If successful, the code creates a PDO object and stores it in the $db variable.

Note

There are other possible settings you might use for creating a PDO connection to a database. You can consult www.php.net/manual/en/book.pdo.php for complete and detailed coverage.

We will import this program into our controller soon.

Sharing the Database Connection with the Poll Model

We can use a constructor with arguments to share a database connection with the poll model.
<?php
//complete code for models/Poll.class.php
class Poll {
    private string $poll_question = "Default Question";
    private int $yes = 0;
    private int $no = 0;
    private $db;
    //new code: declare a constructor
    //method requires a database connection as argument
    public function __construct( $dbConnection ){
        //store the received connection in the $this->db property
        $this->db = $dbConnection;
    }
    public function getPollData() {
        return $this;
    }
    public function getPollQuestion() : string {
        return $this->poll_question;
    }
    public function getYes() : int {
        return $this->yes;
    }
    public function getNo() : int {
        return $this->no;
    }
}
?>
Listing 6-8

NewPoll.class.php

The constructor now accepts the pointer to the database connection object and places it in $db. Next, we will call the Poll class’s constructor and pass the PDO object pointer as an argument. Take a pause to reflect. Where in our code would we load the poll model?

From the poll controller!
<?php
//complete code listing for controllers/poll.php
include_once "models/Poll.class.php";
$poll = new Poll( $db );
$pollData = $poll->getPollData();
include_once "views/poll-html.php";
?>
Listing 6-9

newestpoll.php

Now, we have passed the database connection to the poll model. The code shares a database connection, but it doesn’t use it for anything yet. Let’s update the index.php file to now include our database connection.
<?php
//complete code for index.php
$nav = "";
$info = "";
include_once "models/Page_Data.class.php";
$pageData = new Page_Data();
$pageData->setTitle("PHP/MySQL site poll example");
include_once "models/database.php";
include_once "controllers/newestpoll.php";
$pageData->setContent($info);
require "views/page.php";
echo $page;
?>
Listing 6-10

newestpollindex.php

There are only a couple of minor changes. The database.php program has been included and the controller has been updated to the newest version. If we execute this index file, we will see no changes in the output. However, we have connected to the database and passed the pointer to the data object. We are getting close to a complete program!

The variable $db is declared in newestpollindex.php. It is available in newestpoll.php because it is included in newestpollindex.php. Including a file is a lot like copying all the code from one file and pasting it into another file. As a consequence, all variables declared in any including file can be available in other included files, if those files are included afterward.

Retrieving Data with a PDOStatement

Inside the database, we have a poll table with a poll_question and values for its yes and no attributes. We are ready to retrieve data from the database.

Remember, code dealing with the data for the poll belongs in the poll model. Thus, we need to modify the getPollData method in the Poll class. At this point, the method returns hard-coded poll data; we want to return database-driven poll data.
?php
//complete code for models/Poll.class.php
class Poll {
    private $db;
    //method requires a database connection as argument
    public function __construct( $dbConnection ){
        //store the received connection in the $this->db property
        $this->db = $dbConnection;
    }
    public function getPollData() {
    $sql = "SELECT poll_question, yes, no FROM poll WHERE poll_id = 1";
    $statement = $this->db->prepare($sql);
    $statement->execute();
    $pollData = $statement->fetchObject();
    return $pollData;
    }
}
?>
Listing 6-11

NewestPoll.class.php

The Poll class actually becomes a much smaller program. We no longer need to store the values for the question and the responses because we are passing the object which contains the information from the database back to the calling program. Thus, the properties and get methods are no longer required. It only takes a few lines of code to retrieve our information.
$sql = "SELECT poll_question, yes, no FROM poll WHERE poll_id = 1";
The property $sql contains the SQL statement to retrieve the information needed.
$statement = $this->db->prepare($sql);
The SQL string is converted to a PDOStatement object before it can be executed. prepare() converts a simple SQL string to a PDOStatement object.
$statement->execute();
The PDOStatement method called execute() executes the SQL against the MySQL/MariaDB database.
    $pollData = $statement->fetchObject();
fetchObject() retrieves the one row of data from the queried database table creating a StdClass object ($pollData). This object will publicly expose all the data we have retrieved. However, this is not much of a security concern, since we only intend to display the data to everyone, anyways. If we intended to use this data to update our database, we would need to use a more secure approach. The returned StdClass object is automatically created with properties for poll_question, yes, and no.
    return $pollData;

The object is returned to the calling program (finalpoll.php).

Note

You can consult the official documentation for PDOStatement objects at www.php.net/manual/en/class.pdostatement.php .

<?php
//complete code listing for controllers/poll.php
include_once "models/NewestPoll.class.php";
$poll = new Poll( $db );
$pollData = $poll->getPollData();
include_once "views/finalpoll-html.php";
?>
Listing 6-12

finalpoll.php

finalpoll.php has only been modified to call the updated Poll class and a slightly modified HTML view.
<?php
//complete code for views/poll-html.php
$info = "
<aside id='poll'>
    <h1>Poll results</h1>
    <ul>
        <li> $pollData->yes said yes</li>
        <li> $pollData->no said no</li>
    </ul>
</aside>
";
?>
Listing 6-13

finalpoll-html.php

Since a StdObject is returned from the database, we can directly access the properties without using get methods. Therefore, a slight change has occurred in the view program (finalpoll-html.php).
<?php
//complete code for index.php
$nav = "";
$info = "";
include_once "models/Page_Data.class.php";
$pageData = new Page_Data();
$pageData->setTitle("PHP/MySQL site poll example");
include_once "models/database.php";
include_once "controllers/finalpoll.php";
$pageData->setContent($info);
require "views/page.php";
echo $page;
?>
Listing 6-14

finalpollindex.php

The index file (controller) was also only slightly modified to call the new version of the poll controller. If we execute the finalpollindex.php program in our browser, we will discover the same output format as Figure 6-12. However, the one big difference is now the data is being pulled from the database. A true database-driven website!

Showing a Poll Form

We don’t have much of a site poll yet. Site visitors should be allowed to submit their opinions and, thus, contribute to the poll results. We must provide a form for the site visitors. Remember, all displays are views. So, let’s update our view not only to display poll results but also to accept the site visitor’s opinion.
<?php
//complete code listing for views/poll-html.php
$dataFound = isset( $pollData );
if( $dataFound === false ){
    trigger_error( 'views/completepoll-html.php needs an $pollData object' );
}
$info = "
<aside id='poll'>
    <form method='post' action='completepollindex.php'>
            <p>$pollData->poll_question</p>
            <select name='user-input'>
                <option value='yes'>Yes, it is!</option>
                <option value='no'>No, not really!</option>
            </select>
            <input type='submit' value='post' />
    </form>
    <h1>Poll results</h1>
    <ul>
        <li>$pollData->yes said yes</li>
        <li>$pollData->no said no</li>
    </ul>
</aside>
";
?>
Listing 6-15

completepoll-html.php

$dataFound = isset( $pollData );
if( $dataFound === false ){
    trigger_error( 'views/completepoll-html.php needs an $pollData object' );
}
To increase reliability of the program, we have added an if statement which determines if the $pollData information was properly created. We assumed it was previously. But we should never assume. Always expect the unexpected and check for the possibility that something went wrong. trigger_error will create a custom error message. You can create a try/catch to capture this possible error whenever the form is used in another program (such as the controller).
form method='post' action='completepollindex.php'>
            <p>$pollData->poll_question</p>
            <select name='user-input'>
                <option value='yes'>Yes, it is!</option>
                <option value='no'>No, not really!</option>
            </select>
            <input type='submit' value='post' />
    </form>

A form was created to obtain the customer’s opinion. Once the customer responds by clicking the submit button, the response is passed, via post, to the index (controller).

Updating a Database Table According to Form Input

There is one final step required to complete the site poll example. We need to retrieve any user input submitted through the form and update the poll table with the received input. If a site visitor submits a no, we will increment the value of the no attribute in the poll database table.

All data interactions are a task for a model. Let’s update the poll model class with a method for updating the database table .
<?php
//complete code for models/Poll.class.php
class Poll {
    private $db;
    //method requires a database connection as argument
    public function __construct( $dbConnection ){
        //store the received conection in the $this->db property
        $this->db = $dbConnection;
    }
    public function getPollData() {
    $sql = "SELECT poll_question, yes, no FROM poll WHERE poll_id = 1";
    $statement = $this->db->prepare($sql);
    $statement->execute();
    $pollData = $statement->fetchObject();
    return $pollData;
    }
    public function updatePoll ( $input ) {
    if ( $input === "yes" ) {
        $updateSQL = "UPDATE poll SET yes = yes+1 WHERE poll_id = 1";
    } else if ( $input === "no" ) {
        $updateSQL = "UPDATE poll SET no = no+1 WHERE poll_id = 1";
    }
    $updateStatement = $this->db->prepare($updateSQL);
    $updateStatement->execute();
}
}
?>
Listing 6-16

FinalPoll.class.php

The new method updatePoll() is similar to the getPollData method. However, if the user submitted a yes, we format an SQL string that can update the yes attribute in the poll table. If the user submitted a no, we format a different SQL string to update the no attribute. Notice that the yes or no values are incremented by one to add the user’s choice into the proper location in the database table. Whichever SQL string is created, it is stored in a variable called $updateSQL. Once the $updateSQL string is created, we again use the PDO method prepare() to convert the SQL string to a PDOStatement object, which is stored in the variable $updateStatement. We then call execute() to update the poll table.

Secure Programming

We should always be aware that a hacker can attempt to use SQL Injection to corrupt our tables and databases. SQL Injection is an attempt to insert SQL statements into a program’s use of SQL to insert or update data in the database. In future chapters, we will use prepared statements to create our SQL commands, which will reduce the possibility of successful injection. We will also validate the data before making any updates to the database. In the previous example, we did not use any information gathered from the user to format the SQL statement. Thus, the SQL statement created was secure and resistant to SQL Injection.

Responding to User Input

Controllers are responsible for dealing with user interactions . So, the poll controller is the right place to intercept user input from the poll form. There are a couple of things to add to the script.
<?php
//complete code listing for controllers/poll.php
include_once "models/FinalPoll.class.php";
$poll = new Poll( $db );
//check if form was submitted
$isPollSubmitted = isset( $_POST['user-input'] );
//if it was just submitted...
if ( $isPollSubmitted ) {
    //get input received from form
    $input = $_POST['user-input'];
    //...update model
    $poll->updatePoll( $input );
}
$pollData = $poll->getPollData();
include_once "views/completepoll-html.php";
?>
Listing 6-17

completepoll.php

//check if form was submitted
$isPollSubmitted = isset( $_POST['user-input'] );
//if it was just submitted...
if ( $isPollSubmitted ) {
We will use the isset method to determine if the user has chosen to give their opinion in the survey. If they have, we will update the results.
    //get input received from form
    $input = $_POST['user-input'];
    //...update model
    $poll->updatePoll( $input );
}
If they did, we will retrieve the value selected and place it into $input. Then we will pass that value to the updatePoll method, which will make the changes to the database table.
$pollData = $poll->getPollData();
include_once "views/completepoll-html.php";

Whether or not the user provides their opinion, the program will get the current poll results and display them by calling the HTML page.

Note

A minor change has occurred to the index (controller) program to call completepoll.php. You can view this change in the files downloaded from the publisher’s website.

To see the results, run completepollindex.php in the browser.

An application output (completepollindex.php) shows P H P and Poll results: 2 said YES, and 0 said NO.

Figure 6-13

Output from completepollindex.php

We now have a completely functional poll site!

There is one weakness to this final version. Can you see it? Once the user has entered their opinion, the program allows them to enter it again. How can this be corrected? We can change the output to only display the question the first time the page is shown. Once the opinion has been given, then we can display the page that does not ask the question again, but does display the results. We can change the results to use finalpoll-html.php which was created earlier. This will require just a change to the include statement discussed earlier.

Exercise: Adjust the final poll project to accept more than one input from the user. Adjust the controller, model, and view to request, store, and display this information.

Summary

In this chapter, we’ve learned the basics of SQL statements, as well as how to interact with a database using PHP. We learned how MVC provides a logical structure to creating programs which require data and interfaces. In the next chapters, we’ll learn how to build a blog with a basic entry manager that will allow us to create, modify, and delete entries, as well as display them on a public page.

Projects

1. The power of MVC design is the ability to change the model or view with little or no changes to the other components of the project. Review the PHP documentation ( www.php.net/manual/en/function.file.php ) on reading and writing to files. Change the model code from the examples to read and store the poll information into a file, instead of a database. You should be able to make these changes within very minimal changes to the controller or the viewer. Most of your changes should occur within the model.

2. Visit www.w3schools.in/laravel-tutorial/ and discover how Laravel can help create robust data-driven web applications using PHP and other tools.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.11.16