I've long been of the belief that Drupal's love/hate relationship with Object Oriented programming is kind of an odd situation. Most of the time it's not really a problem, but sometimes we come so close to using OO methodology that it seems like a waste that we're not simply OO. One of the places that really shows up is the Forms API.
The design of the Forms API is exceedingly OO; but we're not using OO at all. I spent a little time thinking about it, and I knocked off an idea of what the current Forms API might look like if it were OO.
For example, defining a form would look something like this:
<?php
class views_ui_form extends drupal_form {
function __construct($view) {
parent::__construct('views_ui_form');
$this->add_widget(array('basic_info'), new drupal_form_fieldset(array(
'title' => t('Basic information'),
'tree' => TRUE,
));
$this->add_widget(array('basic_info', 'name'), new drupal_form_textfield(array(
'title' => t('View name'),
'description' => t('The name of this view'),
'size' => 30,
'max' => 30,
'default_value' => $view->name,
));
$this->add_widget(array('save'), new drupal_form_submit(array(
'value' => t('Save'),
);
}
function validate($widget) {
if ($this->values['basic_info']['name'] == '') {
$this->error(array('basic_info', 'name'), t('Name must not be blank!');
}
}
function submit($widget) {
views_save($this->values);
}
}
?>Advantages:
1) Having each widget as an object makes it easier to document the widgets. Right now, the widgets are surprisingly OO. There exists hook_elements, which is in fact a constructor for widgets. There is a lot of basic functionality that all widgets exhibit, and individual widgets can override that functionality with ease. I won't get into how widgets would be constructed, but you can imagine class drupal_form_fieldset extends drupal_form_widget and applies a constructor and whatever methods are necessary to process and display the widget.
2) Having the various callbacks all grouped together makes it much easier to document the available callbacks and identify them. Having them be methods means less hassle trying to pass $form, $form_values and a slew of other arguments around while processing. We keep the values on the form itself and since as much as possible is a method of drupal_form, we're assured of having all of our data at our fingertips.
3) Ok, I did make one quick modification. Since I was able to eliminate a bunch of arguments from the methods, I had it provide the validate/submit handlers with the widget that triggered the submission. It's a small thing, but I have to tell you, it'd simplify Views a *lot* to have that. Especially if that $widget doesn't need to be named 'op' (and honestly, it shouldn't need to be).
So, if I've got this kind of a form, how do I actually display it? That turns out to be very much the same way that it currently works in Drupal 5:
<?php
$view = views_load_view($name);
$form = new views_ui_form($view);
$form->process();
$output = $form->render();
return $output;
?>Now, this is a place we want to do some work because the more complicated Drupal UI gets, the more we need to do things like chain forms, have self-modifying forms or multi-step forms. I don't want to get into that here, but note how it may be possible to cache the form instead of creating a new one, and adding some methods to do some of the self modification. There could be some very interesting stuff here.
This whole thing does present one problem: form_alter -- I still need to be able to alter my form, add new submit/validate/process/prerender/after_build/etc event methods. Well, that's a little harder, but it turns out it's not really that hard.
<?php
class views_bonus_views_ui_form extends drupal_form_extension {
class validate($form) {
if ($form->values['basic_info']['name'] == 'foobar') {
$form->error(array('basic_info', 'name'), t('The name "foobar" is reserved!');
}
}
}
function views_form_alter_views_ui_form($form) {
$form->add_widget(array('basic_info', 'foobar'), new drupal_form_textfield(array(
'title' => t('View foobar'),
'description' => t('Enter the foobar value'),
'size' => 30,
'max' => 30,
'default_value' => $view->name,
));
$form->add_extension(new views_bonus_views_ui_form);
}
?>I realize that converting forms API to something like this will probably never happen. For one, it'd be a tremendous amount of work. And for two, I'm not sure there are that many people on the project who'd even be comfortable doing what would likely be some very serious OO work. And for three, there isn't any OO in Drupal and there are a few people on the project who like to keep it that way. I find that unfortunate, because I hate keeping a toolbox closed just because it's metric and I like my standard tools. Especially when the metric wrenches turn out to be a better fit on the nuts and bolts.

I would support you in this
I also think you are wrong that Drupal devels are lacking in OO experience. Take as the first case in point, Mr. Dries Buytaert himself, who knows OO paradigms as he knows the back of his own hand.
The next thing you'll want to address is the seminal "Why Drupal isn't OO" argument, made long ago, in a world where PHP5 didn't exist.
Bad arguments
The second point there is wrong:
PHP 5 has an __autoload function which it calls if it cannot find a class. This give you the opportunity, or example, to include the corresponding class file.
I'll have to think about the other objections.
Thanks
Thanks for this great writeup and for taking a stab at this whole topic.
I did some similar investigations when playing around with adrians FAPI3-code (iow that some things would be simpler when using OO).
Adrians FAPI3-code is already great without any OO, but I tend to get lost in function names sometimes with drupal already, and the FAPI3 concept would introduce another bunch of important drupal_foo functions. If some of them were methods and we'd have something like an active record class, things would stay simple IMO (KISS).
So, I think with drupal moving forward (and, PHP moving forward), I think you're quite right to sometimes take a look at actual OO.
When considering forms, a first step could be even simpler and closer to what we have now. We could still store the actual form in an array structure as we do now, but wrap the associated functions together with the form array in a class:
<?php
class views_ui_form extends drupal_form {
function __construct($view) {
$this->id = 'views_ui_form';
$this->form = array()
$this->form['basic_info']['#type'] = 'fieldset';
$this->form['basic_info']['#title'] = t('Basic information');
$this->form['basic_info']['#tree'] = TRUE;
$this->form['basic_info']['name']['#type'] = 'textfield';
$this->form['basic_info']['name']['#title'] = t('View name');
$this->form['basic_info']['name']['#default_value'] = $view->name;
and so forth..
}
function validate($widget) {
if ($this->form['basic_info']['name']['#value'] == '') {
$this->error(array('basic_info', 'name'), t('Name must not be blank!');
}
}
function save() {
...
}
}
class drupal_form() {
function __construct() {
$this->id = get_class($this);
...
}
function alter() {
$this->form = drupal_alter('form', $this->form, $this->id);
}
function render() {
drupal_render($this->form) ...
}
function process() {
drupal_process_form would live here
}
}
?>
... just some ideas.
How timely
It's interesting that you should mention that, Earl. Just the other day I was running through my head ways to make Drupal's node handling really-OO and greatly simplify the code and SQL (read: performance boost). Of course, all of my thoughts used PHP 5-specific object features (since I agree, PHP 5's OO is night and day compared to PHP 4). I actually thought that with form_alter, nodes would be easier to do than forms, especially since as of Drupal 6 we could, potentially, start requiring the use of foreign keys to hint to the code.
Speaking of PHP 5, __autoload() is actually very bad for performance under an opcode cache, at least according to people working for Zend.
The main stumbling block to class-based syntax in Drupal, I think, is the way that Drupal does everything "sideways". FAPI is the perfect example. All of those alters are, potentially, much easier when you have an arbitrarily-deep array you can reference. The flipside is that you get all of the guts exposed to you, so it makes a huge difference whether a checkboxs set you want to change the defaults on is inside a fieldset or not. The various _alter hooks (and cousins like _user, _nodeapi, etc.) would be the trickiest part.
Still, highly fun stuff. I wonder if the PHP 5 database object entity stuff I've been working on would port back to PHP 4 and nodes in any sane way...
Post new comment