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.

Music 2.0: Pitchfork

Christian Decker wrote this at around evening time:
I love my music, I really do. Problem is however that I have far too much of it (2980 Songs, 16 GB currently, not counting audiobooks etc…) to carry it around with me all the time. It just doesn’t fit on my iPod Mini, nor does it fit on my notebook (I know shame on me, but I need the diskspace for work too). So I always have to carry around a selection of the songs I want to listen too, and far too often just the song I want to listen too is on my fileserver, and the trouble to transfer it to my notebook takes too long for me to be still in the mood to listen to it…
Pitchfork is the solution to all of my problems, well almost all… Pitchfork allows me to stream my music over my Local Network, and over the internet, with a nice and responsive interface that facilitates access to my songs.
Advertisement BadgeWhether you’re going to learn the electric guitar or if you would prefer to learn how to play the acoustic guitar instead you may want to go online and read some guitar reviews before buying one brand or another, from Ibanez guitars to many others.

Installing MPD

Since Pitchfork is based upon MPD, which will take care of streaming the data to the various clients and maintain the song database, we will first have to set it up. Sadly my distro (OpenSuSe 10.2) doesn’t have a version of MPD in its package management, so I had to download the sources and compile it myself:
wget http://www.musicpd.org/uploads/files/mpd-0.12.2.tar.bz2
tar -xvjf mpd-0.12.2.tar.bz2
cd mpd-0.12.2/
Now we have the sources, and we have to check that we have all the dependencies. Depending on how you are going to use MPD you have to have the following packages (along with their devel packages):
  • libshout: because we will be streaming the songs to an Icecast server
  • oss: as far as I know this is the most widely used and best supported. If it doesnt work take a look here
  • libmad: for mp3 support
  • libogg: for OGG Vorbis support
  • libvorbis: for Metadata support for Vorbis formats
  • libid3tag: Metadata support for mp3.
Quite a long list, but this is about the most essential stuff, if you have some special needs take a look at the official dependency listing.
Now it’s time to get to the actual compilation:
./configure
make
sudo make install
After the configure step, you’ll see a list of options that are turned on or off, if something is missing it probably means that you are missing some development packages.
So now we have mpd installed, the next step is to configure it. First you have to move the example configuration to the correct location:
sudo cp doc/mpdconf.example /etc/mpd.conf
Then we edit it according to our needs, especially the location of the Songs, Logfiles and Database should be changed: music_directory "/storage/Music/" playlist_directory "/storage/Music/playlists" db_file "/var/lib/mpd.db" log_file "/var/log/mpd.log" error_file "/var/log/mpd.error" pid_file "/var/run/mpd.pid" The pid_file has to be uncommented since we will run MPD in daemon mode, and we want to shut it down later. Now let’s create these files and change the owners:
sudo touch /var/lib/mpd.db /var/log/mpd.log /var/log/mpd.error /var/run/mpd.pid
sudo chown mpd /var/lib/mpd.db /var/log/mpd.log /var/log/mpd.error /var/run/mpd.pid
“mpd” being the user that will later run the daemon (if you don’t have an mpd user refer to your distro documentation on how to create one. It is really important that the mpd user is in the sour). Next we have to set some more settings:
user                            "mpd"
audio_output {
        type                    "oss"
        name                    "Direct OSS output"
}
audio_output {
        type                    "shout"
        name                    "Icecast Stream"
        host                    "localhost"
        port                    "8000"
        mount                   "/mpd.ogg"
        password                "youricecastpassword"
        bitrate                 "96"
        format                  "44100:16:1"
}
mixer_type                      "oss"
mixer_device                    "/dev/mixer"
mixer_control                   "PCM"
mixer_type                      "software"

Initializing the database:

mpd --create-db
This will insert all your songs into the database for faster access. Now MPD is set up and should work. Try with
sudo mpd
MPD will complain that it can’t connect to the Icecast server (which we’ll setup a few lines below this, but everything else should work fine.

Installing Icecast

As I said, my Music resides on a fileserver, and I want the songs to be streamed directly to my other machines, so what’s better than Icecast, an opensource streaming solution, used by many internet radios out in the wild. Luckily most distros have it in their packagemanagement software (I know openSuSe has :D) so it shouldn’t be too hard to get it up and running, just make sure that the source password in /etc/icecast.xml matches the password you specified above, in the /etc/mpd.conf file.

Installing Pitchfork

Installing pitchfork is pretty straight forward. All you need is this:
  • PHP 5.1.3 or newer
  • PHP-Pear
  • DOM2 capable browser (firefox, konqueror, opera, safari, etc)
Please refer to your distro documentation to see how to setup these. My setup uses an apache 2 server with mod_php, and Firefox 2 on my notebook. Now to the installation itself, depending on your distribution the document root of your webserver is /var/www/htdocs or /srv/www/htdocs, change to that directory and do the following:
wget http://pitchfork.remiss.org/files/pitchfork-0.5.2.tar.bz2
tar -xvjf pitchfork-0.5.2.tar.bz2
cd pitchfork-0.5.2/
chmod a+rwx config/
The rest of the configuration is done through the browser, just point your browser to http://localhost/pitchfork-0.5.2/ if you installed pitchfork on the computer you’re currently on, or replace localhot with the IP of your server. Change whatever you’d like to change (but it should not be necessary as the important configuration is done in MPD itself), and then press Save and you’ll be taken to the Pitchfork interface. Voila, it’s all done :D

Installing mpdscribble (optional)

I’m a huge fan of Last.fm, but sadly I can’t update my profile using the Player, because the songs are streamed to it. So why not use the MPD to update them for me? For this purpose I will be using mpdscribble:
wget http://www.frob.nl/projects/scribble/mpdscribble-0.2.12.tar.gz
tar -xvzf mpdscribble-0.2.12.tar.gz
cd mpdscribble-0.2.12
./configure
make 
sudo make install
Now create the basic configuration
mkdir ~/.mpdscribble
It is suggested that mpdscribble is run as your user since it has read access to your username and password-hash. Edit the configuration file ~/.mpdscribble/mpdscribble.conf as follows: username = "lastfm_username" password = "md5sum of lastfm password" Where the md5sum can be found using either this tool or the command line md5sum tool:
echo -n "your_lastfm_password" | md5sum
And that’s it, mpdscribble is set up and ready to be run:
mpdscribble &

Further reading

cwna, Certified Wireless Network Administrator certification exams introduce new technologies of wireless networking system. ccnp boot camp offers all kinds of troubleshooting and booting techniques to tackle any systematic faults. The IT professionals are guided by giving mcp training in the effective manners. If you want more validate information for supporting and troubleshooting of computing system, a+ certification is the best IT certification in order to provide authentic training for IT professionals. The most valuable and measurable rewards are provided by cisco certification which endows perfect skills and training to the professionals of IT industry. The network+ certifications give full knowledge and information which has great significance for networking administrators.

Keeping up with the rest of the world

Christian Decker wrote this in the early evening:
It has become very busy lately and I couldn’t keep up with all the new stuff that was pubblished during these last few days, so, once again I have to do a quick roundup of what hapened and most of all, what’s worth reading :) To start things off I would recommend taking a look at the (lenghty) article of Andy Bakun about Race conditions with Ajax and PHP sessions, which focuses on the downside of doing things asynchronously, namely synchronization issues on both sides of the wire. I remember a post some time back that discussed exactly the other way round (synchronization problems on the client) with suggest boxes, the problem was that sometimes when you haven’t entered a long string yet the response would take really long to complete (because of the sheer amount of data) while shorter responses would take much shorter, thus it might happen that the slower (older) response completes after the shorter (younger) overwriting the result. Andy discusses in depth the issues you might encounter on the server side. GWT on the other side has got some new stuff: And for all those of us who hate having to load and reload static things over and over again from the server, here’s JSOC (JavaScript Object Cache):
The JSOC framework is a a pluggable, extensible, open source client-side caching framework for Web 2.0 applications. JSOC offers Web developers a straightforward way to perform common caching techniques (add, replace, remove, flush, etc.) inside any JavaScript-enabled browser. Since JSOC is a standalone JavaScript module, incorporating JSOC into a Web development project is a matter of including a script reference, and working with common caching methods. Low-level methods are contained in the JSOC JavaScript module so that developers can focus on the Web development task at hand.
Fancy some nice effects for your application? Then take a look at the JavaScript Particle Engine by Jason Harwig (the post includes a fully functional version of the code). It’s cool, but I simply can’t see where it might be usefull, so until somebod proves me wrong I’ll just put it into the “Proof-of-Concept” drawer :D Also there were plenty of releases: Ok I think this should be enough for now, I’ll cover the rest in some more detail later :)

Projax

Christian Decker wrote this around lunchtime:
The guys over at NGCoders have just released version 0.2 of Projax. It is basically a set of PHP Wrappers around Prototype and Script.aculo.us, ported directly from the Ruby on Rails helpers. It may speed up development significantly.
Check out the Demos:
I especially like the inplace editor which in my opinion is one of the most usefull features when developing Web 2.0 applications.

SSLBridge: Ajax Samba Client

Christian Decker wrote this in the early evening:

Jim Kern has created SSLBridge, an Ajax enabled Samba Web GUI, and has released it using the GPL open source license.

SSLBridge features:

  • Authenticates using your existing user name and password against MS Active Directory using Samba.
  • All permissions that apply at office are also applied through SSLBridge.
  • Can download files from computers, just like you are at the office.
  • Can Drag and Drop files between computers.
  • Can select one of more files using both SHIFT-Click and CTRL-Click

Download the PHP based software (requires: Apache, PHP, Samba).

SSL Bridge
[via Ajaxian]