Documenting this for others as needed. I asked directly how best to go about adding a Many to One table relationship for exportable objects for CTools integration. The answer follows:
---
In Drupal 7, CTools actually provides a special callback to handle
one::many (or parent::child) relationships using the export system. This
is called the 'subrecords callback'. In Drupal 7, Views is making use of
this. Views are stored in two tables: There's the core 'view' table,
which is actually very small, and then each 'display' has its own child
record.
Views defines the CTools export info like this:
$schema['views_view'] = array(
'description' => 'Stores the general data for a view.',
'export' => array(
'identifier' => 'view',
'bulk export' => TRUE,
'primary key' => 'vid',
'default hook' => 'views_default_views',
'admin_title' => 'human_name',
'admin_description' => 'description',
'api' => array(
'owner' => 'views',
'api' => 'views_default',
'minimum_version' => '2',
'current_version' => '3.0',
),
'object' => 'view',
// the callback to load the displays
'subrecords callback' => 'views_load_display_records',
// the variable that holds enabled/disabled status
'status' => 'views_defaults',
// CRUD callbacks
'create callback' => 'views_new_view',
'save callback' => 'views_save_view',
'delete callback' => 'views_delete_view',
'export callback' => 'views_export_view',
'cache defaults' => TRUE,
'default cache bin' => 'cache_views',
),
And this is the subrecords callback:
/**
* Export callback to load the view subrecords, which are the displays.
*/
function views_load_display_records(&$views) {
// Get vids from the views.
$names = array();
foreach ($views as $view) {
if (empty($view->display)) {
$names[$view->vid] = $view->name;
}
}
if (empty($names)) {
return;
}
foreach (view::db_objects() as $key) {
$object_name = "views_$key";
$result = db_query("SELECT * FROM {{$object_name}} WHERE vid IN (:vids) ORDER BY vid, position",
array(':vids' => array_keys($names)));
foreach ($result as $data) {
$object = new $object_name(FALSE);
$object->load_row($data);
// Because it can get complicated with this much indirection,
// make a shortcut reference.
$location = &$views[$names[$object->vid]]->$key;
// If we have a basic id field, load the item onto the view based on
// this ID, otherwise push it on.
if (!empty($object->id)) {
$location[$object->id] = $object;
}
else {
$location[] = $object;
}
}
}
}
Do note that there isn't a subrecords save callback specifically, so the
'save callback' has to be defined to take care of that, but it's pretty
straightforward. I won't show Views' save callback here because it goes
through a couple layers of abstraction that make it less effective as an
example, but it's really just a simple process of "first save the parent
record", "then save all the child records".
Note that export has to do something similar.
Comments
Comment #1
sluceroThanks for this! I've been searching for documentation on this for a while now.
Comment #1.0
sluceroAdded PHP tags to make code easier to read.
Comment #2
mustanggb commented