4.3. Native Model "Has One" Related Foreign Record

4.3.1. Setup

In our example domain, a blog entry "has one" summary record. This is a kind of one-to-one relationship where the summary (foreign) record is subordinate to the blog entry. To set up this kind of relationship for a model, the code looks like this:

<?php
class Acme_Model_Blogs extends Acme_Sql_Model
{
    protected function _setup()
    {
        $this->_hasOne('summary');
    }
}
[Note] Singular vs. Plural

The model system assumes all tables (and as a result all model classes) are named in the plural. To facilitate some amount of intuitive naming, the name of the has-one related is assumed to be singular, and is inflected to plural when looking for the associated catalog entry. You can change this behavior using the relationship definition keys described below.

4.3.2. SQL for Lazy-Load

The above code creates a pseudo-property on each blog record called $summary that gets lazy-loaded by a fetch from the summaries entry in the model catalog. That is, each time you ask for a $blog_record->summary, the model system will fetch the related summary record from the database and place it in $summary for you. The SQL for the lazy-load looks similar to this:

SELECT summary.*
FROM summaries AS summary
WHERE summary.blog_id = {$blog_record->id}
LIMIT 1
[Warning] Lazy vs. Eager Loading

Beware the dreaded N+1 problem! If you fetch a collection of 10 blog records and loop through them, displaying the $blog_record->summary->comment_count each time, you will generate 10 additional SELECT queries (one for each related blog) for a total of 11 queries. To avoid this, eager-fetch the related summary values when you fetch the blog collection; more on eager fetching in a later section.

4.3.3. Foreign Key

In a has-one relationship, the foreign key exists in the foreign model/table. In our example domain, this means the foreign key for the related blog is on the summaries table, and maps to the primary key on the blogs table.

[Note] Foreign Column Naming

Each model defines for itself what it expects its foreign column name to be when it is used as a foreign model in a relationship. Native models then ask the foreign model what the foreign model expects to use as a foreign column name.

By default, models define their expected foreign column name as a singular form of their table name, appended with their primary key. For example, if the model/table name is "authors", and the primary key on the authors table is "id", the authors model will default to saying its foreign column should be called "author_id". You can change that value by editing the foreign model setup to add $this->_foreign_col = 'something_else'.

4.3.4. Has One Or Null

When the foreign data does not exist at the database, the Solar model system will create a new related record for you in the main record object when you attempt to get or set that related record. Sometimes this automatic record creation is not what you want. In those cases, you should define the relationship as hasOneOrNull. In a has-one-or-null relationship, a null will be placed in the related property instead of a new record object. When you access it, you will need to check it yourself to see if a record object is present.

4.3.5. Relationship Definition

Although the Solar model system makes some assumptions about how tables and foreign keys are named, these assumptions are not hard-coded. You can define every aspect of the foreign relationship using an array of key-value pairs passed to the _hasOne() method.

<?php
        $this->_hasOne('name', array(
            'option' => 'value',
            // ...
        ));
string foreign_name

Normally, the model system expects the foreign model to be in the catalog uner the plural form of the related name. E.g., {native} _hasOne('foreign') means the catalog name 'foreigns', which will map to some model class.

Use the foreign_name option to specify a different catalog name for the foreign model. This allows you to name the relationship "foo" (and thereby the record property for the foreign) but use the catalog name "bar" to do the work. (The foreign_class option takes precedence over foreign_name.)

string foreign_key

Normally, the model system expects the foreign key on the foreign table to be the same as the one defined by the native model's foreign_col value.

Use the foreign_key option to specify a different column in the foreign table. (This option will be ignored if a specific native_col or foreign_col option is set.)

array cols

Fetch these columns for the related records.

array conditions

Additional conditions on the foreign table when fetching related records. These will be used as WHERE conditions, or as JOIN ON conditions, as appropriate for the kind of fetch being performed (e.g. lazy vs. eager fetch).

array order

Additional ORDER clauses when fetching related records.

[Note] Note

The following are some less common, but more advanced and more finely grained, options for defining relationships:

string foreign_class

The class name of the foreign model. Default is the first matching class for the relationship name, as loaded from the parent class stack.

string foreign_alias

Aliases the foreign table using this name. Default is the relationship name.

string foreign_col

The name of the column to join with in the foreign table, matching against some column in the native table. This forms the foreign half of the relationship condition.

string native_col

The name of column to join with in the native table, matching against some column in the foreign table. This forms the native half of the relationship condition.

string native_by

The strategy to be used for connecting to native records when eager-fetching: 'wherein', meaning a "WHERE IN (...)" a list of native IDs, or 'select', meaning a join against a sub-SELECT.

int wherein_max

When picking a native-by strategy, use 'wherein' for up to this many records in the native result; after this point, use a 'select' strategy.

string merge

Indicates the strategy to use for merging joined foreign rows; 'server' means the database will do it via a single SELECT combined into the native fetch (only possible with to-one relationships), whereas 'client' means PHP will do it, using one additional SELECT for the relationship (always for to-many relationships, optionally for to-one relationships) and then merging the rows in a PHP loop.



Local