<?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 extends POPCore
{
/**
* Name of the schema on the database declared for metamapping
*
* @access protected
* @var string
*/
protected $schema;
/**
* Name of the table on the database declared for metamapping
*
* @access protected
* @var string
*/
protected $table;
/**
* 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
{
POPDebug::exception("You cannot set the ID attribute manually. It must be given by the database.");
}
}
catch (Exception $e)
{
POPDebug::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 ($this->checkAttrName($name))
{
if (isset($this->$name) && get_class($this->$name)=="PArrayOf")
return $this->$name->array;
else if (isset($this->$name))
return $this->$name->value;
}
else
return $this->$name;
}
/**
* 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 for getting the datatype of an attribute
* @param $attr
* @return string
*/
public function getAttrType($attr)
{
return get_class($this->$attr);
}
/**
* Method used to set the value of $id_self, used for extended classes
*
* @param array $arr
*/
public 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 for reseting PArrayOf
* @param $key
* @return unknown_type
*/
public function resetArray($key)
{
$this->$key->reset();
}
/**
* 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)
{
try
{
$this->pop_db_driver->load($value, $this);
}
catch (Exception $e)
{
POPDebug::exception($e->getMessage());
}
}
/**
* 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 ($this->getAttrType($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()
{
try
{
$this->pop_db_driver->save($this);
}
catch (Exception $e)
{
POPDebug::exception($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()
{
try
{
$this->pop_db_driver->delete($this);
}
catch (Exception $e)
{
POPDebug::exception($e->getMessage());
}
}
/**
* 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
{
POPDebug::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)
{
POPDebug::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
{
POPDebug::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
POPDebug::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;
POPDebug::log($sql);
$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)
{
POPDebug::exception("Error: $attr\n".$e->getMessage());
}
}
/**
* 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)
{
POPDebug::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
{
POPDebug::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)
{
try
{
return $this->pop_db_driver->search($this, $return_attributes, $search_attributes_and_values, $output_format);
}
catch (Exception $e)
{
POPDebug::exception($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)
{
POPDebug::exception($e->getMessage());
}
}
/**
* Creates the table associated to the object on the database
*/
public function createTable()
{
try
{
$this->pop_db_driver->createTable($this);
}
catch (Exception $e)
{
POPDebug::exception($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
*/
public 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 != "pop_parent_classes" &&
$key != "schema" &&
$key != "table" &&
$key != "meta_mapping" &&
$key != "pop_meta_mapping"
)
return true;
return false;
}
/**
* Get the table name of the parent object
*
* @return string
*/
public 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
*/
public 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
*/
public 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);
$this->pop_db_driver = POPDBDriverRegistry::getDriver($this->pop_dbtype);
}
/**
* 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
*/
public 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
*/
public 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
*/
public 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"=>""));
POPDebug::info('Object of type '.get_class($this).' instantiated');
}
}
?>