views:

259

answers:

4

I'd like to implement database caching functionality in PHP based on reference counts. For example, code to access the record in table foo with an ID of 1 might look like:

$fooRecord = $fooTable->getRecord(1);

The first time this is called, $fooTable fetches the appropriate record from the database, stores it in an internal cache, and returns it. Any subsequent calls to getRecord(1) will return another reference to the same object in memory. $fooRecord signals $fooTable when it destructs, and if there are no remaining references, it stores any changes back to the database and removes it from the cache.

The problem is that PHP's memory management abstracts away the details about reference counts. I've searched PECL and Google for an extension to do so, but found no results. So question #1 is: does such an extension exist?

In an alternative approach, $fooTable returns a super-sneaky fake object. It pretends to be the record by forwarding __call(), __set(), and __get(), and its constructor and destructor provide the appropriate hooks for reference counting purposes. Tests, works great, except that it breaks type-hinting. All my methods that were expecting a FooRecord object now get a Sneaky object, or maybe a FooSneaky if I feel like creating an empty subclass of Sneaky for every one of my tables, which I do not. Also, I'm afraid it will confuse maintenance programmers (such as myself).

Question #2: Is there another approach I've missed?

A: 

It sounds to me like your wanting some ORM with a framework or API. I have not used it but am aware the ADOdb does it. Alternatively CakePHP uses ORM and might be a good way to go.

Mark Davidson
+1  A: 

Did you look at simple cache implementations in PHP? for example, using something like Zend_Cache:

public function getRecord($id) {
     $cache = $this -> getCache(); //Retrieves an instanced Zend_Cache object or creates one
     $key = $this -> _name . $id; //_name is the table name and with the ID create a unique key

     if(!$result = $cache->load($key)) { 
         //Cache miss, query database
         $result = $this -> query(...);
         $cache->save($result, $key);
     }
     return $result;
}

You can easily implement this caching functionality yourself, or use a ready component like Zend's.

Eran Galperin
Would there not be issues with concurrency using such a method?
Mark Davidson
If you're worried about concurrency, then definitely use a good component like Zend's. It handles file locking very well, and I've used it in several high traffic websites with great success.
Eran Galperin
+2  A: 

For php5, just make use of the destructor in the record class.

class Table {
    /* [...] */
    protected $record_cache = array();
    public function getRecord($id) {
        if(isset($this->record_cache[$id]) return $this->record_cache[$id];

        $r = $db->getRecord($id);
        if($r instanceof Record) {
            $this->record_cache[$id] = $r;
        }
        return $r;
    }

    public function _unregister($id) {
        unset($this->record_cache[$id]);
    }
}

class Record {
    /* [...] */
    function __destruct() {
        this->table->_unregister($this->id);
    }
}

If you don't want to have a public method for this in Table, you could probably use some callback trick or some other clever hack :)

gnud
This method is only good for the lifetime of the request.
Eran Galperin
Yes -- but the same method could be used with some external cache and reference count.
gnud
+1  A: 

Standard Internet Help Forum Caveat: I may not fully understand what you're trying to do.

I'm not sure your idea fits in with PHPs request/response cycle. Traditionally (CGI, mod_php), the life-cycle of a PHP request looks something like

  1. User makes an HTTP Request for a PHP page
  2. The PHP page (and all included files) are turned into opt-code
  3. The opt-code is executed
  4. Once the cycle is complete, everything is thrown away

This was one of the reasons PHP did so well in the market with less experienced programmers. Even the most horrendously coded application could only hang a server for the life-cycle of a request. There are some parallels with HTTP; PHP is (sort of) built such that each request knows nothing about any other request.

I'm lest familiar with the fast-cgi deployment techniques (used in Zend Platform, for example), but as I understand it multiple PHP processes will hang out in memory to handle requests, and then there's some magic voodoo going to that will kill off and re-spawn processes as needed.

The larger point of all this is, the idea of keeping track of where PHP objects are in memory by reference count doesn't make sense with PHPs request model, which is why you're not finding much out there. Caching in PHP userland means using a separate programmable system, along the lines of memcached or the APC to handle the details of memory management for you. Caching is abstracted into a simple key/value pair storage system.

That said, if you press forward with your approach, I'd be interested in seeing the end results :)

Alan Storm