Introduction

PEAR stands for "PHP Extension and Application Repository" and has been slowly building itself over the last few years. There are classes for logging, compression, XML, and on and on. The most popular one is the database abstraction class, which supports most popular databases.
In this article I'm going to lay out how you can go about using this great library in your own classes. Specifically, we will be covering PEAR, PEAR_Error, DB, and Log.
One thing to note here is that if you don't know what OOP stands for or you have yet to dive into PHP OOP programming this will most likely go over your head. Starting with an OOP primer would be a good idea.

The PEAR Class

The base PEAR class is fairly abstracted and shouldn't be used on its own, however, it is a great class to build your classes off of. It's major feature is that it imitates destructors. With PHP5 on its way this will be void, since PHP5 will support destructors natively, I believe. The class file for PEAR also includes the PEAR_Error class, which we will talk about in more detail at a later time. First let's discuss basing your classes on PEAR. We are going to start by creating our own specific base class and then extending that to a user class to be used on a site. Remember to document your classes using PHPDoc!

<?php
  
require_once('PEAR.php');
  require_once(
'DB.php');
  require_once(
'Log.php');

  
/**
  * Default PEAR DSN
  * 
  * @author Joe Stump <joe@joestump.net>
  * @global string BASE_PEAR_DSN
  * @access public
  * @see Base::Base(), Base::$db
  */
  
define('BASE_PEAR_DSN','mysql://root:@localhost/base');

  
/**
  * Base Class
  *
  * Our base class will hold only the basic necessities that all
  * of our child classes will need. Mainly DB connectivity and
  * the ability to log errors.
  *
  * @author Joe Stump <joe@joestump.net>
  */
  
class Base extends PEAR
  
{
    
/**
    * DB Class
    *
    * @author Joe Stump <joe@joestump.net>
    * @access public
    */
    
var $db;

    
/**
    * Log Class
    *
    * @author Joe Stump <joe@joestump.net>
    * @access public
    */
    
var $log;

    
/**
    * Base Contstructor
    *
    * Connect to the DB and create our Log, which can then be
    * used by all children classes.
    *
    * @author Joe Stump <joe@joestump.net> 
    * @acces public
    * @return void
    */
    
function Base()
    {
      
$this->PEAR();

      if(
get_class($this) == 'base')
      {
        
$this = new PEAR_Error('Base is an abstracted class!');
      }
      else
      {
        
$this->db =& DB::connect(BASE_PEAR_DSN,true);
        if(
DB::isError($this->db))
        {
          
$this = new PEAR_Error($this->db->getMessage());
        }
        else
        {
          
$this->db->setFetchMode(DB_FETCHMODE_ASSOC);
          
$this->log =& Log::factory('syslog','Base');
        }
      }
    }

    
/**
    * Base Destructor
    *
    * Just add a '_' to your class's name and voila! you have a PEAR
    * controlled destructor!
    *
    * @author Joe Stump <joe@joestump.net>
    * @access public
    * @return void
    */
    
function _Base()
    {
      if(!
DB::isError($this->db))
      {
        
$this->db->disconnect();
      }
    }
  }

?>
There are a few things that you will notice that are quite a bit different from your average PHP class. The first is it's documented! No, just kidding, we all document our code. Jokes aside you'll notice that all we really needed to do to make our class a true PEAR class is extend it from PEAR and make sure we had a properly named destructor.
There is a side note on destructors. When you create an instance of any PEAR based class you MUST assign by reference, meaning =& and not just =. If you do not assign by reference then your destructors will NOT run!

The PEAR_Error Class

The PEAR_Error class is really easy to use. It's a very basic class that serves a very basic purpose, error checking and error reporting. I now return nothing but PEAR_Error's in my classes, instead of using true/false. Why? I'm not only able to tell easily if my class is in error, but I'm also able to pass along an error message for easy debugging. You'll note in the above class that I assign a new instance of PEAR_Error to $this when an error occurs. This means that if an error occurs while connecting to the database the class that you originally created as an instance of Base will now be an instance of PEAR_Error, not allowing you to do any Base related operations on that instance. Here's a quick example:

<?php

  $base 
=& new Base();
  if(
Base::isError($base))
  {
    echo 
$base->getMessage();
  }

?>
The above code would create an instance of Base, but since Base is an abstracted class it would error out with "Base is an abstracted class!" There are three main things to note about PEAR_Error when you are using it. The first is that the constructor takes your error message as its first parameter. The second is that the function isError() is part of the PEAR class and can be used in any child class with the scope operator ::. The last is the getMessage() function is used to get your error message. But since the classes we write are perfect we won't be needing this, will we?

The PEAR DB Class

I could write two or three articles on the DB class included in PEAR, which is why I'll only briefly cover its use here. I use it flawlessly with MySQL and thus far my only complaint is that it uses sequence tables instead of merely returning the insert id in MySQL's auto_increment fields.

<?php

  
require_once('Base.php');

  
$db =& DB::connect(BASE_DEFAULT_DSN,true);
  if(!
DB::isError($db))
  {
    
$db->setFetchMode(DB_FETCHMODE_ASSOC);
    
    
$sql "SELECT *
            FROM table
            WHERE foo='bar'"
;

    
$result $db->query($sql);
    if(!
DB::isError($result) && $result->numRows())
    {
      while(
$row $result->fetchRow())
      {
        echo 
'&lt;li&gt;'.$row['foo']."\n";
      }
    }
  }
  else
  {
    echo 
$db->getMessage();
  }

?>
This is the most basic of examples on how to use the DB class, but those of you who have used Perl's DBI or the many PHP database functions will note the structure of connecting to a database is pretty much intact. One thing to note is that the result is a separate class from the DB class with its own functions, etc. For a more detailed overview of the DB class check out http://pear.php.net/manual/en/core.db.php.
The PEAR Log Class
I like logging certain errors and information straight to syslog, like failed logins to my site's admin section. It's also a less intrusive way to debug your code. The PEAR Log class lets you log to syslog, your own log files, SQL databases, and more. It also has some more sophisticated features that let you "attach" classes to logs and then "notify" those classes when things are logged. I have yet to tackle those features, but they sound interesting. I'm going to build on the PEAR DB example to incorporate the Log class.

<?php

  
require_once('Base.php');

  
$log Log::factory('syslog','My App');

  
$db =& DB::connect(BASE_DEFAULT_DSN,true);
  if(!
DB::isError($db))
  {
    
$db->setFetchMode(DB_FETCHMODE_ASSOC);
    
    
$sql "SELECT *
            FROM table
            WHERE foo='bar'"
;

    
$result $db->query($sql);
    if(!
DB::isError($result) && $result->numRows())
    {
      while(
$row $result->fetchRow())
      {
        echo 
'&lt;li&gt;'.$row['foo']."\n";
      }
    }
    else
    {
      
$this->log($sql);
    }
  }
  else
  {
    
$log->log($db->getMessage());
  }

?>
Now tail your syslog and run the above example. You should find that there are some errors. It includes all sorts of date information and the identifier (the second argument in the constructor) you assigned your log. You can alternately use your own log files by using the type 'file' instead of 'syslog'.

Putting It All Together!

Now that you basically know how to use the main PEAR classes and we've built our own base class, which is based on PEAR, we can put it all together in a User class for our site!

<?php

  
require_once('Base.php');

  
/**
  * User class
  *
  * A class to retrieve user information and perform basic 
  * user related tasks.
  *
  * @author Joe Stump <joe@joestump.net>
  */
  
class User extends Base
  
{
    
/**
    * @author Joe Stump <joe@joestump.net>
    * @access public
    */
    
var $userID;

    
/**
    * @author Joe Stump <joe@joestump.net>
    * @access public
    */
    
var $data;

    
/**
    * User constructor
    *
    * @author Joe Stump <joe@joestump.net>
    * @access public
    * @param int $userID
    * @return void
    */
    
function User($userID 0)
    {
      
$this->Base();

      if(
$userID)
      {
        
$sql "SELECT *
                FROM users
                WHERE userID='$userID'"
;

        
$result $this->db->query($sql);
        if(!
DB::isError($result) && $result->numRows())
        {
          
$this->data $result->fetchRow();
          
$this->userID $userID;
        }
        else
        {
          
$this = new PEAR_Error("Invalid userID: $userID");
        }
      }
      else
      {
        
$this->userID 0;
        
$this->data = array();
      }
    }

    
/**
    * isAdmin
    *
    * Check to see if the userID is in our admins table
    *
    * @author Joe Stump <joe@joestump.net>
    * @access public
    * @return bool
    */
    
function isAdmin()
    {
      
$sql "SELECT *
              FROM admins
              WHERE userID='"
.$this->userID."'";

      
$result $this->db->query($sql);
      if(
DB::isError($result) && $result->numRows())
      {
        return 
true;
      } 

      
$this->log->log('Failed isAdmin() with userID '.$this->userID.'!');
      return 
false;
    }

    
/**
    * User Destructor
    *
    * @author Joe Stump <joe@joestump.net>
    * @access public
    * @return void
    */
    
function _User()
    {
      
$this->_Base();
    }

  }

?>
As you can see from the above example once you have an instance of your User class you have everything you need to start manipulating data and doing some advanced debugging. Probably the best thing about working with PEAR is that it makes debugging your classes a lot easier.
To wrap things up I'll finish with a simple example of using the User class from within your site.

<?php

  
require_once('User.php');

  
$user =& new User($_COOKIE['userID']);
  if(!
Base::isError($user) && $user->isAdmin())
  {
    echo 
"You are an admin!";
  }
  else
  {
    echo 
"Go AWAY!";
  }

?>
One thing I started to notice the more I used PEAR was that I ended up having a lot less code in my final application. This is a great thing about OOP. Once you get your base class working correctly you know it's always going to work and errors you encounter will most likely be in your very small final application. To boot your code is abstracted and more portable.
Related Materials
About the Author
Joe Stump is in his last semester at Eastern Michigan University studying Computer Information Systems. He enjoys creating complex web applications using PHP, MySQL, and XML. You can find him at http://www.joestump.net or email him at joe (at) joestump (dot) net.