Following is some example code that I whipped up to implement an object store. The idea would be to use this as a base class to implement persistent object properties. At present, there is no error handling, and database parameters are encoded in global variables ($DBType, $DBPersist, $DBServer, $DBUser, $DBPasswd, $DBName, and $DBTable). I've tested it somewhat and it seems to work fine. Next step is to create a WikiPage object that extends this class.

Right now it uses ADODB, but that's easy enough to change since all of the database code is centralized in this one file. The main reason for choosing ADODB was because I was lazy and didn't want to figure out how to do BLOBs in PEAR. :-)

object.php

<?php
// $Id$

include('adodb.inc.php');

// Abstractor for the OO store.

class Object
{
  var $dbh;                             // DB handle (shared among instances).
  var $oid;                             // Object-id.
  var $type     = array();              // Array of property types.
  var $property = array();              // Array of property values.
  var $created  = array();              // Array of was-created flags.
  var $changed  = array();              // Array of was-changed flags.

  function Object($object_id)
  {
    static $handle = 0;                 // DB handle (shared among instances).

    $this->dbh = &$handle;              // Point at static location.

    if($object_id == -1)                // Create a new, random object id.
      { $object_id = $this->_db_new_oid(); }
    $this->oid = $object_id;
  }

  function _db_init()
  {
    global $DBType, $DBPersist, $DBServer, $DBUser, $DBPasswd, $DBName;
    global $ADODB_FETCH_MODE, $ADODB_COUNTRECS;

    $this->dbh = &ADONewConnection($DBType);
    if($DBPersist)
      { $this->dbh->PConnect($DBServer, $DBUser, $DBPasswd, $DBName); }
    else
      { $this->dbh->Connect($DBServer, $DBUser, $DBPasswd, $DBName); }

    $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;

//######
// Detect error?
//######

    // Initialize random number generator.

    list($sec, $usec) = explode(' ', microtime());
    mt_srand($sec * $usec);
    mt_srand(hexdec(md5(uniqid(mt_rand(), 1))));
  }

  function _db_new_oid()
  {
    global $DBTable;

    if($this->dbh == 0)                 // Initialize DB if needed.
      { $this->_db_init(); }

    do                                  // Loop until unique OID found.
    {
      $oid = '';
      for($i = 0; $i < 8; $i++)         // Generate a 128-bit number.
      {
        $str = dechex(mt_rand(0, 65536));
        while(strlen($str) < 4)
          { $str = '0' . $str; }
        $oid = $str . $oid;
      }

      $oid = md5($oid);                 // MD5 it for a new 128-bit number.
    } while(!$this->dbh->Execute('INSERT INTO ' . $DBTable
                                 . '(oid, property, type, value)'
                                 . ' VALUES("' . $oid . '", "oid"'
                                 . ', "private/oid", NULL)'));

    return $oid;
  }

  function _read_property($name)
  {
    global $DBTable;

    if($this->dbh == 0)
      { $this->_db_init(); }
    $result = $this->dbh->Execute('SELECT * FROM ' . $DBTable
                                  . ' WHERE oid = "' . $this->oid . '"'
                                  . ' AND property = "' . $name . '"');
    if(!$result->EOF)
    {
      $this->type[$name] = $result->fields['type'];
      if(function_exists($this->dbh->DecodeBlob))
        { $this->property[$name] = $this->dbh->DecodeBlob($result->fields['value']); }
      else
        { $this->property[$name] = $result->fields['value']; }
    }
    else
    {
      $this->type[$name]     = 'private/null';
      $this->property[$name] = NULL;
    }

    $this->created[$name] = false;      // Wasn't created.
    $this->changed[$name] = false;      // Hasn't changed since read.
  }

  function get_property_value($name)
  {
    if(!isset($this->type[$name]))
      { $this->_read_property($name); }
    return $this->property[$name];
  }

  function get_property_type($name)
  {
    if(!isset($this->type[$name]))
      { $this->_read_property($name); }
    return $this->type[$name];
  }

  function set_property($name, $type, $value)
  {
    if(!isset($this->type[$name]))
      { $this->_read_property($name); }

    if($this->type[$name] == 'private/null')
      { $this->created[$name] = true; } // Have created new property.

    $this->type[$name]     = $type;
    $this->property[$name] = $value;
    $this->changed[$name]  = true;
  }

  function write_object()
  {
    global $DBTable;

    foreach($this->type as $name => $type)
    {
      if(!$this->changed[$name])
        { continue; }
      $value = $this->property[$name];

      if($type == 'private/null')       // Deleting property.
      {
        if(!$this->created[$name])      // It previously existed.
        {
          $this->dbh->Execute('DELETE FROM ' . $DBTable
                              . ' WHERE oid = "' . $this->oid . '"'
                              . ' AND property = "' . $name . '"');
        }
      }
      else                              // Change/create property.
      {
        if($this->created[$name])       // It didn't previously exist.
        {
          $this->dbh->Execute('INSERT INTO ' . $DBTable
                              . '(oid, property, type, value)'
                              . ' VALUES("' . $this->oid . '"'
                              . ', "' . $name . '"'
                              . ', "' . $type . '", NULL)');
          $this->dbh->UpdateBlob($DBTable, 'value', $value,
                                 'oid = "' . $this->oid . '"'
                                 . ' AND property = "' . $name . '"');
        }
        else                            // Change existing property.
        {
          $this->dbh->Execute('UPDATE ' . $DBTable
                              . ' SET type = "' . $type . '"'
                              . ' WHERE oid = "' . $this->oid . '"'
                              . ' AND property = "' . $name . '"');
          $this->dbh->UpdateBlob($DBTable, 'value', $value,
                                 'oid = "' . $this->oid . '"'
                                 . ' AND property = "' . $name . '"');
        }
      }
    }
  }

  function delete_property($name)
  {
    $this->set_property($name, 'private/null', NULL);
  }

  function delete()
  {
    global $DBTable;

    $this->dbh->Execute('DELETE FROM ' . $DBTable
                        . ' WHERE oid = "' . $this->oid . '"');
    $this->oid      = NULL;
    $this->type     = array();
    $this->property = array();
  }
}
?>