C H A P T E R  2

Exceptions and References

In this chapter, we will explore exceptions and references, two fundamental aspects of modern object-oriented programming (OOP). Exceptions are synchronous events. The word “synchronous” means that they're reactions to events in the code itself, not reactions to external events, like signals. For instance, when an operator presses Ctrl-C on the keyboard, a signal is sent to the executing program. Exceptions are used for handling errors in an orderly, standard-compliant way. When the program (or a script, in the case of PHP) attempts to perform division by zero, an exception is raised. Exceptions can be raised (or thrown) and caught. Raising an exception actually means passing the program control to the part of the program designed to deal with those events. Modern programming languages such as PHP have means of doing that in a logical and ordered way.

Exceptions

Exceptions are objects of the class Exception, or any class that extends the class Exception. You will remember from the previous chapter that inheritance is often described as an “is a” hierarchical relationship among classes. The definition of the class Exception, taken from the documentation, is the following:

Exception {
/* Properties */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* Methods */
public __construct ([ string $message = "" [, int $code = 0
                               [, Exception $previous = NULL ]]] )
final public string getMessage ( void )
final public Exception getPrevious ( void )
final public int getCode ( void )
final public string getFile ( void )
final public int getLine ( void )
final public array getTrace ( void )
final public string getTraceAsString ( void )
public string __toString ( void )
final private void __clone ( void )
}

So, exceptions are objects that contain at least the following information when an erroneous event occurs: the error message, an error code, the file in which the exception was thrown, and the line at which the exception was thrown. Not surprisingly, exceptions are extremely useful for debugging programs and making sure that they work correctly. Or, you can think of exceptions as little balls that are thrown out of the running program when something “bad” happens and can be caught to analyze what happened. Listing 2-1 shows an example.

Listing 2-1. Exception Example

<?php
class NonNumericException extends Exception {
    private $value;
    private $msg = "Error: the value %s is not numeric! ";
    function __construct($value) {
        $this->value = $value;
    }
    public function info() {
        printf($this->msg, $this->value);
    }
}
try {
    $a = "my string";
    if (!is_numeric($argv[1])) {
        throw new NonNumericException($argv[1]);
    }
    if (!is_numeric($argv[2])) {
        throw new NonNumericException($argv[2]);
    }
    if ($argv[2] == 0) {
        throw new Exception("Illegal division by zero. ");
    }
    printf("Result: %f ", $argv[1] / $argv[2]);
}

catch(NonNumericException $exc) {
    $exc->info();
    exit(-1);
}
catch(Exception $exc) {
    print "Exception: ";
    $code = $exc->getCode();
    if (!empty($code)) {
        printf("Erorr code:%d ", $code);
    }
    print $exc->getMessage() . " ";
    exit(-1);
}
print "Variable a=$a ";
?>

When executed with different command line arguments, the script produces the following output:

./script3.1.php 4 2

Result: 2.000000
Variable a=my string

./script3.1.php 4 A

Error: the value A is not numeric!

./script3.1.php 4 0

Exception:
Illegal division by zero.

This little script is replete with things to note about the exceptions. The $argv array is a predefined global array that contains the command line arguments. There is also a predefined global variable $argc containing the number of command line arguments, just like in the C language. Now, let's devote our attention to the exceptions and their syntax. First, we defined an Exception class, which essentially ignores the existing structure of the Exception class and doesn't even call the parent::__construct method in the constructor of the extended class. That is not considered a good programming practice, and it was done here only as an illustration. The consequence of this is that our exceptions do not have the familiar getMessage and getCode functions, which will make them harder to use and call.

The usual semantics for exceptions does not apply to our class, which can cause problems if someone decides to use the getMessage() method, for instance. Next, we have created a try block, in which exceptions may occur. We're testing for the exceptions in the catch blocks, also known as exception handlers, immediately following the try block. The try block is not a normal scoping block; variables defined within the try block will remain defined outside the block. The variable $a, in particular, is printed after the division is done in the first execution, when dividing 4 by 2.

Second, observe the syntax of the throw statement: what is thrown is an exception object. Exception handlers in the catch blocks are very similar to functions that take one argument, an exception object. The order of the exception handlers is also important: PHP will pass the exception at hand to the first exception handler that can handle the exception of the type that was thrown. The handler for the exception type Exception must always come last, for it is a “catch all” that can catch an exception of any type.

When an exception is thrown, PHP looks for the first applicable handler and employs it. Had the handler for the default exception class been put before the handler for NonNumericException class, the latter would have never been executed.

The exception handler blocks, or catch blocks, look like functions. That is not a coincidence. PHP also has a “magic” method called set_exception_handler in which it is possible to set up a “catch all” exception handler that catches all uncaught exceptions. Let's rewrite the script from Listing 2-1 (see Listing 2-2).

Listing 2-2. Rewritten Script from Listing 2-1

<?php
function dflt_handler(Exception $exc) {
    print "Exception: ";
    $code = $exc->getCode();

    if (!empty($code)) {
        printf("Erorr code:%d ", $code);
    }
    print $exc->getMessage() . " ";
    exit(-1);
}
set_exception_handler('dflt_handler'),

class NonNumericException extends Exception {
    private $value;
    private $msg = "Error: the value %s is not numeric! ";
    function __construct($value) {
        $this->value = $value;
    }
    public function info() {
        printf($this->msg, $this->value);
    }
}
try {
    if (!is_numeric($argv[1])) {
        throw new NonNumericException($argv[1]);
    }
    if (!is_numeric($argv[2])) {
        throw new NonNumericException($argv[2]);
    }
    if ($argv[2] == 0) {
        throw new Exception("Illegal division by zero. ");
    }
    printf("Result: %f ", $argv[1] / $argv[2]);
}

catch(NonNumericException $exc) {
    $exc->info();
    exit(-1);
}
?>

The result of this script will be the same as the result of the original script from Listing 2-1. The exception handler declared in the set_exception_handler function is a function that takes one argument of the class Exception and is executed after all the declared exception handlers:

./script3.1b.php 4 A
Error: the value A is not numeric!

This is obviously coming from our NonNumericException handler, not from the default handler. If 0 was substituted for the letter “A” in the execution of the script3.1b.php, we would get the original result:

./script3.1b.php 4 0
Exception:
Illegal division by zero.

That was the default exception handler. When are default exception handlers useful? They're particularly useful when dealing with classes written by somebody else, like the SplFileObject class in the previous chapter. Objects of the SplFileClass will throw exceptions of the class Exception if something goes wrong, just like ADOdb.

images Note Classes from the PEAR repository will throw objects of the class PEAR_Exception when there is an error. PEAR_Exception has all the elements of the normal Exception class, with the variable $trace added to the lot. PEAR_Exception will also attempt to show the stack trace, when it is thrown and caught.

Listing 2-3 is an example of a script that attempts to open a non-existing file, using the SplFileObject class. There is also a default exception handler, which will catch the exception thrown by the SplFileObject, despite the fact that there is no explicit try { ..} catch {...} block in the code.

Listing 2-3. An Example of a Script Attempting to Open a Non-existing File Using the SplFileObject Class

<?php
function dflt_handler(Exception $exc) {
    print "Exception: ";
    $code = $exc->getCode();
    if (!empty($code)) {
        printf("Erorr code:%d ", $code);
    }
    print $exc->getMessage() . " ";
    print "File:" . $exc->getFile() . " ";
    print "Line:" . $exc->getLine() . " ";
    exit(-1);
}
set_exception_handler('dflt_handler'),
$file = new SplFileObject("non_existing_file.txt", "r");
?>

When this script is executed, the result looks like this:

Exception:
SplFileObject::__construct(non_existing_file.txt): failed to open stream: No such file or directory
File:/home/mgogala/work/book/Chapter3/script3.2.php
Line:15

When dealing with classes written by somebody else, a default catch all handler can be a very useful, albeit indiscriminate, tool. A default catch all handler will handle all otherwise uncaught exceptions, and it is normally used to terminate the program and allow for easy debugging. Of course, the programmer may want to arrange for special handling of certain situations and do something like this:

try {
    $file = new SplFileObject("non_existing_file.txt", "r");
}
catch (Exception $e) {
    $file=STDIN;
}

If there is a problem with opening the file for reading, the standard input is returned. Of course, that is no longer an object of the class SplFileObject, so the programmer must take care of the possible ramifications. As the default catch all handler is executed last, for the exceptions that are not caught by any other handlers, there are no obstacles to handle things with care and write our own exception handlers.

There is one more thing to mention: nesting of exceptions. PHP doesn't support it, unless the try blocks are nested, too. In other words, in a situation like this, the handler for ExcB will not be called, if the exception is called from the handler for ExcA:

class ExcA extends Exception {...}
class ExcB extends Exception {...}
try {... throw new ExcA(..) }
catch(ExcA $e) {  throw new ExcB(); }
catch(ExcB $e) {  // Will not be called, if thrown from the ExcA }

The only way to nest exceptions is to nest the try blocks. With respect to the exception nesting, PHP5 is the same as Java or C++.

References

The other important kind of objects in PHP are known as references. PHP references are not pointers. PHP, unlike Perl, doesn't have the “reference” type that can be used to address an object through de-reference. In PHP, the word “reference” means just another name to an object. Consider the script in Listing 2-4.

Listing 2-4. References Are Objects in PHP

<?php
class test5 {
    private $prop;
    function __construct($prop) {
        $this->prop = $prop;
    }
    function get_prop() {
        return ($this->prop);
    }
    function set_prop($prop) {
        $this->prop = $prop;
    }
}
function funct(test5 $x) {
    $x->set_prop(5);
}
$x = new test5(10);
printf("Element X has property %s ", $x->get_prop());
funct($x);
printf("Element X has property %s ", $x->get_prop());

$arr = range(1, 5);
foreach ($arr as $a) {
    $a*= 2;
}
foreach ($arr as $a) {
    print "$a ";
}
?>

When this script is executed, the following output is produced:

Element X has property 10
Element X has property 5
1
2
3
4
5

For an object variable $x, the value was changed by manipulating it within the method funct and for the array variable $arr, the value wasn't changed by manipulating the elements within the foreach loop. The answer to this confusing puzzle lies in the fact that PHP passes parameters by copy. That means that, for non-object types like numbers, strings, or arrays, another completely identical instance of the object is created, while for the object types, a reference, or another name of the object is created. When the argument $x of the class test5 was passed to the method funct, another name of the same object was created. By manipulating the new variable, we were manipulating the contents of the original object, as the new variable was just another name for the existing object. For details, please refer to Chapter1. Although PHP, unlike Perl, doesn't allow direct access to references, it still allows a degree of control over how objects are copied. Let us introduce copying by reference (see Listing 2-5).

Listing 2-5. Copying by Reference

<?php
print "Normal assignment. ";
    $x = 1;
    $y = 2;
    $x = $y;
    $y++;
    print "x=$x ";
print "Assignment by reference. ";
    $x = 1;
    $y = 2;
    $x = & $y;
    $y++;
    print "x=$x ";
?>

When this script is executed, the result looks like this:

Normal assignment.
x=2
Assignment by reference.
x=3

This script consists of two parts: normal assignment and an assignment by reference. In the first part, doing the normal assignment, a new copy of the variable $y is created and assigned to $x, throwing the previous content away. Increasing $y had absolutely no effect on the variable $x. In the second part, doing the assignment by reference, the previous content of the variable $x was also thrown away, but the variable was made into an alias (a.k.a. “reference”) for the variable $y. Increasing the variable $y by 1 was also visible in the variable $x, which displays 3 instead of 2, the value of $x following the assignment.

The same operation can also apply to the loops. In Listing 2-4, we had the following code snippet:

$arr = range(1, 5);
foreach ($arr as $a) {
    $a*= 2;
}
foreach ($arr as $a) {
    print "$a ";

The results were unchanged numbers from 1 to 5. Let's now rewrite this using the reference operator &:

$arr = range(1,5);
foreach ($arr as &$a) {
        $a *= 2;
}
print_r($arr);

The result is a changed array:

Array
(
    [0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
    [4] => 10
)

In other words, by adding & to $a, we didn't create a copy of the array element, as was done in the expression foreach($arr as $a). :Instead, we created a reference to the array members, which means that anything we do to $a within the loop will modify the actual array member and not a copy. It is not possible to make a reference to a function.

images Note Be careful when using references to the driving array of the foreach loop. If the code changes the driving array, unpredictable and unexpected results may result.

It is, however, possible to return reference from a function and to pass arguments by reference. Arguments are passed by reference when it is desirable for the function to be able to modify the original variable. The syntax is the same as for the loops: the variable to be passed by reference is simply prefixed with the ampersand character (&). :Listing 2-6 is a small example.

Listing 2-6. It Is Possible to Return References from a Function and Pass Arguments by Reference

<?php
$a = 5;
function f1($x) {
    $x+= 3;
    print "x=$x ";
}
function f2(&$x) {
    $x+= 3;
    print "x=$x ";
}

f1($a);
print "a=$a ";
f2($a);
print "a=$a ";
?>

When this little snippet is executed, the result looks like this:

x=8
a=5
x=8
a=8

When the function f1 was called, the argument was passed by value. The print statement within the function printed the value 8, but the original variable wasn't modified; it retained the value 5. When function f2 was called, the original variable was modified, as is visible from the last printout of the variable a.

References can also be returned from a function. This shouldn't be done to improve performance, as PHP does it automatically. To reiterate: a reference is simply another name for an existing variable. References can be used to circumvent the visibility protection, provided by the keyword private or protected. Listing 2-7 is an example.

Listing 2-7. References can Be Used to Circumvent the Visibility Protection

<?php
class test6 {
    private $x;
    function __construct($x = 10) {
        $this->x = $x;
    }
    function &get_x() {  // Observe the "&" in the functionimages
declaration
        return $this->x;
    }
    function set_x($x) {
        $this->x = $x;
    }
}
$a = new test6();
$b = &$a->get_x(); // $b is a reference to $x->a. Itimages
circumvents protection
                                // provided by the "private"images
qualifier.
print "b=$b ";
$a->set_x(15);
print "b=$b ";        // $b will change its value, afterimages
calling "set_x"
$b++;
print '$a->get_x()='.$a->get_x() . " "; // $a->x will changeimages
its value after $b being
                                                             images
  // incremented
?>

When executed, the output is as expected:

b=10
b=15
$a->get_x()=16

Here, variable $b is made into a reference to $a->x, which is a private member of the class test6. That was enabled by declaring the function get_x() as returning a reference. Of course, having a public reference to the private variable defeats the purpose of the visibility control. .Returning values by reference is usually a very exotic thing to do. It should be properly thought out, because it is easy to allow an unintended access by returning references from functions.

Summary

In this chapter, you learned about exceptions and references in PHP. Both are also found in other modern programming languages. The purpose of exceptions is to provide an easy and concise mechanism for error handling; the purpose of references is mainly to improve code execution speed and, occasionally, to make some programming tricks possible. Both language elements are very useful and can provide numerous benefits to the programmer. Exception handlers make error checking much more elegant, as will be shown in the chapters about the database integration.

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

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