Simple PHP Object Persistence

Christian Decker wrote this mid-afternoon:

A while back I’ve been working on some PHP Projects that required me to create an abstraction layer for my objects. It should be easy to use it and it should be very short as I wanted to concentrate on the real program and not the repetitive work, which persistence still is with PHP. So I wrote my own little Database wrapper with basic functionality making it as lightweight as possible. The next step was modelling a BaseVO (Base Virtual Object) that the other would inherit from. This meant implementing a way to retrieve both many-to-one and one-to-many relations. So let’s get started with the code:

BaseVO.class.php

First of all let’s define the common variables that we will need in each and every class:

  var $table = null;
  var $className = __CLASS__;
  var $id = null;
The $table will tell us what table the instances of this type will be saved in, the $className will tell us what type of class we are working on (more on this later) and the $id should be pretty obvious :D While $id can be left as is in this class, $className and $table will have to be set in every descending class.

Next we need a way to retrieve the object from the database:

  /**
  * Loads an entry given it's id.
 */
  function load($id){
    global $db;
    $this->loadFromArray($db->queryArray("SELECT * FROM ".$this->table." WHERE id='".$id."';"));
  }
 
 /**
  * Loads the object from an array, possibly from an SQL-ResultSet.
  */
  function loadFromArray($arr){
    if(count($arr)<2)
      return;
    foreach($arr as $k =>; $v){
      $this->$k = $v;
    }
  }
As you can see all the magic is in loadFromArray which assigns every value from the resultset to the corresponding variable in the object. load is simply there to make it easy to load the object without a preexisting resultset. Why did I choose to do it this way? Because, as you’ll see later, I will often have ResultSets of more than one row that have to be transformed into an object, and executing a query for each and every one of them is not practical. The idea is just to get a whole bunch of rows and then map them to objects. SQL is our friend remember? :)

Now that we can load the objects from the database, what would be more obvious than trying to save them back to the Database? Here you go:

  function save(){
  	global $db;
    // Get the fields.
	$rs =& mysql_query("DESCRIBE ".$this->table.";");
	while($t = mysql_fetch_assoc($rs)){
      $fields[$t["Field"]] = $this->$t["Field"];
	}
	$db->saveArray($this->table,$fields);
  }
Uhm, not much to see here, sorry. To understand this we’ll have to come back later when discussing the Database-Wrapper. For now just remember that all this does is filter out all the object variables that are not in the corresponding table, as MySQL will return an error when trying to set a variable that is not also in the table. Notice that this seems to be working only with MySQL, since most others don’t understand the DESCRIBE query

Anyway let’s move on to relations. First there is the many-to-one relation which means that the table has a foreign key pointing to another table. This is quite easy to implement, but I decided to give it an extra feature: caching. Let’s see how it’s done:

  function setForeign($className,$key,$value){
  	$cacheName = "_".$key;
  	if(is_numeric($value)){
	  $this->$key =& $value;
	  $this->$cacheName = null;
	}else{
	  $this->$key = $value->id;
	  $this->$cacheName =& $value;
	}
  }
 
  function getForeign($className,$key){
  	$cacheName = "_".$key;
  	if(empty($this->$cacheName)){
  	  $o =& new $className();
	  $o->load($this->$key);
	  $this->$cacheName =& $o;
  	}
	return $this->$cacheName;
  }
This may look a bit tricky but it isn’t. getForeign take a $className that will be used to create the instance of the object we are trying to get, if it has not yet been cached, and it gets a $key, this is mainly the name of the variable inside the current object. It then construct the $cacheName which will be used to access the cached version inside this object and if the object has not yet been cached it retrieves it from the database, using the load function described above.
setForeign works in a similar fashion, but unsets the cache if the passed value is an id and not an instance of the object.

Ok, we’re almoset done with the BaseVO, last thing we have to look at is getMany which represents the one-to-many relation:

  function &getMany($className,$options){
  	global $db;
  	$data =& $db->queryAllArray("SELECT * FROM ".(empty($options['table'])?$className."s":$options['table'])." WHERE ".(empty($options['foreign'])?$this->className:$options['foreign'])."='".$this->id."';");
	$res = array();
	foreach($data as $d){
		$o =& new $className();
		$o->loadFromArray($d);
		$res[]=& $o;
	}
	return $res;
  }
This code is pretty straightforward as it just gets a bunch of rows that have the foreign key set to the current objects id (the usual way to do this in relational mapping), and then creates object instances from them. The new thing is that now we have an options argument which is supposed to be an array containing optional variables such as table and foreign that will tell us which table to use and what column in that is to use as foreign key. That’s about it for the BaseVO class, later I’ll show an example on how to use it.

Database.class.php

As this is simply a wrapper to the mysql-functions I’ll just post it here and comment only the saveArray function.

< ?php
/**
 *  BaseVO, Basic Virtual Object, abstraction for object persistence.
 *  Copyright (C) 2007 Christian Decker
 *
 *  @author Christian Decker <decker.christian@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http ://www.gnu.org/licenses/>.
 */
 
class Database {
	
	var $fd = null;
	
	function Database($host, $username, $password, $dbname){
		$this->fd =& mysql_pconnect($host, $username, $password) or die(mysql_error());
		mysql_select_db($dbname,$this->fd) or die(mysql_error());
	}
	
	function close(){
		return mysql_close($this->fd);
	}
	
	function &query($sql){
		print($sql."\n");
		$rs =& mysql_query($sql) or die(mysql_error());
		return $rs;
	}
	
	function queryArray($sql){
		return mysql_fetch_assoc($this->query($sql));
	}
	
	function exec($sql){
		$this->query($sql);
	}
	
	/**
	 * @return bool - Was the saved array new or did it matched an existing one?
	 */
	function saveArray($table, $arr){
		$atoms = array();
		foreach($arr as $k => $v){
			$atoms[] = $k."='".$v."'";
		}
		$sql = "";
		$new = !$arr['id'] || $arr['id'] == null;
		if($new){
			// INSERT
			unset($arr['id']);
			$sql = "INSERT INTO ".$table." SET ".implode(',',$atoms).";";
		}else{
			// UPDATE
			$sql = "UPDATE ".$table." SET ".implode(',',$atoms)." WHERE id='".$arr['id']."';";
		}
		$this->exec($sql);
		return $new?mysql_insert_id():null;
	}
	
	function &queryAllArray($sql){
	  $res = array();
	  $rs =& $this->query($sql);
	  while($t = mysql_fetch_assoc($rs))
	  	$res[] = $t;
	  return $res;
	}
}
?>

saveArray takes an array of key-value-pairs and saves it into the table. If the id is not set it inserts a new row, otherwise the existing row is updated.

Example

As the above makes no sense at all by itself here comes a bit of code I’m currently working on that uses it. First is a User-class that is used to represent the sites users. A user has many bank accounts:

require_once('BaseVO.class.php');
require_once('Account.class.php');
 
class User extends BaseVO {
  var $username = null;
  var $password = null;
  var $email = null;
  var $accounts = null;
 
  var $table = "users";
  var $className = "User";
 
  function getAccounts(){
  	return $this->getMany("Account",array('table' => 'accounts','foreign' => 'owner'));
  }
}

This uses the getMany-function to retrieve all the accounts a user may have. Let’s see these accounts:

require_once('BaseVO.class.php');
require_once('User.class.php');
require_once('Entry.class.php');
 
class Account extends BaseVO {
  var $owner = null;
  var $name = null;
 
  // Cached items
  var $_owner = null;
 
  var $table = "accounts";
  var $className = "Account";
 
  function setOwner($user){
  	return $this->setForeign("User","owner",$user);
  }
 
  function getOwner(){
  	return $this->getForeign("User","owner");
  }
 
  function getEntries(){
  	return $this->getMany("Entry",array('table' => 'entries'));
  }
}

Besides the normal variable and function declaration there are the functions that rely on setForeign, getForeign and getMany to work on the related objects. Pretty simple, huh?,
Now to complete the relations I’ll just put the Entry class too, but by now you should be able to write it yourself :)

require_once('BaseVO.class.php');
require_once('User.class.php');
 
class Entry extends BaseVO {
  var $account = null;
  var $amount = 0;
  var $description = '';
 
  // Cached items
  var $_account = null;
 
  var $table = "entries";
  var $className = __CLASS__;
 
  function setAccount($account){
  	$this->setForeign("Account","account",$account);
  }
  
  function getAccount(){
  	return $this->getForeign("Account","account");
  }
}

Ok, that’s it for now. Any feedback is very welcome :D
And for all of those that don’t want to reconstruct my files using the above here’s all the code in their files:

There is a worldwide significance of microsoft certification exams which give the training in the practical manner. The apprentice knowledge about network design is provided by ccda, Cisco Certified Design Associate which is the best exams to give perfect training. The secuirity+ certification exams provide knowledge how to secure all databases and networking systems successfully. The exams of ccna are designed to assess the inherent talents of technicians and system administrators. The itil training provides technical services for all IT organizations to establish infrastructures. The mcdba certification exams are very valuable for the Database Administrators, Network Technicians and Technical Support Specialists. There is great importance of mcse certification training for IT engineers and system analysts.

What the iPhone means to WebDev

Christian Decker wrote this at around evening time:

iPhone
Now that the hype around the iPhone starts to subside, the real value is starting to shine through. For the development of web applications it means that a whole new breed of applications now have a market (think of widget that act as a fully fledged application). More and more web applications start to surface that are specifically tailored to portable devices (with small screens). So what I think the iPhone started (and other phones such as OpenMoko will continue) is the era of small, really specialised, applications, pushing Ajax with it.

The other great thing is that OpenID (my other favorite topic :D) will also start being used more extensivel, because we all know that writing on small devices is a real pain. There’s a great post over at FactorCity on OpenID & iPhone, which I think says it all.

Keep your cooler silent, with Olive Oil

Christian Decker wrote this late at night:

Let me explain my problem: I have a fileserver (just an old Pentium II with big disks) in my room and some time ago the cooler started getting really loud, so loud in fact that I had to shut down the server every night if I wanted to get some sleep. I don’t have to tell you that this wasn’t ideal, and I tried everything from the classic pencil blocking the fan (not a good idea) to keeping it on balcony (even worse…) but the thing that worked in the end is … olive oil!

The thing is, that with the time the cooler started getting out of center and unbalanced and I figured that if others can submerge their entire computers in oil, why can’t I just grease the Cooler a bit? Yes, being a student at ETHZ I’ve done the physics behind it, and I decided to give it a shot.

I stopped my server (hopefully never to stop it again after this one :D), detached the cooler, poored some olive oil on it while rotating it slowly, and attached it again to the machine to do a test run. At first it the oil just oozed back out again but it sooned stopped and I was able to mount it back onto the processor.

The result is astonishing: the cooler actually is quieter than the spinning disks (that will be my next goal ;)) and I can keep it on all night once again.

Lux open on ground Oil on Lux Lux oiled

Pitchfork goes on

Christian Decker wrote this at around evening time:
I’m very proud that my article on how to install Pitchfork is still going nicely. It just got listed at UnixTutorials.com:
Music Player Daemon (MPD) allows remote access for playing music (MP3, Ogg Vorbis, FLAC, AAC, Mod, and wave files) and managing playlists. MPD is designed for integrating a computer into a stereo system that provides control for music playback over a local network. Pitchfork on the other hand is a graphical user interface based on Ajax, allowing you to control MPD with your browser. In this tutorial you’ll see how to get them up and running.