Wow, what a thread!
I read it all, but I didn't get all points along nor I remembered most of it, so excuse me if I say something already said, or too irrelevant.
My form generation is based on the scaffolding code, so I'll have to explain bits of my entire framework here.
As I already mentioned in the
scaffolding thread, I want to generate everything. Since that post the system has evolved a bit more, and since having written several small apps it seems okay. I haven't shipped a commercial project yet, so it's not fire tested
Here's a few words for my "framework":
I don't do MVC, something similar (or maybe it's the same and I don't get what MVC is) but not quite. I have models and controllers and the view is either a fiew lines of in-controller code or view/controller class + templates. Forms and all user input actually is handled by "widgets" - a mix of subcontrollers and subviews in MVC terminology (or I think so - does it make sense to you MVC guys?) which are easily "married" to templates and models. The widgets can act on user input and draw themselves. Simplest widget - gets a certain $_REQUEST value and renders as text input with the value set as default.
My design philosophy is to make the library in a way that allows maximally laconical code. Also there are some old pieces of code in it that do things a bit differently, and I happily mix both styles. For example my scaffolding code generates forms through templates, but my "db table" widget (older code) also generates forms, but directly in PHP. There are lots of things I haven't implemented at all - like client-side checks, but I know that the system is easily modifiable to do so. Some things are not translated, as the my translation system is still in tryouts
I have setup a quick
online demo. May contain bugs, as the scaffolding is still in infancy. There are some XSS possibilities in the dbtable. No SQL injection should be possible. The array at the bottom of the page is the SQL query log. You can reset the tables from the SQL submenus, it's intentional (VERY useful for when you change the field descriptions). And yeah, that's PHP4, hence the &-s.
So, here's some code:
Code: Select all
$g_pSql->m_pFields =& new CTableSql('Fields', 'Fields'); //sql table, class name
$g_pSql->m_pLinked =& new CTableSql('Linked', 'Linked');
$g_pSql->m_pFields->AddColumn('m_nId', 'primary', true);
$g_pSql->m_pFields->AddColumn('m_nInt', 'int', false);
$g_pSql->m_pFields->AddColumn('m_sString5', 'string', false, 5);
$g_pSql->m_pFields->AddColumn('m_sDate', 'date', false);
$g_pSql->m_pLinked->AddColumn('m_nId', 'primary', true);
$g_pSql->m_pLinked->AddColumn('m_nOtherId', 'int', false);
$g_pSql->m_pLinked->AddOneToOne('m_sOtherString', $g_pSql->m_pFields, 'm_sString5', Array('m_nOtherId'=>'m_nId'));
Wanna see some more I've written for this demo? There is none.
Everything you see in the demo is generated from these 9 lines of code.
As you see (apart from the fact that I use Hungarian notation

) these define two tables with some fields, and the second table is in a one to one relationship with the first. Actually I haven't renamed my functions since I've written the scaffolding code (it started as an SQL generator), but these lines do more than SQL. In fact they do:
Code generation for models, controllers, and controller/views.
Templates for displaying, editting, updating and printing.
SQL generation, both for runtime queries and .sql files if you want to setup the databases manually
The menu items are added automatically, and as you can see one of them also shows the generated SQL and can be used when developing to update the columns.
The "List" submenus show my other way of doing things - the "db table" widget. It does CRUD, searching, sorting, pagination (visible only for many items) and can do custom actions - by default "custom edit" and "print" which are currently labeled in Bulgarian. The idea is to use this widgets for admin panels and the template-based stuff for the fancy shmancy forms the users would see. The templates I generate are ugly, but functional - they contain placeholders for all the fields and error messages. One can make the template beautiful, or take an already beautiful html and paste the variables from the generated template files.
Here's a snippet from the constructor of the "custom edit" view/controller object (actually a widget too), demonstrating a widget in action:
Code: Select all
$this->m_pFields =& new Fields( GetInt( $_REQUEST['id'] , NULL) ); //create the model object
$this->m_bEditOrAdd = ($this->m_pFields->m_nId != NULL); //are we editting it, or adding a new one?
...
//here's how the int field is handled:
$this->m_nInt =& new CInputTextWidget( $this->GetVarName('m_nInt') , $this->m_pFields->m_nInt);
$this->m_pFields->m_nInt = $this->m_nInt->Value();
First the widget is created, first param is name (used as index in $_REQUEST), second is default value. The default value is the one that the m_pFields model had when it was filled from the database. The second line gets the actual value from the widget and updates the model. Below that I check if the user is submitting a form - if so I call $this->m_pFields->Update() (or ->Create() ), otherwise nothing. This was the controller part of the object. The view part comes in the form of a ToString() method, which just inits a template with the widget objects.
Here's the print template (the simplest one):
Code: Select all
<hr>m_nId = [[m_nId]]<br>
m_nInt = [[m_nInt]]<br>
m_sString5 = [[m_sString5]]<br>
m_sDate = [[m_sDate]]<br>
<hr>
Here's the relevant model functions:
Code: Select all
function Update() {
if ( !$this->CanUpdate() )
return FALSE;
$pSql =& Fields::Sql();
DBQuery( $pSql->GetUpdate('all', $this->m_nId, $this) );
}
function CanUpdate() {
$pSql =& Fields::Sql();
foreach ($pSql->m_aColumns as $sName=>$pType) {
if ( !$pType->IsValidInput($this->$sName, false) )
return FALSE;
}
return TRUE;
}
And here's the descriptor class responsible for handling the "int" type, so you can see the IsValidInput check:
Code: Select all
class IntDescriptor extends BaseDescriptor {
function IntDescriptor($sType, $default) {
BaseDescriptor::BaseDescriptor($sType, $default); //true = always blank when inserting!
}
function IsValidInput($sValue, $bInsert) {
if ($sValue=='')
return ($this->m_Default !== false); //if the default value is false, do not allow empty values
$sValue = $this->AdjustInput($sValue);
return ctype_digit("$sValue");
}
function AdjustInput($value, $bForSearch = FALSE) {
return intval($value);
}
function ToDefinitionSql($sColumn) {
return "`$sColumn` INT(11) NOT NULL";
}
}
To summarize, I seem to belong to the crowd that does custom, template-based layouts, but generates the form fields. The "rules and filters" arborint talks about are still in a crude form, but they stem from the description code I pasted. I haven't thought much for a more custom rules - but everything should be possible by writing a descriptor class. Here's a more complex example that handles ip-s:
Code: Select all
class IpDescriptor extends StringDescriptor {
...
function IsValidInput($sValue, $bInsert) {
$a = explode('.', $sValue);
if (count($a) != 4) return FALSE;
for ($i=0; $i<4; ++$i) {
if (!ctype_digit($a[$i]) || strlen($a[$i])>3) return FALSE;
if ((int)$a[$i] > 255) return FALSE;
}
return TRUE;
}
...
};
Phiew, that's some LONG post for so short a piece of code

I'd like to compare how you MVC guys would handle these and how many LOC it would take with your framework of choice.