Learn PHP 7: Object-Oriented Modular Programming using HTML5, CSS3, JavaScript, XML, JSON, and MySQL (2016)

Chapter 6. Data Objects

Steve Prettyman

(1)

Georgia, USA

Electronic supplementary material

The online version of this chapter (doi:10.1007/978-1-4842-1730-6_6) contains supplementary material, which is available to authorized users.

“I’m an idealist. I don’t know where I’m going, but I’m on my way.” —Carl Sandburg, Incidentals (1904)

Chapter Objectives/Student Learning Outcomes

After completing this chapter, the student will be able to:

·               Create a data class that inserts, updates, and deletes XML or JSON data

·               Explain how to create a data class that updates MySQL data using a SQL script

·               Create a PHP program that creates a change backup log

·               Create a PHP program that can recover data from a previous backup

·               Apply changes to create up-to-date valid information

·               Use dependency injection to attach a data class to another class in the BR tier

·               Create a three-tier PHP application

The Data Class

The interface and business rules tiers should not store application information. These tiers should not even be aware of how the information is stored (text file, XML, or database) or the location of the stored information. Any information that is stored must be passed from the business rules tier to the data tier. The data tier is also responsible for reacting to requests for information from the business rules tier.

This allows the interface tier and business rules tier to be unaware of any changes in types of storage methods (text file, XML, or database) and the locations of stored items. The signature (parameters accepted) and items returned from the data tier should remain unchanged over the life of the application. As long as these do not change, there should be no changes needed in the other tiers when changes occur in the data tier.

Security and performance—When using databases it may seem logical to build a SQL string in the business rules tier and pass the string to the data tier. This would cause a major security hole in the application. Hackers could pass any SQL string (including a delete string). It may also seem logical to pass SQL update commands (DELETE, UPDATE, and INSERT) into the data tier. Again this provides a major hole. Passing data for a WHERE SQL command is also a bad idea as it might allow hackers to delete or change any combination of data in the database.

A data class should provide complete functionality for manipulating information. This includes the ability to read, insert, update, and delete information. Even if the current application does not require all these commands, logically, they should exist in the data class for future use.

A balance should be achieved between performance and the requirement to store information. While highly important information might require immediate storage, other information can be held in a data structure (list, array, and dataset) in the application until the user has completed any updates. Holding and making changes to information in the memory of the server, instead of the storage location, is much more efficient. Storing the information only after all changes have been completed will reduce several calls to the storage location down to two (initial retrieval of the information and saving of the updated information). Making changes to information in memory is always more efficient than making changes on a storage device (such as a hard drive).

Using a data class provides a logical ability to populate a data structure and to save information in a storage location automatically. Assuming that an instance of the data class will only be created when it is necessary to update information, the constructor of the class can be used to retrieve the information from storage and place it in the memory of the server. When the data object is no longer needed, logically, no more changes are required to the information. The destructor of the class can be used to return the information from memory to storage.

class dog_data

{

        function __construct()

        {

                 $xmlfile = file_get_contents(get_dog_application("datastorage"));

                 $xmlstring = simplexml:load_string($xmlfile);

                 $array = (array)$xmlstring;

                 print_r($array);

        }

}

This example constructor comes very close to providing useful information from an XML file. The PHP file_get_contents method opens a text file, drops the contents into a string, and closes the file. The constructor calls this method along with the get_dog_application method (same method that was used in dog_container in Example 5-5) to determine the file name and location of the XML data file. The contents of the file are then placed in $xmlfile. The PHP simplexml:load_string method then formats the data to allow the SimpleXML data model to traverse the information. At this point, the SimpleXML methods could be used to display and manipulate the data. However, the next line attempts to convert the XML data into an array. The (array) statement tries to use type casting. The print_r statement displays the results.

<?xml version="1.0" encoding="UTF-8"?>

<dogs>

<dog>

<dog_name>Woff</dog_name>

<dog_weight>12</dog_weight>

<dog_color>Yellow</dog_color>

<dog_breed>Lab</dog_breed>

</dog>

<dog>

<dog_name>Sam</dog_name>

<dog_weight>10</dog_weight>

<dog_color>Brown</dog_color>

<dog_breed>Lab</dog_breed>

</dog>

</dogs>

Assuming that the XML file is formatted as shown here, the output includes:

Array ( [dog] => Array ( [0] => SimpleXMLElement Object ( [dog_name] => Woff [dog_weight] => 12

[dog_color] => Yellow [dog_breed] => Lab ) [1] => SimpleXMLElement Object ( [dog_name] => Sam

[dog_weight] => 10 [dog_color] => Brown [dog_breed] => Lab ) ) )

A combination of multidimensional arrays and SimpleXML objects have been created. This does not provide useful data that can easily be manipulated. However, you can use JSON methods to trick PHP into creating a multidimensional associate array.

class dog_data

{

        function __construct()

        {

                 $xmlfile = file_get_contents(get_dog_application("datastorage"));

                 $xmlstring = simplexml:load_string($xmlfile);

                 $json = json_encode($xmlstring);

                 print_r($json);

        }

}

{"dog":[{"dog_name":"Woff","dog_weight":"12","dog_color":"Yellow","dog_breed":"Lab"},{"dog_name"

:"Sam","dog_weight":"10","dog_color":"Brown","dog_breed":"Lab"}]}

Using the PHP json_encode method changes the data into well-structured JSON data. You could use one of the several PHP techniques to manipulate JSON data or, with one additional statement (json_decode), you can create a well-structured multidimensional associate array.

class dog_data

{

        function __construct()

        {

                 $xmlfile = file_get_contents(get_dog_application("datastorage"));

                 $xmlstring = simplexml:load_string($xmlfile);

                 $json = json_encode($xmlstring);

                 $dogs_array = json_decode($json,TRUE);

                 print_r($dogs_array);

        }

}

Array ( [dog] =>

Array (

[0] => Array ( [dog_name] => Woff [dog_weight] => 12 [dog_color] => Yellow [dog_breed] => Lab )

[1] => Array ( [dog_name] => Sam [dog_weight] => 10 [dog_color] => Brown [dog_breed] => Lab ) ) )

As you can see, there is no longer a mixture of arrays and SimpleXML objects. An associate array has been created that uses keywords instead of numerical values for subscripts (indexes). In the previous example, an array called "dog" has been created with two rows (each row is represented by an array). In each row, the columns (cells) are referenced by a column name (dog_name, dog_weight, dog_color, and dog_breed) instead of indexes (0, 1, 2, 3). These rows and columns can be manipulated using some of the techniques you have seen in previous chapters.

Once you have completed all changes to the array (as requested by the business rules tier), you will return the information to the storage location in the destructor.

private $dogs_array = array(); // defined as an empty array initially

function __construct()

        {

               $xmlfile = file_get_contents(get_dog_application("datastorage"));

               $xmlstring = simplexml:load_string($xmlfile);

               $json = json_encode($xmlstring);

               $this->dogs_array = json_decode($json,TRUE);

        }

function __destruct()

{

            $xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

            $xmlstring .= "\n<dogs>\n";

            foreach ($this->dogs_array as $dogs=>$dogs_value) {

                foreach ($dogs_value as $dog => $dog_value)

                {

                        $xmlstring .="<$dogs>\n";

                        foreach ($dog_value as $column => $column_value)

                        {

                        $xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";

                        }

                        $xmlstring .= "</$dogs>\n";

                }

    }

$xmlstring .= "</dogs>\n";  file_put_contents(get_dog_application("datastorage"),$xmlstring);

}

There are many ways that you can create XML data in PHP. The previous example takes a simplistic approach by supplying the XML tags from the array. As seen in the structure, there are three sets of arrays in this multidimensional array. The first foreach loop is used to flow through the first array (dogs). The second foreach loop handles the dog arrays (rows). Once inside this loop, the third foreach loop controls the columns in each dog array (each row).

The third loop retrieves the column names (from $column) and places them in XML tags. $column is also used to pull the value in the column ($dog_value[$column]). The $xmlstring supplies the same tags and structure as in the original XML file. Note that each line includes a newline character (\n) to display different lines in the file. The structure would work without this addition. However, if makes the file more readable in a text editor.

Once the $xmlstring has been created, the code uses a combination of the PHP file_put_contents method and the get_dog_application method (from Chapter 4) to open the XML file, replace the contents with the string contained in $xmlstring, and close the file.

You need to make one more final adjustment to the constructor to allow it to handle XML parsing errors. A parsing error occurs when something is wrong with the XML structure. The previous dog_breed and dog_application XML files are not updated by the application and are fairly stable. However, the XML file for the dog’s information will be updated frequently. You need to handle any problems that may occur. You will raise a general error, which will be treated by dog_interface as an important error that is logged and e-mailed to support personnel. It will also display a "System currently not available please try again later" message to the users.

private $dogs_array = array(); defined as an empty array initially

libxml:use_internal_errors(true);

function __construct() {

         $xmlfile = file_get_contents(get_dog_application("datastorage"));

         $xmlstring = simplexml:load_string($xmlfile);

         if ($xmlstring === false) {

                  $errorString = "Failed loading XML: ";

                  foreach(libxml:get_errors() as $error) {

                         $errorString .= $error->message . " ";  }

                  throw new Exception($errorString); }

$json = json_encode($xmlstring);

$this->dogs_array = json_decode($json,TRUE);

}

By default, XML parsing errors will cause the system to display the errors to the user and shut down the program. The libxml:user_internal_errors(true) method will suppress the errors. When the string is converted to XML format via the simplexml:load_string method, the XML is parsed to determine if it is valid. If it is not valid, the method will return FALSE instead of the XML information. The if statement shown will create an $errorString and use the foreach statement to loop through each error returned by the libxml:get_errors method (which returns an array containing the errors). Once all errors are collected, it will raise an exception passing the $errorString. The dog_interface program will catch this error and process it, as shown in Chapter 5.

This example does make one bad assumption (which simplifies the example). It assumes that the $errorString does not exceed the maximum capacity of 120 characters for the log file. A very badly formatted file could quickly cause $errorString to exceed this size. This limit can be adjusted in the PHP configuration file.

With the data automatically being saved whenever the data object is removed from memory, the insert, update, and delete methods only need to adjust the contents of the multidimensional associative array. Let’s take a first look at creating a delete method since you have already seen an example in Chapter 5.

In the readerrorlog program (in Example 5-8) you created a deleterecord method. The method was used for regular multidimensional arrays. We could make a few adjustments to this routine to create the deleteRecord method for the dog_data class.

function deleteRecord($recordNumber) {

        foreach ($this->dogs_array as $dogs=>&$dogs_value) {

                for($J=$recordNumber; $J < count($dogs_value) -1; $J++)

                            {

                        foreach ($dogs_value[$J] as $column => $column_value)

                        {

                                $dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

                        }

                }

unset ($dogs_value[count($dogs_value) -1]);

         }

}

In the previous deleterecord method, the number of rows in the array and the array itself were passed into the method. The array in dog_data class is populated by the XML file containing the dog information. There is no property set with the number of records. This is not a problem. The PHP method count will return the size of an array. You can access and update the dogs_array (which is a protected private property) using the $this pointer. Methods in classes can use the this pointer to access and update protected properties; it is not necessary to pass them into a method. The only property you need to pass to the deleteRecord method is the record number ($recordNumber) to delete.

The associate array has three dimensions. The outer dimension is related to the dogs tag from the XML structure. Although there is only one “row” for dogs, a loop is still needed to move into the next array (dog). The foreach loop penetrates the dogs array and provides access to the dogarray (which was created from the dog tags in the XML file). $dogs will contain the number of the dog row currently used. $dogs_value will contain the contents of the row (an array with the values in dog_name, dog_weight, dog_color, and dog_breed).

To move through each row (array) contained in the dog array, the method uses a for loop. The conditional statement ($J < count($dogs_value) -1) uses the count method to determine the size of the dog array. The count method returns the size of the array, not the last position of the array. Thus the loop count must be less than (<) the size returned from count. One is subtracted from this value. As stated in Chapter 5, any row after a deleted row must be moved up one from its current position. The last position of the array will no longer be needed, which reduces the number of loops needed by one.

In the Chapter 5 example, a for loop was used to pull each column from the row and place it in the row above. With an associate array, you use a foreach loop. The $column parameter contains the column name ($J contains the row number) to place the values in the columns into the proper locations. After the values in the rows have been moved, the last position of the array is removed using the PHP unset method.

Similar logic can be used in almost any program language. However, PHP associate arrays allow index numbers to be skipped. Unlike other languages which would place a NULL value in a missing index, PHP associate arrays just skip the actual index. Thus any array could have indexes of 0, 1, 2, 4, 5 and a foreach loop would properly loop through the array. With this in mind we can greatly simplify the previous example delete example to only contain one line of code, just the unset command shown. The unset command would remove the index passed into the method from the dog_array. Any for loop using the dog_array would still properly loop through the array. The example code contained in the demo website provides this ability.

You can also make a few adjustments to the displayRecords method from Chapter 5 to return any record(s) that a calling program (such as the Dog class) requests.

function readRecords($recordNumber) {

if($recordNumber === "ALL") {

                return $this->dogs_array["dog"];

}

else {

                return $this->dogs_array["dog"][$recordNumber];

        } }

As you can see the readRecords method is more simplistic that the displayRecords method. All formatting of the results of this method are left to the calling program (if needed). Remember that displaying and formatting of output occurs in the interface tier, not the data tier (or business rules tier).

This method allows the calling program to request all records or a specific record. In either case it returns an array with either one row (the specific record requested) or all rows. When all rows are returned, the top array (representing the rows XML tag) is removed to keep the number of dimensions (two) the same for either selection.

The insertRecords method accepts an associate array with the subscript names previously mentioned. However, in order to allow for dependency injection and flexibility in the tag names for the XML file, the calling program does not need not know the tag names until an instance of thedog_data class is created. This can be accomplished by using the readRecords method to pull the first record and then have the calling program examine the subscript names returned from that record.

function insertRecords($records_array)

{

        $dogs_array_size = count($this->dogs_array["dog"]);

        for($I=0;$I< count($records_array);$I++)

        {

                $this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

         }

}

Note

The process of creating the dog_array using the JSON functions shown previously will create one inconsistency in creating the dog_array. If the dog_data.xml file contains only one record, the JSON functions will not create a numeric index (such as '0'). When more than one record is contained in the xml file the numeric indexes will be created (such as '0', '1'). An alternative solution to which handles these differences is provided in the demo files on the textbook website.

In the insertRecords method, all records are added to the end of the array (the calling program can sort them if needed). The current size of dogs_array is determined by the count method and stored into $dogs_array_size. The count method is also used inside the for structure to determine the size of the $records_array and to determine the number of loops. Since the results of the count method produces the size of the array, which is one more than the last subscript position, the result of count also gives the next position available to insert a record.

In the first loop, $I is 0. The first record of $records_array is placed into $dogs_array_size plus 0, or $dogs_array_size (the first open row to place a record). The next time through the loop, the second record of $records_array ($I was incremented by the loop) is placed into position$dogs_array_size plus 1. This is the next position available after the first record has been inserted. The loop will continue until there are no more records in the $records_array. By the way, this method also works well with just one record to insert (as long as it is passed as an associate array). The loop will execute only once.

The last method you need to examine is an update method. This method is a very simple form of the destructor method.

function updateRecords($records_array)

{

        foreach ($records_array as $records=>$records_value) {

                foreach ($records_value as $record => $record_value) {

                         $this->dogs_array["dog"][$records] = $records_array[$records];

                }

        }

}

This little tiny method will take any size associate array and update the dogs array. It is based on PHP’s ability to dynamically build arrays.

$records_array = Array (

0 => Array ( "dog_name" => "Jeffrey", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ),

2 => Array ( "dog_name" => "James", "dog_weight" => "21", "dog_color" => "Black", "dog_breed" => "Mixed" ));

Dynamically built arrays are not required to have values for every position in the array. If the dynamic array shown previously is passed into the updateRecords method, records 0 and 2 would be updated with the new information. The value in position 1 in the dogs array would remain untouched.

Take a moment to look at these methods. There are only two XML tags that have been coded in the methods (dogs and dog). Even those two could have been retrieved from the XML file. However, the assumption that these tags will always exist in a valid dog XML file makes logical sense. By dynamically pulling all of the other tags (dog_name, dog_weight, dog_color, and dog_breed) from the XML file, changes can be made to the file without causing any code changes. Additional tags can be added, removed, and/or changed.

Let’s put it all together.

Example 6-1. The dog_data.php file

<?php

class dog_data

{

private $dogs_array = array(); //defined as an empty array initially

private $dog_data_xml = "";

function __construct() {

        libxml:use_internal_errors(true);

        $xmlDoc = new DOMDocument();

        if ( file_exists("e5dog_applications.xml") )        {

        $xmlDoc->load( 'e5dog_applications.xml' );

        $searchNode = $xmlDoc->getElementsByTagName( "type" );

                foreach( $searchNode as $searchNode )

                {

                        $valueID = $searchNode->getAttribute('ID');

                        if($valueID == "datastorage")

                        {

                                $xmlLocation = $searchNode->getElementsByTagName( "location" );

                                $this->dog_data_xml = $xmlLocation->item(0)->nodeValue;

                                break;

                        }

                }  }

        else { throw new Exception("Dog applications xml file missing or corrupt"); }

$xmlfile = file_get_contents($this->dog_data_xml);

        $xmlstring = simplexml:load_string($xmlfile);

        if ($xmlstring === false) {

                $errorString = "Failed loading XML: ";

                foreach(libxml:get_errors() as $error) {

                        $errorString .= $error->message . " " ;  }

                throw new Exception($errorString); }

        $json = json_encode($xmlstring);

        $this->dogs_array = json_decode($json,TRUE);

        }

function __destruct()

{

        $xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

            $xmlstring .= "\n<dogs>\n";

            foreach ($this->dogs_array as $dogs=>$dogs_value) {

                foreach ($dogs_value as $dog => $dog_value)

                {

                        $xmlstring .="<$dogs>\n";

                                foreach ($dog_value as $column => $column_value)

                                {

                                $xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";

                                }

                        $xmlstring .= "</$dogs>\n";

                }

    }

        $xmlstring .= "</dogs>\n";

        file_put_contents($this->dog_data_xml,$xmlstring);

}

function deleteRecord($recordNumber)

{

        foreach ($this->dogs_array as $dogs=>&$dogs_value) {

                for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {

                        foreach ($dogs_value[$J] as $column => $column_value)

                        {

                                $dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

                        }

}

        unset ($dogs_value[count($dogs_value) -1]);

        }

}

function readRecords($recordNumber)

{

        if($recordNumber === "ALL") {

                return $this->dogs_array["dog"];

        }

        else

        {

                return $this->dogs_array["dog"][$recordNumber];

        }

}

function insertRecords($records_array)

{

        $dogs_array_size = count($this->dogs_array["dog"]);

        for($I=0;$I< count($records_array);$I++)

        {

                $this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

        }

}

function updateRecords($records_array)

{

        foreach ($records_array as $records=>$records_value)

        {

                foreach ($records_value as $record => $record_value)

                {

                            $this->dogs_array["dog"][$records] = $records_array[$records];

                }

        }

}

}

?>

Note

An alternative solution which handles associate arrays with missing indexes, and the possibility that the dog_data.xml file may contain one or zero records, is provided on the textbook website.

The only change in this final version of the dog_data class is the inclusion of get_dog_application method code in the constructor to retrieve the location and name of the XML file holding the dog data.

Example 6-2. The testdata.php file

<?php

include("dog_data.php");

$tester = new dog_data();

$records_array = Array (

0 => Array ( "dog_name" => "Sally", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ));

$tester->insertRecords($records_array);

print_r ($tester->readRecords("ALL"));

print("<br>");

$records_array = Array (

1 => Array ( "dog_name" => "Spot", "dog_weight" => "19", "dog_color" => "Green", "dog_breed" => "Lab" ));

$tester->updateRecords($records_array);

print_r ($tester->readRecords("ALL"));

print("<br>");

$tester->deleteRecord(1);

print_r ($tester->readRecords("ALL"));

$tester = NULL; // calls the destructor and saves the xml records in the file

?>

Example 6-2 tests some of the possible scenarios of using the dog_data class. Notice the last line of code calls the destructor (to save the data). This is accomplished by setting the pointer to the object ($tester) to NULL, which releases the object. This will inform the garbage collector of the operating system that the object should be removed from memory. This will cause the destructor to execute, which will update the XML file and remove the object from the memory of the server.

JSON Data

Let’s take a second to back up and look at the ability to read and write JSON data. Using the example code shown in this chapter, only the constructor and destructor will need to be adjusted when you use other forms of data besides XML. Accessing and using JSON data is even easier than using XML data.

...

$json = file_get_contents($this->dog_data_JSON);

$this->dogs_array = json_decode($json,TRUE);

if ($this->dogs_array === null && json_last_error() !== JSON_ERROR_NONE)

{

     throw new Exception("JSON error: " . json_last_error_msg());

}

...

In the constructor, after the if else structure that retrieves the location of the data from the dog_application.xml file, the multiple lines accessing and formatting the XML data can be replaced by the lines shown previously. The json_decode method (as shown previously) will attempt to format the data from the text file into the associate array format. If the data is not a valid JSON format, an exception is thrown passing the error message. Since the Exception class is used, the dog_interface program would log this information in the error log, e-mail the support personnel, and display a general message to the users.

$json = json_encode($this->dogs_array);

file_put_contents($this->dog_data_JSON,$json);

The complete code for the destructor requires only two lines. The json_encode method will convert the associate array data into JSON format. The file_put_contents method will then save the information to the proper location of the JSON file ($this->dog_data_JSON). No changes are required to any of the other methods in dog_data. Note: An example application using JSON data is available on the book’s web site under Chapter 6.

MySQL Data

This book is intended as an introduction to the PHP language. Thus, you will not spend much time learning about database usage. However, this is a good time to give a brief example of adjustments you can make to the constructor and destructor methods to access and update database information.

Note

mysql has been removed since PHP5.5. It is recommended that you use mysqli or pdo_mysql.

$mysqli =mysqli_connect($server, $db_username, $db_password, $database);

if (mysqli_connect_errno())

  {

          throw new Exception("MySQL connection error: " . mysqli_connect_error());

  }

$sql="SELECT * FROM Dogs";

$result=mysqli_query($con,$sql);

If($result===null)

{

           throw new Exception("No records retrieved from Database");

}

$this->dogs_array = mysqli_fetch_assoc($result);

mysqli_free_result($result);

mysqli_close($con);

Most of the code required for the constructor method is associated to connecting, retrieving, and disconnecting from the database. The mysqli_connect method uses the server location ($server), database user ID ($db_username), database password ($db_password), and database name ($database) to connect to the database. If mysqli_connect_errno contains any errors an Exception is thrown describing the error. If there are no errors, a SQL SELECT statement ($sql) is used to retrieve all the records from the Dogs table in the database. If no records are retrieved, another exception is thrown. If records are retrieved, the mysqli_fetch_assoc method will convert the data into an associate array. The mysqli_free_result statement releases the data from $result. The mysqli_close method closes access to the database.

The destructor takes a little more coding. However, the looping is similar to saving the XML data.

$mysqli = new mysqli($server, $db_username, $db_password, $database);

if ($mysqli->connect_errno)

{

          throw new Exception("MySQL connection error:" . $mysqli->connect_error);

}

If( (!$mysqli->query("DROP TABLE IF EXISTS Dogs") ||

   (!$mysqli->query("CREATE TABLE IF NOT EXISTS Dogs (dog_id CHAR(4), dog_name CHAR(20), dog_weight CHAR(3), dog_color CHAR(15), dog_breed CHAR(35)") )

{

throw new Exception("Dog table can't be created or deleted. Error: " . $mysqli->error);

}

foreach ($this->dogs_array as $dogs=>$dogs_value) {

        foreach ($dogs_value as $dog => $dog_value)

        {

                $dog_id = $dog_value["dog_id"];

                $dog_name = $dog_value["dog_name"];

                $dog_weight = $dog_value["dog_weight"];

                $dog_color = $dog_value["dog_color"];

                $dog_breed = $dog_value["dog_breed"];

                If(!$mysqli->query("INSERT INTO Dogs(dog_id, dog_name, dog_weight, dog_color,

                dog_breed) VALUES ('$dog_id', '$dog_name', '$dog_weight', '$dog_color',

                '$dog_breed')"))

                {

                        throw new Exception("Dog Table Insert Error: " . $mysqli->error);

                }

        }

}

...

The destructor method attempts to connect to the database. If the connection is successful, the method removes any preexisting Dogs table and creates a new one with the required fields. (Note: It would probably be better to rename the old one and create a new one.). If the old table can be removed and the new table created, then the method attempts to insert rows into the table. The SQL INSERT statement places the values from $dog_name, $dog_weight, $dog_color, and $dog_breed into a row in the table. The foreach loops retrieve each row from the associate array to be placed into the table. If any of the inserts are not successful, an exception is thrown. An example program is located under Chapter 6 on the book’s web site.

Programming note—The Apache server must be properly configured and MySQL must be properly installed to run this (or a similar) database example. $server must be set to the URL, “localhost”, or “127.0.0.1”. $db_username must be set to the user ID name to access the database ('root' if a user ID has not been configured). $db_password must be set to the database password (or '' if there is no password). $database must be set to the database name. There is a large varieties of ways to access and manipulate databases in the PHP language.

Do It

1.

Download the example files for this section from the book’s web site. Adjust the deleteRecords method to allow the ability to delete multiple records. However, also include a check to limit the amount of records that can be deleted. It would not be very secure to allow all records to be deleted. If an attempt is made to delete all records (or too many records), an exception should be raised. The exception should cause the calling program (eventually dog_interface) to write an error message to the main log file, e-mail the support personnel, and display the general message to the users (shown in Chapter 5). Adjust the testdata program to test the ability to delete multiple records and catch the exceptions.

2.

Download the example files for this section from the book’s web site. Adjust the testdata program to test all remaining scenarios that have not already been tested. These are related to inserting, updating (more than one), reading, and deleting records. Be sure to test improperly formatted information. Create a try catch block in the testdata program to capture any exceptions. You can use the try catch block from dog_interface in Chapter 5 as an example.

Backup and Recovery

There is always a possibility that something can go wrong when changes are made to stored information. While a well-developed application must filter and clean data before it is saved; it must also be prepared to handle the possibility that bad data may still flowed through and corrupt the information. In addition to intentional corruption, unforeseen problems (such as system crashes) may occur. An application must provide the ability to recover without the loss of data. This can be accomplished by logging change requests and backing up valid information. Recovery can be accomplished by using a valid backup and reapplying valid changes to the backup files to produce up-to-date information.

You can make just a few minor changes to the dogdata file (Example 6-1) to create a change log and to provide backup and recovery capability. First, you will create a main method (processRecords) that will interpret any data passed into the class. This function will simplify the recovery process by allowing the recovery program to pass all change log information into one method. This will also make dependency injection easier to accomplish.

function processRecords($change_Type, $records_array)

{

switch($change_Type)

{

        case "Delete":

                $this->deleteRecord($records_array);

                break;

        case "Insert":

                $this->insertRecords($records_array);

                break;

        case "Update":

                $this->updateRecords($records_array);

                break;

        case "Display":

                $this->readRecords($records_array);

                Break;

        default:

                throw new Exception("Invalid XML file change type: $change_Type");

}

}

All requests for changes will now be passed through this method. The method accepts a change type (Insert, Delete, Update, or Display) and the array (for Insert or Update) or the record number (for Delete or Update). The values are passed into $record_array. $record_array is dynamically created as either an array or a string. This allows the processRecords method to provide polymorphism (the ability for the same method call to accept different parameters), which is one of the requirements of an object-oriented language (along with encapsulation and inheritance). The switch statement looks at $change_Type to determine which method to call. It then calls the related method. If an invalid type is passed, an exception is thrown.

Security and performance—In a ‘live’ environment, it would be more secure to pass “codes” into this type of method instead of using a value that indicates the action that will take place. For example, 101 could be used to indicate an update. The switch statement could easily be adjusted to examine the codes to determine which method to call.

Each of the methods that you have examined previously (except for the constructor and destructor) are now set to 'private'. This makes the process much more secure; changes can only occur by using the processRecords method. Three code lines have also been added to the end of each of the three methods to provide backup and recovery capabilities.

...

$change_string = date('mdYhis') . " | Delete | " . $recordNumber . "\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

error_log($change_string,3,$chge_log_file); // might exceed 120 chars

...

The first line formats a string for a change log file. The format used is similar to the format you look at in Chapter 5. In the previous example, the record number is passed as required for the delete method.

$change_string = date('mdYhis') . " | Update | " . serialize($records_array) . "\n";

For update and insert, the arrays are passed. However, an array cannot be placed into a string. The serialize method transforms an array into a string format similar to the following.

a:1:{i:0;a:4:{s:8:"dog_name";s:7:"Spot";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}

The data in a serialized string can be returned to an array format (or another format) using the unserialize method. The second line creates a string ($chge_log_file), which uses the date method and the log file name located in the dog_applications XML file to create a backup file name (and location). The string created is then passed to this log using the error_log method. The contents of the log file will look similar to the following.

07142015042510 | Insert | a:1:{i:0;a:4:{s:8:"dog_name";s:7:"tester1";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}

07142015042510 | Update | a:1:{i:1;a:4:{s:8:"dog_name";s:7:"tester2";s:10:"dog_weight";s:2:"19";s:9:"dog_color";s:5:"Green";s:9:"dog_breed";s:3:"Lab";}}

07142015042510 | Delete | 1

This format provides all the information needed to help with the recovery process. If the current version of the dog data file is corrupted, the change log file can be used to apply changes to a good version of the file to develop a new current version.

The only other changes needed to the data class are a few additional code lines in the destructor.

$new_valid_data_file = preg_replace('/[0-9]+/', '', $this->dog_data_xml);

// remove the previous date and time if it exists

$oldxmldata = date('mdYhis') . $new_valid_data_file;

if (!rename($this->dog_data_xml, $oldxmldata))

         {

           throw new Exception("Backup file $oldxmldata could not be created.");

        }

file_put_contents($new_valid_data_file,$xmlstring);

Before the destructor uses the file_put_contents method to apply changes to the XML file, a backup should be created in case the changes cause corruption to the current data. The recovery process will allow the support personnel to select which data file contains good data and which change file(s) will be applied to the data to produce the correct current version of the data.

Because this process may use a backup file of the data, which includes a file name with a date and time, the preg_replace method is used to remove any numerical information from the data file name. The regular expression (/[0-9]+/) in the first parameter directs the method to search for all occurrences of numbers in $this->dog_data_xml. If any occurrence is found, it is replaced with the value in the second parameter (''). In this case, nothing. The new file name is then placed in $new_valid_data_file. This will not cause any change to a “normal” non-backup file name because it does not contain any numerical information. A new backup file name is created using the file name in $new_valid_data_file with the date and time information. The new backup file name is stored in $oldxmldata.

Now the last valid data can be moved to the new backup file using the rename method. The data in $this->dog_data_xml (the location of the good data without changes) is copied to the new backup file location ($oldxmldata). If the file cannot be renamed, an exception is thrown.

Finally the valid changed data (located in $xmlstring) can be placed into the new location of the valid data (which is the same file name without any date information) contained in the $new_valid_data_file property.

For example, if 07142015042510dog_data.xml contains the last valid data available, 07152015001510dog_data.xml might be the new location of this data before any changes are applied. dog_data.xml would be the location of the valid data after changes have been applied. The last coding change to the dog data class is the inclusion of a set method.

function setChangeLogFile($value)

{

$this->dog_data_xml = $value;

}

To allow the recovery application to use the last valid data, the application must have the ability to change the location of the valid data. The setChangeLogFile method changes the value in $this->dog_data_xml. This property was originally set from locating information in the dog application XML file. However, that might not currently be the location of valid data. The code added to the destructor will use the new location of the valid data, apply the changes needed, and place valid data back into the original data file. There is no need to make any changes to thedata_application XML file. After the destructor completes, the data file will now contain the most up-to-date valid data.

Example 6-3. The dogdata.php file with logging as well as backup and recovery processes

<?php

class dog_data {

private $dogs_array = array(); //defined as an empty array initially

private $dog_data_xml = "";

private $change_log_file = "change.log";

function __construct() {

        libxml:use_internal_errors(true);

        $xmlDoc = new DOMDocument();

        if ( file_exists("e5dog_applications.xml") )        {

        $xmlDoc->load( 'e5dog_applications.xml' );

        $searchNode = $xmlDoc->getElementsByTagName( "type" );

                foreach( $searchNode as $searchNode )         {

                        $valueID = $searchNode->getAttribute('ID');

                        if($valueID == "datastorage") {

                                $xmlLocation = $searchNode->getElementsByTagName( "location" );

                                $this->dog_data_xml = $xmlLocation->item(0)->nodeValue;

                                break;

                        }

                }

        }

        else { throw new Exception("Dog applications xml file missing or corrupt"); }

        $xmlfile = file_get_contents($this->dog_data_xml);

        $xmlstring = simplexml:load_string($xmlfile);

        if ($xmlstring === false) {

                $errorString = "Failed loading XML: ";

                foreach(libxml:get_errors() as $error) {

                        $errorString .= $error->message . " " ;  }

                throw new Exception($errorString); }

        $json = json_encode($xmlstring);

        $this->dogs_array = json_decode($json,TRUE);

}

function __destruct() {

        $xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

            $xmlstring .= "\n<dogs>\n";

            foreach ($this->dogs_array as $dogs=>$dogs_value) {

                foreach ($dogs_value as $dog => $dog_value) {

                        $xmlstring .="<$dogs>\n";

                        foreach ($dog_value as $column => $column_value)

                        {

                                $xmlstring .= "<$column>" . $dog_value[$column] . "</$column>\n";

                        }

                        $xmlstring .= "</$dogs>\n";

        }   }

$xmlstring .= "</dogs>\n";

$new_valid_data_file = preg_replace('/[0-9]+/', '', $this->dog_data_xml); // remove the previous date and time if it exists

$oldxmldata = date('mdYhis') . $new_valid_data_file;

if (!rename($this->dog_data_xml, $oldxmldata)) {   throw new Exception("Backup file $oldxmldata could not be created."); }

file_put_contents($new_valid_data_file,$xmlstring);

}

private function deleteRecord($recordNumber)

{

foreach ($this->dogs_array as $dogs=>&$dogs_value) {

                for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {

                        foreach ($dogs_value[$J] as $column => $column_value)

                        {

                                $dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

                        }

}

        unset ($dogs_value[count($dogs_value) -1]);

        }

        $change_string = date('mdYhis') . " | Delete | " . $recordNumber . "\n";

        $chge_log_file = date('mdYhis') . $this->change_log_file;

        error_log($change_string,3,$chge_log_file); // might exceed 120 chars

}

private function readRecords($recordNumber)

{

        if($recordNumber === "ALL") {

                return $this->dogs_array["dog"];

        } else {

        return $this->dogs_array["dog"][$recordNumber];

        }

}

private function insertRecords($records_array)

{

        $dogs_array_size = count($this->dogs_array["dog"]);

        for($I=0;$I< count($records_array);$I++) {

                $this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

        }

        $change_string = date('mdYhis') . " | Insert | " . serialize($records_array) . "\n";

        $chge_log_file = date('mdYhis') . $this->change_log_file;

        error_log($change_string,3,$chge_log_file); // might exceed 120 chars

}

private function updateRecords($records_array)

{

        foreach ($records_array as $records=>$records_value)

        {

                foreach ($records_value as $record => $record_value)

                {

                            $this->dogs_array["dog"][$records] = $records_array[$records];

                }

        }

$change_string = date('mdYhis') . " | Update | " . serialize($records_array) . "\n";

$chge_log_file = date('mdYhis') . $this->change_log_file;

        error_log($change_string,3,$chge_log_file); // might exceed 120 chars

}

function setChangeLogFile($value)

{

        $this->dog_data_xml = $value;

}

function processRecords($change_Type, $records_array)

{

switch($change_Type)

{

        case "Delete":

                $this->deleteRecord($records_array);

                break;

        case "Insert":

                $this->insertRecords($records_array);

                break;

        case "Update":

                $this->updateRecords($records_array);

                break;

        default:

                throw new Exception("Invalid XML file change type: $change_Type");

} } }

Note

An alternative solution is provided on the textbook website which will allow associate arrays with missing indexes and one or no records within the dog_data.xml file.

Now that you have the ability to provide backup and recovery, let’s make some adjustments to the readerrorlog file (in Example 5-8). The new application will need to allow the support personnel to select (and modify) any valid change log file, select the most valid data file available, and apply the changes from the change log file to produce a new valid data XML file.

if(isset($_POST['data_File']))

{

          update_XML_File_Process();

}

else if(isset($_GET['rn']))

{

          delete_Process();

}

else if(isset($_POST['change_file']))

{

          display_Process();

}

else

{

        select_File_Process();

}

Since the code has grown in length, it will be much easier to follow the logical flow (and to modify the code when necessary) if much of the work is done in methods. As with many applications, the main flow of the program becomes one embedded if else statement.

A978-1-4842-1730-6_6_Fig1_HTML.jpg

Figure 6-1.

The readchangelog.php file requesting selection of a change log file

Upon opening the application, a list box will allow the users to select which valid change log file to use (and possibly update). The else portion of the statement will direct the program to select_File_Process, which will handle this request.

A978-1-4842-1730-6_6_Fig2_HTML.jpg

Figure 6-2.

The readchangelog file displaying the selected log and requesting the valid data file

Once the user has selected a change file, the contents of the file will be displayed in the same manner as shown in Chapter 5. The display_Process method will provide this information. The user can decide to remove some entries in the change file selected. If deletions are needed, thedelete_Process method will complete the process, using the same techniques shown in Chapter 5. In addition, the same method will allow the user to select the most recent valid data file to apply changes.

Once the data file has been selected, update_XML_File_Process will apply the changes to the file using the dogdata program (in Example 6-3). The process will display a changes completed message to the user.

The select_File_Process method uses similar logic as found in the getbreeds.php program (in Example 4-5).

function select_File_Process()

{

$directory = "";

$files = glob($directory . "*change.log");

echo "<form id='file_select' name='file_select' method='post' action='readchangelog.php'>";

echo "<h3>Select a file to display</h3>";

echo "<select name='change_file' id='change_file'>";

foreach($files as $file)

{

        echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

The PHP glob method places all file names in a given directory ($directory) into an array ($files). Setting $directory to "" indicates that the current directory will be searched. The second parameter provides the ability to filter the file types retrieved. *change.log directs the method to pull all files with the ending change.log. The * (asterisk) is a wildcard character that accepts any characters. This combination will pull all the change log files produced by the dog_data class. The remaining lines create an HTML drop-down list displaying the file names retrieved. A submitwill cause the program to call itself again with the selected file residing in the change_file property.

function display_Process()

{

$change_Array = load_Array();

$row_Count = count($change_Array) -1;

displayRecords($row_Count, $change_Array, $_POST['change_file']);

}

display_Process is called when a change file has been selected. This method calls the load_Array method .

function load_Array()

{

$change_File = $_POST['change_file'];

$logFile = fopen($change_File, "r");

$row_Count = 0;

while(!feof($logFile))

{

            $change_Array[$row_Count] = explode(' | ', fgets($logFile));

            $row_Count++;

}

$row_Count--;

fclose($logFile);

return $change_Array;

}

The load_Array method is very similar to the constructor in the dog_data class. This method retrieves the value in change_file and places it in $change_File. This file is then opened and all entries in the file are placed into $change_Array. The explode method will produce three columns (date/time, change type, array or string used for the change). It returns this array to the calling program (display_Process).

The array is returned to $change_Array in display_Process. The count method determines the size of this array. Its value is placed in $row_count. displayRecords is called, passing the row_count, the change_Array, and the change_file into displayRecords.

function displayRecords($row_Count, $change_Array, $change_File)

{

echo "<html><head>";

echo "<style> table { border: 2px solid #5c744d;}  </style>";

echo "</head><body>";

echo "<table>";

echo "<caption>Log File: " . $change_File . "</caption>";

echo "<tr><th></th><th>Date/Time</th><th>Change Type</th><th>Change Data</th></tr><tr>";

for ($J=$row_Count -1; $J >= 0; $J--)

{

        echo "<td><a href='readchangelog.php?rn=$J&change_File=$change_File'>Delete</a></td>";

        for($I=0; $I < 3; $I++)

                {

                echo "<td> " . $change_Array[$J][$I] . " </td> ";

        }

        echo "</tr>";

}

echo "</table>";

echo "</body></html>";

$directory = "";

$files = glob($directory . "*dog_data.xml");

echo "<form id='data_select' name='data_select' method='post' action='readchangelog.php'>";

echo "<h3>Delete entries above or select a file to update with change log $change_File</h3>";

echo "<select name='data_File' id='data_File'>";

foreach($files as $file)

{

        echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='hidden' id='change_file' name='change_file' value='$change_File'>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

displayRecords displays the contents of the change log file using almost exactly the same logic used in the displayRecords method of the readerrorlog program (in Example 5-8). It also uses almost the same logic as the selectFileProcess (explained previously) to display the data files for the user to select the last uncorrupted file.

If the user decides to delete some records from the change log, the delete_Process is called.

function delete_Process()

{

$change_Array = load_Array();

deleteRecord($_GET['rn'], $row_Count, $change_Array);

saveChanges($row_Count,$change_Array,$change_File);

displayRecords($row_Count,$change_Array,$change_File);

}

The delete_Process method will place the change file records into $change_Array using the same change_Array as shown before. It will pass the record number to be deleted ($_GET['rn']), the number of rows in the array ($row_Count), and the array ($change_Array) into the deleteRecordmethod. The deleteRecords method will use the same logic as shown in the deleteRecord method from the readerrorlog (in Example 5-8) program. The delete_Process will then call the saveChanges method, passing in the row_count, change_Array, and change_File information.

function saveChanges($row_Count,$change_Array,$change_File)

{

        $changeFile = fopen($change_File, "w");

        for($I=0; $I < $row_Count; $I++)

        {

            $writeString = $change_Array[$I][0] . " | " . $change_Array[$I][1] . " | " . $change_Array[$I][2];

           fwrite($changeFile, $writeString);

        }

        fclose($changeFile);

}

The saveChanges method builds the date/time-changetype-changedata format, seen previously, from the change_Array. This information is saved in $writeString and is used to replace the change log file with the updated version (minus the record that was deleted).

The delete_Process method then recalls the displayRecords method (described earlier) to display the updated change log (minus the record deleted) and the data file drop-down list.

Once the user selects the data file to be changed, the update_XML_File_Process method is called.

function update_XML_File_Process()

{

$change_Array = load_Array();

require_once("dog_data.php");

$data_Changer = new dog_data();

$row_Count = count($change_Array) -1;

for($I=0;$I < $row_Count; $I++)

{

   if($change_Array[$I][1] != "Delete")

        {

                $temp = unserialize($change_Array[$I][2]);

        }

        else

        {

                $temp = (integer)$change_Array[$I][2];

        }

        $data_Changer->processRecords($change_Array[$I][1], $temp);

}

The method calls the load_Array method to return the changes into $change_Array. The dog_data file is imported into the method to prepare for the changes to the data file selected by the user. An instance of the data_data class is created ($data_Changer).

A for loop is used to peruse the change array and to pass each change into the processRecords method of the data class. However, before the records are passed, the serialized data must be returned to an associate array format using the unserialize method. If the change request is Delete, type casting must occur to change the data (record number) into an integer. This is one of the few times that PHP requires type casting. Serialized data is not considered a data type. The data must be transformed by either unserializing it or type casting it. The change type (Update, Delete, orInsert) is passed into the first parameter of the processRecords method. The change array or the record number is passed into the second parameter of the method. All changes are made to the data, the file is backed up, and a new change log is created, in case there are more corruption problems.

Example 6-4. The displaychangelog.php file

<?php

function displayRecords($row_Count, $change_Array, $change_File) {

echo "<html><head>";

echo "<style> table { border: 2px solid #5c744d;}  </style>";

echo "</head><body><table><caption>Log File: " . $change_File . "</caption>";

echo "<tr><th></th><th>Date/Time</th><th>Change Type</th><th>Change Data</th></tr><tr>";

for ($J=$row_Count -1; $J >= 0; $J--) {

        echo "<td><a href='readchangelog.php?rn=$J&change_File=$change_File'>Delete</a></td>";

        for($I=0; $I < 3; $I++) {

                echo "<td> " . $change_Array[$J][$I] . " </td> ";

        }

        echo "</tr>";

}

echo "</table>";

echo "</body></html>";

echo "</table>";

echo "</body></html>";

$directory = "";

$files = glob($directory . "*dog_data.xml");

echo "<form id='data_select' name='data_select' method='post' action='readchangelog.php'>";

echo "<h3>Delete entries above or select a file to update with change log $change_File</h3>";

echo "<select name='data_File' id='data_File'>";

foreach($files as $file) {

           echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='hidden' id='change_file' name='change_file' value='$change_File'>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

function deleteRecord($recordNumber, &$row_Count, &$change_Array) {

for ($J=$recordNumber; $J < $row_Count - 1; $J++) {

        for($I=0; $I < 3; $I++) {

                $change_Array[$J][$I] = $change_Array[$J + 1][$I];

        }

}

unset($change_Array[$row_Count]);

$row_Count--;

}

function saveChanges($row_Count,$change_Array,$change_File)

{

        $changeFile = fopen($change_File, "w");

        for($I=0; $I < $row_Count; $I++)

        {

            $writeString = $change_Array[$I][0] . " | " . $change_Array[$I][1] . " | " . $change_Array[$I][2];

                fwrite($changeFile, $writeString);

        }

        fclose($changeFile);

}

function delete_Process() {

$change_Array = load_Array();

deleteRecord($_GET['rn'], $row_Count, $change_Array);

saveChanges($row_Count,$change_Array,$change_File);

displayRecords($row_Count,$change_Array,$change_File);

}

function load_Array() {

$change_File = $_POST['change_file'];

$logFile = fopen($change_File, "r");

$row_Count = 0;

while(!feof($logFile)) {

            $change_Array[$row_Count] = explode(' | ', fgets($logFile));

            $row_Count++;  }

$row_Count--;

fclose($logFile);

return $change_Array;

}

function display_Process() {

$change_Array = load_Array();

$row_Count = count($change_Array) -1;

displayRecords($row_Count, $change_Array, $_POST['change_file']);

}

function select_File_Process() {

$directory = "";

$files = glob($directory . "*change.log");

echo "<form id='file_select' name='file_select' method='post' action='readchangelog.php'>";

echo "<h3>Select a file to display</h3>";

echo "<select name='change_file' id='change_file'>";

foreach($files as $file) {

echo "<option value='$file'>$file</option>";

}

echo "</select>";

echo "<input type='submit' id='submit' name='submit' value='select'>";

echo "</form>";

}

function update_XML_File_Process() {

$change_Array = load_Array();

require_once("dog_datad.php");

$data_Changer = new dog_data();

$row_Count = count($change_Array) -1;

for($I=0;$I < $row_Count; $I++) {

           if($change_Array[$I][1] != "Delete") {

                $temp = unserialize($change_Array[$I][2]);

            } else {

                $temp = (integer)$change_Array[$I][2];

        }

        $data_Changer->processRecords($change_Array[$I][1], $temp);

}

$data_Changer->setChangeLogFile($_POST['data_File']);

$data_Changer = NULL;

echo "Changes completed";

}

// main section

if(isset($_POST['data_File'])) {

            update_XML_File_Process();

} else if(isset($_GET['rn'])) {

            delete_Process();

} else if(isset($_POST['change_file'])) {

            display_Process();

} else {

select_File_Process();

}

?>

JSON Backup and Recovery

What changes are needed to provide backup and recovery for JSON data instead of XML data? Actually, no changes at all. As long as the changes from the first section of this chapter are implemented, the displaychangelog program and the changes to the dog_data class will handle JSON in the same manner as XML data.

MySQL Backup and Recovery

As you might be guessing, as long as the changes from the second section of this chapter are implemented, no additional changes will be required for backup and recovery of MySQL data. However, you can take a moment to look at an alternative way of handling MySQL data.

It is a common practice to create a SQL script file to execute against a database. A script file contains all the SQL code necessary to update the database. Using this type of file will allow you to do proper INSERT, UPDATE, and DELETE SQL commands instead of only an INSERT as previously shown. The previous example required creating an INSERT command for every record in the associate array. This includes records that were not changed. This would be inefficient for medium to large databases. You only need to update the record that changed.

You can develop the scripting file from records in the associate array that have changed. You can use the change log as the script file, as the SQL script lists all changes that have been requested. It can be rerun to fix any corrupted data.

For example, in the updateRecords method, you can create any required SQL UPDATE commands.

private function updateRecords($records_array)

{

        $chge_log_file = date('mdYhis') . $this->change_log_file;

        $chge_string = "";

        foreach ($records_array as $records=>$records_value)

        {

                $this->dogs_array["dog"][$records] = $records_array[$records];

                $chge_string .=  "UPDATE Dogs ";

                $chge_string .= "SET dog_name='" . $records_array[$records]['dog_name'] ."', ";

                $chge_string .= "dog_weight='" . $records_array[$records]['dog_weight'] ."',

                $chge_string .= "dog_color='" . $records_array[$records]['dog_color'] ."', ";

                $chge_string .= "dog_breed='" . $records_array[$records]['dog_breed'] ."' ";

                $chge_string .= "WHERE dog_id='" . $records_array[$records]['dog_id'] . "';\n";

        }

        $chge_log_file = date('mdYhis') . $this->change_log_file;

        error_log($chge_string,3,$chge_log_file); // might exceed 120 chars

}

These changes would build all the update requirements from the associate array. Similar changes could also be done to the insert and delete methods.

private function deleteRecord($recordNumber)

{

        foreach ($this->dogs_array as $dogs=>&$dogs_value) {

                for($J=$recordNumber; $J < count($dogs_value) -1; $J++) {

                        foreach ($dogs_value[$J] as $column => $column_value)

                        {

                                $dogs_value[$J][$column] = $dogs_value[$J + 1][$column];

                        }

}

        unset ($dogs_value[count($dogs_value) -1]);

        }

        $dog_id = $this->dogs_array['dog'][$recordNumber]['dog_id'];

        $chge_string = "DELETE FROM Dogs WHERE dog_id='" . $dog_id . "';\n";

        $chge_log_file = date('mdYhis') . $this->change_log_file;

        error_log($chge_string,3,$chge_log_file); // might exceed 120 chars

}

This example delete method deletes one record at a time. Thus, the delete string is built outside of the loop. The update method allows for multiple records to be updated, so the update string is built inside of the loop. The insert method will also require you to build the string inside the loop.

private function insertRecords($records_array)

{

        $chge_string = "";

        $dogs_array_size = count($this->dogs_array["dog"]);

        for($I=0;$I< count($records_array);$I++)

        {

                $this->dogs_array["dog"][$dogs_array_size + $I] = $records_array[$I];

                $dog_id = rand(0,9999); // get a number between 0 and 9999

                while (in_array($dog_id, $this->dogs_array, true)) // in array?

                { $dog_id = rand(0,9999); // if it is get another number

}

$chge_string .="INSERT INTO Dogs VALUES('";

                $chge_string .= $dog_id . "', '" . $records_array[$I]['dog_name'] . "', '";

                $chge_string .= $records_array[$I]['dog_weight'] . "', '";

                $chge_string .= $records_array[$I]['dog_color'] . "', '";

                $chge_string .= $records_array[$I]['dog_breed'] . "');";

        }

        $chge_log_file = date('mdYhis') . $this->change_log_file;

        error_log($chge_string,3,$chge_log_file); // might exceed 120 chars

}

If you review the changeRecords method, a SQL WHERE clause was built using a property named dog_id. In the XML and JSON examples you did not have this field. However, SQL UPDATE requires a where clause to determine which record(s) to update. The property used needs to be unique to identify the exact record(s). The only place the code must generate this dog_id is when a new record is created in the database (in the insertRecords method). This can be done using the PHP rand method.

The PHP rand method produces random numbers. The first parameter is the starting number (0) and the second parameter is the last number (9999). The size of this field is set to char(4) in the database, which allows up to four characters. This would allow you up to 10,000 dogs. I am sure that will be more than enough!

The while loop in the insertRecords method uses the PHP in_array method to determine if the number is already in the dogs_array (which contains all the current records in the database). A third parameter, which determines if a strict search (comparing data types) should occur, must be set to produce reliable results with multidimensional associate arrays. If the number does exist, the logic continues to generate a new random number until a unique one is found. The value is then placed in $dog_id, which will be inserted into the database along with the other fields (dog_name,dog_weight, dog_color, and dog_breed). Note: This code assumes that the Dogs table in the database has been created with the fields in the order shown (dog_id, dog_name, dog_weight, dog_color, and dog_breed).

The change log (which is now also a SQL script file) would now contain statements similar to the following:

INSERT INTO Dogs VALUES('2288', 'tester1', '19', 'Green', 'Lab');

UPDATE Dogs SET dog_name='tester1', dog_weight='19', dog_color='Green', dog_breed='Lab' WHERE dog_id='0111';

UPDATE Dogs SET dog_name='tester2', dog_weight='19', dog_color='Green', dog_breed='Lab' WHERE dog_id='1211';

DELETE FROM Dogs WHERE dog_id='1111';

This file can be run against the database when all changes have been logged. The destructor can now execute this file (instead of removing the table and inserting all the records back into a new table).

$mysqli = new mysqli($server, $db_username, $db_password, $database);

if ($mysqli->connect_errno)

{

           throw new Exception("MySQL connection error:" . $mysqli->connect_error);

}

$chge_log_file = date('mdYhis') . $this->change_log_file;

$sql = explode(";",file_get_contents($chge_log_file));

foreach($sql as $query)  {

          If(!$mysqli->query($query))

                {

                        throw new Exception("Dog Table Change Error: " . $mysqli->error);

                }

}

The code for the destructor becomes simpler than in the original MySQL example. The destructor does not need to format any SQL statements. It only needs to execute them. The method reads the change records from the change log, splitting each via the ; at the end of each SQL command line. Each line is placed in the array $sql. The logic then loops through the array and executes each statement via the query command. If any of the SQL statements has a problem, an exception is thrown (which will also send an e-mail to support personnel via the dog_interfaceprogram). An example program is available on the book’s web site.

Note: As stated, the MySQL examples are shown to help the reader see that the overall logic of the dog_data class works well with all data types. Complete books are written on using PHP to interact with databases. It is not the intent of this book to train the user to have complete knowledge of database manipulation.

Do It

1.

The dog_data class creates a new log file every time it is run. This could cause a lot of log files to be created in a very short period of time. Your mission is to either update the readchangelog file (download it from the book’s web site) or to create your own maintenance program. The code will ask the users for the number of log files (and data files) to keep. The program will then keep the most recent number of files requested. The glob method, as shown previously, can be used to retrieve all the file names. The unlink method can be used to delete a file.

unlink($file);

2.

The MySQL examples shown now produce different contents in the change log file. Download the readchangelog program from the book’s web site and make any adjustments needed to the code to properly view and delete the change log. Assuming that the database administrator has reversed the contents of the database to the last valid set of data, adjust the program to execute the change log selected against the database. Hint: Your completed program will have less code than the example from the book’s web site.

Connecting the Data Tier

Now that a reliable, well tested, data class has been created, it is time to connect it to the business rules tier. The Dog class will use the dog_data class to store the dog information in an XML file.

if (method_exists('dog_container', 'create_object')) {

$this->breedxml = $properties_array[4];

$name_error = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';

$breed_error = $this->set_dog_breed($properties_array[1]) == TRUE ? 'TRUE,' : 'FALSE,';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

$this->save_dog_data();

if(stristr($this->error_message, 'FALSE'))

{

        throw new setException($this->error_message);

}

} else

{

        exit;

}

The constructor of the dog class sets all the properties and throws an exception if there are problems. If no problems exist, the information is saved (via save_dog_data), and the program closes (exit).

In order to keep the data tier independent of the business rules tier, dependency injection will be used to discover the location and name of the dog_data class and to call the processRecords method from the class. You will borrow the logic from Chapter 4. Actually, you can use thedog_container from Example 4-10 without any changes. If you don’t remember the details of this class, revisit Chapter 4.

The dog_container class includes the get_dog_application method, which uses the logic discussed several times to search the dog application XML file for the name of the file needed (dog_data.php). The set_app method allows you to pass the application type (dogdata) to search inget_dog_application. It also includes the create_object class that will determine the class name (dog_data), make an instance of the class, and pass the class (the address of the class in memory) back to the calling program. The class does require that a clean_input function exist in the calling program. You don’t currently have one in the Dog class. However, you can create a shell (an empty function) in the class to meet this requirement.

To use the container, you can use the logic that was in the dog_interface program to make an instance of the container, find the location of dog_data, and make an instance of dog_data (without knowing the class name).

function clean_input() { }

private function save_dog_data()

{

if ( file_exists("e5dog_container.php")) {

        require_once("e5dog_container.php"); // use chapter 5 container w exception handling

} else {

        throw new Exception("Dog container file missing or corrupt");

}

        $container = new dog_container("dogdata"); // sets the tag name to look for in XML file

        $properties_array = array("dogdata"); // not used but must be passed into create_object

        $dog_data = $container->create_object($properties_array); // creates dog_data object

        $method_array = get_class_methods($dog_data);

        $last_position = count($method_array) - 1;

        $method_name = $method_array[$last_position];

        $record_Array = array(array('dog_name'=>"$this->dog_name", 'dog_weight'=>"$this->dog_weight", 'dog_color'=>"$this->dog_color", 'dog_breed'=>"$this->dog_breed"));

        $dog_data->$method_name("Insert",$record_Array);

        $dog_data = NULL;

}

The lines to accomplish this are the same as seen previously in the dog_interface; you create the dog_container, find the location of the dogdata file, and create an instance of the dog_data class. The only difference is that “dogdata” is passed in for the search. The PHP functionget_class_methods is used to create a list of methods in the dog_data class. The last method in the class is processRecords. The name of this method is pulled and placed into $method_name. The record_Array is then built to be passed into processRecords. The method is called, passing "Insert"and the record_Array. Finally, the dog_data object is set to NULL, which causes the destructor to save the data.

This allows complete dependency injection. The dog object does not know the name of the dog_data class, the location of the dog_data class, or the name of the method to call until it is determined by this code. This creates a complete break between the data tier and the business rules tier, as required for three-tier design.

Example 6-5. The dog.php file using dog_data.php to save data

<?php

class Dog

{

// ---------------------------------- Properties ------------------------------------------

private $dog_weight = 0;

private $dog_breed = "no breed";

private $dog_color = "no color";

private $dog_name = "no name";

private $error_message = "??";

private $breedxml = "";

// ---------------------------------- Constructor -----------------------------------------

function __construct($properties_array)

{

if (method_exists('dog_container', 'create_object')) {

$this->breedxml = $properties_array[4];

$name_error  = $this->set_dog_name($properties_array[0]) == TRUE ? 'TRUE,' : 'FALSE,';

$color_error = $this->set_dog_color($properties_array[2]) == TRUE ? 'TRUE,' : 'FALSE,';

$weight_error= $this->set_dog_weight($properties_array[3]) == TRUE ? 'TRUE' : 'FALSE';

$breed_error = $this->set_dog_breed($properties_array[1])  == TRUE ? 'TRUE,' : 'FALSE,';

$this->error_message = $name_error . $breed_error . $color_error . $weight_error;

$this->save_dog_data();

if(stristr($this->error_message, 'FALSE'))

{

        throw new setException($this->error_message);

} }

else

{ exit; }

}

function clean_input() { }

private function save_dog_data()

{

if ( file_exists("e5dog_container.php")) {

                require_once("e5dog_container.php"); // use chapter 5 container w exception handling

        } else {

                throw new Exception("Dog container file missing or corrupt");

        }

        $container = new dog_container("dogdata"); // sets the tag name to look for in XML file

        $properties_array = array("dogdata"); // not used but must be passed into create_object

        $dog_data = $container->create_object($properties_array); // creates dog_data object

        $method_array = get_class_methods($dog_data);

        $last_position = count($method_array) - 1;

        $method_name = $method_array[$last_position];

        $record_Array = array(array('dog_name'=>"$this->dog_name", 'dog_weight'=>"$this->dog_weight", 'dog_color'=>"$this->dog_color", 'dog_breed'=>"$this->dog_breed"));

        $dog_data->$method_name("Insert",$record_Array);

        $dog_data = NULL;

}

function set_dog_name($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 20) ? $this->dog_name = $value : $this->error_message = FALSE;

return $this->error_message;

}

function set_dog_weight($value)

{

$error_message = TRUE;

(ctype_digit($value) && ($value > 0 && $value <= 120)) ? $this->dog_weight = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_breed($value)

{

$error_message = TRUE;

($this->validator_breed($value) === TRUE) ? $this->dog_breed = $value : $this->error_message = FALSE;

return $this->error_message; }

function set_dog_color($value)

{

$error_message = TRUE;

(ctype_alpha($value) && strlen($value) <= 15) ? $this->dog_color = $value : $this->error_message = FALSE;

return $this->error_message;

}

// ----------------------------- Get Methods -----------------------------------------------

function get_dog_name()

{

return $this->dog_name;

}

function get_dog_weight()

{

return $this->dog_weight;

}

function get_dog_breed()

{

return $this->dog_breed;

}

function get_dog_color()

{

return $this->dog_color;

}

function get_properties()

{

return "$this->dog_name,$this->dog_weight,$this->dog_breed,$this->dog_color.";

}

// ------------------------------General Method---------------------------------------------

private function validator_breed($value)

{

$breed_file = simplexml:load_file($this->breedxml);

$xmlText = $breed_file->asXML();

if(stristr($xmlText, $value) === FALSE)

{

return FALSE;

}

else

{

return TRUE;

}

}

}

?>

The Dog application now has three complete tiers.

A978-1-4842-1730-6_6_Fig3_HTML.jpg

Figure 6-3.

Three-tier dog application

The interface tier contains the lab.html file and the dog_interface.php program. The business rules tier includes the dog.php class and the get_breeds class. The dog_data class is in the data tier. Any requests from the dog_interface program for communications with the business rules tier are handled by the dog_container class. Any requests for communications with the data tier (from the business rules tier) are also handled by the dog_container class. Access to the data tier is only available from or through the business rules tier. Access to the interface tier only occurs from or through the business rules tier.

The interface tier does not know the location/name of any class or method in the business rules tier. This information is discovered by using the dog_container. The business rules tier does not know the location/name of any class or method in the data tier. This information is also discovered by using the dog_container. This allows complete independence of each tier, which allows changes to occur in one tier without requiring changes in the other two tiers.

Do It

1.

Download all the files for the dog application from this chapter. Adjust file names in the dog_application XML file to discover how missing files are handled in the application. Does the application handle these problems properly? Adjust the dog_data XML file to include badly formed XML data. Run the application. Does it handle these problems properly? Empty the dog_data XML file (except for the dogs and dog tags). Run the application. Does it handle this situation properly? For any instance that causes the application to error instead of raising an exception, attempt to adjust the code to anticipate the problem and raise an exception.

Chapter Terms

Data Tier

file_get_contents

SimpleXML Data Model

simplexml:load_string

Type casting

JSON data

JSON Methods

associative array

json_encode

json_decode

Keywords

new line character

file_put_contents

libxml:user_internal_errors(true)

libxml:get_errors

count

$this

unset

Dynamic arrays

releasing an object

mysqli_connect

mysqli_connect_errno

mysqli_fetch_assoc

mysqli_free_result

mysqli_close

SQL SELECT

SQL INSERT

polymorphism

Change log file

serialize

unserialize

backup and recovery

preg_replace

regular expression

glob

embedded if then else

SQL UPDATE

SQL script file/log

SQL WHERE

rand

strict search

unlink

shell

dependence injection

Chapter Questions and Projects

Multiple Choice

1.

Which of the following describes type casting?

a.

It’s rarely needed in PHP

b.

It exists in most languages

c.

It’s needed for serialized data

d.

All of the above

2.

Which of the following describes an associative array?

a.

It has a key and value relationship (key->value)

b.

It uses numerical subscripts

c.

It does not have an index

d.

A and B

3.

Which of the following is the newline character?

a.

&

b.

.

c.

;

d.

None of these

4.

Which of the following describes JSON data?

a.

It has a similar format to arrays

b.

It cannot only be used with JavaScript

c.

It requires much more coding than when using XML data

d.

It is more secure than XML data

5.

Which of the following describes unlink?

a.

It can be used to release a parameter of an array

b.

It can be used to delete a file or directory

c.

It can be used to split apart an array

d.

None of these

6.

Which of the following describes polymorphism?

a.

The container that holds and secures an application

b.

The ability to pass multiple data types into the same signature of a method

c.

The ability to use the methods and properties of a parent class

d.

None of these

7.

Which of the following describes rand?

a.

It can be used to produce a random number

b.

The first parameter is the starting number

c.

The second parameter is the last number

d.

All of the above

8.

When using in_array, strict search does which of the following?

a.

It compares data types

b.

It is the third parameter (set to true)

c.

It should be used when searching associative arrays

d.

All of these

9.

Which of the following describes a shell method?

a.

It contains no data

b.

It has no signature

c.

It includes JSON data

d.

All of these

10.

The data tier does which of the following?

a.

Updates data

b.

Returns data

c.

Filters data

d.

All of the above

True/False

1.

Log files are important for successful recovery of information.

2.

Data is serialized to convert the data type to string.

3.

After updates have been completed, all backup copies of data can be destroyed.

4.

Dependency injection is necessary to keep the tiers (interface, business rules, and data) independent of each other.

5.

SQL script files update all records including those that have not changed.

Short Answer/Essay

1.

Explain the process used to correct data files that have been corrupted.

2.

Why is data stored in a database usually more secure than data stored in a text file?

3.

Compare and contrast the methods used to update XML data to the methods used to update MySQL data. Which is more efficient? Why?

4.

When should an e-mail be sent to the system administrator when data is being updated? What should this e-mail contain? What should it not contain? Why?

5.

How can a system administrator determine which data file is the last non-corrupted version?

Projects

1.

Adjust the code from Chapter 4 project #1 or #2 to include backup and recovery ability.

2.

Create a complete storage (XML or JSON format) and a backup and recovery system for one of the previous projects you have completed. The system should include the ability for the users to limit the number of recovery files, the ability to adjust contents of a selected file (update, insert, and delete), and the ability to execute the file against the most recent valid data. When the process is complete, any corrupted files should automatically be removed. The system should also keep its own log file to indicate when changes have occurred.

Term Project

1.

Update the ABC Computer Parts Inventory program to include storage of the data (XML or JSON format) and complete backup and recovery capabilities. The application should include a change log to indicate any data changes. Additional support programs should be included to allow for easy recovery of any corrupted data. Your complete application should use logic similar to the examples shown in this chapter.