elgg class hell

Tue, Oct 27, 2009

I’ve been porting blog notifications from Elgg 0.9.2 to 1.6 and so needed to craft a domain model for it. Plumping for a new BlogWatch class with a subscribers “attribute” seemed the way to go:

class BlogWatch extends ElggEntity {
The first thing to contend with is when you create a new class:
$blog_watch = new BlogWatch();
the system calls the load() method for you. However, when you get an existing class via:
$blog_watch_objects = get_entities_from_metadata(“watched_guid”, $blog_guid);
it doesn’t.

Where’s the sense in that? You never know if your object is going to be a dud or not. Why do you need a load() method? Well, there are two main kinds of instance variables in an Elgg class, metadata and simple attributes, plus one more that I’ll call complex attributes. Attributes are more for class use, whereas metadata is used to load the class based on SQL. To define metadata instance variables, you simply refer to them directly:

$blog_watch->watched_guid = $blog_guid;
whereas instance attributes are set differently:
class BlogWatch extends ElggEntity {
  $this->attributes[‘blog_url’] = “;
  …
}
$blog_watch = new BlogWatch();
$blog_watch->blog_url = $blog_url;
Note how the attribute is first “declared” in the class. You then can’t use blog_url to find the class. You need to use metadata for that:
$blog_watch_objects = get_entities_from_metadata(“watched_guid”, $blog_guid);
So you have to be careful how you “declare” and use Elgg class instance variables. Also, there are two “special” metadata variables you should use, type and subtype. When I first saw these I thought I could detect OOP smell. Why would you want to declare an object hierarchy via attributes and not inheritance? Well it turns out these two oddities are really for finding classes in the database as you can narrow searching via metadata using the object’s type and/or subtype.

What about complex instance attributes? BlogWatch stores the subscribers to that particular blog as an Array, which is really a Map, which means I store that particular attribute in a separate table, blogwatch and it’s easy to do so by using serialize:

$subscribers_data = mysql_real_escape_string(serialize($this->subscribers));
and using raw SQL to save() the class:
insert_data(“INSERT into {$CONFIG->dbprefix}blogwatch (blog_guid, subscribers)
  values (‘{$this->watched_guid}‘, ‘$subscribers_data’)“);
and load() it back:
$row = get_data_row(“SELECT * from {$CONFIG->dbprefix}blogwatch where
  blog_guid={$this->watched_guid}“);
$objarray = (array)$row;
$this->subscribers = unserialize($objarray[‘subscribers’]);
but that’s where a major problem appears. When you delete() a class, a complex internal dance is started where your class is instantiated over and over and over and over, mostly by this method:
ElggAnnotation::clear_annotations() {
  …
  get_entity($entity_guid);
}
which results in Elgg instantiating your class for every annotation it needs to delete when your class is being deleted from the database. But as you saw earlier, load() is called, which means the class expects to load its complex instance attributes but it can’t key them as by the time the annotations are causing mayhem, the class metadata instance variables have been deleted, so the whole thing falls over.

comments powered by Disqus