Model References

One model may reference another model. Typically you would have two ways: a relation can do one-to-many and many-to-one. There is also one-to-one which is a special case of two-sided many-to-one reference. Many-to-many relations are implemented through an intermediate table which is referenced through one-to-many.

References are always defined between models. To avoid loops, a reference should use the name of the model class and not the object itself.

hasOne() reference

class Model_Book extends Model_Table {
  function 
init(){
    
parent::init();
    
$this->hasOne('Author');
  }
}
// select id,author_id,(select name from author where author.id=book.author_id) as author from book

The "hasOne" reference in example above specifies that each book has a field pointing to a record in the Author model. Multiple book records can belong to the same author. hasOne() method will create a new field of type Field_Reference and initialize it. That field will also create an expression field for showing a read-only column in a grid.

In the code above, model 'Author' will be examined for it's "table" property. That property is then used in making assumptions about the referencing field. If Model_Author->table = 'author' then hasOne() function will use "author_id" field by default. If a different field is used, you can specify it as a second argument to hasOne().

By default one more fields will be created. This field is called "dereferenced field" and it is defined as a sub-select expression selecting "name" field from related entity. If "name" field is not set in the related model, then the field will show "Record #n" instead. You can specify a different field for the expression by setting the 3rd argument of hasOne();

The field in your current model is "id" or your customized identifier field specified through $model->id_field;

class Model_Book extends Model_Table {
  function 
init(){
    
parent::init();
    
$this->hasOne('Author','written_by','full_title');
  }
}
// select id,written_by,(select full_name from author where author.id=book.written_by) as written_by_2 from book

The reason why title_2 is used, is because "dereferenced" field will be using unique identifier to avoid conflict with original field. This might not be what you want, so you can create a reference manually for greater control

§
class Model_Book extends Model_Table {
  function 
init(){
    
parent::init();
    
$ref=$this->add('Field_Reference','written_by');
    
$ref->dereferenced_field='written_str';

    
$m=$this->add('Model_Author');
    
$m->addField('extra_name');

    
$ref->setModel($m,'extra_name');
  }
}
// select id,written_by,(select extra_name from author where author.id=book.written_by) as written_str from book

Traversing references

Once your primary model is loaded, you can traverse through relationships using "ref" method. This method will initialise referenced model and load appropriate record too.

$model $this->add('Model_Book')->load($book_id);
$author $model->ref('author_id');
$author['sales']++;
$author->save();

You can obviously traverse through multiple models like that too.

$book->ref('author_id')->ref('address_id');

If you call ref() multiple times, it will return new model object to you every time.

If reference is destroyed it will also destroy dereferenced field.

$book->hasOne('Publisher')->load($book_id);

$publisher=$book->get('publisher');   // contains publisher.name

$book->unload();
$book->getElement('publisher_id')->destroy();
$book->load($book_id);

$publisher=$book->get('publisher');   // produces exception

Extending References

To further enhance reference objects, you can extend it with your own and redefine its methods. Extend Field_Reference class.

hasMany() reference

Unlike hasOne() this function will not add any fields to your model. It's typical to have two-sided link like this:

$book->hasOne('Author');
$author->hasMany('Book');

You would probably define those references inside respective init() methods of your models.

hasMany reference does not modify any aspect of your model in any way, however it creates an SQL_Many object with the name matching the name of the model. When traversing through a one-to-many relation, you should use the model rather than the field:

$book $author->ref('Book');

The returned model will have an extra condition set which will restrict models to only books of that particular author.

$book $author->ref('Book');
$book->dsql->do_delete();    // deletes all books of particular author

Defining related fields

$author->hasMany('Book','written_by');

The second argument to hasMany() specifies the field in the related model that will be used in the condition. Third argument allows you to override "author"'s field, which by default is "id" (id_field).