Right now, to a field mapping you can add

  ->callbacks('html_entity_decode', array($this, 'myMigrationMember'));

It takes multiple arguments, each one a PHP callback type which takes a single argument (the source field value) and returns a value that will become the new source field value. But, what if you wanted to pass additional arguments to the callback, such as a character set to html_entity_decode? You would have to do it in prepareRow() or prepare(), not through a callback. So, I'd like to implement a new callback mechanism:

  ->callback('html_entity_decode', ENT_QUOTES, 'ISO8859-1')
  ->callback(array($this, 'myMigrationMember'));

First argument is the function/member to call, further arguments get passed after the source value when calling it. It would also be nice to be able to pass values from source data, as we do with arguments():

  ->callback('html_entity_decode', ENT_QUOTES, array('source_field' => 'charset'));

Upon implementing this, callbacks() will be deprecated.

Comments

drewish’s picture

I'm kind meh on this, mostly because I hate that whole array('source_field') aspect. I think it's the most cumbersome aspect of the arguments API so I'd love to see if there was a more elegant way to proceed.

mikeryan’s picture

I'm open to suggestions on the source_field thing - how can we pass row values instead of hard-coded values to either arguments or callbacks?

drewish’s picture

I wonder if we could use something like a class name... I guess if I actually spent sometime allowing subfields it would get past the wart that is the arguments. One thing that would be really cool would be the ability to use PHP 5.3's anonymous functions. So we could pass in the $row and $fieldname so the 5.3 code could look like:

  ->callback(function($row, $fieldname) {
    return html_entity_decode(ENT_QUOTES, $row->$fieldname);
  });

So if you needed to do something crazy you could go pull other values from the row. Maybe we give this a slightly different signature callbackAnon() or something so it's not conflicting with the 5.2 and below way of doing things.

I guess what'd like is for it to look more like straight PHP than strange arrays.

mikeryan’s picture

Issue tags: +Migrate 2.4

I'd like to address the original idea for Migrate 2.4 (the source_field digression has been rendered obsolete by the subfield support already implemented).

mikeryan’s picture

Priority: Normal » Minor
Issue tags: -Migrate 2.4

Deprioritizing - there doesn't seem to be much demand, and I haven't actually had a use case myself.

eddie_c’s picture

I'd really like to see this feature and would be happy to help out. I have a use case whereby I need to build a URL alias based on three different fields from the source.

I could handle them in prepare or prepareRow but I think this could get messy because all the source fields are mapped individually to different destination fields besides the URL alias.

I think being able to fetch multiple source values in a callback would make the solution a lot neater.

vacilando’s picture

I also think this is very much needed. I have a situation where a source field needs to be split and fed into several Drupal fields. I can use the current callbacks system but have to create a private function per task, which is inefficient and inelegant. If there were an option to specify additional arguments I would be able to dynamically specify which Drupal field is being processed and so one single callback function could do the splitting on the basis of the argument.

pcambra’s picture

StatusFileSize
new560 bytes

I've just found a use case of dependant fields that need the value from another field to fill a yes/no

I'll try to post this done properly, but for the moment the patch attached just adds the $mapping context to the callback which was what I needed in case someone else finds it useful.

pcambra’s picture

Status: Active » Needs review
StatusFileSize
new2.46 KB

What about this?

pcambra’s picture

Issue summary: View changes

Proper quotes

mikeryan’s picture

Issue summary: View changes
Status: Needs review » Needs work

We can't simply remove callbacks(), it'll break too much existing migration code. The new callback() should be additive, with callbacks() being deprecated (see arguments(), just a couple functions down).

That being said, I'm concerned that introducing a new method with such a similar name would cause more confusion than it's worth - any ideas on a clearly distinguishable name?

wonder95’s picture

As an added note to this, I started to use this callbacks functionality for the first time in a custom class, and I couldn't figure out why I was getting an error saying that my callback couldn't be found. After looking at the code that calls the callbacks, I figured out that since it uses call_user_func() (and call_user_func_array() in the latest patch above) instead of something like $this->{$callback}, my callback had to be outside of the class (e.g. in the .module file or another included file). Looking at the example on the doc page, it's not apparent that the callbacks need to be outside of the class.

Is there any reason the callback shouldn't be in the migration class itself? To me, this makes sense, since the addition of the callback is done in the class constructor anyway (I'm no OOP guru, so there very well could be a good reason). If not, I would suggest that the docs page be updated to indicate that the callbacks need to be outside of the migration class in order to be found. I'll be happy to change the docs myself, but I want to make sure it's the correct thing to do first.

wonder95’s picture

Sigh, ignore that last comment. I misread the docs.

As shown, each callback may be either a standalone function or an object and method. (These are passed directly as the first argument to call_user_func().)

Back to my hole...

joelstein’s picture

Status: Needs work » Needs review
StatusFileSize
new2.17 KB

I like the idea of the additive approach. See the attached patch which adds callback() and keeps backwards compatibility with callbacks().

Thus, you can do something like this:

$this->addFieldMapping('body', 'body')
  ->callbacks(array($this, 'addTitlePrefix'), 'drupal_strtoupper')
  ->callback('html_entity_decode', ENT_QUOTES, 'ISO8859-1')
  ->callback(array($this, 'doSomething'), 'param1', 'param2');
joelstein’s picture

StatusFileSize
new2.17 KB

Renamed the patch.

joelstein’s picture

FYI, I'm using this solution heavily on a big site migration, chaining both callback() and callbacks(). It improves the DX quite a bit.

Mike, it was great to meet you at DrupalCamp STL. Thanks for the great presentation, and for all your hard work on the Migrate module.

osopolar’s picture

This is awesome and works fantastic.

  • mikeryan committed e7004ff on 7.x-2.x authored by joelstein
    Issue #1406802 by joelstein, pcambra: Add callback() field mapping...
mikeryan’s picture

Status: Needs review » Fixed
Issue tags: +Migrate 2.9

Committed, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.