summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2009-03-30 03:44:55 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2009-03-30 03:44:55 +0000
commitfbabc0d98553d136f54073dc1624310bd07e11dc (patch)
treefdafeda88d090a9ad65d851b007688f8c4dd7d42
parent28aaa036e471a5d96be3938545c49e2dc71e342e (diff)
downloadbrdo-fbabc0d98553d136f54073dc1624310bd07e11dc.tar.gz
brdo-fbabc0d98553d136f54073dc1624310bd07e11dc.tar.bz2
#368674 by bjaspan, Eaton, chx, and yched: Provide hooks to allow hybrid field/bundle-level storage for fields in core.
-rw-r--r--modules/field/field.api.php87
-rw-r--r--modules/field/field.attach.inc105
-rw-r--r--modules/field/field.crud.inc6
-rw-r--r--modules/field/modules/field_sql_storage/field_sql_storage.module65
4 files changed, 191 insertions, 72 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index ef08b0b75..143366908 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -440,8 +440,40 @@ 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
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index d52f9db2a..2b05937f4 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -94,16 +94,33 @@ define('FIELD_STORAGE_INSERT', 'insert');
*
* 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) {
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index 93f24bf56..b96dbfb4b 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -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;
}
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index 774d275bd..4d17110d0 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -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) {