This problem is completely understood and an interim solution is suggested.
It can be considered a bug in the sense that form_execute_handlers($type, &$form, &$form_state) makes the "unwarranted" (i.e., not OO-friendly) assumption that a validation or submit handler (such as specified by $form['#validate']) can be a function but not a static class method.
It is a "Feature request" in the sense that a request is made for changes that better admit use of object-oriented techniques with Drupal7 forms.
Problem/Motivation
Because form_execute_handlers($type, &$form, &$form_state) uses $function($form, $form_state) on line 1471 (Drupal-7.25 form.inc), where $function is a named handler, it fails on callbacks to static handler methods of classes.
This makes it hard to use an encapsulating FormController with custom static handler methods; compare with the Drupal7 Page controller project.
If the handler function name $function is in the form "$class::$method" the call needs instead to be made by call_user_func_array($function,array($form,$form_state)).
function form_execute_handlers($type, &$form, &$form_state) {
...
foreach ($handlers as $function) {
..
}
else {
$function($form, $form_state);//1471 (Drupal-7.25 form.inc): fails for static class method.
If $function is a static class method that method is not found as a function, and PHP Fatal error: Call to undefined function is issued.
This is made clear at PHP manual: Variable functions and illustrated by the following PHP interactive session:
php > class Form { static public function validate($form,&$form_state) {echo "validate"; } }
php > $form = array();
php > $form_state = array();
php > $method='validate';
php > Form::$method($form,$form_state);
validate
php > $function = 'Form::validate';
php > $function($form,$form_state);
PHP Fatal error: Call to undefined function Form::validate() in php shell code on line 1
Proposed resolution
I offer initially the following verbose solution, known to work for my own FormController static method case:
//$function($form, $form_state);//ORIGINAL: fails for static class method
//2013-01-03 Webel: also handle static class methods:
$sep = '::';
$is_static_method = strpos($function,$sep);
if ($is_static_method) {
call_user_func_array($function,array($form,$form_state));
}
else $function($form, $form_state);
The above enables one to encapsulate form validation and submit handlers, along with many other aspects of a form, in a FormController class, and thus promotes and supports more object-orientation already in Drupal7.
Remaining tasks
(reviews needed, tests to be written or run, documentation to be written, etc.)
I can perform tests as I am currently developing an entire object-oriented controller system for Drupal7, including form controllers (along with other object-oriented examples that integrate with Drupal7). However, I am not familiar with how other people might wish to use, for example, the $form['#validate'] for custom validation handlers, and whether other usages might break my simple test above, but I can't imagine anybody would use a validation function name with double-colon '::' in it unless they are also using a static class method handler.
TODO: Document the new capability to support static class method signatures as names handlers.
API changes
None, other than to document the new capability to support static class method signatures as named handlers.
Comments
Comment #1
webel commentedMoved from a feature request to a bug report.
Updated from Drupal7.23 to Drupal7.25.
Priority updated to Major in awareness of Priority categories, because I consider compatibility with object-oriented techniques (and especially with object-oriented encapsulation of callbacks in OO controllers) a crucial matter, and I require this change for support of an upcoming major project/module release, an OO mapping system for Drupal7. If this is not included in future Drupal7 core, I will have to recommend a hacked version of form.inc to support my OO mapping system.
Comment #2
sammarks15 commentedAs a heads up, the same applies for validation callbacks on machine_name field types. The offending line of code is line 1424 of includes/form.inc on Drupal 7.26.
I would imagine it doesn't work for #validate either, but I haven't tested that.
Comment #3
pganore1@gmail.com commentedThis was a great help, I was having issues with file upload; I had switched the default behavior to File override in settings.php.
This change fixed my issue. More details if requested.
Comment #4
sammarks15 commentedI've updated the version number to target the latest version of Drupal. I've also attached a patch so that Drupal core can be patched to fix this problem using Drush makefiles.
Comment #5
sammarks15 commentedAttaching a new patch - I went ahead and added this same functionality to AJAX callbacks.
Comment #6
sammarks15 commentedOkay, this should be the last time. I ran into some bugs with the last one. I apologize for not testing my fixes completely before reporting them.
Comment #7
webel commented@sammarks15 Thanks for your attention to this, much appreciated, I will test the latest patch, it may take a few days before I get back to you, Webel.
Comment #8
webel commented@sammarks I tested drupal-form_execute_handlers-2166371-6.patch with non-AJAX mode, it works fine.
However this fix alone is of limited use unless #2165895: drupal_retrieve_form() issues Undefined index: warning on line 764 when static class method used as $form_id: hinders use of object-oriented FormController (for which I also provide a fix suggestion for the non-AJAX case) is also patched, and it likewise affects form.inc.
I can prepare a patch, but I don't want to cause confusion with chained/clashing patches, that is I should patch form.inc (and potentially ajax.inc) after applying your patch here. If I prepare a patch for #2165895: drupal_retrieve_form() issues Undefined index: warning on line 764 when static class method used as $form_id: hinders use of object-oriented FormController with the usual patch numbering it could be applied out of order.
Q: Is there a convention for indicating chained patch ordering in the patch naming ? I did not find one.
Please advise.
Comment #9
sammarks15 commentedI'd say apply my patch, modify the code further to your liking and then upload a new version of the patch. From what I've seen, the conventions for naming patches are followed loosely (as long as you have the correct information in the name of the patch somewhere, it seems like it's fine).
Comment #10
webel commentedPlease find attached a Git diff patch against the very latest 7.x (dev beyond just released 7.36), which incorporates the original patch (to form.inc and ajax.inc) for the issue here by @sammarks15, as well as my additional tweaks to form.inc,, addressing both of these very closely related issues:
- #2166371: Drupal 7.36: form.inc 1471: form_execute_handlers($type, &$form, &$form_state): $function($form, $form_state); Does not work for static class method 'Drupal\mymodule\FormContoller::handler'
- #2165895: drupal_retrieve_form() issues Undefined index: warning on line 764 when static class method used as $form_id: hinders use of object-oriented FormController
drupal-oo_static_method_support_for_form_execute_handlers_and_drupal_retrieve_form_and_ajax_form_callback-2166371-2165895.patch
Since the patch covers 2 issues they are included by number in the verbose patch name, and no comment numbers are used, since it does not relate to a single comment.
I would be most grateful for community testing of this - and for inclusion of this in the next Drupal core release 7.37 - so that other developers can more easily explore my unique UML-friendly Object Oriented Example (OOE) tutorial module with a demonstration of a pure OO bridge for Drupal7.
Comment #11
webel commentedComment #12
webel commentedThere are now instructions on how to download and apply this core patch and how to install the related tutorial module OOE = Object Oriented Examples = One Of Each at Download: a Drupal-7.x-dev patch to support the OO form controllers of the OOE tutorial module.
Comment #13
rdeboerThe patch in #10 looks fine and innocent enough to me.
The line
$function_exists = false;is superfluous (and false should be FALSE according to Drupal coding standards).Comment #14
almaudoh commentedThis could be a useful feature for back-porting D8 modules to D7.
Comment #15
webel commented@almaudoh wrote:
You got it ! This single sentence with deep insight might just be the most welcome feedback I have received on Drupal.org ever. Webel.
Comment #16
joachim commentedAs the number of parameters is known, this should be call_user_func(), which while still slow, is not as slow as call_user_func_array() -- see https://mwop.net/blog/121-Benchmarking-dynamic-functionmethod-calls.html for instance.
The else should start a new line.
I think class methods as form IDs is problematic -- how will hook_form_FORM_ID_alter() work?