HowTo: CakePHP 1.2 Model ORM Inheritance Hack

[Edit: Fixed a bug in model to filter by subtype]
[Edit: again]
[Edit: Oops. I forgot to add the cvar function!]
[Edit: Fixing more bugs in the query generation. Add DboSource mod. Give me a break. it's my first week using CakePHP]

The Problem

Simple CakePHP ORMCakePHP is fairly good as an ORM Framework. You create database tables and it automagically maps them into Model classes for use in a nice MVC application. And, in most cases, this is just fine for most database tables. Relations get mapped and linked, things are peachy. But Cake has it’s shortcomings. The ORM in Cake is nowhere near as capable as say, Hibernate, and in particular (at least as of 1.2r5875) Cake’s Model class mapping does not support inheritance.Inheritance ORM Case
Hopefully Cake developers realize this is a deal breaker for a lot of people. For one of my own projects at least, I needed ORM inheritance and for reasons I won’t go into here, I had to use Cake.

The Solution

What I came up with is very much a hack. It involves modifying model.php (internals of Cake proper). If you’re doing a one off project, this is fine, but be warned that if you implement this hack, upgrading cake won’t be a simple matter of moving your app directory.
Anyhow, it works like this…

  1. add a new relation to Model extendedBy
  2. hack the constructor to recognize AppModel subsubclasses[sic] and to build the extendedBy relation
  3. Merge the sub/superclass schemas (ugly!)
  4. add a naming filters to translate between subclass attributes and superclass attributes during read and save ops
  5. cross fingers

Why does it work? Well honestly, I’m cheating fate a little at this point. Part of creating the extendedBy relation also building anonymous model instances for the subclass extensions. When I map the subclass table, I actually just tell the dbo that the subclass isa superclass instance and that it should map the superclass table. Then I instantiate anonymous instances of the subclass(es) to map to map to the subclass tables. Since I need these anonymous instances, I really do have to hack model.php (to modify the constructor and it’s private variables).

Yes, it’s dirty. First of all, post-find filters are required to copy subclass attributes to the superclass and vice verse. This is because we need to ensure the rest of Cake can figure out what the heck this model’s attributes are. In any event, if the attributes and schemas are translated correctly, we can trick Cake into seeing all the superclass attributes in our controller. So this means that, yes, scaffolding works with with ORM Inheritance!

Somethings to note:

  • The subclass and the superclass db rows MUST have the same ID. I did this because it’s the way I wanted to implement it :-P
  • You can reserve a column in the superclass table to identify which subclass (leaf) the record’s type is.
    Just stick this in the superclass: var $abstractTypeField = 'whatevercolumnyouchoose';
    This is useful if you’re doing your own custom SQL and joins on the two tables.
  • To use the ORM inheritance you must add
    require_once(APP.DS.'models'.DS.'yoursuperclass.php');
    to load the superclass in subclasses.
  • Your subclass declaration should look like this: class Subclasstable extends Someothertable {… (e.g. class Event extends Item {). It’s that easy!

The Code

You probably came here for the downloads. If you haven’t read the rest of this post, then you get no sympathy from me when you break your Cake. In any case, applying the hack is relatively simple. If you’re using 1.2r5875 all you have to do is replace the Model and Controller library classes. For future version, you probably want to check the CakeTrac to make sure real ORM inheritance hasn’t been implemented (if it has, you can just disregard this post entirely), and if it still hasn’t then diff these files with your version of Cake and merge by hand (what a pain!).

  • Controller class (/cake/libs/controller/controller.php)
  • Model class (/cake/libs/model/model.php)
  • DboSource class (cake/libs/model/datasources/dbo_source.php)

You’ll also need to add this function to basics.php:

/**
* Get the default value of a class member variable.
*
* @param string $class_name The name of the class to inspect
* @param string $variable The name of the member variable to inspect
* @return mixed The default value of the member variable inspected.
*/
function cvar($class_name, $variable) {
$vars = get_class_vars($class_name);
return $vars[$variable];
}

***No, I have not done any kind of reasonable testing on this hack. It seems to work, though there may be gremlins.

If you make or find an improvement, replacement, or commentary on this hack please ping this post.

I may or may not have a live example in my CakePHP halfbakery. If you find this hack helpful (or profitable!) feel free to send me gifts (or cash!).

This entry was posted in HowTos and tagged , , , . Bookmark the permalink.

4 Responses to HowTo: CakePHP 1.2 Model ORM Inheritance Hack

  1. speedmax says:

    Hello there, good work man.

    Interestingly, i have implemented a similar solution right inside Specific models , however fairly primitive, but similarly it does merge data on on Find. save dynamically to separate table on Save.

    I would very much like to investigate this with you. perhaps we can implement this try not to touching too much of core lib files.

    I recently read about django’s quest on model inheritance, they have done good investigation.
    http://code.djangoproject.com/wiki/ModelInheritance

    I am building sometime it may interest you. a behavior (ObjectizeBehavior allows model to return object instance) , i am writing a article on bakery.. at the mean time here is the code
    http://bin.cakephp.org/saved/25886

    PS: look for speedmax on irc,

  2. speedmax says:

    I think generally Model inheritance is a cool concept. but in the context of relational database it will be quite a effort and overhead.

    we are talking about the whole CRUD operation here, usually ActiveRecord/ORM implemented on the lowest feature set available across multiple database, some supperts/some doesn’t support native table inherithance. it will be painful.

    If we implement to some extend (eg: limit to max 1 or 2 level of inheritance), i definitely see the practical reason.

  3. kelvin says:

    I tried to keep my implementation as simple as possible. As a result it’s still kind of kludgy; the attributes aren’t perfectly inherited and some operations don’t work cleanly. But from what I’ve used it for, generalizing associations, my hack works pretty well.

    Performance-wise, it’s only 1 more SQL JOIN on the subclass tables, so it’s relatively fast imo. With just scaffolding and some naive controllers, CRUD works pretty cleanly so far. I did run into some issues with changing the recursive attributes, but other than that I’ve had no problems.

  4. speedmax says:

    hi kevin,

    can you please post the source code of the sample app?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>