Commit fbabc0d9 authored by webchick's avatar webchick

#368674 by bjaspan, Eaton, chx, and yched: Provide hooks to allow hybrid...

#368674 by bjaspan, Eaton, chx, and yched: Provide hooks to allow hybrid field/bundle-level storage for fields in core.
parent 28aaa036
......@@ -439,9 +439,41 @@ function hook_field_widget_error($element, $error) {
function hook_field_attach_form($obj_type, $object, &$form, &$form_state) {
}
/**
* Act on field_attach_pre_load.
*
* This hook allows modules to load data before the Field Storage API,
* optionally preventing the field storage module from doing so.
*
* This lets 3rd party modules override, mirror, shard, or otherwise store a
* subset of fields in a different way than the current storage engine.
* Possible use cases include : per-bundle storage, per-combo-field storage...
*
* @param $obj_type
* The type of objects for which to load fields; e.g. 'node' or 'user'.
* @param $objects
* An array of objects for which to load fields. The keys for primary id and
* bundle name to load are identified by hook_fieldable_info for $obj_type.
* @param $age
* FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
* FIELD_LOAD_REVISION to load the version indicated by each object.
* @param $additions
* An array of field data for the objects being loaded, keyed by entity id,
* field name, and item delta number.
* @param $skip_fields
* An array keyed by names of fields whose data has already been loaded and
* therefore should not be loaded again. The values associated to these keys
* are not specified.
* @return
* Loaded field values are added to $additions and loaded field names are set
* as keys in $skip_fields.
*/
function hook_field_attach_pre_load($obj_type, $objects, $age, &$additions, &$skip_fields) {
}
/**
* Act on field_attach_load.
*
*
* This hook is invoked after the field module has performed the operation.
*
* See field_attach_load() for details and arguments.
......@@ -482,27 +514,47 @@ function hook_field_attach_presave($obj_type, $object) {
/**
* Act on field_attach_insert.
*
* This hook is invoked after the field module has performed the operation.
*
* See field_attach_insert() for details and arguments.
* This hook allows modules to store data before the Field Storage
* API, optionally preventing the field storage module from doing so.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object with fields to save.
* @param $skip_fields
* An array keyed by names of fields whose data has already been written and
* therefore should not be written again. The values associated to these keys
* are not specified.
* @return
* Saved field names are set set as keys in $skip_fields.
*/
function hook_field_attach_insert($obj_type, $object) {
function hook_field_attach_pre_insert($obj_type, $object, &$skip_fields) {
}
/**
* Act on field_attach_update.
*
* This hook is invoked after the field module has performed the operation.
*
* See field_attach_update() for details and arguments.
* This hook allows modules to store data before the Field Storage
* API, optionally preventing the field storage module from doing so.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object with fields to save.
* @param $skip_fields
* An array keyed by names of fields whose data has already been written and
* therefore should not be written again. The values associated to these keys
* are not specified.
* @return
* Saved field names are set set as keys in $skip_fields.
*/
function hook_field_attach_update($obj_type, $object) {
function hook_field_attach_pre_update($obj_type, $object, &$skip_fields) {
}
/**
* Act on field_attach_delete.
*
*
* This hook is invoked after the field module has performed the operation.
*
* See field_attach_delete() for details and arguments.
......@@ -592,11 +644,15 @@ function hook_field_attach_delete_bundle($bundle) {
* FIELD_LOAD_CURRENT to load the most recent revision for all
* fields, or FIELD_LOAD_REVISION to load the version indicated by
* each object.
* @param $skip_fields
* An array keyed by names of fields whose data has already been loaded and
* therefore should not be loaded again. The values associated to these keys
* are not specified.
* @return
* An array of field data for the objects, keyed by entity id, field
* name, and item delta number.
*/
function hook_field_storage_load($obj_type, $queried_objs, $age) {
function hook_field_storage_load($obj_type, $queried_objs, $age, $skip_fields) {
}
/**
......@@ -609,8 +665,12 @@ function hook_field_storage_load($obj_type, $queried_objs, $age) {
* @param $op
* FIELD_STORAGE_UPDATE when updating an existing object,
* FIELD_STORAGE_INSERT when inserting a new object.
* @param $skip_fields
* An array keyed by names of fields whose data has already been written and
* therefore should not be written again. The values associated to these keys
* are not specified.
*/
function hook_field_storage_write($obj_type, $object, $op) {
function hook_field_storage_write($obj_type, $object, $op, $skip_fields) {
}
/**
......@@ -627,6 +687,9 @@ function hook_field_storage_delete($obj_type, $object) {
/**
* Delete a single revision of field data for an object.
*
* Deleting the current (most recently written) revision is not
* allowed as has undefined results.
*
* @param $obj_type
* The entity type of object, such as 'node' or 'user'.
* @param $object
......
......@@ -94,16 +94,33 @@ function __construct($errors) {
*
* Fieldable types call Field Attach API functions during their own
* API calls; for example, node_load() calls field_attach_load(). A
* fieldable type may is not required to use all of the Field Attach
* fieldable type is not required to use all of the Field Attach
* API functions.
*
* Most Field Attach API functions define a corresponding hook
* function that allows any module to act on Field Attach operations
* for any object, and access or modify all the field, form, or
* display data for that object and operation. These all-module hooks
* are distinct from those of the Field Types API, such as
* hook_field_load(), that are only invoked for the module that
* defines a specific field type.
* for any object after the operation is complete, and access or
* modify all the field, form, or display data for that object and
* operation. For example, field_attach_view() invokes
* hook_field_attach_view(). These all-module hooks are distinct from
* those of the Field Types API, such as hook_field_load(), that are
* only invoked for the module that defines a specific field type.
*
* field_attach_load(), field_attach_insert(), and
* field_attach_update() also define pre-operation hooks,
* e.g. hook_field_attach_pre_load(). These hooks run before the
* corresponding Field Storage API and Field Type API operations.
* They allow modules to define additional storage locations
* (e.g. denormalizing, mirroring) for field data on a per-field
* basis. They also allow modules to take over field storage
* completely by instructing other implementations of the same hook
* and the Field Storage API itself not to operate on specified
* fields.
*
* The pre-operation hooks do not make the Field Storage API
* irrelevant. The Field Storage API is essentially the "fallback
* mechanism" for any fields that aren't being intercepted explicitly
* by pre-operation hooks.
*/
/**
......@@ -241,7 +258,7 @@ function _field_attach_form($obj_type, $object, &$form, $form_state) {
function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
$queried_objects = array();
// Fetch avaliable nodes from cache.
// Fetch avaliable objects from cache.
foreach ($objects as $object) {
list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
$cid = "field:$obj_type:$id:$vid";
......@@ -254,17 +271,45 @@ function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
$queried_objects[$id] = $objects[$id];
}
}
// Fetch other nodes from the database.
// Fetch other objects from the database.
if ($queried_objects) {
// We need the raw additions to be able to cache them, so
// content_storage_load() and hook_field_load() must not alter
// nodes directly but return their additions.
$additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age);
// The loading order is:
// - hook_field_attach_pre_load()
// - storage engine's hook_field_storage_load()
// - field-type modules hook_field_load()
// - hook_field_attach_load()
// We need the raw additions to be able to cache them, so the hooks must
// not alter objects directly but return their additions. At each step,
// results are merged into the $queried_objects, and into the $additions
// array, that will eventually get cached.
// Invoke hook_field_attach_pre_load(): let any module load field
// data before the storage engine, accumulating along the way.
$additions_pre_load = array();
$skip_fields = array();
foreach (module_implements('field_attach_pre_load') as $module) {
$function = $module . '_field_attach_pre_load';
$function($obj_type, $queried_objects, $age, $additions_pre_load, $skip_fields);
}
// Invoke the storage engine's hook_field_storage_load(): the field storage
// engine loads the rest.
$additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age, $skip_fields);
// First, merge the additions from the storage engine.
foreach ($additions as $id => $obj_additions) {
foreach ($obj_additions as $key => $value) {
$queried_objects[$id]->$key = $value;
}
}
// Then, merge the pre_load additions, so that they take precedence.
foreach ($additions_pre_load as $id => $obj_additions) {
foreach ($obj_additions as $key => $value) {
$queried_objects[$id]->$key = $value;
$additions[$id][$key] = $value;
}
}
// TODO D7 : to be consistent we might want to make hook_field_load() accept
// multiple objects too. Which forbids going through _field_invoke(), but
......@@ -281,13 +326,15 @@ function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
}
}
// Invoke field-type modules hook_field_load().
$custom_additions = _field_invoke('load', $obj_type, $object);
foreach ($custom_additions as $key => $value) {
$queried_objects[$id]->$key = $value;
$additions[$id][$key] = $value;
}
// Let other modules act on loading the object.
// Invoke hook_field_attach_load(): let other modules act on loading the
// object.
// TODO : this currently doesn't get cached (we cache $additions).
// This should either be called after we fetch from cache, or return an
// array of additions.
......@@ -464,14 +511,18 @@ function _field_attach_presave($obj_type, &$object) {
*/
function _field_attach_insert($obj_type, &$object) {
// Let other modules act on inserting the object.
foreach (module_implements('field_attach_insert') as $module) {
$function = $module . '_field_attach_insert';
$function($obj_type, $object);
_field_invoke('insert', $obj_type, $object);
// Let other modules act on inserting the object, accumulating saved
// fields along the way.
$saved = array();
foreach (module_implements('field_attach_pre_insert') as $module) {
$function = $module . '_field_attach_pre_insert';
$function($obj_type, $object, $saved);
}
_field_invoke('insert', $obj_type, $object);
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT);
// Field storage module saves any remaining unsaved fields.
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT, $saved);
list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
if ($cacheable) {
......@@ -489,14 +540,18 @@ function _field_attach_insert($obj_type, &$object) {
*/
function _field_attach_update($obj_type, &$object) {
// Let other modules act on updating the object.
foreach (module_implements('field_attach_update') as $module) {
$function = $module . '_field_attach_update';
$function($output, $obj_type, $object);
_field_invoke('update', $obj_type, $object);
// Let other modules act on updating the object, accumulating saved
// fields along the way.
$saved = array();
foreach (module_implements('field_attach_pre_update') as $module) {
$function = $module . '_field_attach_pre_update';
$function($obj_type, $object, $saved);
}
_field_invoke('update', $obj_type, $object);
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE);
// Field storage module saves any remaining unsaved fields.
module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE, $saved);
list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
if ($cacheable) {
......
......@@ -346,10 +346,12 @@ function field_create_instance($instance) {
_field_write_instance($instance);
module_invoke_all('field_create_instance', $instance);
// Clear caches
field_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_create_instance', $instance);
return FALSE;
}
......
......@@ -152,6 +152,9 @@ function _field_sql_storage_schema($field) {
);
}
/**
* Implementation of hook_field_storage_create_field().
*/
function field_sql_storage_field_storage_create_field($field) {
$schema = _field_sql_storage_schema($field);
foreach ($schema as $name => $table) {
......@@ -159,6 +162,9 @@ function field_sql_storage_field_storage_create_field($field) {
}
}
/**
* Implementation of hook_field_storage_delete_field().
*/
function field_sql_storage_field_storage_delete_field($field_name) {
// Mark all data associated with the field for deletion.
$table = _field_sql_storage_tablename($field_name);
......@@ -168,22 +174,9 @@ function field_sql_storage_field_storage_delete_field($field_name) {
}
/**
* Load field data for a set of objects from the database.
*
* @param $obj_type
* The entity type of objects being loaded, such as 'node' or
* 'user'.
* @param $objects
* The array of objects for which to load data.
* @param $age
* FIELD_LOAD_CURRENT to load the most recent revision for all
* fields, or FIELD_LOAD_REVISION to load the version indicated by
* each object.
* @return
* An array of field data for the objects, keyed by entity id, field
* name, and item delta number.
* Implementation of hook_field_storage_load().
*/
function field_sql_storage_field_storage_load($obj_type, $objects, $age) {
function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_fields = array()) {
$etid = _field_sql_storage_etid($obj_type);
$load_current = $age == FIELD_LOAD_CURRENT;
......@@ -200,6 +193,10 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age) {
$additions = array();
foreach ($field_ids as $field_name => $ids) {
if (isset($skip_fields[$field_name])) {
continue;
}
$field = field_info_field($field_name);
$table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name);
......@@ -229,13 +226,20 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age) {
return $additions;
}
function field_sql_storage_field_storage_write($obj_type, $object, $op) {
/**
* Implementation of hook_field_storage_write().
*/
function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fields) {
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
$etid = _field_sql_storage_etid($obj_type);
$instances = field_info_instances($bundle);
foreach ($instances as $instance) {
$field_name = $instance['field_name'];
if (isset($skip_fields[$field_name])) {
continue;
}
$table_name = _field_sql_storage_tablename($field_name);
$revision_name = _field_sql_storage_revision_tablename($field_name);
$field = field_read_field($field_name);
......@@ -298,14 +302,9 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op) {
}
/**
* Delete all field data for a single object. This function actually
* deletes the data from the database.
* Implementation of hook_field_storage_delete().
*
* @param $obj_type
* The entity type of the object being deleted, such as 'node' or
* 'user'.
* @param $object
* The object for which to delete field data.
* This function actually deletes the data from the database.
*/
function field_sql_storage_field_storage_delete($obj_type, $object) {
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
......@@ -328,16 +327,9 @@ function field_sql_storage_field_storage_delete($obj_type, $object) {
}
/**
* Delete field data for a single revision of a single object.
* Deleting the current (most recently written) revision is not
* allowed as has undefined results. This function actually deletes
* the data from the database.
* Implementation of hook_field_storage_delete_revision().
*
* @param $obj_type
* The entity type of the object being deleted, such as 'node' or
* 'user'.
* @param $object
* The object for which to delete field data.
* This function actually deletes the data from the database.
*/
function field_sql_storage_field_storage_delete_revision($obj_type, $object) {
list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
......@@ -357,8 +349,12 @@ function field_sql_storage_field_storage_delete_revision($obj_type, $object) {
}
}
/**
* Implementation of hook_field_storage_delete_instance().
*
* This function simply marks for deletion all data associated with the field.
*/
function field_sql_storage_field_storage_delete_instance($field_name, $bundle) {
// Mark all data associated with the field for deletion.
$table_name = _field_sql_storage_tablename($field_name);
$revision_name = _field_sql_storage_revision_tablename($field_name);
db_update($table_name)
......@@ -371,6 +367,9 @@ function field_sql_storage_field_storage_delete_instance($field_name, $bundle) {
->execute();
}
/**
* Implementation of hook_field_storage_rename_bundle().
*/
function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new) {
$instances = field_info_instances($bundle_old);
foreach ($instances as $instance) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment