Features Magic: how features adds additional items to export
If you've used features, you'll likely noticed that adding one thing will automatically add other items. The items added are sometimes dependencies (e.g. adding a view will automatically add dependency for 'views'), but other times it'll be additional items (e.g. adding a blog adds the blog fields.). However other items, like settings added to the content type form won't be, which often surprises the newer feature builder. This is because features, or modules hooking in, need to automatically tell the feature to add any component.
It does these through a series of hook.
The three main hooks handle similar arguments
- $data is an array of the current items for a single component being processed this round. For example it would be array('blog', 'article'); for the 'node' component in a feature exporting blog and article.
- $export is an array that will be rendered to info file. keys in the info file are turned to keys in the associated array. Non-feature component items are added to $export, like dependencies
- $pipe is an array of component type to names. For example if adding 'blog' and 'article' to the export, $pipe would be array('node' => array('blog', 'article')).
The first hook, hook_features_export, is a fuax hook; it's implemented by the given component being added: 'user_permission' is a component, so even though user_permission is not a module, user_permission_features_export is called.
The job of this hook is do the initial pass:
- Confirm the component exists (and give up if doesn't)
- Add any relevant dependencies
- Add any items it thinks should be added to the export.
Below is a slightly simplified version of hook_features_export for the node. You'll see it adding the node type, if exists, then suggesting the fields be added.
<?php
/**
* Implements hook_features_export.
*/
function node_features_export($data, &$export, $module_name = '') {
$pipe = array();
$map = features_get_default_map('node');
foreach (
$data as $type) {
// Confirms that the node ty
if ($info = node_type_get_type($type)) {{
$export['features']['node'][$type] = $type;
$export['dependencies']['node'] = 'node';
$export['dependencies']['features'] = 'features';
}
$fields = field_info_instances('node', $type);
foreach ($fields as $name => $field) {
$pipe['field'][] = "node-{$field['bundle']}-{$field['field_name']}";
}
}
}
return
$pipe;
}
?>The next two hooks are related, hook_features_pipe_alter and hook_features_pipe_COMPONENT_alter. These are where other modules can pipe in more items. Most common is COMPONENT variant, as modules know what component they'll be hooking into and it's better performance to use the more specific hook.
Below strongarm hooks into the 'node' component and suggest various variables be added to the export.
<?php
/**
* Implements hook_features_pipe_alter() for node component.
* Add node type variables on behalf of core modules.
*/
function strongarm_features_pipe_node_alter(&$pipe, $data, $export) {
if (!empty($data)) {
$variables = array(
'comment',
...,
'node_submitted',
);
foreach ($data as $node_type) {
foreach ($variables as $variable_name) {
$pipe['variable'][] = "{$variable_name}_{$node_type}";
}
}
}
}
?>Below is a completely bs-ed, thrown together of hook_features_pipe_alter, which /removes/ pipped items from the export based on some unknown criteria.
<?php
/**
* Implements hook_features_pipe_alter().
*/
function mymodule_features_pipe_alter(&$pipe, $data, $export) {
foreach (mymodule_remove_fromexport() as $component => $key) {
if (!empty($pipe[$component]) && in_array($key, $pipe[$component]) {
$pos = array_search($key, $pipe[$component]); // right order?
unset($pipe[$pos]);
}
}
}
?>It's important that new items be returned/added to $pipe and not $export; $pipe is processed recursively via this set of hooks (ie they turn into $data arrays and go through the same hooks that components added manually are).
There's a fourth hook at the total end, after everything else is done being processed, hook_features_export. This is only called once per features export(whereas the other hooks have possibilities have being called many times).
The BS-ed example below removes a dependency from the export.
<?php
/**
* Implements hook_features_export_alter().
*/
function mymodule_features_export_alter(&$export, $module_name) {
if ($module_name == 'blog_whatever' && !empty($export['dependencies'])) {
// something is adding 'bleh' as a dependency; remove it.
$pos = array_search('bleh', $export['dependencies']);
if ($pos !== FALSE) {
unset($export['dependencies'][$pos]);
}
}
}
?>So basically anyone can add any component to an export (or remove). However, since there's currently no UI for removing items from an export, it's limiting to the feature builder to add too much. It could be argued that node fields and variables should not be added -- they're not dependency for that component. However, they pass the 80% use case and help the generic feature builder, so I'm generally cool with them them, and for making a way in the UI to remove auto detected components (and keep em excluded). Till that's solved, need to be careful when using these hooks in contrib.
Or could make a module with the intent of totally and utterly populating the feature via just adding the node type and call it feature pipexplosion. In there you'll see examples of how to automatcally add permission, views, contextes, and fields to an export (based on content type). Course it has a high chance of adding something you don't want, so be careful!
Add new comment