<?
/**
* This is my Object class for data abstractions. I wrote this because
* I got sick of writing the same code over and over again. It works well
* for me, but that doesn't mean it will work well for you. I have made
* a few assumptions and have listed them here:
* You are using my Connection class or have implemented your own
* Connection class with the same methods and functionality.
* You have your database designed with each table containing a
* PRIMARY_KEY that is an int. I always call this column 'id'
* but you can tell the Object class which column is your primary key
* by calling setPrimaryKeyElement.
* You do not modify any of the member variables and use the methods
* provided to modify the data in the member variables.
* I can't think of any reason for not modifying them, but I just don't
* think you should because it's bad practice and future releases may have
* a problem with you directly modifying them.
* Caveats: This Object class only really represents on table row in a
* database. It gets one row from the database and doesn't deal with
* objects that may span multiple tables. However, you could override
* all the methods in this class get fancier. I choose to approach
* this type of problem by overriding the get() method in my implementation
* class. Since I setup an extension of this class for every table
* in the database and only want to deal with data as objects, simply
* overriding the get() method works well for me. In my get() method
* I use the foriegn key to return an object for whatever data I am after.
* For example:
* User extends Object and user has data in a Permissions
* table that we will want to ge able to access as well.
* I override the get() in my User class that returns an array of
* Permission objects when a program calls get("permission").
* This is a bad example and I am still working on the implementation.
* @author Keith Vance (hide@address.com) February 2002
* Copyright 2002 Vance Consulting LLC
*/
include_once("Connection.class.php");
include_once("config.php");
class DataObject extends Connection {
var $autocommit;
var $_logging;
var $_logging_fp;
var $_verify;
var $_verify_err;
var $_verified;
var $_class;
var $_object;
var $_elements;
var $_object_type;
var $_id;
var $_pkey;
var $_ini;
/**
* Constructor.
*
* @param String $primary_key Database primary key column name
* @param String [$type] This is more of name than a type. Currently
* this usually corresponds to the name of the table.
* @param Int [$id] This is the primary key for record to load when
* constructing this object.
* @param String [$datasource] You could override the config.php
* DS_NAME value here if you want to specify the data source name
* as something different from what is set in the config.php file
* @author Keith Vance <hide@address.com>
*/
function DataObject($primary_key, $type = false, $id = 0,
$datasource = DS_NAME) {
$debug = FALSE;
// Not yet doing anything with this.
if (phpversion() >= "4.2") {
$this->_ini = ini_get_all();
}
$this->autocommit = AUTO_COMMIT;
$this->_elements = FALSE;
$this->_logging = FALSE;
$this->_verify = FALSE;
$this->_verified = TRUE;
$this->_verify_err = TRUE;
$this->_pkey = FALSE;
if (!$type) new FatalError(NO_OBJECT_TYPE, __LINE__, "Object");
$this->_pkey = $primary_key;
$this->_object_type = $type;
if (!$this->_dbhost) {
$this->_dbhost = DS_HOST;
}
if (!$this->_database) {
$this->_database = DS_NAME;
}
if (!$this->_dbuser) {
$this->_dbuser = DS_USER;
}
if (!$this->_dbpassword) {
$this->_dbpassword = DS_PASSWD;
}
if ($debug) {
echo "db host: " . $this->_dbhost . "<br>\n";
echo "database: " . $this->_database . "<br>\n";
echo "db user: " . $this->_dbuser . "<br>\n";
}
parent::Connection();
if (!$this->live()) new FatalError(CONNECT_FAILED, __LINE__,
$this->_class);
// First load up the _elements member with all the column names
$this->_elements = $this->getTableData($this->_object_type);
if (count($this->_elements) == 0) {
return new Error(RECORD_NOT_FOUND, __LINE__, $this->_class);
}
// If id is set to something other than 0, try and get the
// record from the database and load up the object.
if ($id > 0) {
$sql = "select * from " . $this->_object_type .
" where (" . $this->_pkey . " = '$id')";
if (!$this->live()) {
$this->connect();
if (!$this->live()) {
new FatalError(CONNECT_FAILED, __LINE__, $this->_class);
}
}
$this->query($sql);
if ($this->numRows() == 0) {
return new Error(RECORD_NOT_FOUND, __LINE__, $this->_class);
} elseif ($this->numRows() > 1) {
new FatalError(DUPLICATE_RECORD_FOUND, __LINE__,
$this->_class);
}
$this->next();
if ($debug) echo "Loading DataObject<br>\n";
for ($i = 0, $j = count($this->_elements); $i < $j; $i++) {
if ($debug) {
echo "element -> " . $this->_elements[$i] .
" value -> " . $this->_record[$this->_elements[$i]] .
"<br>\n";
}
$this->_object[$this->_elements[$i]] =
$this->_record[$this->_elements[$i]];
}
$this->_id = $id;
} else {
for ($i = 0, $j = count($this->_elements); $i < $j; $i++) {
$this->_object[$this->_elements[$i]] = "";
}
}
}
/**
* Checks to see if a record exists with the $attribute and $value
* specified. This is convenient if you wish to find out if a
* user exists with the username ($attribute) of 'keith' ($value).
*
* @param String $attribute to query against.
* @param String $value of attribute quering against
* @return Boolean
* @author Keith Vance <hide@address.com>
*/
function exists($attribute, $value) {
if (!$this->live()) {
$this->connect();
if (!$this->live()) {
new FatalError(CONNECT_FAILED, __LINE__, $this->_class);
}
}
$sql = "select * from " . $this->_object_type .
" where $attribute='$value'";
$this->query($sql);
if ($this->numRows() > 0) {
return TRUE;
} else {
return FALSE;
}
}
/**
* Returns TRUE if verifying is enabled.
*
* @return Boolean
* @author Keith Vance <hide@address.com>
*/
function isVerifyOn() {
return $this->_verify;
}
/**
* Turn verification on or off.
*
* @param Boolean TRUE to turn on verification, FALSE to disable
* @author Keith Vance <hide@address.com>
*/
function verify($boo) {
if ($boo) $this->_verify_err = array();
$this->_verify = $boo;
}
/**
* Reset the verification error array.
*
* @author Keith Vance <hide@address.com>
*/
function resetVerificationError() {
$this->_verify_err = array();
}
/**
* Set a verification error.
*
* @param String $name Verification error name, key in Hash
* @param String $msg Error message for verification error.
* @author Keith Vance <hide@address.com>
*/
function setVerificationError($name, $msg) {
$this->_verify_err[$name][] = $msg;
}
/**
* Checks to see if verification is enabled.
*
* @return Boolean
* @author Keith Vance <hide@address.com>
*/
function isVerified() {
return empty($this->_verify_err);
}
/**
* Get a Verification error from the _verify_err Hash
*
* @param Boolean [$reset] Defaults to FALSE, but if set to TRUE
* the verification errors are cleared.
* @author Keith Vance <hide@address.com>
*/
function getVerificationError($reset = FALSE) {
if ($reset) reset($this->_verify_err);
$ret = current($this->_verify_err);
next($this->_verify_err);
return $ret;
}
/**
* Get the verification error hash
*
* @return Hash Verification error hash the keys are the name of error
* and the value is the error message.
* @author Keith Vance <hide@address.com>
*/
function getVerificationErrors() {
return $this->_verify_err;
}
/**
* Turns logging on or off.
*
* @param Boolean [$boo] Defaults is FALSE, if TRUE turn on logging
* and attempt to create log dir with 2775 permissions.
* @author Keith Vance <hide@address.com>
*/
function logging($boo = FALSE) {
if ($boo) {
@mkdir("log", 2775);
$this->_logging_fp = @fopen("log/" . $this->_class . ".log", "w");
@fwrite($this->_logging_fp, date(DATE_FORMAT) .
" Logging Stopped\n");
} else {
if ($this->_logging_fp) {
@fwrite($this->_logging_fp, date(DATE_FORMAT) .
" Logging Stopped\n");
@fclose($this->_logging_fp);
}
}
$this->_logging = $boo;
}
/**
* Write to log file.
*
* @param String $page The page which caused the error.
* @param $tring $msg The message to log
* @param Int [$line] The line number where the error occured.
* @param Boolean [$notifiy] Send email notification of log entry.
* @author Keith Vance <hide@address.com>
*/
function log($page, $msg, $line = 0, $notify = FALSE) {
if (!$this->isLoggingOn()) {
return;
}
$text = date(DATE_FORMAT) . " $page $msg [$line]\n";
if ($notify) {
mail(ERROR_MESSAGE_RECPT, "DataObject using " .
$this->_class . " class Error", $text);
}
@fwrite($this->_logging_fp, $text);
@fflush($this->_logging_fp);
}
/**
* Determine if logging is on or off.
*
* @return Boolean
* @author Keith Vance <hide@address.com>
*/
function isLoggingOn() {
return $this->_logging;
}
/**
* This sets the primary key for the database this object is working
* with. I use only one column as the primary and I always call it 'id',
* some people may not call it 'id' and that's fine 'cause you can
* set it here.
* However I don't think this will work if you are making your primary key
* for your table something more complicated like a combination of columns.
* @author Keith Vance 2.20.02 11:23pm
*/
function setPrimaryKeyElement($key) {
$this->_pkey = $key;
}
function getPKey() {
return $this->getPrimaryKeyElement();
}
/**
* Gets the primary key.
* @author Keith Vance 2.20.02 11:23pm
*/
function getPrimaryKeyElement() {
if ($this->_pkey == "") {
new FatalError(NO_PRIMARY_KEY, __LINE__, $this->_class);
}
return $this->_pkey;
}
/**
* Returns the element from this object.
* Elements are the same as column names and are
* in fact generated by getting a list of columns
* from the database.
* This allows you to reference the column names to retrieve
* any piece of data you wish about a given object.
* @param String $element name of element to begotten
* @return String element you wish to get or FALSE if element is not
* defined
* @author Keith Vance 2.20.02 10:00pm
*/
function get($element) {
if (isset($this->_object[$element])) {
return stripslashes($this->_object[$element]);
} else {
return FALSE;
}
}
/**
* Returns this object hash.
*
* @return Hash
* @author Keith Vance (hide@address.com) 03.24.02
*/
function getObject() {
return $this->_object;
}
/**
* Returns the array of elements.
* The elements array is derived from getting
* a list of all columns in the table.
*
* @return Array
* @author Keith Vance 2.20.02 10:04pm
*/
function getAllElements() {
return $this->_elements;
}
/**
* Sets the specified element with the specified value.
*
* @param String $element Element in object to set.
* @param String $value Value to set element to.
* @author Keith Vance 2.20.02 10:05pm
*/
function set($element, $value) {
if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
$this->_object[$element] = $value;
} else {
$this->_object[$element] = addslashes($value);
}
if (SET_LAST_MODIFIED) {
$this->_object[LAST_MODIFIED] = time();
}
}
/**
* Sets all the elements and values.
* $values is a hash where the keys are the
* element names and the values are the values.
* If you put element names in the array key that don't
* exist in the table you will either get an error you save.
* This method overwrites any data in this object and if you
* call save after this everything dump in here will be written
* to the database, unless you have specified invalid data then
* and error will ocurr when trying to save.
*
* @param Hash
* @author Keith Vance 2.20.02 10:41pm
*/
function setAll($values) {
if (is_array($values)) {
foreach ($values as $key=>$val) {
$this->set($key, $val);
}
} else {
return FALSE;
}
return TRUE;
}
/**
* Saves this Object to the database.
* If FatalError occurs application dies hard.
* If Error occurs the error is thrown back to the
* calling script, allowing it to deal with it. For instance,
* DUPLICATE_RECORD error - this may not be a problem but the calling
* script needs to determine whether it is a problem or not.
* @author Keith Vance 2.20.02 10:05pm
*/
function save() {
$debug = FALSE;
// Make sure the connetion is still and if not fire it up.
if (!$this->live()) {
$this->connect();
if (!$this->live()) {
new FatalError(CONNECT_FAILED, __LINE__, $this->_class);
}
}
if ($this->_object[$this->_pkey] == 0) {
// insert data into database.
// Build the INSERT sql statement
$cols = "";
$vals = "";
for ($i = 0, $j = count($this->_elements); $i < $j; $i++) {
// If the element is the PRIMARY_KEY in the database
// don't attempt to insert it, I am assuming that the
// primary key is an auto_incrementing int and will be
// set by the database.
if ($this->_elements[$i] != $this->getPrimaryKeyElement() &&
isset($this->_object[$this->_elements[$i]])) {
$cols .= $this->_elements[$i] . ", ";
$vals .= "'" . $this->_object[$this->_elements[$i]]."', ";
}
}
// whack the trail comma and space from cols and vals, else
// we will have invalid sql.
$cols = substr($cols, 0, strlen($cols) - 2);
$vals = substr($vals, 0, strlen($vals) - 2);
$sql = "INSERT INTO " . $this->_object_type .
" ($cols) VALUES ($vals)";
if ($debug) echo "Object->save():: $sql <br>\n";
$this->log($_SERVER['PHP_SELF'],
"save() INSERT sql: $sql", __LINE__);
$this->query($sql);
// Grab the new PRIMARY KEY for the record just inserted and
// stick into the object.
$this->set($this->getPrimaryKeyElement(), $this->insertId());
// if error is 1062 Duplicate record throw an Error object
// back to the calling script so they can deal with it the way
// they want to.
if ($this->_errornumber == 1062)
return new Error(DUPLICATE_RECORD, __LINE__, $this->_class);
if ($this->affectedRows() != 1 || $this->_errornumber != 0) {
new FatalError(INSERT_FAILED, __LINE__, $this->_class);
}
} else {
// update database.
$sql = "UPDATE " . $this->_object_type . " SET ";
for ($i = 0, $j = count($this->_elements); $i < $j; $i++) {
if (isset($this->_object[$this->_elements[$i]])) {
$sql .= $this->_elements[$i] . " = '" .
addslashes($this->_object[$this->_elements[$i]]) . "'";
$sql .= ", ";
}
}
// whack the comma off the end of the $sql variable
$sql = substr($sql, 0, (strlen($sql) - 2));
$sql .= " WHERE " . $this->_pkey . " = '" .
$this->_object[$this->_pkey] . "'";
if ($debug) echo "Object->save():: $sql<br>\n";
$this->query($sql);
if ($this->_errornumber != 0) {
new FatalError(UPDATE_FAILED, __LINE__, $this->_class);
}
}
return TRUE;
}
/**
* Deletes this Object from the database.
* @author Keith Vance2.20.02 10:05pm
*/
function delete() {
$debug = false;
// Make sure the connetion is still and if not fire it up.
if (!$this->live()) {
$this->connect();
if (!$this->live()) {
new FatalError(CONNECT_FAILED, __LINE__, $this->_class);
}
}
if ($this->_object[$this->_pkey] > 0) {
$sql = "delete from " . $this->_object_type . " where " .
$this->_pkey . " = '" . $this->_object[$this->_pkey] . "'";
$this->query($sql);
if ($this->affectedRows() != 1 || $this->_errornumber != 0) {
new FatalError(DELETE_FAILED, __LINE__, $this->_class);
}
} else {
return false;
}
$this->_object = false;
return true;
}
/**
* Gets all the Objects from the database.
* This needs to be implemented in the class that extends this one,
* it makes more sense because I want to return an array of objects
* which of the correct type so you can call all the methods on
* the objects in the array that are available to the specific
* object not just the ones available here. This may not be the
* smartest thing to do but it seems to make sense from my point of view.
*
* @author Keith Vance 2.20.2 10:17pm
*/
function getAll() {
new FatalError(METHOD_NOT_IMPLEMENTED, __LINE__, $this->_class);
}
/** The following methods are for debugging and testing and
not intended for any other purpose.
**/
function printElements() {
echo "<br><font color='blue'>print \$this->_elements<br>\n";
for ($i = 0, $j = count($this->_elements); $i < $j; $i++) {
echo $i . " " . $this->_elements[$i] . "<br>\n";
}
echo "<br></font><br><br>\n";
}
function printObject() {
echo "<br><font color='red'>print \$this->_object<br>\n";
for ($i = 0, $j = count($this->_object); $i < $j; $i++) {
echo "key->" . $this->_elements[$i] . " val->" . $this->_object[$this->_elements[$i]] . "<br>\n";
}
echo "<br></font><br><br>\n";
}
function scrubText(&$str) {
$str = strip_tags($str, '<i><b><br>');
//$str = addslashes($str);
}
function dump_var($msg, $var) {
ob_start();
echo $msg . "\n";
print_r($var);
error_log(ob_get_contents());
ob_end_clean();
}
} // end Object class
/**
* This is my generic error handler class for handling Fatal errors
* that kill the script when the occur.
* @author Keith Vance (hide@address.com) 2.20.02
* Copyright 2002 Vance Consulting LLC
*/
class FatalError {
var $_debug; // If debug is TRUE the script will die and
// print an error message
// If debug is FALSE the script is meta-refreshed
// to error.php and a message is sent to the
// webmaster.
var $_messages;
var $_code;
function FatalError($code = 0, $line, $class) {
$this->_debug = TRUE;
$this->_code = $code;
$this->_messages[NO_OBJECT_TYPE] = "You need to specify a Object type when you instantiate this class.";
$this->_messages[NO_OBJECT] = "You are trying to use a non-object where an object is required";
$this->_messages[RECORD_NOT_FOUND] = "The record you are looking for doesn't exist.";
$this->_messages[QUERY_FAILED] = "Your query failed.";
$this->_messages[CONNECT_FAILED] = "Your attempt to connect to the datasource failed.";
$this->_messages[DUPLICATE_RECORD_FOUND] = "Duplicate record found.";
$this->_messages[NO_PRIMARY_KEY] = "You need to set which key is your primary key before you can retrieve it.";
$this->_messages[INSERT_FAILED] = "Data INSERT failed.";
$this->_messages[UPDATE_FAILED] = "Data UPDATE failed.";
$this->_messages[DELETE_FAILED] = "Data DELETE faile.";
$this->_messages[METHOD_NOT_IMPLEMENTED] = "You are called a method that has not been properly implemented.<br>This probably means that you should override the method in your class and provide the logic.";
if ($debug) {
die(sprintf("Error [%d]<br>Class [%s]<br>Line Number [" . $line . "]<br>%s", $this->getCode(), $class, $this->getMessage()));
} else {
mail(ERROR_MESSAGE_RECPT, "Fatal Error " . SITE_NAME, "A fatal error ocurred at " . date(DATE_FORMAT) . " running $class on $line\n");
echo "<meta http-equiv='Refresh' content='1'>\n";
}
}
function getCode() {
return $this->_code;
}
function setCode($c) {
$this->_code = $c;
}
function getMessage() {
return $this->_messages[$this->getCode()];
}
} // end FatalError class
class Error {
var $_messages;
var $_code;
function Error($code = 0, $line, $class) {
$this->_code = $code;
$this->_messages['RECORD_NOT_FOUND'] = "The record you are looking for doesn't exist.";
$this->_messages['DUPLICATE_RECORD'] = "Duplicate record found.";
$this->_messages['NO_SAVE_VERIFICATION'] = "You need to override this save method in order to use verification.";
}
function getCode() {
return $this->_code;
}
function setCode($c) {
$this->_code = $c;
}
function getMessage() {
return $this->_messages[$this->getCode()];
}
}
?>