I was chatting with Auzigog on IRC about how he's altering OA features slightly to work better in his GSOC distro using the new hook_COMPONENT_alter() functions and how it is pretty painful process to get things working correctly. The process he went through (he'll be writing a blog post soon) was to export his changes, do a diff against the current feature's export, then modify the default export in the hook_COMPONENT_alter().

After a bit of brainstorming we figured it'd be possible to automate the process. We figure by using Feature's diff integration, it'd be possible to automatically create a feature-alter (there's probably a better name) module.

So a sample use case.

Say Eduglu wants to use an Open Atrium feature but doesn't need some of the fields in a view + needs to add 2 extra fields to the content type. To avoid forking the Open Atrium and having to deal w/ potential merges in the future, I decide to create a feature-alter module. I download the OA feature into Eduglu and make my changes to the View and content type. Once things are working as I'd like, I go to the feature page and select "export overrides as feature-alter module". This module would be created and downloaded as normal. I'd then place this alongside the OA module in my profile.

Is the concept sound? I'm pretty busy the next while but if someone else doesn't get to it first, I'll take a stab at creating a patch.

Comments

Anonymous’s picture

Yeah. It would be really phenomenal to have this functionality. I don't see the patch being too complex, either.

Here are the steps I go through to get a hook_COMPONENT_alter() ready.

* Make changes to the view/field/context/etc
* Export those changes manually and copy paste them to test_view_mods.txt
* Find the original declaration somewhere in one of the *.features.inc files and copy paste it to test_orig_mods.txt
* Scan the diff myself. Ignore things like changes in weights. Most other things are relevant.
* For each array that needs changes, declare an array with all the keys leading up to the one you want to add to, then include the array of modified info (see code below for an example)
* $original_component_data_array = array_merge_recursive($original_component_data_array, $new_data_arr)

Here is some sample code where I add a block to an existing context in open atrium:

/**
 * Implementaiton of hook_context_default_contexts_alter().
 */
function casetracker_duedate_context_default_contexts_alter(&$contexts) {
  dsm($contexts, 'contexts');
  
  if (isset($contexts['casetracker_listing'])) {
    $conditions_mods = array(
      'views' => array(
        'values' => array(
          'casetracker_upcoming' => 'casetracker_upcoming',
        ),
      ),
    );
    $reactions_mods = array(
      'block' => array(
        'blocks' => array(
          'views-casetracker_upcoming-block_1' => array(
            'module' => 'views',
            'delta' => 'casetracker_upcoming-block_1',
            'region' => 'right',
            'weight' => 3,
          ),
        ),
      ),
    );
    
    $context = &$contexts['casetracker_listing'];
    
    $context->conditions = array_merge_recursive($context->conditions, $conditions_mods);
    $context->reactions = array_merge_recursive($context->reactions, $reactions_mods);
  }
}

Of course, each of the arrays for conditions and reactions are much larger when they are declared the first time. But here, we only specify the array elements that are new or different, so the arrays are generally shorter. The innermost array is usually the new/modified data and all the keys leading up to that reflect the normal structure of the object. The keys allow array_merge_recursive to do the magic for you.

It seems like features would be really good at this kind of thing since all the pieces are already there (generating code and arrays, doing diffs, etc). I don't think I'm proficient enough to be able to submit a patch for this. But I'm totally willing to help in every other way.

nedjo’s picture

Title: Export changes to feature as a "feature-alter" module » Export changes to feature as a set of hook_DEFAULT_HOOK_alter() implementations
Status: Active » Needs review
StatusFileSize
new9.6 KB

Yep, this is needed. E.g. I'm working on a set of baseline features, Debut, that will need enhancing and tweaking by other features.

Here's a patch.

Neither diff nor array_merge_recursive() really does what we need here. We need both to alter existing values and to unset existing ones.

Approach:
* Compare normal to default components to detect both additions and deletions.
* Structure overrides as an array, containing keys, key types (object or array), and values.
* Generate
* Add a checkbox to the features generate form to select whether to override existing features. Store in and load from .info file.
* Overrides are added as hook_DEFAULT_HOOK_alter() implementations in featurename.features.inc.

Rough draft, more or less working but only basically tested. (E.g., haven't tested deletions.)

To test:

* Select a feature and make modifications to one or more of its elements.
* Create a new feature.
* Add a dependency on the existing feature you altered.
* Select the checkbox to alter existing features.
* Generate the new feature.

Sample output:


/**
 * Implementation of hook_context_default_contexts_alter().
 */
function test_context_default_contexts_alter(&$items) {
  $items['test']->description = 'alter tag';
  $items['test']->reactions['debug'] = array(
    'debug' => 1,
  );

}

yhahn’s picture

Status: Needs review » Closed (won't fix)

My initial response to this is no, and not anytime soon. May be an interesting experiment elsewhere but here are the questions you will need to answer before this has a realistic shot:

  • You cannot make assumptions about how an exportable works or is structured. Example: Views displays and the way the set_override() method works. An exportable is not always a stdClass object or a simple PHP array and you cannot assume that manipulating it is as straightforward as replacing properties on an object or blowing away parts of the array.

  • The goal of Features is to leverage the power of exportables and stop where exportables stop. Currently, it is an open question of how to represent alterations in exportables in a granular, consistent fashion. At the moment, the drupal_alter() pattern makes all of these valid alterations:

    $items['foo'] = $replacement;
    

    or

    $items['foo']['bar'] .= ' concatenated string!'
    

    or

    foreach ($items as $key => $val) {
      // some complex conditions for matching
      $items[$key]['foo'] = 'bar';
    }
    

    Referring to an "alteration" is not even close to as rigorous or as defined as referring to an exportable, which can be referred to by a single, unique machine name, has a specific type, and can be assigned to and passed around as a single PHP variable. The concept of "alterations," on the other hand, is far more nebulous. It is like referring to "form alters" as though they were an actually designed/structured concept.

  • If you follow through the points above, you'll see that any approach to this based on the current state of exportables is going to be very limited -- basically, limited to an initial alter export. After that, you're on your own in terms of manually updating the original export, or manually updating one of your alters, or trying to figure out which of a handful of alters that affect the same exportable needs to be updated ...

The short is there is a large gap between Features being able to show you a diff between a feature and its overrides and it being able to manipulate "alters" or "deltas" as though they are first class objects.

It's not clear where the real work to be done on this front is, but IMHO it's not at the Features or Exportables level per-se. It's more about exportable/object-design, e.g. consider how Views starts to define its object structure (handlers and their types, plugins and their types) so that you might be able to describe an alteration to a View in a coherent conceptual manner, "x overrides field handler y on view A" that is not PHP code.

Feel free to pick my brain on irc or so if you want to talk more about this or related issues.

nedjo’s picture

Title: Export changes to feature as a set of hook_DEFAULT_HOOK_alter() implementations » Export changes to feature components
Status: Closed (won't fix) » Active
StatusFileSize
new3.55 KB

@yhahn: thanks, very valuable insights.

As I'm seeing it, we currently have up to two basic forms of a given exportable: default and normal. If there is an override of a default component, the normal version is (for most exportables, at least), basically a fork, or a clone that shares an ID. Where I suspect we want to get to is, an override is a defined object. A normal exportable is a default exportable that has had one or more overrides applied, with the last override still in the DB and not yet in code.

But we're not nearly there yet.

To explore how close we could come with just our current limited tools, here's another draft.

It defines a format for overrides--a very limited one that will work only for a subset of object types. And it makes these overrides exportable. Still uses drupal_alter(), but on the basis of the defined overrides. Only makes overrides exportable if the component is part of a feature. Rationale: makes possible a uniform alter for all component types; features is probably the strongest use case for exporting overrides.

So the process of extending an existing feature would be:

* Make changes to one or more components in a feature.
* Export the overrides (at admin/build/features/features-override).
* Add the overrides to a different feature.

Requires a patch on features to introduce a generic alter hook for feature components, so we don't need a different implementation for each component type.

A lot still missing, including:
* Dependencies (overrides shd require the feature their component comes from).
* I broke something in using CTools exportables UI, which probably isn't very useful here anyway.

I accept this isn't nearly appropriate for inclusion in Features. But setting to active to continue discussion/review.

nedjo’s picture

StatusFileSize
new789 bytes

Sample exported override:


/**
 * Implementation of hook_features_override_default_features_overrides().
 */
function example_default_features_overrides() {
  $export = array();
  $features_override = new stdClass;
  $features_override->disabled = FALSE; /* Edit this to true to make a default features_override disabled initially */
  $features_override->api_version = 1;
  $features_override->name = 'example_override';
  $features_override->description = '';
  $features_override->component_type = 'context';
  $features_override->component_id = 'example_context';
  $features_override->value = array(
    'additions' => array(
      0 => array(
        'keys' => array(
          0 => array(
            'type' => 'object',
            'key' => 'description',
          ),
        ),
        'value' => 'example description',
      ),
    ),
    'deletions' => array(),
  );

  $export['test_override'] = $features_override;
  return $export;
}

Patch attached adds hook_features_default_alter() to features.

nedjo’s picture

Would need to designate which component types support this basic overriding. Object types could declare support. Could also consider a parallel to what's done with export, anticipating a future method in which objects can export overrides. Something like:


  if (is_object($normal) && method_exists($normal, 'export_override')) {
    $output = $normal->export_override($default);
  }
  else {
    $output = features_override_export($normal, $default);
  }

amitaibu’s picture

StatusFileSize
new1.45 KB

@nedjo,

As requested, I took a look at the module. I wasn't able to test it, because my somehow features_override_get_overrides() doesn't return overriden features. Anyway, I've attached a diff that might fix the links that don't work. Further more:

- Is there a reason for features_override_load() and not use CTools generic load?
- Instead of allowing a user to "Add" when there are no overrides, you an disallow "Add". See stylizer_ui::access() in CTools' stylizer_ui.class.php

Anonymous’s picture

I haven't had a chance to test this, but I'm very interested in it still. Hopefully I'll be able to test it in a couple weeks.

With regard to this not making it into features core, I think that's reasonable. Would it make sense to have a separate module for this functionality? Maybe once it grows enough, we could merge it into features core. Thoughts? Nedjo, would you be interested in starting a repository for this?

nedjo’s picture

Status: Active » Closed (won't fix)

Thanks Amitaibu!

Posted as a new module, Features Override. Testing and co-maintainers invited.

nedjo’s picture

Issue tags: +Debut enabler

Tagging.