Location: PHPKode > scripts > Persistent Objects for PHP > persistent-objects-for-php/core/Persist.php
<?php

	/**
	 * Defines a persistent object using PDO and Databases
	 * It supports, basically, PostgreSQL, MySQL, MSSQL and
	 * Oracle databases.
	 * Things get easy when all you need is:
	 * $obj = new Person();
	 * $obj->name = "John Doe";
	 * $obj->phone = 55555555;
	 * $obj->save();
	 * 
	 * @author Pablo Santiago Sánchez <hide@address.com>
	 * @copyright Copyright (c) 2008, Pablo Santiago Sánchez
	 * @license http://opensource.org/licenses/bsd-license.php BSD License
	 * @package pop
	 * @subpackage core
	 */

	/**
	 * Class used as base for all persistent objects created under
	 * the POP paradigm
	 * 
	 * @package pop
	 * @subpackage core
	 */
	abstract class Persist
	{
		/**
		 * Used to hold the id of the object on the database
		 * 
		 * @access protected
		 * @var string
		 */
		protected $id;
		/**
		 * Used to load objects specialized from existent parents
		 * 
		 * @access protected
		 * @var string
		 */
		protected $id_self;
		/**
		 * Database connection that will be used by the object/class must be a
		 * PDO connection object protected $pop_db;
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_db = "pop_db";
		/**
		 * Type of the database in use
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_dbtype;
		/**
		 * Internal use - An Instance of the class from which this extends
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_parent;
		/**
		 * Internal use - The class name from which this extends
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_parent_type;
		/**
		 * Internal use - Array of Attributes of this object 
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_attrs;
		/**
		 * Internal use - Array of Attributes of this object parents
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_parent_attrs;
		/**
		 * Internal use - Array of Attributes only avaliable on this object
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_self_attrs;
		/**
		 * Internal use - Name of the schema on the database
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_schema;
		/**
		 * Name of the schema on the database declared for metamapping
		 * 
		 * @access protected
		 * @var string
		 */
		protected $schema;
		/**
		 * Internal use - Name of the table on the database
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_table;
		/**
		 * Name of the table on the database declared for metamapping
		 * 
		 * @access protected
		 * @var string
		 */
		protected $table;
		/**
		 * Name of the parent's schema on the database declared for metamapping
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_parent_schema;
		/**
		 * Name of the parent's table on the database declared for metamapping
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_parent_table;
		/**
		 * Holds the driver for the current database
		 * 
		 * @access protected
		 * @var string
		 */
		protected $pop_db_driver;
		/**
		 * Classes that contains this class (used for ORM)
		 * 
		 * @access protected
		 * @var string
		 */
		protected $parent_classes;
		/**
		 * Metamapping of Attributes and Fields on the Database
		 * @access protected
		 * @var string
		 */
		protected $meta_mapping;

		/**
		 * this is the only thing that will be changed on classes that extends
		 * Persist __construct on extended classes must call the
		 * parent::__construct() method due to the fact that the data typing of
		 * attributes must be set in the constructor
		 */
		public function __construct()
		{
			//creates the ID attribute, the only one that cannot be set manually
			$this->id = new PInteger();

			//object must be initialized
			$this->initialize();
		}

		/**
		 * Magic method used to set the value and check if it's valid
		 * 
		 * @param string $name name of the value
		 * @param string $value value itself
		 * @return bool 
		 */
		public function __set($name, $value)
		{
			try
			{
				if (get_class($this->$name) != "PArrayOf")
				{
					$this->$name->value = trim($value);
					if ($this->pop_parent && isset($this->pop_parent->$name))
						$this->pop_parent->set($name,trim($value));
					return true;
				}
				else
				{
					throw new Exception("You cannot set the ID attribute manually. It must be given by the database.");
				}
			}
			catch (Exception $e)
			{
				throw new Exception("Error setting ".$name.": ".$e->getMessage());
			}
		}

		/**
		 * Magic method used to get the value of an attribute
		 * 
		 * @param string $name name of the value
		 * @return mixed 
		 */
		 public function __get($name)
		{
			if (isset($this->$name) && get_class($this->$name)=="PArrayOf")
				return $this->$name->array;
			else if (isset($this->$name))
				return $this->$name->value;
		}

		/**
		 * Method used to set the value and check if it's valid
		 * 
		 * @param string $name name of the value
		 * @param string $value value itself
		 * @return bool 
		 */
		public function set($name, $value)
		{
			$this->__set($name, $value);
		}

		/**
		 * Method used to get the value of an attribute
		 * 
		 * @param string $name name of the value
		 * @return mixed 
		 */
		public function get($name)
		{
			return $this->__get($name);
		}

		/**
		 * Method used to load the object with the id value passed, or
		 * can receive an array of attributes and values it should return
		 * 
		 * @param mixed $value Integer of the ID on database or other criterias as an array
		 * @return mixed 
		 */
		public function load($value)
		{
			if ($value)
			{
				$sql  = "select";

				$tables = array();
				$tables[$this->getTargetName()] = $this->getTargetName();

				$sql .= "\n\t".$this->getTargetName().".".$this->getMetaName("id")." as id";

				$comma = ",";

				//first, we get our own metas
				foreach ($this->pop_self_attrs as $key=>$attr)
				{
					if (get_class($this->$key) != "PArrayOf" && $this->checkAttrName($key))
					{
						$type = get_class($this->$key);
						$sql .= $comma."\n\t".$this->pop_db_driver->filterSelect($this->getTargetName(), $this->getMetaName($key), $key, $type);
					}
				}

				//now, we get our parents metas
				$parents = $this->getParentMetaAttrs();
				foreach ($parents as $table=>$attrs)
				{
					$tables[$table] = $table;

					foreach ($attrs as $attr=>$meta)
						if (get_class($this->$attr) != "PArrayOf" && $this->checkAttrName($attr))
						{
							$type = get_class($this->$attr);							
							$sql .= $comma."\n\t".$this->pop_db_driver->filterSelect($table, $meta, $attr, $type);
						}
				}

				//now we load the id of each object and it's parents
				foreach ($tables as $table)
				{
					if (strstr($table, '.'))
						$tablename = explode(".",$table);
					else
						$tablename = array("",$table);
					$sql .= $comma."\n\t".$table.".".$this->getMetaName("id")." as id_self_".$tablename[1];
				}

				//now, we "from" with left joins
				$sql .= "\nfrom";
				$sql_left_joins = "";
				$count = count($tables) - 1;
				$i = 0;
				foreach ($tables as $table)
				{
					if ($i==$count)
						$sql_left_joins = "\n\t".$table.$sql_left_joins;
					if ($i!=0)
						$sql_left_joins = str_replace("{next_table_id}",$table.".".$this->getMetaName("id"), $sql_left_joins);
					if ($i!=$count)
						$sql_left_joins = "\n\t\tleft join ".$table." on ".$table.".".$this->getMetaName("id")." = {next_table_id}".$sql_left_joins;
					$i++;
				}

				$sql .= $sql_left_joins;

				//now, we "where"
				$sql .= "\nwhere";
				$and = "";

				//now, we'll get by the selected attributes.
				//now, we build the where clause
				if (is_array($value))
				{
					foreach ($value as $key => $paramvalue)
					{
						$in_parent = false;
						$in_this = false;

						//again, we must check what is not in the parent
						if ($this->checkParentAttrExistence($key))
							$in_parent = true;

						if ($this->checkAttrExistence($key))
							$in_this = true;

						if (!$in_parent && $in_this)
						{
							if (is_int($paramvalue) || is_float($paramvalue))
								$sql .= $and."\n\t".$this->getTargetName().".".$this->getMetaName($key)." = ".$paramvalue;
							else
								$sql .= $and."\n\t".$this->getTargetName().".".$this->getMetaName($key)." = '".$paramvalue."'";
						}
						else
						{
							foreach ($parents as $table=>$attrs)
							{
								if (isset($attrs->$key))
								{
									if (is_int($paramvalue) || is_float($paramvalue))
										$sql .= $and."\n\t".$table.".".$this->getMetaName($key)." = ".$paramvalue;
									else
										$sql .= $and."\n\t".$table.".".$this->getMetaName($key)." = '".$paramvalue."'";
								}
							}

							if ($key=="id")
								$sql .= $and."\n\t".$table.".".$this->getMetaName($key)." = ".$paramvalue;
						}

						$and = " and ";
					}
				}
				else
					$sql .= $and."\n\t".$table.".".$this->getMetaName("id")." = ".$value;

				/****************still must check code************/
				try
				{
					if (POPEnvironment::$debug)
						echo "<pre><font color=#009900>".$sql."</font></pre><br>";
					$rs = POPDB::getConnection($this->pop_db)->query($sql);
					$result = $rs->fetchAll();
					$rs->closeCursor();

					if (!$result)
					{
						throw new Exception("No data found by this criteria.\n");
					}

					$count = 0;

					foreach ($result as $row)
					{
						$count++;

						foreach ($this->pop_attrs as $key => $objvalue)
						{
							if ($this->checkAttrName($key) && get_class($objvalue)!= "PArrayOf" && (isset($row[$key]) || isset($row[strtoupper($key)])))
								$this->set($key,$row[$this->pop_db_driver->filterResult($key)]);
							else if (get_class($objvalue)== "PArrayOf")
								$this->$key->reset();
						}

						//acrescentar o carregamento do id_self aqui
						$temp_selves = array();
						foreach ($row as $key => $cell)
						{
							if (strstr($key,"id_self") || strstr($key,"ID_SELF"))
								$temp_selves[$key] = $row[$this->pop_db_driver->filterResult($key)];
						}

						$this->setIdSelf($temp_selves);
					}

					if ($count == 1)
						return true;
					else
					{
						throw new Exception("More than one result returned. Load criteria must be more specific.\n");
					}
				}
				catch (Exception $e)
				{
					throw new Exception('Invalid SQL: <font color=#FF0000>"'.$sql.'"</font>'."\n". $e->getMessage());
				}
			}
		}

		/**
		 * Method used to set the value of $id_self, used for extended classes
		 * 
		 * @param array $arr
		 */
		protected function setIdSelf($arr)
		{
			$idselfname = "id_self_".$this->pop_table;

			if (!isset($arr[$idselfname]))
				$idselfname = substr($idselfname, 0, 30);

			$this->id_self = $arr[$idselfname];
			$this->id->value = $this->id_self;
			
			if ($this->pop_parent_type != "Persist")
				$this->pop_parent->setIdSelf($arr);
		}

		/**
		 * Method used to load the object and all associations with the id value passed, or
		 * can receive an array of attributes and values it should return
		 * 
		 * @param mixed $value Integer of the ID on database or other criterias as an array
		 * @return mixed 
		 */
		public function loadAll($value)
		{
			$this->load($value);
			foreach ($this->pop_attrs as $key=>$attr)
			{
				if (get_class($this->$key) == "PArrayOf")
				{
					$this->loadAllAssociated($key);
				}
			}
		}

		/**
		 * Method used to save the object on the database
		 * 
		 * @return bool true on sucess, false and Exception on failure
		 */
		public function save()
		{
			//this must be called after checking id
			//otherwise it could run the update instead
			//of insert for new records
			if ($this->pop_parent_type != "Persist")
				$this->pop_parent->save();

			//used to control if SQL should be executed or not in cases
			//where object heritages from another but no self attribute
			//is created
			$control = false;

			//if has PK, than update, else, insert
			if (!$this->id_self && $this->getTargetName() != $this->pop_parent_schema.".".$this->pop_parent_table)
			{
				if ($this->pop_parent_type != "Persist")
					$this->id->value = $this->pop_parent->id->value;

				$sql  = "insert into ".$this->getTargetName()." (";
				//get current values to set to fields
				$comma = "";
				$values = "values (";

				foreach ($this->pop_attrs as $key => $objvalue)
				{
					if ($this->checkAttrName($key) && get_class($objvalue)!= "PArrayOf")
					{
						$in_parent = false;

						if ($key != "id" && $this->pop_parent_type != "Persist")
							if ($this->checkParentAttrExistence($key))
								$in_parent = true;

						if ($key == "id" && $this->pop_parent_type == "Persist")
							$in_parent = true;

						if (!$in_parent)
						{
							$control = true;

							$sql .= $comma.$this->getMetaName($key);

							if ($objvalue || $objvalue === 0)
								$value = $objvalue->value;
							else
								$value = "null";

							$type = get_class($objvalue);
							
							$values .= $comma.$this->pop_db_driver->filterInsert($value, $type, $objvalue->defaultval);

							$comma = ", ";
						}
					}
				}
				$values .= ")";
				$sql .= ") ".$values;
			}
			else
			{
				$sql = "";

				if (count($this->pop_self_attrs))
				{
					$sql  = "update ".$this->getTargetName()." set ";
					$sql_id = "";
					//get current values to set to fields
					$comma = "";

					foreach ($this->pop_attrs as $key => $objvalue)
					{
						if ($this->checkAttrName($key) && get_class($objvalue)!= "PArrayOf")
						{
							if ($key=="id")
							{
								if ($this->getTargetName() == $this->pop_parent_schema.".".$this->pop_parent_table)
								{
									$this->id->value = $this->pop_parent->id->value;
									$this->id_self = $this->id->value;
								}

								$sql_id .= " where ".$this->getMetaName($key)." = ".$this->id->value;
							}
							else
							{
								$in_parent = false;

								if ($this->checkParentAttrExistence($key))
									$in_parent = true;

								if (!$in_parent)
								{
									$control = true;

									if ($objvalue || $objvalue === 0)
										$value = $objvalue->value;
									else
										$value = "null";

									$type = get_class($objvalue);
									
									$value = $this->pop_db_driver->filterUpdate($value, $type, $objvalue->defaultval);

									$sql .= $comma.$this->getMetaName($key)." = ".$value;
									$comma = ", ";
								}
							}
						}
					}

					$sql .= $sql_id;
				}
			}

			if ($control)
			{
				try
				{
					if (POPEnvironment::$debug)
						echo "<pre><font color=#009900>".$sql."</font></pre><br>";

					$this->pop_db_driver->beginTransaction($this->pop_db);
					POPDB::getConnection($this->pop_db)->exec($sql);

					if (!$this->id->value)
					{
						$this->id->value = $this->pop_db_driver->getLastId($this->pop_db, $this->getParentTable(),$this->getMetaName("id"));
						if (POPEnvironment::$debug)
							echo "Last Insert Id: <font color=#FF0000>".$this->id->value."</font>";
					}
					
					$this->id_self = $this->id->value;

					$this->pop_db_driver->commit($this->pop_db);
					return true;
				}
				catch (Exception $e)
				{
					$this->pop_db_driver->rollBack($this->pop_db);
					throw new Exception('Invalid SQL: <font color=#FF0000>"'.$sql.'"</font>'."\n". $e->getMessage());
				}
			}
		}
		
		/**
		 * Method used to save the object and all associations on the database
		 */
		public function saveAll()
		{
			$this->save();

			foreach ($this->pop_attrs as $key=>$attr)
			{
				if (get_class($this->$key) == "PArrayOf")
				{
					$this->saveAllAssociated($key);
				}
			}
		}
		
		/**
		 * Method used to delete the object from the database
		 */
		public function delete()
		{
			if ($this->id->value)
			{
				$sql = "delete from ".$this->getTargetName()." where ".$this->getMetaName("id")." = ".$this->id->value;
				try
				{
					if (POPEnvironment::$debug)
						echo "<pre><font color=#009900>".$sql."</font></pre><br>";

					$this->pop_db_driver->beginTransaction($this->pop_db);
					POPDB::getConnection($this->pop_db)->exec($sql);
					$this->pop_db_driver->commit($this->pop_db);

					if ($this->pop_parent_type!="Persist")
						$this->pop_parent->delete();
					return true;
				}
				catch (Exception $e)
				{
					$this->pop_db_driver->rollBack($this->pop_db);
					throw new Exception('Invalid SQL: <font color=#FF0000>"'.$sql.'"</font>'."\n". $e->getMessage());
				}
			}
			else
			{
				throw new Exception("This object is not in the database yet.\nYou have to save it first to generate an id.");
			}
		}
		
		/**
		 * Method used to add an object to an attribtute that is a PArrayOf
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 * @param mixed $element Name of the attribute that holds the PArrayOf
		 */
		public function addAssociated($associated, $element)
		{
			$this->$associated->add($element);
		}
		
		/**
		 * Method used to delete an object from a collection hold on a PArrayOf attribute
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 * @param string $criteria Criteria used ("index"||"id")
		 * @param integer $value Value of the criteria
		 */
		public function delAssociated($associated,$criteria,$value)
		{
			if($criteria == "index")
			{
				$index = $value;
				if ($index < count($this->$associated->array) && $this->$associated->array[$index])
					$this->$associated->array[$index]->delete();
				else
				{
					throw new Exception("There is no object loaded on $associated with index: $index.");
				}
			}
			else if($criteria == "id")
			{
				$exists = false;
				foreach($this->$associated->array as $index => $obj)
				{
					if ($obj->id->value == $value)
					{
						$this->$associated->array[$index]->delete();
						$exists = true;
					}
				}
				if (!$exists)
				{
					throw new Exception("There is no object loaded on $associated with this id: $value.");
				}
			}

			$this->$associated->del($criteria,$value);
		}

		/**
		 * Method used to get an object from a collection hold on a PArrayOf attribute
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 * @param string $criteria Criteria used ("index"||"id")
		 * @param integer $value Value of the criteria
		 */
		public function &getAssociated($associated,$criteria,$value)
		{
			if($criteria == "index")
			{
				$index = $value;
				if ($index < count($this->$associated->array) && $this->$associated->array[$index])
					return $this->$associated->array[$index];
				else
				{
					throw new Exception("There is no object loaded on $associated with index: $index.");
				}
			}
			else if($criteria == "id")
			{
				foreach($this->$associated->array as $index => $obj)
					if ($obj->id->value == $value)
						return $this->$associated->array[$index];

				//if it doens't find any, throw exception
				throw new Exception("There is no object loaded on $associated with this id: $value.");
			}
		}
		
		/**
		 * Internal Method used to get the name of an associated object
		 * 
		 * @param object $obj Object used for the extraction 
		 */
		public function getAssociatedName($obj)
		{
			if (in_array($this->pop_parent_type, $obj->parent_classes))
			{
				foreach ($obj->parent_classes as $classname)
				{
					if ($this->pop_parent_type == $classname)
						return strtolower(get_class($this->pop_parent));
					else
						return strtolower(get_class($this));
				}
			}
			else
			{
				if(in_array(get_class($this), $obj->parent_classes))
				{
					foreach ($obj->parent_classes as $classname)
						return strtolower(get_class($this));
				}
				else
					return strtolower($this->pop_parent->getAssociatedName($obj));
			}
		}

		/**
		 * Method used to get load an object to a collection hold on by a PArrayOf attribute
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 */
		public function loadAssociated($associated)
		{
			try
			{
				$obj = new $this->$associated->objecttype();
				$associated_name = $this->getAssociatedName($obj);
				$id = $obj->getMetaName("id_".$associated_name);

				$id_value = $this->id->value;

				if (!$this->id->value && $this->pop_parent->id->value)
					$id_value = $this->pop_parent->id->value;

				$sql = "select ".$obj->getMetaName("id")." as id from ".$this->pop_schema.".".$obj->pop_table." where $id = ".$id_value;
				if (POPEnvironment::$debug)
					echo "<pre><font color=#009900>".$sql."</font></pre><br>";
				$rs = POPDB::getConnection($this->pop_db)->query($sql);
				$result = $rs->fetchAll();
				$rs->closeCursor();

				$this->$associated->reset();

				foreach ($result as $row)
				{
					$obj = new $this->$associated->objecttype();
					$obj->load($row["id"]);

					$this->$associated->add($obj);
				}
			}
			catch (Exception $e)
			{
				throw new Exception("<pre><font color=#FF0000>Error: $attr<br>".$e->getMessage()."</font>");
			}
		}

		/**
		 * Method used to get load all objects to a collection hold on by a PArrayOf attribute
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 */
		public function loadAllAssociated($associated)
		{
			$this->loadAssociated($associated);

			foreach ($this->$associated->array as $obj)
			{
				$obj->loadAll($obj->id->value);
			}
		}

		/**
		 * Method used to get save all objects hold on a collection by a PArrayOf attribute
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 */
		public function saveAssociated($associated)
		{
			if (!$this->id->value)
			{
				throw new Exception("This object is not in the database yet.\nYou have to save it first to generate an id.");
			}

			$obj = new $this->$associated->objecttype();
			$associated_name = $this->getAssociatedName($obj);
			$id = "id_".$associated_name;

			foreach ($this->$associated->array as $obj)
			{
				foreach ($obj->parent_classes as $objparent)
				{
					$id_parent = "id_".strtolower($objparent);

					if (!$obj->$id->value)
						$obj->$id->value = $this->id->value;

					if(!$obj->pop_attrs[$id_parent]->value)
					{
						if($this->$id_parent->value)
							$obj->pop_attrs[$id_parent]->value = $this->$id_parent->value;
						else
						{
							throw new Exception("ID not set.");
						}
					}
				}

				$obj->save();
			}
		}

		/**
		 * Method used to get save all objects and associated objects hold on a collection 
		 * by a PArrayOf attribute
		 * 
		 * @param string $associated Name of the attribute that holds the PArrayOf
		 */
		public function saveAllAssociated($associated)
		{
			$obj = new $this->$associated->objecttype();
			$associated_name = $this->getAssociatedName($obj);
			$id = "id_".$associated_name;

			foreach ($this->$associated->array as $obj)
			{
				foreach ($obj->parent_classes as $objparent)
					if (isset($obj->$id) && !$obj->$id->value)
						$obj->$id->value = $this->id->value;

				$obj->saveAll();
			}
		}

		/**
		 * Method used to get search objects on the database, given the criterias
		 * 
		 * @param array $return_attributes Array listing the attributes/columns you want to be returned 
		 * @param array $search_attributes_and_values Criterias used for searching
		 * @param string $output_format Format of the return. Can be "PDO" or "XML". Default is PDO.
		 * @return mixed PDO Recordset Object or XML string
		 */
		public function search($return_attributes = null, $search_attributes_and_values = null, $output_format = null)
		{
			//if ($return_attributes && $search_attributes_and_values)
			{
				$comma = "";

				$sql  = "select";

				$tables = array();
				$tables[$this->getTargetName()] = $this->getTargetName();

				$sql .= "\n\t".$this->getTargetName().".".$this->getMetaName("id")." as id";

				$comma = ",";

				if (is_array($return_attributes))
				{
					foreach ($return_attributes as $field)
					{
						//first, we get our own metas
						foreach ($this->pop_self_attrs as $key=>$value)
						{
							if ($field == $key)
							{
								if (get_class($this->$key) != "PArrayOf" && $this->checkAttrName($key))
								{
									$type = get_class($this->$key);
									$sql .= $comma."\n\t".$this->pop_db_driver->filterSelect($this->getTargetName(), $this->getMetaName($key), $key, $type);
								}
							}
						}

						//now, we get our parents metas
						$parents = $this->getParentMetaAttrs();
						foreach ($parents as $table=>$attrs)
						{
							$tables[$table] = $table;

							foreach ($attrs as $attr=>$meta)
							{
								if ($field == $attr)
								{
									if (get_class($this->$attr) != "PArrayOf" && $this->checkAttrName($attr))
									{
										$type = get_class(get_class($this->$attr));
										$sql .= $comma."\n\t".$this->pop_db_driver->filterSelect($table, $meta, $attr, $type);
									}
								}
							}
						}
					}
				}
				else// if($return_attributes == "*")
				{
					//first, we get our own metas
					foreach ($this->pop_self_attrs as $key=>$value)
					{
						if (get_class($this->$key) != "PArrayOf" && $this->checkAttrName($key))
						{
							$type = get_class($this->$key);
							$sql .= $comma."\n\t".$this->pop_db_driver->filterSelect($this->getTargetName(), $this->getMetaName($key), $key, $type);
						}
					}

					//now, we get our parents metas
					$parents = $this->getParentMetaAttrs();
					foreach ($parents as $table=>$attrs)
					{
						$tables[$table] = $table;

						foreach ($attrs as $attr=>$meta)
						{
							if (get_class($this->$attr) != "PArrayOf" && $this->checkAttrName($attr))
							{
								$type = get_class(get_class($this->$attr));
								$sql .= $comma."\n\t".$this->pop_db_driver->filterSelect($table, $meta, $attr, $type);
							}
						}
					}
				}

				//now, we "from" with left joins
				$sql .= "\nfrom";
				$sql_inner_joins = "";
				$count = count($tables) - 1;
				$i = 0;
				foreach ($tables as $table)
				{
					if ($i==$count)
						$sql_inner_joins = "\n\t".$table.$sql_inner_joins;
					if ($i!=0)
						$sql_inner_joins = str_replace("{next_table_id}",$table.".".$this->getMetaName("id"), $sql_inner_joins);
					if ($i!=$count)
						$sql_inner_joins = "\n\t\tinner join ".$table." on ".$table.".".$this->getMetaName("id")." = {next_table_id}".$sql_inner_joins;
					$i++;
				}

				$sql .= $sql_inner_joins;

				//now, we'll get by the selected attributes.
				//now, we build the where clause
				if (is_array($search_attributes_and_values))
				{
					//now, we "where"
					$sql .= "\nwhere";

					foreach ($search_attributes_and_values as $key => $paramvalue)
					{
						$in_parent = false;
						$in_this = false;

						//again, we must check what is not in the parent
						if ($this->checkParentAttrExistence($key))
							$in_parent = true;

						if ($this->checkAttrExistence($key))
							$in_this = true;

						if (!$in_parent && $in_this || $key=="id")
						{
							if (is_array($paramvalue))
							{
								foreach($paramvalue as $value)
								{
									$sql .= "\n\t".$this->getTargetName().".".$this->getMetaName($key)." ".$value;									
								}
							}
							else
								$sql .= "\n\t".$this->getTargetName().".".$this->getMetaName($key)." ".$paramvalue;
						}
						else
						{
							foreach ($parents as $table=>$attrs)
							{
								if (isset($attrs->$key))
								{
									if (is_array($paramvalue))
									{
										foreach($paramvalue as $value)
										{
											$sql .= "\n\t".$table.".".$this->getMetaName($key)." ".$value;											
										}
									}
									else
										$sql .= "\n\t".$table.".".$this->getMetaName($key)." ".$paramvalue;
								}
							}
						}
					}
				}

				/****************still must check code************/
				try
				{
					if (POPEnvironment::$debug)
						echo "<pre><font color=#009900>".$sql."</font></pre><br>";
					$rs = POPDB::getConnection($this->pop_db)->query($sql);
					$result = $rs->fetchAll();
					$rs->closeCursor();

					if (!$output_format || ($output_format != "xml" && $output_format != "XML"))
						return $result;
					else if ($output_format == "xml" || $output_format == "XML")
					{
						$return = "<result>";

						foreach ($result as $item)
						{
							$return .= "<item id=\"".$item["id"]."\">";
							if ($return_attributes)
							{
								foreach ($return_attributes as $key)
								{
									$return .= "<".$key.">".$item[$key]."</".$key.">";
								}
							}
							else
							{
								foreach ($this->pop_attrs as $key => $value)
								{
									if ($this->checkAttrName($key))
										$return .= "<".$key.">".$item[$key]."</".$key.">";
								}
							}
							$return .= "</item>";
						}

						$return .= "</result>";

						return $return;
					}
				}
				catch (Exception $e)
				{
					throw new Exception('Invalid SQL: <font color=#FF0000>"'.$sql.'"</font>'."\n". $e->getMessage());
				}
			}
		}

		/**
		 * Converts the array to a JSON String, useful on WebServices
		 * 
		 * @param string $attr Array of the attributes you want to be converted. null means you want all of them
		 * @param string $tab Internal use - for identation
		 * @param string $endcomma Internal use - for identation
		 * @return string 
		 */
		public function toJSON($attr=null, $tab = "", $endcomma="")
		{
			$comma = "";
			$json = $tab."{";

			foreach ($this->pop_attrs as $key => $objvalue)
			{
				if ($this->checkAttrName($key) && (!$attr || ($attr && in_array($key,$attr))))
				{
					if ($objvalue instanceof PArrayOf)
					{
						$json .= $comma."\n".$tab."\t".$key." : [\n";
						$json .= $objvalue->toJSON($attr, $tab, $endcomma);
						$json .= $tab."\t]";
					}
					else if (isset($objvalue))
						$json .= $comma."\n".$tab."\t".$key ." : ".$objvalue->value;

					$comma = ",";
				}
			}
			$json .= "\n".$tab."}".$endcomma."\n}\n";

			return $json;
		}

		/**
		 * Converts the array to a XML String, useful on WebServices
		 * 
		 * @param string $attr Array of the attributes you want to be converted. null means you want all of them
		 * @return string 
		 */
		public function toXML($attr=null)
		{
			$xml = "<".get_class($this)." id=\"".$this->id->value."\">";

			foreach ($this->pop_attrs as $key => $objvalue)
			{
				if ($this->checkAttrName($key) && (!$attr || ($attr && in_array($key,$attr))))
				{
					if ($objvalue instanceof PArrayOf)
					{
						$xml .= "<".$key .">";
						$xml .= $objvalue->toXML($attr);
						$xml .= "</".$key .">";
					}
					else if (isset($objvalue))
						$xml .= "<".$key .">". $objvalue->value . "</".$key .">";
				}
			}

			$xml .= "</".get_class($this).">";

			return $xml;
		}

		/**
		 * Drops the table associated to the object from the database
		 */
		public function dropTable()
		{
			try
			{
				$this->pop_db_driver->dropTable($this->pop_db, $this->pop_schema, $this->pop_table);
			}
			catch (Exception $e)
			{
				throw new Exception('Invalid SQL: <font color=#FF0000>"'.$sql.'"</font>'."\n". $e->getMessage());
			}
		}

		/**
		 * Creates the table associated to the object on the database
		 */
		public function createTable()
		{
			$comma = "";
			$sql = "";
			$sql_fields = "";

			foreach ($this->pop_attrs as $key => $objvalue)
			{
				if ( $this->checkAttrName($key) && get_class($objvalue)!= "PArrayOf")
				{
					$in_parent = false;

					if ($key == "id" && $this->pop_parent_type == "Persist")
						$sql_fields .= $comma."\n\t".$this->getMetaName("id").$this->pop_db_driver->identityField();
					else
					{

						if ($this->checkParentAttrExistence($key))
							$in_parent = true;

						if ($key == "id")
							$in_parent = false;

						if (!$in_parent)
						{
							$type = strtolower(get_class($objvalue));
							$type = substr($type,1,(strlen($type) - 1));

							$sql_fields .= $comma."\n\t".$this->getMetaName($key)." ";
							
							$size = null;
							
							if (!is_null($objvalue->size))
								$size = $objvalue->size;

							$sql_fields .= $this->pop_db_driver->fieldCreation($type, $objvalue->defaultval, $size);
						}
					}
					if (!$in_parent)
						$comma = ",";
				}
			}

			$sql .= "create table ".$this->getTargetName()."\n(";

			if ($sql_fields=="")
				$comma = "";

			$sql .= $sql_fields.$comma."\n\tprimary key (".$this->getMetaName("id").")";

			if ($this->parent_classes)
			{
				foreach($this->parent_classes as $parenttype)
				{
					if (!$this->checkParentAttrExistence($this->getMetaName("id_".strtolower($parenttype))))
					{
						$sql .= ",\n\tforeign key(".$this->getMetaName("id_".strtolower($parenttype)).")";
	
						$parent_class_temp = new $parenttype();
	
						if(count($this->pop_attrs['meta_mapping'])>0 || count($parent_class_temp->pop_attrs['meta_mapping'])>0)
						{
							$sql .= "\n\t\treferences ".strtolower($parent_class_temp->getTargetName())."(".$parent_class_temp->getMetaName("id").")";
						}else
							$sql .= "\n\t\treferences ".strtolower($parent_class_temp->getTargetName())."(id)";
							
						$sql .= $this->pop_db_driver->createPropagation();
					}
				}
			}

			if ($this->pop_parent_type != "Persist")
			{
				//foreign key
				$sql .= ",\n\tforeign key(".$this->getMetaName("id").")";
				$sql .= "\n\t\treferences ".$this->pop_parent->getTargetName()."(".$this->pop_parent->getMetaName("id").")";
				$sql .= $this->pop_db_driver->createPropagation();
				$sql .= "\n)";
			}
			else
				$sql .= "\n)\n";
			if(POPEnvironment::$engine)
				$sql .= "ENGINE=".POPEnvironment::$engine;

			$sql .= $this->pop_db_driver->sequenceCreation($this->getTargetName());

			try
			{
				//first, try to create the schema (if necessary)
				$this->pop_db_driver->createSchema($this->pop_db,$this->pop_schema);
				
				$this->pop_db_driver->beginTransaction($this->pop_db);
				POPDB::getConnection($this->pop_db)->exec($sql);
				$this->pop_db_driver->commit($this->pop_db);

				if (POPEnvironment::$debug)
					echo "<pre><font color=#009900>".$sql."</font></pre><br>";

				return true;
			}
			catch (Exception $e)
			{
				$this->pop_db_driver->rollBack($this->pop_db);
				throw new Exception('Invalid SQL: <font color=#FF0000>"'.$sql.'"</font>'."\n". $e->getMessage());
			}
		}

		/**
		 * Alters the table associated to the object on the database to the current object spacification.
		 * If the table doesn't exists, it tries to create it.
		 */
		public function alterTable()
		{

		}

		/**
		 * Check if the attribute can be used, or if it's an internal attribute
		 * 
		 * @return bool
		 */
		protected function checkAttrName($key)
		{
			if (
					$key != "id_self" &&
					$key != "pop_db" &&
					$key != "pop_dbtype" &&
					$key != "pop_parent" &&
					$key != "pop_parent_type" &&
					$key != "pop_attrs" &&
					$key != "pop_parent_attrs" &&
					$key != "pop_self_attrs" &&
					$key != "pop_schema" &&
					$key != "pop_table" &&
					$key != "pop_parent_schema" &&
					$key != "pop_parent_table" &&
					$key != "pop_db_driver" &&
					$key != "parent_classes" &&
					$key != "schema" &&
					$key != "table" &&
					$key != "meta_mapping"
				)
				return true;

			return false;
		}

		/**
		 * Get the table name of the parent object
		 * 
 		 * @return string
		 */
		protected function getParentTable()
		{
			if ($this->pop_parent_type != "Persist")
			{
				return $this->pop_parent->getParentTable();
			}
			else
				return $this->getTargetName();
		}

		/**
		 * Check if the attribute exists with that name
		 * 
		 * @return bool
		 */
		protected function checkAttrExistence($key)
		{
			foreach ($this->pop_attrs as $var => $objvalue)
				if ($key == $var)
					return true;
			return false;
		}

		/**
		 * Check if the attribute exists with that name on the parent
		 * 
		 * @return bool
		 */
		protected function checkParentAttrExistence($key)
		{
			foreach ($this->pop_parent_attrs as $var => $parobjvalue)
				if ($key == $var)
					return true;
			return false;
		}

		/**
		 * Get the name of the attribute from the meta mapping
		 * 
		 * @return string
		 */
		public function getMetaName($key)
		{
			$key = strtolower($key);
			if ($this->meta_mapping && isset($this->meta_mapping[$key]))
				return $this->meta_mapping[$key];
			else
			{
				if($this->checkParentAttrExistence($key))
                    return $this->pop_parent->getMetaName($key);
                else
                    return $key;
			}
		}

		/**
		 * Get the name of the field from the meta mapping
		 * 
		 * @return string
		 */
		public function getRealName($meta)
		{
			$meta = strtolower($meta);
			if ($this->meta_mapping && in_array($meta,$this->meta_mapping))
				return array_search($meta,$this->meta_mapping);
			else
				return $meta;
		}

		/**
		 * Set the PDO DB connection that should be used by the object and get default properties
		 * from the driver, like default schema. 
		 * 
		 * @param string $pop_db name of the global used for database connection
		 */
		public function setDBConnection()
		{
			if ($this->db)
				$this->pop_db = $this->db;
				
			POPDB::getConnection($this->pop_db)->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			$this->pop_dbtype = POPDB::getConnection($this->pop_db)->getAttribute(PDO::ATTR_DRIVER_NAME);
		
			$driver = POPDBDriverRegistry::getDriver($this->pop_dbtype);
			$this->pop_db_driver = new $driver($this->pop_db);
		}

		/**
		 * Get the PDO DB connection name in use
		 * 
		 * @return string
		 */
		public function getDBConnection()
		{
			return $this->pop_db;
		}

		/**
		 * Get the name of the schema from the meta mapping
		 * 
		 * @return string
		 */
		protected function getSchemaName()
		{
			if ($this->schema)
				$this->pop_schema = $this->schema;
			else
				$this->schema = $this->pop_schema = strtolower($this->pop_db_driver->schema);

			return $this->pop_schema;
		}

		/**
		 * Get the name of the table from the meta mapping
		 * 
		 * @return string
		 */
		protected function getTableName()
		{
			if ($this->table)
				$this->pop_table = $this->table;
			else
				$this->table = $this->pop_table = strtolower(get_class($this));

			return $this->pop_table;
		}
		
		/**
		 * Get the target name (schema + table)
		 */
		public function getTargetName()
		{
			if ($this->pop_schema)
				return $this->pop_schema.$this->pop_db_driver->separator.$this->pop_table;
			else
				return $this->pop_table;
		}

		/**
		 * Get the attributes mapped on parent
		 * 
		 * @return mixed
		 */
		public function getParentMetaAttrs($arr = array())
		{
			if ($this->pop_parent_type != "Persist")
			{
				if (!isset($arr[$this->pop_parent->getTargetName()]))
					$arr[$this->pop_parent->getTargetName()] = array();

				$temp_arr = $this->pop_parent->pop_self_attrs;

				foreach ($temp_arr as $key=>$value)
					if ($this->pop_parent->checkAttrName($key))
						$arr[$this->pop_parent->getTargetName()][$key] = $this->pop_parent->getMetaName($key);

				return $this->pop_parent->getParentMetaAttrs($arr);
			}
			else
				return $arr;
		}

		/**
		 * Initializes the class
		 */
		protected function initialize()
		{
			//object will use the Global PDO DB connection created before
			$this->setDBConnection();
			//get the schema name to be used
			$this->getSchemaName();
			//get the table name to be used
			$this->getTableName();

			$this->pop_attrs = get_object_vars($this);
			$this->pop_parent_attrs = array();
			$this->pop_self_attrs = array();

			//if has parent and it is not Persist, we must have a way to get it
			$this->pop_parent_type = get_parent_class($this);

			if ($this->pop_parent_type != "Persist")
			{
				$this->pop_parent = new $this->pop_parent_type();
				$this->pop_parent_attrs	= get_object_vars($this->pop_parent);
				$this->pop_parent_table = strtolower($this->pop_parent->getTableName());
				$this->pop_parent_schema = strtolower($this->pop_parent->getSchemaName());

				if ($this->pop_parent->parent_classes == $this->parent_classes)
					$this->parent_classes = array();
			}

			$this->pop_self_attrs = array_diff_key($this->pop_attrs, $this->pop_parent_attrs,array("id"=>""));
		}
	}

?>
Return current item: Persistent Objects for PHP