<?php
// Modification log
// (date)       (author)    (activity/purpose)
// 11.09.04     T. Gildseth Changed class name from DBFront to DBObject
// 12.09.04     T. Gildseth Use DBTableInfo to get information about the table
//                          instead of information about columns, primary keys etc
//                          having to be supplied by implementing class.
// 12.09.04     T. Gildseth Created DBOError class, and moved all constant error
//                          codes there.
// 25.10.04     T. Gildseth Added check for isset on getter/setter functions
//                          return value
//
// Todo
// (date)       (author)    (activity/purpose)
// 08.09.04     T. Gildseth Fix _populate and _where to use placeholders and
//                          pearDB placeholders instead in the call to getRow.
//                          Also affects update()
// 09.09.04     T. Gildseth Find a way to work with one-to-many and many-to-many
//                          relationships
// 09.09.04     T. Gildseth Using functions in the SQL query... how?
//
require_once 'class.DBTableInfo.php';
require_once 
'DBTableInfo/class.DBTIError.php';
require_once 
'DBObject/class.DBOError.php';

define('DBO_REQUIRED'true);
define('DBO_OPTIONAL'false);
define('DBO_INSERT_OK'true);
define('DBO_UPDATE_OK'true);
define('DBO_SELECT_OK'true);

/**
 * This is an abstract class ment to hide the details of dealing with a database.
 *
 * This class is ment to be inherited by a class which implements a specific
 * database table. There are 3 publically available methodes in this class.
 * update, insert and delete which are called to insert the data contained
 * within the class into the database table.
 * And implementing class needs to supply the table name and a PEAR DB instance (or
 * compatible).
 * A minimal implementation need a constructor, and a public accessor for the
 * _charValue and/or _numValue methods.
 *
 * @author Tommy Gildseth <tommy@akili.no>;
 * @copyright Tommy Gildseth, September 2004
 * @version 0.1
 * @access inherrit?
 * @package DBObject
 */
class DBObject {
    
//{{{ Class members
    /**
     * An instance of the PEAR DB class. Must be supplied by the implementing class
     * @access private
     */
    
var $_pearDB;

    
/**
     * The values from a DB table row, which the implementing class represents
     * @access private
     */
    
var $_row;

    
/**
     * A boolean value indicating wether any values has been modified.
     *
     * A call to insert or update will be ignored if no values has been
     * added or modified
     *
     * @access private
     */
    
var $_modified false;

    
/**
     * An array containing the names of the primary key column(s) in the DB table.
     * @access private
     */
    
var $_keyColumns;

    
/**
     * The name of the database table which the implementing class is working with.
     * @access private
     */
    
var $_table;

    
/**
     * An associative array with the columns from the DB table.
     *
     * The keys of the array are the column names, and the value is one of
     * DBO_OPTIONAL or DBO_REQUIRED, indicating weather the column is mandatory.
     *
     * @access private
     */
    
var $_columns;

    
/**
     * An instance of the DBTableInfo class, containing information about the current table
     * @access private
     */
    
var $_dbtInfo;
    
//}}}


    //{{{ _tableData(keyColumnData)
    /**
     * Get information about the table specified in $table.
     *
     * This method finds the names of all columns in a DB table, which columns
     * are primary keys and which columns are required
     *
     * @param string    $table  The table name of the DB table this object represents.
     * @throws DBTIError
     * @access private
     */
    
function _tableData$table) {
        
$info = new DBTableInfo($this->_pearDB$table);
        if (
PEAR::isError($info)) {
            return 
$info;
        }

        
$this->_dbtInfo $info;

        
$this->_table $table;
        
$this->_keyColumns $info->pKey();

        foreach(
$info->columns() as $col) {
            
$this->_columns[$col] = DBO_OPTIONAL;
        }

        foreach(
$info->reqColumns() as $reqCol) {
            
$this->_columns[$reqCol] = DBO_REQUIRED;
        }
    }
//}}} end _tableData


    //{{{ _populate(keyColumnData)
    /**
     * Retrieves a row from a DB table, using the specified primary key value(s)
     *
     * @param array $keyColumnData  An associative array of primary key columns and data.
     * @throws DBOError
     * @return boolean  Boolean value true if a column matched the supplied values
     * @access private
     */
    
function _populate$keyColumnData) {
        if (!
is_array($keyColumnData)) {
            return new 
DBOError(DBO_ARGUMENT_IS_NOT_ARRAY'DBObject::_populate()');
        }

        
$where $this->_where($keyColumnData);
        
$query "SELECT *
                      FROM "
.$this->_table."
                      WHERE "
.$where;

        
$row $this->_pearDB->getRow($query);
        if (!empty(
$row) && !DB::isError($row)) {
            
$this->_row $row;
            return 
DBO_SELECT_OK;
        } else if (
DB::isError($row)){
            return new 
DBOError(DBO_SELECT_FAILED$row);
        } else {
            return new 
DBOError(DBO_SELECT_EMPTY);
        }
    }
//}}}


    //{{{ _charValue(column, value)
    /**
     * Set and/or get the value of the specified column.
     *
     * @param string $column Name of the column to modify
     * @param string $value New value to assign to the column
     *                      If $value equals false in value and type, $value is
     *                      not set, but simply returned.
     * @return string The (new) value of the specified column
     * @access private
     */
    
function _charValue$column$value ) {
        if (
$value !== false && @$this->_row[$column] != $value) {
            
$this->_row[$column] = $value;
            
$this->_modified true;
        }

        return isset(
$this->_row[$column])?$this->_row[$column]:$value;
    } 
//}}}end method _charValue


    //{{{ _numValue(column, value)
    /**
     * Set and/or get the numeric value of the specified column.
     *
     * @param string $column Name of the column to modify
     * @param number $value New value to assign to the column
     *                      If $value equals false in value and type, $value is
     *                      not set, but simply returned. $value must be numeric.
     * @return number The (new) value of the specified column
     * @access private
     */
    
function _numValue$column$value ) {
        if (
$value !== false && is_numeric($value) && @$this->_row[$column] != $value) {
            
$this->_row[$column] = $value;
            
$this->_modified true;
        }

        return isset(
$this->_row[$column])?$this->_row[$column]:$value;
    } 
//}}}end method _numValue


    //{{{ _boolValue(column, value)
    /**
     * Set and/or get the boolean value of the specified column.
     *
     * @param string $column Name of the column to modify
     * @param string $value New value to assign to the column
     *                      If $value equals false in value and type, $value is
     *                      not set, but simply returned. $value must be either
     *                      litteral true or false.
     * @return number The (new) value of the specified column
     * @access private
     */
    
function _boolValue$column$value ) {
        if (
$value !== false &&
            (
$value == 'true' || $value == 'false') &&
            @
$this->_row[$column] != $value) {
            
$this->_row[$column] = $value;
            
$this->_modified true;
        }

        return isset(
$this->_row[$column])?$this->_row[$column]:$value;
    } 
//}}}end method _boolValue


    //{{{ update( )
    /**
     * Save the updated values to the DB table
     *
     * @throws DBOError
     * @return boolean  Boolean value true if row was successfully updated
     * @access public
     */
    
function update( ) {
        if (
$this->_modified) {
            
$columns $this->_row;

            foreach(
$this->_keyColumns as $col) {
                unset(
$columns[$col]);
            }

            
$where $this->_where($this->_row);

            
$res $this->_pearDB->autoExecute($this->_table,
                                               
$columns,
                                               
DB_AUTOQUERY_UPDATE,
                                               
$where);
            if (
DB::isError($res)) {
                
$return = new DBOError(DBO_UPDATE_FAILED$res);
            } else {
                
$return DBO_UPDATE_OK;
            }
        } else {
            
$return = new DBOError(DBO_NOT_MODIFIED);
        }
        return 
$return;
    } 
//}}}end method update


    //{{{ insert( )
    /**
     * Insert the new values into the DB table
     *
     * @throws DBOError
     * @return boolean  Boolean value true if row was successfully inserted
     * @access public
     */
    
function insert( ) {
        if (
$this->_modified) {
            
$isOK true;
            
$columns = array();
            
$missing = array();

            foreach(
$this->_columns as $colName=>$required) {
                if (!isset(
$this->_row[$colName]) && $required) {
                    
$isOK false;
                    
$missing[] = $colName;
                } else if (isset(
$this->_row[$colName])) {
                    
$columns[$colName] = $this->_row[$colName];
                }
            }

            if (
$isOK) {
                
$res $this->_pearDB->autoExecute($this->_table,
                                                   
$columns,
                                                   
DB_AUTOQUERY_INSERT);
                if (
DB::isError($res)) {
                    
$return = new DBOError(DBO_INSERT_FAILED$res);
                } else {
                    
$pkVal $this->_dbtInfo->pkVal( );

                    if (
PEAR::isError($pkVal)) {
                        
$return $pkVal;
                    } else if (!empty(
$pkVal)) {
                        
$this->_row[key($pkVal)] = $pkVal[key($pkVal)];
                        
$return DBO_INSERT_OK;
                    } else {
                        
$return DBO_INSERT_OK;
                    }
                }
            } else {
                
$return = new DBOError(DBO_MISSING_COLUMN$missing);
            }
        } else {
            
$return = new DBOError(DBO_NOT_MODIFIED);
        }
        return 
$return;
    } 
//}}}end method insert


    //{{{ delete( )
    /**
     * Delete the row this column represents from the DB table
     *
     * @access public
     */
    
function delete( ) {
        
$columns = array();

        
$where $this->_where($this->_row);

        
$query "DELETE FROM ".$this->_table."
                  WHERE "
.$where;
        
$this->_pearDB->query($query);
    } 
//}}}end method delete


    //{{{ _where( )
    /**
     * Put together a where condition, identifying a row, based on the supplied data
     *
     * @param array     $keyColumnData  An associative array with column names as keys, and
     *                                  values as column values
     * @return string   A string containing the where condition.
     * @access public
     */
    
function _where$keyColumnData) {
        
$where = array();
        foreach (
$this->_keyColumns as $eachCol) {
            
$where[] = $eachCol."='".$keyColumnData[$eachCol]."'";
        }
        
$where implode(' AND '$where);
        return 
$where;
    } 
//}}}end method _where
}