The UI for selecting your file connection settings (FTP vs. SSH) at authorize.php (added via #538660: Move update manager upgrade process into new authorize.php file (and make it actually work)) is totally broken without JS enabled. At one point, it used to degrade properly into a multi-step form, but no longer.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dww’s picture

Title: authorize.php connection settings form totally broken without JS » authorize.php connection settings form broken (doesn't degrade, can't extend, pointless fieldset)

Since all these problems involve the exact same code, it's going to be hard to handle them in separate patches. So, I'm just going to re-scope this issue to cover all of them:

A) The form is completely broken with JS disabled.

B) system_get_filetransfer_settings_form() is very broken -- it makes it impossible to add new FileTransfer classes in contrib (and lots of effort has already gone into making that possible). We already have the info about the settings form callback function in the FileTransfer backend info array (which is in $_SESSION by the time we hit authorize.php). So, we should just use those directly, instead of trying to go back through system.module for this. So long as those functions live in the same .inc file as the FileTransfer sub class, the class autoloader will ensure it's available to authorize.php during this settings form.

C) We don't want the outer "FTP Connection settings" fieldsets. Those are pointless, and making Bojhan and Dries weep over at #613912: Move host and port into a collapsed "Advanced settings" fieldset on the authorize.php connection settings forms.

dww’s picture

Assigned: Unassigned » dww
Status: Active » Needs review
FileSize
5.09 KB

This fixes #1.B. The changes are fairly self-contained, and this could probably go in as an initial patch here before tackling (A) and (C). Those two really are intertwined, but this system_get_filetransfer_settings_form() junk can die right now as a first step.

Note that the old form didn't actually handle nested forms at all (e.g. forms with fieldsets). The new helper does. That's why we're adding more lines here than removing, but it's a bug fix, especially now that #613912: Move host and port into a collapsed "Advanced settings" fieldset on the authorize.php connection settings forms is in.

The alternative to the recursive helper here is to actually make the handling of default settings the problem of the settings_form callback functions. In that case, we'd pass the $defaults array as an argument to the callback, and punt all this default setting business to each FileTransfer class's own settings form. Seems like more duplicate code than a single helper that just always does this for all settings forms. It's include/authorize.inc's job to save these defaults, seems like it should be its responsibility to set them again...

sun’s picture

+++ includes/authorize.inc	27 Oct 2009 23:30:41 -0000
@@ -116,6 +115,62 @@ function authorize_filetransfer_form($fo
+  // Invoke the callback to get the settings form.
+  $form = call_user_func($info['settings_form']);

I have yet to see a use-case where cuf() actually makes sense. Instead of cuf(), we can do the following here (like everywhere else in core):

$form = array();
$function = $info['settings_form'];
if (function_exists($function)) {
  $form = $function($form, $form_state);
}
+++ includes/authorize.inc	27 Oct 2009 23:30:41 -0000
@@ -116,6 +115,62 @@ function authorize_filetransfer_form($fo
+function _authorize_filetransfer_connection_settings_set_defaults(&$element, $key, array $defaults) {
+  // If we're operating on a form element which isn't a fieldset, and we have
+  // a default setting saved, stash it in #default_value.
+  if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'fieldset') {
+    $element['#default_value'] = $defaults[$key];
+  }
+  // Now, we walk through all the child elements, and recursively invoke
+  // ourself on each one. Since the $defaults settings array can be nested
+  // (because of #tree, any values inside fieldsets will be nested), if
+  // there's a subarray of settings for the form key we're currently
+  // processing, pass in that subarray to the recursive call. Otherwise, just
+  // pass on the whole $defaults array.
+  foreach (element_children($element) as $child_key) {
+    _authorize_filetransfer_connection_settings_set_defaults($element[$child_key], $child_key, ((isset($defaults[$key]) && is_array($defaults[$key])) ? $defaults[$key] : $defaults));
+  }
+}

Hmmmm... sorry, I forgot the reason - why do we fork form.inc, resp. form_builder() again?

This review is powered by Dreditor.

sun’s picture

So dww clarified that this function is basically what http://api.drupal.org/api/function/_system_settings_form_automatic_defau... is doing, however, not 1:1 using system variables, but rather expanding a serialized array (in a system variable) into #default_values in a form.

I don't like the special-casing, but since we're past API freeze and this is non-critical, I guess we need to live with a custom implementation in authorize.inc. :-/

dww’s picture

Right. I don't know of a good way to cleanly handle this without more far-reaching API changes. At this point, a small localized helper function in authorize.inc seems better than trying to change FormAPI itself, or to punt on all the #default_value setting into every single FileTransfer setting form callback. So, I left _authorize_filetransfer_connection_settings_set_defaults() alone, but the new patch is not using call_user_func().

While I was at it, I noticed that fixing (C) above is also pretty trivial -- instead of a fieldset, we just put each connection type in its own div. ;) All the JS works exactly as it does now, unmodified. If you change the "Connection method" setting, the form elements under you get swapped out as appropriate. Before/after screenies attached for review.

All that remains is (A). But, for the sake of the kittens, I'd rather RTBC this and get it in, then work on (A) as its own patch.

yoroy’s picture

The UI guideline is to not wrap the main interaction of a form into a fieldset, so very good fix there. :)

catch’s picture

Status: Needs review » Reviewed & tested by the community

OK so _authorize_filetransfer_connection_settings_set_defaults() made me wince a bit, but fully agreed that it's self-contained and we don't have a lot of options at this point. Also +1 for less random crap in system.module. Looks like sun's review is fixed / no longer applies. Comments are verbose. RTBC.

dww’s picture

Status: Reviewed & tested by the community » Needs work

Actually, I want to re-roll this to add an optional "settings_form_file" attribute to the info hook. Actually, it should be "settings form file" (and I should rename "settings_form" to "settings form", since we don't usually use _ in attribute names in core info hooks like this -- something that snuck in with the original FileTransfer patch). Personally, I think it's fine to just stick the settings form callback in the same .inc file that your class lives in, but some of the OO purists among us are not so happy with this. But, it's a small change, and it gives people more flexibility if they have a legitimate reason for it... I'll work on this tomorrow, once I've had some sleep.

dww’s picture

Sorry kittens, I couldn't help it. :(

- Renamed hook_filetransfer_backends() to hook_filetransfer_info() since it's another info registry hook. (DX++)
- Added drupal_get_filetransfer_info() as a real class registry function. This handles defaults, sorting, altering, etc. (DX++)
- Actually documented the info and alter hooks in system.api.php. (DX++).
- Changed the 'settings_form' key to be 'settings form' to match the format of the keys in every other info hook. (DX++)
- Added 'settings form file' and 'settings form path' to the info hook. If you don't define the path, it defaults to drupal_get_path('module', whatever). (So it's actually possible to define your own FileTransfer classes in contrib and it'll actually work)
- Changed the session variable we care about from $_SESSION['authorize_filetransfer_backends'] to $_SESSION['authorize_filetransfer_info'] for consistency. (DX++)
- Moved the settings forms for FTP and SSH out of system.module into system.filetransfer.inc. (Bloat--)

phew!

Personally, I think a lot of this would be cleaner and better if we just had a FileTransfer::settingsForm() method in the file transfer classes. :/ Then, we wouldn't need any of the 'settings form *' stuff in the info hook, we wouldn't need system.filetransfer.inc at all, and the settings forms would live next to the code that they're generating settings for. Jacob threw a fit about this when I first suggested it to him in IRC, since hypothetically someone could reuse our FileTransfer classes outside of Drupal. Even if they did, so what? Don't want a FAPI settings form array?... then don't call the settingsForm() method. ;) Views does this already (handler classes provide their own methods that define their forms), and I think the whole thing works pretty nicely. However, that's probably too big a change to the whole FileTransfer system at this stage, maybe in D8...

So, new patch and interdiff vs. patch #5 attached.

Status: Needs review » Needs work

The last submitted patch failed testing.

dww’s picture

Status: Needs work » Needs review

I think the bot was broken. When I patch HEAD, I get:

% php -l includes/common.inc
No syntax errors detected in includes/common.inc

Requesting a retest.

JacobSingh’s picture

Status: Needs review » Needs work

My reason for not wanting to put Drupal form definitions into a class is that classes should do something, not everything. Just because you can add it, doesn't mean you should. The FileTransfer classes have nothing to do with a form to configure them. I don't mind the approach in the patch, but I think there are other things.

+++ modules/system/system.api.php	30 Oct 2009 01:01:31 -0000
@@ -2665,5 +2665,72 @@ function hook_url_outbound_alter(&$path,
+function hook_filestransfer_info() {
+  $info['sftp'] = array(
+    'title' => t('SFTP (Secure FTP)'),
+    'class' => 'FileTransferSFTP',
+    'settings form' => 'sftp_filetransfer_settings_form',
+    'settings form file' => 'sftp.filetransfer_settings.inc',
+    'weight' => 10,
+  );
+  return $info;
+}

So the "kitten killing" is the extra delcarations for the settings form. I personally don't think this matters very much at all since these things are almost never declared, and now an _alter could change it without changing the class.

Anyway, what about something more like this:

Why so verbose? False stability / security


+++ modules/system/system.api.php	30 Oct 2009 01:01:31 -0000
@@ -2665,5 +2665,72 @@ function hook_url_outbound_alter(&$path,
+function hook_filestransfer_info() {
+  $info['sftp'] = array(
+    'title' => t('SFTP (Secure FTP)'),
+    'factory' => array('system_get_ftp_filetransfer', 'sftp'),
+    'settings form' => 'sftp_filetransfer_settings_form',
+    'file' => 'sftp.filetransfer_settings.inc',
+    'weight' => 10,
+  );
+  return $info;
+}

So authorize.php would load the one file in 'file' (this is the same as hook_theme and hook_menu. That file would contain a function to return a FileTransfer class when the 'factory' is called. factory would pass in the $settings array which currently goes to the factories in the classes themselves.

This lets the module implement the instantiation however it needs to, and keeps the settings form in the module where it belongs.

Should I roll this?

This review is powered by Dreditor.

Status: Needs work » Needs review
Issue tags: -Usability, -Update manager

Re-test of 609772-9.filetransfer_settings_forms-BC.patch from comment #9 was requested by sun.core.

Status: Needs review » Needs work
Issue tags: +Usability, +Update manager

The last submitted patch, 609772-9.filetransfer_settings_forms-BC.patch, failed testing.

sun.core’s picture

Priority: Critical » Normal

Not critical. Please read and understand Priority levels of Issues, thanks.

dww’s picture

Priority: Normal » Major

Crap, I just realized this is still broken. :( While not a critical, this is definitely major. authorize.php is completely broken without JS. Actually, everything I said in #1 is still true. I'll see if I can give this a quick re-roll and see if I can get this fixed.

dww’s picture

Status: Needs work » Needs review
FileSize
16.97 KB

Here's a straight re-roll of #9 so it applies cleanly to HEAD. This at least fixes B and C from #1. I'll see if I can figure out A (the non-degradability) and work on that next.

I'm terrified Drieschick aren't going to let me fix this at all at this stage (even though hours of work to make pluggable FileTransfer possible in contrib are wasted effort until this is fixed). So, I'm not sure it's reasonable to even further change the API with all the factory() stuff Jacob proposes in #12. I'll see if I can get webchick's input on this before I spend the effort...

dww’s picture

Status: Needs review » Needs work

Yeah, actually trying to add another file transfer type via contrib shows #17 won't work, and we'll need something like #12. The class auto-loader won't work if we're not bootstrapping all the modules. So, we need to have enough info in the info hook to load a file and call a function that can instantiate our object for us. This other file can either include the class definition itself, or include a file that does.

Rerolling, stay tuned...

dww’s picture

Title: authorize.php connection settings form broken (doesn't degrade, can't extend, pointless fieldset) » Impossible to extend the FileTransfer class system in contrib
Status: Needs work » Needs review
Issue tags: +Needs tests
FileSize
19.1 KB

Re-titling this issue to focus on the fact you can't extend FileTransfer system in contrib (part B). I moved A and C into a separate issue, since this one is already enough of a monster:

#932846: authorize.php connection settings form broken (doesn't degrade, pointless fieldset)

Here's a patch that basically implements Jacob's proposal in #12. Not quite every exact detail, but the spirit is exactly there. And, I confirmed this works via contrib! ;)

I'd like to add a test that proves contrib can inject its own FileTransfer backend, but I wanted to get the guts of the patch up now for review. I have to run off to teach a class right now, but I hope to return to this later tonight or tomorrow to add the test. The test isn't going to be able to prove that the contrib FileTransfer class actually *works* (that's a bigger problem to address over at #605276: Add tests for the update manager) but I think it should be possible to at least see contrib's transfer backend when we hit authorize.php itself...

dww’s picture

Here's a test. It's a bit unfair, since it's written to the new API. ;) But, it sort of works, although this patch should fail 2 assertions. I could spend more time to re-write this test to try to use the broken HEAD API, but it's still going to fail, so that doesn't seem worth it.

I'll post another with this test plus the changes in #19 plus a few other minor fixes next.

dww’s picture

This both the test from #20 plus the API change from #19. And this is definitely an API change. ;) However, it's necessary since the existing API is completely broken and unusable. So, we know we're not going to make work for people who are already using this API since clearly no one has tried it yet or we would have seen bugs in the queue. ;)

Speaking of which, we also haven't tried swapping the Updater class. :/ I wouldn't be surprised if that's broken, too. However, this is already a huge patch, so I submitted #933392: Add tests to verify that you can plug in your own Updater class to handle this 2nd aspect, to avoid scope creep and any more kitten slaying in here.

p.s. the "Usability" aspects of this issue moved to #932846: authorize.php connection settings form broken (doesn't degrade, pointless fieldset)...

Status: Needs review » Needs work

The last submitted patch, 609772-21.pluggable_filetransfer_classes.patch, failed testing.

dww’s picture

Status: Needs work » Needs review
FileSize
27.14 KB

Sorry, patch failure. Forgot to include all of the fixes (and the new file) in the previous patch. This should work better.

After applying and before committing, don't forget the:

cvs add modules/system/system.filetransfer.inc
dww’s picture

Re-rolled to remove a stray space character I just noticed:

-    $form['connection_settings'][$name] += system_get_filetransfer_settings_form($name, $current_settings);
+     $form['connection_settings'][$name] += _authorize_filetransfer_connection_settings($name, $backend);

;)
Applies with no fuzz to current HEAD.

ksenzee’s picture

Status: Needs review » Needs work

I agree with everybody that setting the form defaults in authorize.inc instead of extending Form API is most unfortunate but also the only reasonable choice at this point in the cycle.

I have to admit I am not a huge fan of this much indirection in the class instantiation. I would rather see the settings form live with the class (as Derek suggested a year ago) than have a .inc that defines a settings form and a factory function which in turn includes its own .inc and instantiates its own class. We're building this stuff for Drupal, not a final exam in OOP, and it's going to be a confusing API to implement. But that is a matter of opinion only; since there's disagreement on it I am happy to defer. Even if this API is confusing, it's workable, which is a lot better than the current state of HEAD. So my vote is to go ahead with it.

Marking CNW for the following nitpicks:

+++ modules/simpletest/tests/system_test.module	7 Oct 2010 18:57:50 -0000
@@ -311,3 +317,56 @@ function _system_test_second_shutdown_fu
+ * Implements hook_filestransfer_info().

s/filestransfer/filetransfer

+++ modules/system/system.filetransfer.inc	7 Oct 2010 18:57:50 -0000
@@ -0,0 +1,81 @@
+ * Returns the form to configure the FileTransfer class for FTP

This docblock and the next two need periods at the end of their sentences.

Powered by Dreditor.

dww’s picture

Thanks for the review! I totally agree with "We're building this stuff for Drupal, not a final exam in OOP". So much so, that just for comparison, I'm going to re-roll this with the settings form as part of the object and making this thing a little more sane. That way, we at least have two real patches to compare and see which one we like better.

Meanwhile, here's an updated version of #24 fixing the doc nitpicks...

Status: Needs review » Needs work
Issue tags: -API change, -Update manager

The last submitted patch, 609772-26.pluggable_filetransfer_classes.patch, failed testing.

dww’s picture

Status: Needs work » Needs review
Issue tags: +API change, +Update manager
dww’s picture

Stupid non-deterministic tests (care of Field form tests (FieldFormTestCase) [Field API]). I periodically see random failures in there which are completely unrelated to whatever patch was submitted. We should fix that. Dunno if there's already an issue or not, but don't have time now to search.

JacobSingh’s picture

Status: Needs review » Reviewed & tested by the community

Seems fine to me.

How is the settings form now part of the class?

system_filetransfer_settings_form_ftp() seems to be outside of the class, but doesn't matter to me either way.

Honestly, if we shipped it to not be extendable, I wouldn't really care because I see very few cases of people using SMB to install mods. Still... looks like it's more verbosely documented and clearer.

dww’s picture

webchick’s picture

This is a pretty enormous patch, and I confess I don't quite understand what all is happening here. And it changes APIs.

However, the APIs that it's touching aren't affecting any contributed modules at the moment, since this patch allows there to even be contributed modules that extend the FileTransfer methods.

So I think I'm okay with this going in, under the auspices of "blatant API oversight in new Drupal 7 feature". But I'll leave RTBC for 24 hours in case Dries believes otherwise.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Ok, dww was kind enough to spend a half hour or so going through this patch with me.

Firmly wearing my D7 core maintainer hat, the following is true:

- There's nothing about fixing this bug that requires hook_filetransfer_backends() to hook_filetransfer_info(). This should move to D8.
- There's nothing about fixing this bug that requires renaming $_SESSION['authorize_filetransfer_backends'] to $_SESSION['authorize_filetransfer_info']. This should move to D8.
- There's nothing about fixing this bug that requires moving system_filetransfer_backend_form_ssh() et al from system.module to system.filetransfer.inc. This should move to D8.

Then if I analyze the actual changes that are part of the patch, this whole 'factory' thing, while it might make sense to folks with a strong OO background, creates confusion and inconsistency with the rest of Drupal.

Everywhere else that we use an _info()-style hook (hook_theme(), hook_menu() et al), the 'file' index means "this is where to find the code for this thing." Here, it means "this is where to find a dummy wrapper function that includes the code that you actually want to call." Confusing as heck.

It seems to me like we could solve this problem very easily, and with a much smaller patch, by doing the following:

1. Move the settings form methods to the actual class definition. I read Jacob Singh's #12, and I think that would be just peachy if we were rolling this patch a year and a half ago and had ample time to tweak things, but we are rolling it now. For D7, I see no reason why we can't just couple these things together. Especially since, as dww points out, people don't have to call the settingsForm method, and since, as Jacob points out, most people won't override these from contrib anyway. In D8, if Drupal ends up going a lot more OO in its design, it probably totally makes sense to revisit this in context of what the entire system is doing, but in D7 a one-off here is just a total WTF for developers.

2. That would involve getting rid of the 'factory' and 'settings form' indexes in hook_filetransfer_backends() in favour of just 'class' and 'file'. Class is the name of the class, and 'file' is the file to include to get that class definition. Same as hook_entity_info(), same as other places in core.

3. Tests + documentation.

And that's it. That seems like it accomplishes the simplest thing that could possibly work to solve this semi-critical bug and leave room for contrib to improve about core's offerings here.

dww’s picture

I'm willing (I'd almost say "happy") to re-roll for putting the settings form in the class and removing this whole factory callback indirection. However, at this point, not calling this an info hook is a pointless DX WTF for the only reason of "those are the rules". Rules are important, but as Dries pointed out recently, blindly following the rules just for the sake of the rules isn't going to win us any points.

- The existing API is totally broken and changing in this issue, no matter what we do.

- Since we're going to completely change how this hook works, no existing code is going to work after this change, even if we keep the name the same.

- I already did all the work to give this hook a consistent name and document it.

...

So, the *only* reason to revert to the crappy name is "because that's how we do things" which is a pointless reason in this case. The people we're "helping" by keeping it "compatible" are known to not exist. If anyone was actually trying to use this API yet, there would have been bug reports about it being hopelessly broken. Not making this an info hook like every other info hook in Drupal would be a complete mistake -- there are no good reasons to revert to the crappy name, and lots of good reasons to use the new one.

Therefore, my plan is to mostly do what webchick is requesting, but I'm not going to revert the info hook to a "backends" hook. Sorry. ;)

sun.core’s picture

Status: Needs work » Needs review

Looks like webchick's directions entirely stopped work on this issue. Can we find a compromise? FWIW, I agree with dww that API changes in this subsystem aren't really API changes - I'd recommend to respect do-ocracy here. dww is more or less the only one who actually works on all of these update manager issues. (Thanks for that!)

cosmicdreams’s picture

It seems to me that this is the one of the most critical bugs for the lifecycle of D7. I'll see if I can step into this code tonight to see if I can make a meaningful contribution.

webchick’s picture

I'm not owning "webchick's directions entirely stopped work on this issue". I spent a great deal of effort explaining why the patch in #26 is uncommittable (dww agreed), and laid out very explicit instructions on how to make it so. That no one stepped up to work on this is regrettable, but certainly not my doing.

There's still time though, but not much. Tick, tick, tick.

dww’s picture

Assigned: dww » Unassigned
Status: Needs review » Needs work

For now. If anyone else wants to run with this, great. If that doesn't happen in a few days, I hope to return to this issue...

ksenzee’s picture

Assigned: Unassigned » ksenzee

I'll see if I can do it this afternoon.

OpenDomain’s picture

Oh wow.
Please forgive the intrusion, but I could not help but step out of my normal lurker role
I learned more about open source in this one thread than any conference I ever went to.
Do you really sacrifice so much just to meet an imaginary deadline? The main motivator here is passion and these rules seem more of a reflection of the corporate. Code may be fundamentally math, but here I see the ART. Do we ignore the best design because of misinterpretation of true object (or even aspect) orientation?
I am sorry for the rant - I respect your hard work and am not really part of the community, I would like to see the best D7. I do not want to wait years for such an important feature.

ksenzee’s picture

Status update: I have a patch for this halfway done, but it's Thanksgiving and I may not get back to it for a day or two.

OpenDomain, I can't tell from your comment which design you think is best, but there's disagreement on that among the participants in the thread. I don't think it's due to anyone's misunderstanding of software design principles. We just disagree on our priorities and audience. When that happens, we look to a committer to provide feedback. We got our feedback, and now we're acting on it. It seems to me like good teamwork, not corporate dysfunction.

And yeah, we have a deadline. If we didn't, we'd never release. I don't want to go down in history next to Duke Nukem Forever. :)

dww’s picture

@ksenzee: Thanks both for picking up the torch on this issue and for your thoughtful reply. I eagerly await the chance to review the new patch once it's ready.

Happy Thanksgiving!
-Derek

cosmicdreams’s picture

anxiously awaiting ksenzee's take on this patch.

cosmicdreams’s picture

I'm setting some time aside tonight to be able to test this issue if a patch becomes ready tonight.

ksenzee’s picture

Status: Needs work » Needs review
FileSize
25.04 KB

Here it is. Sorry that took so long. I went ahead and renamed hook_filetransfer_backends() to hook_filetransfer_info(), partly because I was starting from the existing patch and that was easier than changing it back, but also partly because if there did exist an implementation of hook_filetransfer_backends(), the rename would be an obvious hint that it has to be rewritten. I did ask webchick first to make sure she wouldn't literally skewer me over it.

cosmicdreams’s picture

woohoo! Now pardon me for asking, how should i test this?

dww’s picture

Try to add a dummy SFTP FileTransfer class via contrib that just inherits the FTP class exactly, but uses a different label and class name. See if you see an "SFTP" option when you land on authorize.php, and make sure that it lets you install a new module or update missing releases via FTP.

dww’s picture

p.s. I should have also said...

@ksenzee: Score! ;) Thanks!!! I need to look more closely at the patch, but it sounds great from your description. I'm in the middle of a meeting now (and it's nearly midnight where I'm currently living), but hopefully I can do a thorough review before I crash for the night...

dww’s picture

Status: Needs review » Needs work

This looks great. 2 incredibly tiny nitpicks, which I'm going to re-roll for:

A) Is there a reason this needs two lines:

+      $jail = DRUPAL_ROOT;
+      $filetransfer = $backend_info['class']::factory($jail, $settings);

What's wrong with just:

+      $filetransfer = $backend_info['class']::factory(DRUPAL_ROOT, $settings);

?

B) Comment doesn't match code:

+    // Since we have to manually set the 'form path' default for each
...
+              $values['file path'] = drupal_get_path('module', $module);

Everything else looks perfect. I'll re-roll for those two and test it, then I'm calling this RTBC.

dww’s picture

Argh, screw you PHP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Re: A) I'll tell you what's wrong. PHP completely and utterly sucks. :( I can't believe it, but using a variable to hold a class name doesn't work when calling static methods until PHP 5.3. You've got to be kidding me.

+      $filetransfer = $backend_info['class']::factory(DRUPAL_ROOT, $settings);

Only works in 5.3. In 5.2, we either need to keep the call_user_func_array() or do an evil trick with eval():

+      eval('$filetransfer = ' . $backend_info['class'] . '::factory(DRUPAL_ROOT, $settings);');

Shoot me. So, here are two patches. One with call_user_func_array() and the other with eval. Take your pick. I've tested both on PHP 5.2 and they both work. However, I can no longer RTBC this with a clear conscience, since I think someone else should look at this first. I vote for call_user_func_array()...

dww’s picture

p.s. Both patches in #50 fix #49.B. I should have said so at the time, but I was too busy hating PHP to write clearly.

bojanz’s picture

Status: Needs review » Reviewed & tested by the community

Okay, so this code already went through multiple iterations and reviews, and the only thing that's holding back dww from RTBC-ing it is the call_user_func_array VS eval question.

I too vote for call_user_func_array(). Easier to understand, looks cleaner to me, + no reason to even explain why I hate eval :) Existing approach > evil trick.

Peeked at the patch, didn't see anything strange, but I'm not really familiar with the update manager.

Let's get this to RC1 :)

oriol_e9g’s picture

In general call_user_func_array() is faster than eval()

Straight function call: 0.392249107361 secs
Dynamic function call: 0.671601057053 secs
call_user_func function call: 1.45473217964 secs
call_user_func_array function call: 1.61347579956 secs
eval function call: 3.61229491234 secs

Straight static method call: 0.701809167862 secs
call_user_func static method call: 2.67288184166 secs
call_user_func_array static method call: 2.81385302544 secs
eval static method call: 4.31622505188 secs

Straight method call: 0.700944185257 secs
call_user_func method call: 2.17300820351 secs
call_user_func_array method call: 2.32969498634 secs
eval method call: 4.98666906357 secs

Code test:

class myTest
{
    public static function test()
    {
        return true;
    }

    public function testMe()
    {
        return true;
    }
}

function testMe()
{
    return true;
}

$o = new myTest();

$function = 'testMe';

echo '<br />Straight function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    testMe();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />Dynamic function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    $function();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />call_user_func function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func($function);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />call_user_func_array function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func_array($function, null);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />eval function call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    eval('testMe();');
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br /><br />Straight static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    myTest::test();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />call_user_func static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func(array('myTest', 'test'));
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />call_user_func_array static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func_array(array('myTest', 'test'), null);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />eval static method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    eval('myTest::test();');
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br /><br />Straight method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    $o->testMe();
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />call_user_func method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func(array($o, 'testMe'));
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />call_user_func_array method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    call_user_func_array(array($o, 'testMe'), null);
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";

echo '<br />eval method call: ';
$start = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    eval('$o->testMe();');
}
$end = microtime(true);
$elapsed = $end - $start;
echo $elapsed, ' secs', "\n";
dww’s picture

Thanks for the benchmark info oriol_e9g. However, this isn't a performance sensitive code path. The latency of connecting via SSH or FTP and doing file I/O is going to completely dwarf the eval() vs. call_user_func_array() costs. That said, doesn't hurt to do it the faster way. ;) And I think we all agree that call_user_func_array(), while annoying, is easier to read and understand. So http://drupal.org/files/issues/609772-50.pluggable_filetransfer_classes-... is definitely the one to commit. ;)

ksenzee’s picture

Agreed that the first patch in #50 is the one to commit.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Ok. I think this is a necessary change so we can continue to iterate on the Update Manager in contrib through the D7 lifecycle.

Committed #51a to HEAD. Down with eval(). :P

Randy, this is tagged "API change" because it is, but there's absolutely no reason to announce this. This only affects theoretical contrib modules that couldn't exist before this patch. :P

Status: Fixed » Closed (fixed)

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

dww’s picture

FYI, anyone looking at CVS blame... #977408: Subdirectory in "update-extraction" is not deleted before extraction of other versions was accidentally committed with this issue, since I accidentally included that fix in my patches at #50 and no one noticed. ;)