Login   Register  
PHP Classes
elePHPant
Icontem

File: includes/class.dbobject.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of SPAM  >  pork.dbObject  >  includes/class.dbobject.php  >  Download  
File: includes/class.dbobject.php
Role: Class source
Content type: text/plain
Description: This is the main dbObject class
Class: pork.dbObject
Map objects into MySQL database table rows
Author: By
Last change:
Date: 8 years ago
Size: 19,842 bytes
 

Contents

Class file image Download
<?php

/**
	
	Pork.dObject version 1.0
	By Jelle Ursem
	see http://www.schizofreend.nl/ for more info

*/
define('RELATION_SINGLE', 'RELATION_SINGLE');
define('RELATION_FOREIGN', 'RELATION_FOREIGN');
define('RELATION_MANY', 'RELATION_MANY');
define('RELATION_NOT_RECOGNIZED', 'RELATION_NOT_RECOGNIZED');
define('RELATION_NOT_ANALYZED', 'RELATION_NOT_ANALYZED');

class dbObject
{
	var $databaseInfo, $databaseValues, $changedValues, $relations, $db;


	/**
		This initializes the O/R mapper and sets up the connection. This is what you will call in the constructor of your object.
		example: 

		class Blog extends dbObject
		{
			function __construct($ID=false)
			{
				$this->__setupDatabase('blogs', // db tabel
					array('ID_Blog' => 'ID',	// db veld => object property
							'strPost' => 'Post', 
							'datPosted' => 'Posted', 
							'strPoster' => 'Poster', 
							'strTitle' => 'Title'),
							'ID_Blog',	// primary db key 
							$ID);	// primary key value
				$this->addRelation('Reaction');
				$this->addRelation('Tag', 'Blogtag');
		}

	*/	
	function __setupDatabase($table, $fields, $primarykey, $id=false, $altDatabase=false)
	{
		global $db;
		$this->databaseInfo->table = $table;
		$this->databaseInfo->fields = $fields;
		$this->databaseInfo->primary = $primarykey;
		$this->databaseInfo->ID = $id;
		$this->databaseValues = false;
		$this->changedValues = array();
		$this->relations= array();
		$this->db = ($altDatabase != false)? $altDatabase : $db;
		if($id != false) $this->__init();
	}

	function __init() 
	{
		if($this->databaseInfo->ID != false) 
		{
			$fieldnames = implode(",", array_keys($this->databaseInfo->fields));
			$input = $this->db->fetchRow("select {$fieldnames} from {$this->databaseInfo->table} where {$this->databaseInfo->primary} = {$this->databaseInfo->ID}", 'mysql_fetch_assoc');
			$this->databaseValues = $input;
		}
	}

	function __get($property) 
	{
		return $this->__getInternal($property);
	}

	function __getInternal($property, $isProperty=true)
	{
		$field = false; // 
		if(array_key_exists($property, get_object_vars($this))) { // it's private property
			return($this->$property); 
		}
		if($isProperty)	{ // are we calling the 'mapped' way?
			$field = $this->fieldForProperty($property);
		}
		else {
			$field = (is_array($this->databaseInfo->fields) && array_key_exists($property, $this->databaseInfo->fields)) ? $property : false;
		}
		if($field != false && is_array($this->changedValues) && array_key_exists($field, $this->changedValues)) { // is this an udated property?
			return($this->changedValues[$field]); // get it from internal changed values list
		}
		if($field != false && is_array($this->databaseValues) && array_key_exists($field, $this->databaseValues)) {
			return($this->databaseValues[$field]); // return the original value from the database
		}
		
		if($field == false) 
		{
			// It's an unmapped property, you could die() here 
			// The framework will throw an excerpt of a debug_backtrace() in a popup
			// or return nothing
			return;
		}
	}

	function __set($property, $value) // catch the default setter
	{
		if($this->hasProperty($property)) {
			$this->changedValues[$this->fieldForProperty($property)] = $value;
		}
		else
		{
			// It's an unmapped property, you could die() here 
			// The framework will throw an error in a popup
			// in this case we do nothing.

		}	
	}

	function getFieldNames() { // gets the array of  database fields
		return(array_values($this->databaseInfo->fields));
	}
	
	
	function hasProperty($property) {
		return array_search($property, $this->databaseInfo->fields);
	}

	function fieldForProperty($property) { // get db field by it's property name
		return array_search($property, $this->databaseInfo->fields);
	}
	

	function deleteYourSelf() { //deletes the current object from database.
		if($this->databaseInfo->ID != false) 
		{
			$this->db->query("delete from {$this->databaseInfo->table} where {$this->databaseInfo->primary} = {$this->databaseInfo->ID}");
		}
		
	}

	/**
		Insert this object into the database:
		* prepare the query with just a null value for primary key
		* append the changed fields and (addslash'd)values of this object if needed
		* execute the query
	*/
	function InsertNew()
	{
		$insertfields = $this->databaseInfo->primary;
		$insertValues = 'null';
		if ($this->changedValues != false) { // do we have any new-set values?
			$insertfields .= ', '.implode(",", array_keys($this->changedValues));
			foreach ($this->changedValues as $property=>$value) { // append each value escaped to the query
				$insertValues .= ', "'.addslashes($value).'"';
				$this->databaseValues[$property] = $value; // and store it so we don't save it again
			}
			$this->changedValues = false; // then clear the changedValues 
		}
		$this->databaseInfo->ID = $this->db->query("insert into {$this->databaseInfo->table} ({$insertfields}) values ($insertValues);");
		$this->databaseValues[$this->databaseInfo->primary] = $this->databaseInfo->ID; // update the primary key
		return($this->databaseInfo->ID); // and return it 
	}

	function Save() 
	{
		if($this->changedValues != false && $this->databaseInfo->ID == false) { // it's a new record for the db
			$id = $this->InsertNew();
			$this->analyzeRelations(); // re-analyze the relation types so we can use Find()
			return $id;
		}
		elseif ($this->changedValues != false) { // otherwise just build the update query
			$updateQuery = "";
			foreach ($this->changedValues as $property=>$value) {
				$updateQuery .= ($updateQuery != "") ? ", " : "";
				$updateQuery .=" {$property} = '".addslashes($value)."'";
				$this->databaseValues[$property] = $value; // store the value so we don't have to save it again
			}
			$this->db->query("update {$this->databaseInfo->table} set {$updateQuery} where {$this->databaseInfo->primary} = {$this->databaseInfo->ID}");
			$this->changedValues = false; 
			return($this->databaseInfo->ID);
		}	
		return false;
	}
		
	/**
		Add a new relation to the relation list and set it to be analyzed if used
	*/
	function addRelation($classname, $connectorclassname=false) 
	{
		$this->relations[$classname]->relationType = RELATION_NOT_ANALYZED;
		if($connectorclassname != false) $this->relations[$classname]->connectorClass = $connectorclassname;
		if($this->databaseInfo->ID != false) $this->analyzeRelations();		
	}


	/** 
		This is where the true magic happens
	*/
	function analyzeRelations() 
	{
		foreach($this->relations as $classname=>$info) {
			if(is_subclass_of($classname, 'dbObject')) {// the class to connect is a dbObject
				$obj = new $classname(false);
				$info->className = $classname;
					
				if(array_key_exists('connectorClass', get_object_vars($info)) && $info->connectorClass != '' && is_subclass_of($info->connectorClass, 'dbObject')) {
					// this class has a connector class. It could be many:many relation
					$connector = $info->connectorClass;
					$connectorobj = new $connector(false);
					// if the 
					if(array_key_exists($this->databaseInfo->primary, $connectorobj->databaseInfo->fields) && array_key_exists($obj->databaseInfo->primary, $connectorobj->databaseInfo->fields)) {
						// yes! The primary key of the relation now appears in this object, the connector class and one of the connected class. it's a many:many relation
						$info->relationType = RELATION_MANY; 
					} 
					else { 
						unset($info->connectorClass); // it's not connected to our relations
					}
				}
				if(	$info->relationType == RELATION_NOT_ANALYZED && 
					array_key_exists($obj->databaseInfo->primary, $this->databaseInfo->fields) && array_key_exists($this->databaseInfo->primary, $obj->databaseInfo->fields)) { 
					// if the primary key of the connected object exists in this object, 
					// and the primary key of this object exists in the connected object it's a 1:1 relation
					$info->relationType = RELATION_SINGLE; 
				}
				elseif($info->relationType == RELATION_NOT_ANALYZED 
					&& (array_key_exists($this->databaseInfo->primary, $obj->databaseInfo->fields) && !array_key_exists($obj->databaseInfo->primary, $this->databaseInfo->fields) || !array_key_exists($this->databaseInfo->primary, $obj->databaseInfo->fields) && array_key_exists($obj->databaseInfo->primary, $this->databaseInfo->fields)) ) {
					// if the primary key of the connected object exists in this object (or the other way around), but 
					// the primary key of this object does not exist in the connected object (or the other way around) 
					// it's a many:1 or 1:many relation
						$info->relationType = RELATION_FOREIGN;			

				}
				elseif($info->relationType == RELATION_NOT_ANALYZED) {
					// we don't recognize this type of relation. in the framework a popup error with a backtrace will be thrown
					$info->relationType = RELATION_NOT_RECOGNIZED;		
				}
				$this->relations[$classname] = $info;
			}
			else
			{
				// tried to connect a non-dbobject object. in the framework a popup error with a backtrace will be thrown
				unset($this->relations[$classname]);
			}
		}	
	}

	
	/*
		This connects 2 dbObjects together, with a connector class if needed.
	*/
	function Connect($object) 
	{
		$className = get_class($object);
		if($this->databaseInfo->ID == false) $this->Save(); // save both objects if they are new
		if($object instanceof dbObject && $object->databaseInfo->ID == false) $object->Save(); 
		if($this->relations[$className]->relationType == RELATION_NOT_ANALYZED) $this->analyzeRelations(); // if we didn't run the analyzer yet, run it.
		
		if(array_key_exists($className, $this->relations)) {
			switch($this->relations[$className]->relationType)
			{
				case RELATION_SINGLE: // link the 2 objects' primary keys
					$this->changedValues[$object->databaseInfo->primary] = $object->databaseInfo->ID;
					$object->changedValues[$this->databaseInfo->primary] = $this->databaseInfo->ID;
					$this->Save();
					$object->Save(); //
				break;
				case RELATION_FOREIGN: // determine wich one needs to have the primary key set for the 1:many or many:one relation 
					if(array_key_exists($this->databaseInfo->primary, $object->databaseInfo->fields)) {
						$object->changedValues[$this->databaseInfo->primary] = $this->databaseInfo->ID;
						$object->Save();
					}
					elseif(array_key_exists($object->databaseInfo->primary, $this->databaseInfo->fields)) {
						$this->changedValues[$object->databaseInfo->primary] = $object->databaseInfo->ID;
						$this->Save();
					}
				break;
				case RELATION_MANY: // create a new connector class, set both primary keys and save it.
					$connector = $this->relations[$className]->connectorClass;
					$connector = new $connector(false);
					$property = $connector->databaseInfo->fields[$this->databaseInfo->primary];
					$connector->$property = $this->databaseInfo->ID;
					$property = $connector->databaseInfo->fields[$object->databaseInfo->primary];
					$connector->$property = $object->databaseInfo->ID;
					$connector->Save();
				break;
				default:
					// the framework will throw a nice error here
				break;
			}
		}	
	}

	// see Connect() for how this works, it's the other way around.
	function Disconnect($object, $id=false) 
	{
		if(!$object && !$id) return;
		if(!$object instanceof dbObject && $id != false) {
			$object = new $object(false);
			$object->databaseInfo->ID = $id;
		}
		$className = get_class($object);
		if(array_key_exists($className, $this->relations)) {
			switch($this->relations[$className]->relationType)
			{
				case RELATION_SINGLE:
					$this->changedValues[$object->databaseInfo->primary] = '';
					$object->changedValues[$this->databaseInfo->primary] = '';
					$this->Save();
					$object->Save();
				break;
				case RELATION_FOREIGN:
					if(array_key_exists($this->databaseInfo->primary, $object->databaseInfo->fields)) {
						$object->changedValues[$this->databaseInfo->primary] = '';
						$object->Save();
					}
					elseif(array_key_exists($object->databaseInfo->primary, $this->databaseInfo->fields)) {
						$this->changedValues[$object->databaseInfo->primary] = '';
						$this->Save();
					}
				break;
				case RELATION_MANY:
					$connectors = $this->Find($this->relations[$className]->connectorClass, array($object->databaseInfo->primary => $object->databaseInfo->ID, $this->databaseInfo->primary => $this->databaseInfo->ID));
					$connectors[0]->deleteYourSelf();
				break;
			}
		}	
	}

	/**
		Checks if this is a 'connecting' object between 2 tables by checking if the passed classname
		is a connection class.
	*/
	function isConnector($className)
	{
		foreach ($this->relations as $key => $val) { // walk all relations
			if(array_key_exists('connectorClass', get_object_vars($val)) && $val->connectorClass == $className) return true; 
		}
		return false;	
	}

	function Import($values) { // import a pre-filled object (like a table row)
		$this->databaseValues = $values;
		$this->databaseInfo->ID = (!empty($values[$this->databaseInfo->primary])) ? $values[$this->databaseInfo->primary] :false;
	}


	/**
		Imports an array of e.g. db rows and returns filled instances of $className
		This will not run the analyzerelations or other stuff for performance and recursivity reasons.
	*/
	function importArray($className, $input) 
	{
		$output = array();
		foreach ($input as $array) 
		{
			$elm = new $className(false);
			$elm->Import($array);
			if(sizeof($input) > 0 && $elm->ID != false)	{
				$output[]= $elm;
			}
		}
		return($output);
		
	}

	/*
		input: a query with mapped properties ($this->databaseInfo->fields)
		output: a query with the corresponding table name prefixed to the actual table field names.
	*/	
	function mapfields($query, $object) 
	{
		$words = preg_split("/([\s|,]+)/", $query, -1, PREG_SPLIT_DELIM_CAPTURE);	
		if(!empty($words)) {
			foreach($words as $key=>$val) {  
				if(strpos($val, '.') !== false) {
					$expl = explode(".", $val);
					if(sizeof($expl) == 2 && $expl[0] == $object->databaseInfo->table)  $val = $expl[1];
					else continue;
				}
				if($object->hasProperty($val)) { 
					$words[$key] = $object->databaseInfo->table.'.'.$object->fieldForProperty($val);
				}
				elseif(array_key_exists($val, $object->databaseInfo->fields)) {
					$words[$key] = $object->databaseInfo->table.'.'.$val;
				}
			} 
		}
		return(implode("", $words));
	}
			
	/**
		This builds the where clause and eventual order and group by clauses.
	*/
	function buildQuery($filters=array(), $extra = array() ) 
	{
		$where = '';
		$filtered = array();
		if(!empty($filters)) { 
			foreach ($filters as $key => $val) {  // walk the array of filters, and if there's a non-integer array key, assume where $key = $value. otherwise: assume it's a pre-built where clause: just append it.
				$filtered[] = (is_int($key))  ?  "{$val}" : "{$key} = '{$val}'"; 
			}
			$where = (!empty($filtered)) ? " where ".$where : $where;
			$where .= implode(" AND ", $filtered);
		}
		$where .= (sizeof($extra) > 0) ? " ".implode(" ", $extra) : ""; //append order by, group by, etc.
		return($where);
	}


	/**
		More magic happens here. This determines what type of relation we're searching for and fetches connected objects with sql filtering options. examples available online, and if you can read SQL you know how this works.
	*/
	function Find($className, $filters=array(), $where=array(), $justThese=array()) 
	{
		if($className == get_class($this)) { 
			// this might look weird, but you can create an empty instance of an object, and fetch the objects you need though this function
			if(sizeof($justThese) == 0) $justThese = array_keys($this->databaseInfo->fields);
			foreach($justThese as $key=>$val){ $justThese[$key] = $this->databaseInfo->table.'.'.$val; } // prepend table name.
			$fields = implode(", ", $justThese);
			$query = $this->mapfields("select {$fields} from {$this->databaseInfo->table} ".$this->buildQuery($filters, $where),$this);
			$input = $this->db->fetchAll($query, 'mysql_fetch_assoc');
			return($this->importArray($className, $input));			
		}
		if(array_key_exists($className, $this->relations) || $this->isConnector($className)) {
			$ob = new $className(false);
			$these = (sizeof($justThese) == 0) ? array_keys($ob->databaseInfo->fields) : $justThese;	
			foreach($these as $key=>$val){ $these[$key] = $ob->databaseInfo->table.'.'.$val; } // prepend table name.

			// $justthese is a list of fields you want to fill 
			// (usefull if e.g. you don't want to fetch just the large textfields, but do want the other data)
			$fields = implode(", ", $these);
			switch($this->relations[$className]->relationType) {
				case RELATION_SINGLE:	
					if($this->databaseInfo->ID != false) $filters[$ob->databaseInfo->primary] = $this->__getInternal($ob->databaseInfo->primary, false);
					$input = $this->db->fetchAll($this->mapfields("select {$fields} from {$ob->databaseInfo->table} ".$this->buildQuery($filters, $where), $ob), 'mysql_fetch_assoc');
					return($this->importArray($className, $input));				
				break;
				case RELATION_FOREIGN:
					if($this->databaseInfo->ID != false) $filters[$this->databaseInfo->table.'.'.$this->databaseInfo->primary] = $this->databaseInfo->ID;
					if(array_key_exists($this->databaseInfo->primary, $ob->databaseInfo->fields)) 
					{				
						$input = $this->db->fetchAll($this->mapfields("select {$fields} from {$this->databaseInfo->table} left join {$ob->databaseInfo->table} on {$ob->databaseInfo->table}.{$this->databaseInfo->primary} = {$this->databaseInfo->table}.{$this->databaseInfo->primary} ".$this->buildQuery($filters, $where),$this), 'mysql_fetch_assoc');
					}
					if(array_key_exists($ob->databaseInfo->primary, $this->databaseInfo->fields)) 
					{
						$input = $this->db->fetchAll($this->mapfields("select {$fields} from {$this->databaseInfo->table} left join {$ob->databaseInfo->table} on {$this	->databaseInfo->table}.{$ob->databaseInfo->primary} = {$ob->databaseInfo->table}.{$ob->databaseInfo->primary} ".$this->buildQuery($filters, $where),$ob), 'mysql_fetch_assoc');
					}
					return($this->importArray($className, $input));		
				break;
				case RELATION_MANY:	
					$connectorClass = $this->relations[$className]->connectorClass;
					$conn = new $connectorClass(false);
					if($this->databaseInfo->ID != false) $filters[$conn->databaseInfo->table.'.'.$this->databaseInfo->primary] = $this->databaseInfo->ID;
					$input = $this->db->fetchAll($this->mapfields($this->mapfields("select {$fields} from {$conn->databaseInfo->table} left join {$ob->databaseInfo->table} on  {$conn->databaseInfo->table}.{$ob->databaseInfo->primary} = {$ob->databaseInfo->table}.{$ob->databaseInfo->primary} ".$this->buildQuery($filters, $where), $conn), $ob), 'mysql_fetch_assoc');
					return($this->importArray($className, $input));
				break;
				case RELATION_NOT_ANALYZED:
					if(sizeof($this->changedValues) > 0) $this->Save();
					$this->analyzeRelations();
					return($this->Find($className, $filters, $where, $justThese));
				break;
				default:
					if($this->isConnector($className)) {
						$input = $this->db->fetchAll($this->mapfields("select {$fields} from {$ob->databaseInfo->table} ".$this->buildQuery($filters, $where), $ob), 'mysql_fetch_assoc');
						return($this->importArray($className, $input));
					}
				break;
			}
		}
	}

	/**
		// try to save the object if changed.
	*/
	function __destruct()
	{
		$this->Save();
	}

}

?>