php-activerecord icon indicating copy to clipboard operation
php-activerecord copied to clipboard

Allow single table inheritance

Open anther opened this issue 11 years ago • 4 comments

This patch adds single table inheritance the same way that Yii framework goes about it.
http://www.yiiframework.com/wiki/198/single-table-inheritance.

/** Here's what we want */
$book = new Book();
$book->name = 'AwesomeBook';
$book->save();
$awesome = Book::find($book->id);
echo get_class($awesome); #AwesomeBook

$book = new Book();
$book->name = 'NormalBook';
$book->save();
$awesome = Book::find($book->id);
echo get_class($awesome); #Book


/** Here's how it's implemented */
class Book extends ActiveRecord\Model
{
    public static $table_name = 'books';

    /** @return a string representing the class to use */
    public static function instantiate($attributes)
    {
        if(isset($attributes['name']))
        {
            if($attributes['name'] == 'AwesomeBook')
                return 'AwesomeBook';
        }

        return parent::instantiate($attributes);
    }
}

class AwesomeBook extends Book
{
    public static $table_name = 'books';
}

I've implemented it as having to return a string instead of a class instance since Model's constructor is the only way to mass assign attributes unguarded unfortunately, and I didn't want to complicate the process of extending the method. return new Book($data,false,true,false); //Would have to actually worry about hydrating the object within instantiate()

Preferably it'd work like this, where you can return a model to use with the data, and then have Table populate the data.

public static function instantiate($attributes)
{
  return new AwesomeBook();
  /** And then have Table worry about pushing the attributes into this particular instance */
}

as right now I think the default constructor for Model.php does too much at the moment, as it locks the entire implementation for getting around guarded attributes.

I like this feature because currently I've had to implement this sort of inheritance in this extremely cludgey way (Be prepared to avert your eyes in disgust =P)

public static function get_all_fields_as_actual_class()
{
    $fields = self::all(array('order'=>'order_by ASC'));
    foreach($fields as &$field)
    {
        $field = Field_Factory::convert_to_type($field);
    }
    return $fields;
}
/** And another method that I had to add just in case I did not want all of the fields at once */
public function convert_to_child()
{
    return Field_Factory::convert_to_type($this);
}

//So.. everytime I want to use a Field with its specific interfaced behavior.. I have to use it as
// $field = Field::find($id)->convert_to_child();, which is also essentially meaning the class has to be double instantiated in order to use child classes.

This change would allow this behavior to be the default and prevent convoluted solutions to this pretty simple inheritance issue.

Thoughts, feelings? Emotions? =P

anther avatar May 15 '13 15:05 anther

Just proposing to punt this to post-1.2 since it is a big feature addition. A good one, though! :D

al-the-x avatar May 18 '13 03:05 al-the-x

Are there any plans to merge this PR? I've started a project what will benefit from STI.

HeikoBornholdt avatar Apr 08 '15 20:04 HeikoBornholdt

Feels like a 1.0 addition. It's backwards compatible and it's five lines that are pretty easy to follow what they do, on top of having tests!

anther avatar Apr 08 '15 20:04 anther

One of many PR's lingering around.. But instantiate isn't a correct function name, because it's not instantiating anything

koenpunt avatar Apr 10 '15 21:04 koenpunt