Wednesday, August 12, 2009

IMPROVEMENT THROUGH INHERITANCE

Anyone who has played Monopoly knows that a player’s financial situation can be improved through inheritance. In object-oriented programming (OOP), inheritance can also bring improvement. In this chapter we’ll use inheritance to improve the MySQL classes developed in Chapter 9, by simplifying error trapping and by modifying the behavior of the MySQLResultSet class.
Trapping errors is not a job that developers approach with enthusiasm. It is tedious and a distraction from the task at hand. No one sets out to write error-handling code; it is a necessary evil. Not only that, error trapping is ugly. It clutters up well-written code and often ends up obscuring what was initially readable. Further, it’s not a good idea to write error trapping in the early stages of development because you want errors to be readily apparent. For these reasons error trapping is often left until the final stages of develop- ment and, if it is done at all, it is tacked on more or less as an afterthought.

One of the big advantages of OOP is the ability to catch exceptions rather than trap errors. By catching exceptions, the task of handling errors can be centralized. This makes for much tidier code and eases the transition from development to production code—erasing the need to tack on error trapping at the end.
This improvement to error handling is made possible because of a built- in class, Exception. In this chapter you will use this class as the base class for building your own exception class.
The second improvement we’ll apply involves modifications to the user- defined class, MySQLResultSet. As you saw in the previous chapter, result sets and arrays have characteristics in common; you often need to iterate through them to examine each element. It is exceptionally easy to traverse an array by using a foreach loop. This easy traversal is the modification in behavior that we have in mind for the MySQLResultSet class. In this case, a built-in interface (rather than a class) facilitates adding this behavior to the MySQLResultSet class.

The Standard PHP Library

These planned improvements to the MySQL classes use the Standard PHP Library (SPL), a collection of classes and interfaces aimed at solving common programming problems. The SPL, new to PHP 5, is roughly comparable to the Standard Template Library in C++ or the many classes built in to the Java language. But whereas there are thousands of classes to draw upon in Java, the number available in PHP is much more modest.
We’ll use the Exception class to form the basis for a MySQLException class and the SPL to adapt the MySQLResultSet class for use with a foreach loop by using the Iterator interface. Besides the classes belonging to the SPL, there are well over 100 built-in classes in PHP 5. We’ll deal with some of the other built-in classes in Chapters 12, 14, 15, and 16.

Extending a Class Through Inheritance

One of the major advantages of OOP is that you don’t always have to start from scratch. Existing classes may be able to do the job for you. If an existing class does exactly what’s required, then you can simply use it “as is.” If it does something similar, but not exactly what you need, you can adapt it. This process of adaptation is called inheritance.
Inheritance is one of the most important features of object-oriented (OO) languages. It allows us to create new classes from existing ones, exploiting behavior that is already defined and adjusting it as necessary. The term “inheritance” is appropriate because the data members and methods of the original class become part of the newly created class. However, as with genetic inheritance, the child may be similar to the parent in some respects but different in others.
Because a child class is derived from a parent class, it is also referred to as a derived class, and its parent is called the base class. Parent classes are also some- times referred to as superclasses and derived classes as subclasses.

The Exception Class

The first step in inheriting from a class is to understand the structure of the parent class. For example, Listing 10-1 lists all the data members and meth- ods of the Exception class.

protected $message; protected $code; protected $file; protected $line; private $string; private $trace;
public function __construct($message = null, $code = 0);
public function __toString(); final public function getCode(); final public function getMessage(); final public function getFile(); final public function getLine(); final public function getTrace();
final public function getTraceAsString();
final private __clone();

Listing 10-1: Data members and methods of the Exception class

You’ll notice some unfamiliar keywords (such as protected and final) as well as a couple of unfamiliar methods that begin with a double underscore (magic methods). We’ll discuss each of these in turn.

protected

You should now have a good understanding of the keywords private and public as applied to data members or methods (if not, see Chapter 4). However, one additional piece of information about access modifiers not mentioned so far is that private methods or data members are not inherited by a derived class.
In some cases though, you may want new classes to inherit private data members. To do this, you use the access modifier, protected, in place of private. A protected data member, like a private data member, cannot be directly accessed outside its class, but it can be inherited and directly accessed by a derived class. In the specific case in question, any class derived from Exception will have direct access to $message, $code, $file, and $line, but no direct access to $string or $trace. This means that the following assignment is allowed from within a class derived from Exception:

$this->message = 'New Error';

and this is disallowed:

$this->string = 'Any string';

In addition to restricting access and controlling the way that a client
programmer can use a class, the keywords private, protected, and public play a role in inheritance. Protected methods and data members are inherited by a derived class, but restricted access is preserved.

final

The keyword final also has meaning only in the context of inheritance.
When a method is defined as final, no derived class can change it. With non-final methods, a derived class can always redeclare the function and have it do something different. A class derived from the Exception class cannot create a new getCode method. On the other hand, there are no restrictions on creating a derived method called __toString.

NOTE From the point of view of the class originator, the keyword final is a way of ensuring that certain elements of a class are not changed. Additionally, when final is applied to a class as a whole, nothing in that class can be changed.

More Magic Methods

The Exception class contains two new magic methods. We’ll discuss each in turn.

toString

If the __toString method of a class is defined, it is invoked automatically when- ever that class is displayed. For example, suppose you create an instance of the Exception class and want to echo it to the screen like so:

$e = new Exception();
echo $e;

The expected result when echoing a simple variable to the screen is obvious. For example, if the variable is an integer with the value of 5, you expect 5 to appear on the screen. However, if you echo an instance variable to the screen, and the __toString method is not defined, you’ll see something like Object id#3. Because objects are composite, that is, they are made up of a number of data members and methods, it is not readily apparent what the string representation of an object should be.
The __toString method was introduced to control what happens when a
class is displayed. It allows you to define a more meaningful string represen- tation of your class. It is called “magic” because it is invoked in the background whenever an instance variable is the object of the print or echo functions. In the example code snippet above, when $e is output to the screen, an implicit call is made to the __toString method.
A __toString method can be a convenient way of looking at the properties of an object in much the same way that the print_r function displays all the keys and values of an array. (We’ll examine this method again later in this chapter when we discuss the MySQLException class in connection with catching exceptions.)

clone
The __clone method is a bit more problematic than __toString. Whereas
__toString allows you to adjust the behavior of an object when it is displayed,
__clone is invoked when you copy your object using the clone operator. This operator (new to PHP 5) allows you to create a copy of an object rather than just a reference to it. (For those of you familiar with other OO languages, this magic method acts like a copy constructor.)
You should generally implement the __clone method for any class that is an aggregate object. An aggregate object is an object that has at least one data member that is itself an object. For example, if both Player and Team are objects and Team contains Players, then Team is an aggregate object.

NOTE See Chapter 13 for a more detailed description of the __clone method and the clone
operator and for a more extensive treatment of aggregate classes.

Replacing Errors with Exceptions

Before you create your derived class, let’s look at the code that the MySQLException class will replace. The MySQLConnect class constructor provides a good example of how your exception class will be used.
Recall that your main goal when creating your own exception class is to rid your code of messy error trapping procedures. You’ve achieved this to some extent simply by incorporating error trapping into class methods, but you can reap further benefits.
In Listing 10-2, exceptions will replace the code that currently calls the die function. In the first case you terminate the program because mysql_connect cannot create a connection, whether because of an incorrect host, username, or password, or perhaps because the MySQL server is down. In any case, a look at the built-in error messages will help identify the problem and whether or not it is within your control.

function __construct($hostname, $username, $password){
if(MySQLConnect::$instances == 0){
$this->connection = mysql_connect($hostname, $username, $password) or
die(mysql_error(). " Error no: ".mysql_errno()); MySQLConnect::$instances = 0;
}else{
$msg = "Close the existing instance of the ". "MySQLConnect class.";
die($msg);
}
}

Listing 10-2: Code of MySQLConnect class constructor that calls the die function

In the second case execution is deliberately terminated because your class is being misused. You don’t want a client programmer to attempt to open two connections simultaneously because one is all that’s required. Here, you have two different kinds of errors, one of indeterminate cause and the other an instance of class misuse.

NOTE Recall that because PHP is not a compiled language there is no such thing as a compile- time error. By creating your own exception class you partially remedy this situation by creating messages that indicate class misuse.

The MySQLException Class

You can improve the functionality of a class when using inheritance by adding new methods or changing inherited ones. Changing inherited methods of a class is called overriding. In this particular case, the number of changes you can make to existing methods by overriding them is severely limited because, as you saw in Listing 10-1, there are only two non-final methods of the Exception class: the constructor and the __toString method. Let’s change both of these methods (see Listing 10-3).

class MySQLException extends Exception{
//no new data members
public function __construct($message, $errorno){
//check for programmer error if($errorno >= 5000){
$message = __CLASS__ ." type. Improper class usage. ". $message;
}else{
$message = __CLASS__ . " - ". $message;
}
//call the Exception constructor parent::__construct($message, $errorno);
}
//override __toString
public function __toString(){
return ("Error: $this->code - $this->message");
}
}

Listing 10-3: Code to create the MySQLException class

To inherit from an existing class and add its functionality to a newly created class, use the keyword extends and the parent class name. Since Exception is a built-in class, there’s no need to explicitly include any files. The keyword extends is all that’s needed in order to give our newly created class immediate access to all the public and protected methods and data mem- bers of its parent. This is a very succinct and elegant way of reusing code.

Overridden Methods
Listing 10-3 shows all the code required to create a class derived from Exception. There are only two methods and both are overridden parent class methods.
But let’s take a more detailed look, beginning with the constructor. Note how it checks the value of the error number. This test is designed to separate errors attributable to the programmer from all other errors.
We’ve chosen the range 5,000 and greater because this range is not used by built-in MySQL errors. The message associated with programmer errors indicates misuse of the class, and differentiating client programmer errors from other errors makes it easier to use the database classes.
For clarity, the error message includes the class name, which we avoid hard-coding by using the constant __CLASS__. After identifying the type of error, the Exception class constructor is called using the scope resolution operator and the keyword parent. ( You encountered similar syntax when you referenced a static variable in Chapter 9.) This is the syntax for calling any parent method from within a derived class, and one of the few cases where it’s necessary to invoke a magic method directly.
As you can see, there is no need to hard-code the parent class name because all constructors are invoked by calling __construct—the very reason for introducing a magic construction method in PHP 5.

NOTE If a derived class overrides a parent constructor, there is no implicit call to the parent.
The call must be made explicitly, as in Listing 10-3.

The __toString method defined in Listing 10-3 replaces the __toString method inherited from the parent class. As a result, a MySQLException echoed to the screen shows only the error number and the associated message, which is much less informative than the __toString method of the parent class (which traces the error and shows its line number). This makes for more secure production code because it reduces the information associated with an exception, but it also makes development of applications more difficult.
( You may want to comment out this code while debugging an application. By so doing, you revert to the more informative method of the parent.)

Changes to the MySQLConnect Class

The changes required so that the MySQLConnect class can use MySQLException objects are minimal. Of course the MySQLConnect class needs to know about this derived exception class, but this is easily accomplished with the following statement:

require 'MySQLException.php';

Next, you need an error code number that is greater than or equal to
5,000 (that is, outside the range used by MySQL). Then define a constant class value using the keyword const and give this constant a name using uppercase letters (per convention). The const keyword performs the same task for OOP as the define function does for procedural programming—it declares a variable that cannot be changed. Constant data members do not use access modifiers, so they are effectively public.

const ONLY_ONE_INSTANCE_ALLOWED = 5000;

The only other changes involve the constructor, as shown in Listing 10-4.

public function __construct($hostname, $username, $password){
if(MySQLConnect::$instances == 0){
if(!$this->connection = mysql_connect($hostname, $username,$password )){
throw new MySQLException(mysql_error(), mysql_errno());
}
MySQLConnect::$instances = 1;
}else{
$msg = "Close the existing instance of the ". "MySQLConnect class.";
throw new MySQLException( $msg, self::ONLY_ONE_INSTANCE_ALLOWED);
}
}

Listing 10-4: Changes to the MySQLConnect constructor

Compare Listing 10-4 with Listing 10-2. Notice that the calls to the die func- tion have been removed, and an exception has been constructed in their place. The new keyword throw ( ) is used exclusively with exceptions. It hands off the exception to be dealt with elsewhere (as you’ll see in the following section).
The first MySQLException is constructed using the built-in MySQL error number and message. In the second case an appropriate message is created and the class constant, ONLY_ONE_INSTANCE_ALLOWED, is passed to the constructor. (Notice the syntax for referencing a class constant using the scope resolution operator and the keyword self; this is exactly the same way that a static vari- able is referenced.)

Prodding Your Class into Action

If you force an exception by attempting to create a second connection without closing the first one, you see this message:

Error: 5000 – MySQLException type. Improper class usage. Close the existing instance of the MySQLConnect class.

This tells you the class the exception belongs to, that the error results from misuse of the class, and how to rectify the error.
Changes to the MySQLResultSet class are identical to the changes shown above. Constant data members with values greater than 5,000 are added to the

class in order to identify class usage errors, but otherwise existing error num- bers and messages are used. (We won’t deal with the details here; to view those changes, download the files associated with this chapter.)

NOTE Were you to develop the MySQL classes further, you might end up with an unwieldy number of constants. In that case it would make sense to remove constant data members from their respective classes and store them in a file associated with the MySQLException class, or perhaps define them all in the MySQLConnect class, thereby avoiding possible numbering conflicts.

Catching Exceptions

You have now finished all the changes in your database classes that relate to exceptions. All you need to do now is to see how exceptions are caught by enclosing your code within a try block.
A try block is a programming structure that is used to enclose code that may cause errors. It is always followed by a catch block. An error, or more properly speaking an exception, that occurs within the try is thrown and handled by the catch. This is why a try/catch block is said to handle exceptions.
However, there are important differences between error trapping and exception handling. The argument to a catch clause is always an object. Any Exception that occurs within the scope of the try block will look for a catch that has a matching Exception type as its argument.

NOTE The identification of the object type in a catch block is called type hinting. We’ll discuss this in greater detail in Chapter 11.

You should begin the try block immediately before the first line of code that might throw an exception (namely, where we create a connection object). Then enclose every subsequent line of code within the try block, except
for the catch blocks. The code is otherwise identical to that in the page.php file, included with the file downloads for Chapter 9; only the relevant parts are reproduced in Listing 10-5.

try{
$con = new MySQLConnect($hostname, $username, $password);
//all remaining code
...
}
catch(MySQLException $e){
echo $e;
exit();
}
catch(Exception $e){
echo $e;
exit();
}

Listing 10-5: The try block and catch blocks, showing how exceptions are caught

You follow the try block with two catch blocks: one to catch the MySQLException class and the other to catch the parent class, Exception. Any code that throws an exception will be caught by one of the catch blocks.
A thrown exception looks for the first matching exception type in the following catch blocks. When it finds a match, it executes the code within that block. It ignores all other catch blocks (unless it is re-thrown). For example, if a MySQLException is thrown in the try block of Listing 10-5, it will be caught by the first catch, and the code in the second catch won’t execute.
The order of the catch blocks is the inverse order of inheritance: The child class must precede its parent. Should the catch block for a parent class precede the child class, the exception will always be caught by the parent, and the child catch will be unreachable.
When using typical procedural error handling, you must check for errors immediately following the code that may cause problems. As you can see in Listing 10-5, an Exception may be caught many lines away from where the problem occurs, which is an advantage because it makes for more readable and maintainable code.

D E AL IN G W ITH E X C E P T IO N S

Your catch blocks in Listing 10-5 simply output the error number and message and end the application; there’s no need to recover from these exceptions or take any other action. But this isn’t always the case. For example, suppose you create an application that allows users to create their own SQL statements to query a data- base. When errors in syntax occur it would make sense to display the error message and reload the web page rather than simply exit the application. There are some notable differences between error handling in PHP and other languages. For instance, PHP doesn’t require that exceptions to be caught and does not support a finally block.

Implementing an Interface

Inheriting from an existing class is a very powerful tool in the OO program- mer’s arsenal. However, it’s not always the appropriate one to use, because PHP doesn’t allow a class to have more than one parent class.
This generally seems to be a good thing; it avoids the complexity that can be introduced with multiple inheritance. However, suppose that you had cre- ated a more abstract database result set class and derived your MySQLResultSet from it. With single inheritance it would be impossible for your class to also inherit from any other class.
For this reason PHP allows multiple inheritance, but only for interfaces. As you saw in Chapter 2, an interface is a class with no data members that declares but does not define methods (something that is left to the derived class). An interface acts like a skeleton, and the implementing class provides the body. Although a class can have only one parent class, it can implement any number of interfaces.

Listing 10-6 shows the code for the interface we wish to use to improve the MySQLResultSet class: the Iterator.

interface Iterator{
public function current(); public function key(); public function next(); public function rewind(); public function valid();
}

Listing 10-6: Methods of the Iterator interface

Note that instead of beginning with the keyword class, Iterator begins with interface, but otherwise it looks like a class. Notice too that method names have access modifiers and that the method declarations are followed by semicolons. There are no braces following the method names because there is no implementation—which is precisely what makes an interface an interface. The interface is a skeleton; an implementing class must flesh it out.

Learning About the Iterator Interface

Here’s a brief description of each method in the Iterator interface:

current Returns the current element
key Returns the key of the current element next Moves forward to the next element rewind Rewinds the iterator to the first element
valid Checks to see if there is a current element after calls to rewind
or next

A bit more can be gleaned from watching an iterator in action. For exam- ple, the code shown in Listing 10-7 traverses an iterable object using all of the methods in the Iterator interface.

$iterator->rewind();
while($iterator->valid()){ echo $iterator->key(); print_r($iterator->current());
$iterator->next();
}

Listing 10-7: Using the methods in the Iterator interface to traverse an iterable object

You begin by calling the rewind method to ensure that you are at the start of the result set. The call to valid controls the while loop so that it continues only as long as there is another record to retrieve. In our implementation, the key returned by the key method will be a number; it is displayed here simply for demonstration purposes. The method current returns the record that the result set currently points to. Finally, a call to next advances the record pointer.

You’ve probably used foreach loops in many different circumstances (most likely with arrays), but you may not have given much thought to what goes on in the background. Listing 10-7 shows what happens in a foreach loop. At the start of the loop an implicit call is made to the rewind method, ensuring that you are at the beginning and that the first record is ready to be displayed. If there is a valid record you can enter the loop with the record pointer pointing to the current row. The record pointer is then advanced— by making an implicit call to next—and the process is repeated until the end of the record set is reached.

IT ER AT OR M E T H OD S

We’ll seldom use the iterator methods directly. We’re implementing this interface so that we can use a MySQLResultSet within a foreach loop. In a sense, these methods are magic because they are invoked in the background by the foreach construct in much the same way that the toString method of the MySQLException class is invoked when a MySQLException object is displayed. Any object used within a foreach loop must devise its own implementation of the iterator methods. The implementation will differ depending upon the nature of the object—an iterator that traverses file directories will differ significantly from a result set iterator, for example, but all objects that implement a specific interface will exhibit common behaviors. The point of an interface is that it guarantees the existence of specific methods without specifying what exactly these methods should do.

Implementation

To implement an interface, you need to indicate inheritance in your class def- inition. When inheriting from a class you use the keyword extends, but when inheriting from an interface you use implements. Your class definition now reads

class MySQLResultSet implements Iterator

Implementing an interface also requires that all methods be defined.
In this particular case you must add the five methods of an iterator, as well as the new data members currentrow, valid, and key, to your existing class. The currentrow member will hold the value(s) of the current row. The member valid is a Boolean that indicates whether there is a current row. The member key simply functions as an array subscript.

Five New Methods

The first three methods that your new class MySQLResultSet inherits from the Iterator interface are straightforward accessor methods that return the value of the newly added data members, like so:

public function current (){
return $this->currentrow;
}
public function key (){
return $this->key;
}

public function valid (){
return $this->valid;
}

The method current returns the value of the current record if there is one; key returns its array subscript; and valid returns true unless the record pointer is positioned at the end of the record set. The more interesting meth- ods, however, are next and rewind. First, let’s look at the next method:

public function next (){
if($this->currentrow = mysql_fetch_array($this->result)){
$this->valid = true;
$this->key++;
}else{
$this->valid = false;
}
}

In this code, you see that next attempts to retrieve the next row from the
result set, and then resets the data members valid and key accordingly.
As you would expect, rewind resets the record pointer to the beginning of the result set after first checking that the number of rows is greater than 0. This method must also maintain the valid and key data members. The data member valid indicates whether there is a current row, and key is reset to 0.
Here’s the rewind method:

public function rewind (){
if(mysql_num_rows($this->result) > 0){
if( mysql_data_seek($this->result, 0)){
$this->valid = true;
$this->key = 0;
$this->currentrow = mysql_fetch_array($this->result);
}
}else{
$this->valid = false;
}
}

This method works because your result set is buffered; it was created using the function mysql_query. Because a buffered result set stores all rows in mem- ory, the record pointer can be repositioned.

NOTE An unbuffered result set uses a forward-only cursor, so it cannot use the mysql_data_seek
function. Unbuffered result sets are discussed in both Chapter 15 and Chapter 16.

What to Do with Flightless Birds
Flightless birds such as the emu and the ostrich are unquestionably birds, but they lack one defining characteristic of birds—flight. Like flightless birds, unbuffered result sets lack one characteristic of an iterator. Unbuffered result sets are unquestionably iterable, but they cannot be rewound.

When I introduced interfaces I defined them as classes that have meth- ods but no body for those methods. A class that implements an interface must provide the body for every method of the interface. What, then, do you do with an unbuffered result set and the rewind method?
Just as flightless birds simply don’t fly, an unbuffered result set can define a rewind method that does nothing.

NOTE The problem of unwanted methods of an interface is not peculiar to PHP. Other OO languages such as Java circumvent this problem by using “adapter” classes that provide an empty implementation of unwanted methods and only require that desired methods be defined.

Leaving a Method Undefined

If you implement an interface but don’t define all of its methods, you’ll receive a fatal error message. For example, if you try to use the MySQLResultSet class without defining the key method, you’ll see a fatal error like this:

Class MySQLResultSet contains 1 abstract methods and must therefore be declared abstract (Iterator::key)

Not the error you would expect, perhaps, but an error nonetheless, and an informative one at that. As you can see, even though you haven’t imple- mented the key method, it hasn’t gone away because it is inherited from the Iterator interface. (The key method is considered abstract because it has no implementation.)
There are two ways to eliminate this error message. The obvious one, of course, is to define the key method. However, you could also create error-free code by adding the modifier abstract to your class by changing the declara- tion class MySQLResultSet to abstract class MySQLResultSet.
You’ve just created your first abstract class, which is a class with one or more methods that lack an implementation. A purely abstract class is one in which all methods lack an implementation, as with all methods in an inter- face. The only difference between a purely abstract class and an interface is that it is defined as a class rather than as an interface.

NOTE You cannot create an instance of an abstract class; you must inherit from it and implement the abstract method(s), as with an interface. You’ll learn about abstract classes in the next chapter.

Implementation and Access

By removing the key method and forcing an error we learned a few more things about OOP. Let’s see what we can learn by changing the access modifier of the rewind method from public to private. Do this and preview the class in your browser. You should see this fatal error:

Access level to MySQLResultSet::rewind() must be public (as in class Iterator)

Not only must you implement all the methods of the Iterator interface, you cannot make access to those methods more restrictive. If you think about it this makes good sense. The foreach construct needs a public rewind method— it would not have access to a private rewind method.
However, you can make access less restrictive because doing so will not interfere with the way other classes expect your implementation to behave. For example, you could make protected methods public. (This rule applies in all cases of inheritance, not just to interfaces.)

Iterating Through a MySQLResultSet

In Chapter 9 you traversed your result set using a while loop and the getRow
method like so:

while($row = $rs->getRow()){ echo $row[0]." - ".$row[1]; echo "<br />\n";
}

Because you’ve implemented the Iterator interface you can traverse your result set using a foreach loop. The while loop above is now replaced by this:

foreach($rs as $row ){
echo $row[0]." - ".$row[1];
echo "<br />\n";
}

As you can see, it is more difficult to implement the Iterator interface than it is to create a method suitable for use in a while loop. Although this may seem like a lot of pain for no gain, there are advantages to this approach. For exam- ple, we can iterate through a record set a number of times, by simply starting another foreach loop. The record pointer will be reset in the background with- out any action on your part. Had you used your original code, you would have had to write a rewind method and explicitly call it before repeating a while loop.

NOTE Learning about the Iterator interface is time well spent as a number of built-in classes and interfaces inherit from this interface. For example, there is a DirectoryIterator class— a versatile replacement for the DirectoryItems class you developed in the early chapters.

Where to Go from Here

In this chapter we’ve improved on our original database classes by creating our own exception class. This, in turn, allowed us to take a completely OO approach to handling exceptions rather than simply trapping errors and terminating the application. We added the ability to use a MySQLResultSet in a foreach loop by implementing the Iterator interface, and we explored the concept of inheritance both for classes and for interfaces.
We’ve spent a lot of time creating database classes because they are useful tools for making websites dynamic. In the next chapter, we’re going to take a detailed look at some of the concepts introduced here. After that we’ll take a look at other ways to add content to a website dynamically.

0 comments: