ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


A Detailed Look at PEAR

by Joao Prado Maia
07/19/2001

PEAR as a framework for building web applications is itself built on top of a base of standard classes. These classes support basic error-handling routines, needed variables, and constants to the classes that inherit them. That is, most PEAR libraries and packages are themselves subclasses of the standard base PEAR classes.

The PEAR development team has gone to great effort to create a standard error-handling class and emulation for destructors that is extremely expandable so it can be used by all the other derived classes of the PEAR Repository. This emulation for destructors on PEAR classes is very important to programmers, as PHP itself still does not support destructors on classes.

What this means is that you need a homegrown workaround to emulate destructors on new PEAR classes. This is important to programmers that need to run specific functions when objects are to be destroyed, or when they are not needed anymore and can be removed from PHP's memory allocation.

The standard PEAR_Error class

PEAR classes should use the PEAR_Error class to handle error routines, as implemented on the database abstraction package PEAR::DB. This class is used statically using the new feature of PHP 4.0, which lets normal PHP scripts to call methods of classes without having to instantiate an object for that class. The example below shows this feature in action:

<?php
class Test
{
  function printMsg()
  {
    echo "This is a message from Test::printMsg()!";
  }

  function getValue()
  {
    $var = "PEAR is a standard!";
    return $var;
  }
}

// this next line prints the message above
Test::printMsg();

// this next line gets the string from the specified method
$msg = Test::getValue();
echo $msg;
?>

PEAR packages do the same thing with other important PEAR classes. They usually call raiseError() to create an error object that has several pieces of information on what went wrong at the time of the error. An example from the PEAR::DB MySQL driver is as follows:

<?php
  @ini_set('track_errors', true);
  if ($dbhost && $user && $pw) {
    $conn = @$connect_function($dbhost, $user, $pw);
  } elseif ($dbhost && $user) {
    $conn = @$connect_function($dbhost, $user);
  } elseif ($dbhost) {
    $conn = @$connect_function($dbhost);
  } else {
    $conn = false;
  }
  @ini_restore('track_errors');
  if (empty($conn)) {
    if (mysql_error() != '') {
      return $this->raiseError(DB_ERROR_CONNECT_FAILED, 
                               null, null, mysql_error());
    } elseif (empty($php_errormsg)) {
      return $this->raiseError(DB_ERROR_CONNECT_FAILED);
    } else {
      return $this->raiseError(DB_ERROR_CONNECT_FAILED, 
                          null, null, null, $php_errormsg);
    }
  }
?>

Related article:

An Introduction to PEAR -- Find yourself wishing PHP had an easy way to manage additional modules? Joao Prado Maia explains PEAR and shows how it fills this role.

The code actually checks if the connection to the MySQL database server was successful. The line that uses ini_set() turns on the PHP feature to track errors and to save these errors in a special variable called $php_errormsg. This variable will then be checked on the next conditional statement to determine why the connection to MySQL was not established.

More on PEAR::DB error handling

One of the nicest things about PEAR is its error-handling routines. Because PHP is an interpreted language, errors are often only discovered when users try to load your page in ways you didn't think of before. It's quite common for a developer with too little time to test his personal site to get e-mail from users saying that the site has a bug.

In some cases, the problem might be the database server that crashed, or some other reason besides his code. One good thing about PEAR error handling is that you can catch simple errors like that and use a special function to show a "Sorry, we are experiencing technical difficulties" page.

When you combine PEAR's error handling with PHP's new output buffering functions, you can even cache the page already created and show only the error message page, as follows:

<?php
// start output buffering
ob_start();

// set the error reporting level
error_reporting(E_USER_ERROR | E_USER_WARNING
   | E_USER_NOTICE);

function my_handle_error()
{
    ob_end_clean();
    include("error_page.html");
    exit;
}
// so any error in the PHP code itself (E_USER_ERROR, 
// E_USER_WARNING or E_USER_NOTICE) will be handled 
// by the function above
set_error_handler("my_handle_error");

require_once("DB.php");
$dsn = array(
    'phptype'  => "mysql",
    'hostspec' => "localhost",
    'database' => "test_db",
    'username' => "scott",
    'password' => "wrong_password"
);
// now any errors on the PEAR code will be 
//handled by the same function above
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK,
   'my_handle_error');
$dbh = DB::connect($dsn);

// flush the buffered page if no errors happened
ob_end_flush();
?>

Note: you will need the output buffering functions from PHP version 4.0 and up to make the above snippet work.

That is just an example of what can be done with PEAR and PHP's error-handling functions and routines. Considering PHP is a 5-year-old scripting language, this technique is quite powerful for logging errors.

The PEAR::DB class factory

If you ever tried to open PEAR's DB package to see how it works, you probably got confused at the way that it handles the classes and subclasses of all the database drivers and error routines. I hope that I helped you understand the standard PEAR_Error class and how PEAR libraries call PEAR_Error to create a special object with information about the error. Now we are going to study the "factory" method of PEAR::DB to use the appropriate database driver that is specified at runtime from the caller script.

This "factory" method is quite simple once you get used to the way it works. Example code showing PEAR::DB works follows:

<?php
include_once("DB.php");

$dsn = array(
    'phptype'  => "mysql",
    'hostspec' => "localhost",
    'database' => "test_db",
    'username' => "scott",
    'password' => "tiger"
);
$dbh = DB::connect($dsn);
?>

It works by sending an array with all the specifications of host, type of database (MySQL, PostgreSQL, or whatnot), database name, and other information to the connect() method of the DB class. Once again, we are using static method calls to connect to the appropriate server.

The connect() method of DB is what people call a "factory" method. It includes the appropriate PHP file from the DB subdirectory and instantiates an object from that class definition. It works like this:

<?php
class DB
{
    function connect($dsn)
    {
        $type = $dsn["phptype"];
        @include_once("DB/${type}.php");

        $classname = "DB_${type}";
        @$obj =& new $classname;
        $obj->connect($dsn);

        return $obj;
    }
}
?>

This includes a file and connects to the database server using its own connect() method. It will return a database handler, which is just an object with the methods from the DB_${type} class, which in our case is just the DB_mysql class.

This way of handling the drivers is much cleaner and more professional than by forcing the user know the correct class and functions in order to connect to a database server. In this case, this would be the PHP developer using the PEAR::DB package. It serves as an abstraction library, which generalizes as much as possible the way the database works and how it is accessed.

Hierarchical view of the PEAR::DB structure

The structure and general design of PEAR::DB is why everything works so well and why it is so expandable. Creating a new driver for PEAR::DB is not a hard task -- all one must do is to create the new driver and store it under the DB subdirectory of PEAR. The main DB class should be able to use it correctly by just changing one line of the PHP code, the front-end script that calls the package:

<?php
$dsn = array(
    'phptype'  => "NEW_DATADASE_NAME_HERE",
    'hostspec' => "localhost",
    'database' => "test_db",
    'username' => "scott",
    'password' => "tiger"
);
?>

The library is also very well designed to make the drivers share important functionality, by storing the utility functions in DB/common.php. This file contains the DB_common class, which is inherited by all other database classes from the DB directory, as follows:

<?php
require_once "DB/common.php";

class DB_mysql extends DB_common
{
    // ...
}
?>

This makes it easier to re-use code among the drivers, and most of the functions and methods found on this utility class are standard methods such as getRow(), numRows(), and affectedRows().

I hope this article helped shed some light into some of the advanced coding techniques of PEAR, and made reading the PEAR source code a little bit easier.

Joao Prado Maia is a web developer living in Houston with more than four years of experience developing web-based applications and loves learning new technologies and programming languages.


Return to the PHP DevCenter.

Copyright © 2009 O'Reilly Media, Inc.