Introduce Forms concept to move form pre-processing from model into view

15 views
Skip to first unread message

Alexander Obuhovich

unread,
Apr 15, 2011, 2:04:23 PM4/15/11
to In-Portal Development
You of course know MVC (Model-View-Controller) design pattern. Almost all of todays frameworks uses it and so do In-Portal. However In-Portal uses a slight modified version of MVC pattern, where most of processing (except of form validation and database interactions) is moved away from model into a controller.


In-Portal also has unit configs, which are PHP files, used to tune up standard MVC classes for a specific purpose. E.g. to work with users or to work with categories.

Model has a list of database table fields (retrieved from unit config), which it uses to detect if a given field value from a model should be saved to database or not.

However model also retrieves from unit config such parameters for each database field (see kDBItem::ValidateFIeld method http://source.in-portal.org/in-portal/releases/5.1.2/core/kernel/db/dbitem.php?view=markup):
  • field required status (yes/no)
  • field default value
  • field unique status (should this field value in one table record be unique across all table)
  • possible min/max value in the field
  • min/max length of field's value
As you can see these additional rules for each field are pretty much hard-coded and are not dependent of the place, where this model is used.

Let's take user registration form for example:
A website has 2 registration forms where some fields are required on 1st form and some fields are required on 2nd form. So basically during registration on the 1st form user should not be forced to enter fields, that are required on the 2nd form.
To implement such functionality now we have to make all fields non-required by default. Then before validation happens (kDBEventHandler::OnBeforeValidate method) we add if statements based on page url being visited (and if url of that page changes, then all validation goes away).


I'll propose a solution below, that won't require any template-based if statements and code will look more professional.
  1. I think, that we need to introduce a form term. In most simple case one that form will be equivalent to a HTML form that is submitted from a website.
  2. Move all form-specific processing (all items listed in previous section) into a form definition.
  3. Submit a form name to use along with a form fields from the site.
Since the validation now will be performed according to a given form name, so how do we protect website from faking form name to skip whole validation.
My answer is: Easy

Here is how:

Wrap the events, we use to process form data into another events, that will check for a specific form name and then call usual event. For example:
  • event OnCreate creates a record in database based on form data
  • for user registration we'll create event called OnRegister, that will check that form name is "FrontUserRegisration" and if so call OnCreate event from within it
  • then we give execution permissions on OnRegister event to anybody and remove all permissions from OnCreate event, so nobody on Front-end could call it directly

Now here is a real transformation example of "agent" unit config:

=== BEFORE ===
'Fields' => Array (
'AgentId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),

'AgentName' => Array (
'type' => 'string', 'max_len' => 255,
'unique' => Array (),
'required' => 1, 'not_null' => 1, 'default' => ''
),

'AgentType' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_User', 2 => 'la_opt_System'), 'use_phrases' => 1,
'required' => 1, 'not_null' => 1, 'default' => 1
),
'Status' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Active', 0 => 'la_opt_Disabled'), 'use_phrases' => 1,
'required' => 1, 'not_null' => 1, 'default' => 1
),
'Event' => Array (
'type' => 'string', 'max_len' => 255,
'formatter' => 'kFormatter', 'regexp' => '/^[a-z-]*[.]{0,1}[a-z-]*:On[A-Za-z0-9]*$/',
'required' => 1, 'not_null' => 1, 'default' => ''
),
'RunInterval' => Array ('type' => 'int', 'required' => 1, 'not_null' => 1, 'default' => 0),
'RunMode' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (reBEFORE => 'la_opt_Before', reAFTER => 'la_opt_After'), 'use_phrases' => 1,
'required' => 1, 'not_null' => 1, 'default' => 2
),
'LastRunOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL),

'LastRunStatus' => Array (
'type' => 'int',
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Success', 0 => 'la_opt_Failed', 2 => 'la_opt_Running'), 'use_phrases' => 1,
'not_null' => 1, 'default' => 1
),

'NextRunOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'required' => 1, 'default' => '#NOW#'),
'RunTime' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
),

=== AFTER ===
'Forms' => Array (
'AdminForm' => Array (
'AgentId' => Array ('default' => 0),
'AgentName' => Array (
'max_len' => 255, 'unique' => Array (), 'required' => 1, 'default' => ''
),
'AgentType' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_User', 2 => 'la_opt_System'), 'use_phrases' => 1,
'required' => 1, 'default' => 1
),
'Status' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Active', 0 => 'la_opt_Disabled'), 'use_phrases' => 1,
'required' => 1, 'default' => 1
),
'Event' => Array (
'max_len' => 255,
'formatter' => 'kFormatter', 'regexp' => '/^[a-z-]*[.]{0,1}[a-z-]*:On[A-Za-z0-9]*$/',
'required' => 1, 'default' => ''
),
'RunInterval' => Array ('required' => 1, 'default' => 0),
'RunMode' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (reBEFORE => 'la_opt_Before', reAFTER => 'la_opt_After'), 'use_phrases' => 1,
'required' => 1, 'default' => 2
),
'LastRunOn' => Array ('formatter' => 'kDateFormatter', 'default' => NULL),
'LastRunStatus' => Array (
'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_Success', 0 => 'la_opt_Failed', 2 => 'la_opt_Running'), 'use_phrases' => 1,
'default' => 1
),
'NextRunOn' => Array ('formatter' => 'kDateFormatter', 'required' => 1, 'default' => '#NOW#'),
'RunTime' => Array ('default' => 0),
),
),

'Fields' => Array (
'AgentId' => Array ('type' => 'int', 'not_null' => 1),
'AgentName' => Array ('type' => 'string', 'not_null' => 1),
'AgentType' => Array ('type' => 'int', 'not_null' => 1),
'Status' => Array ('type' => 'int', 'not_null' => 1),
'Event' => Array ('type' => 'string', 'not_null' => 1),
'RunInterval' => Array ('type' => 'int', 'not_null' => 1),
'RunMode' => Array ('type' => 'int', 'not_null' => 1),
'LastRunOn' => Array ('type' => 'int'),
'LastRunStatus' => Array ('type' => 'int', 'not_null' => 1),
'NextRunOn' => Array ('type' => 'int'),
'RunTime' => Array ('type' => 'int', 'not_null' => 1),
),


As you can clearly see only database-specific properties are now left in Fields array. There also a form called "AdminForm" defined, which now has all of possible validation/formatting rules and field default values.


As a next step we can define default values based on field type somewhere, so you don't even need to set 'default' => ... , like I did with "RunTime" field. Then if no form-specific field settings, then you can even skip if in that form definition.
On the other hand we can actually set only fields listed in the form (in unit config) to that model no matter how much fields are actually submitted from a site.

Also both real (from database) and virtual fields can be listed in "Forms" array.


To keep backwards compatibility with unit configs, that were created before I propose this:

  1. set form name from kDBEventHandler::dbBuild method (default will be empty form)
  2. when form name is empty, then get validation options from Fields array
  3. when form name is set, then get validation options from appropriate form
So if you want to use forms functionality, then transform Fields array in a proper way. No mixed ways will be supported (half validation in Fields array and other half in Forms array).



We'll add some tag, like FieldModificator, which will set form name in request (when no submit is made) and will create a hidden field to pass that form name to appropriate event.


Phil -- wbtc.fr --

unread,
Apr 15, 2011, 4:13:38 PM4/15/11
to in-por...@googlegroups.com
sounds very interesting!

Does our actual "Website & Content > Forms" section already work a bit like this?

2011/4/15 Alexander Obuhovich <aik....@gmail.com>

Alexander Obuhovich

unread,
Apr 16, 2011, 4:06:04 AM4/16/11
to in-por...@googlegroups.com
You see, it's not always what is named the same will be actually the same in In-Portal :)

Phil -- wbtc.fr --

unread,
Apr 16, 2011, 4:50:20 AM4/16/11
to in-por...@googlegroups.com
but what you describe on user side seems exactly the same: abeing able to specify which fields are required or not, and may actual "Forms" section could benefit of your new idea, if it's better than the actual code... or I misunderstood something?

2011/4/16 Alexander Obuhovich <aik....@gmail.com>

Alexander Obuhovich

unread,
Apr 16, 2011, 5:00:11 AM4/16/11
to in-por...@googlegroups.com
Forms section you mentioned under "Website & Content -> Forms" allows to create various "Contact Us"-like forms to submit data into a single "Forms Submissions" section. All form data is stored the same way in same table there.


On the other hand I propose to more fine-grained way to control other forms, used for data input into other tables, like:
  • link suggest/modify
  • article suggest/modify
  • user register/user profile editing
and so on.

In time, maybe, we can create a section, that will be alike Website & Content -> Forms where administrator can name a Name field for all links non-required very easy. Also maybe he can add new fields to a forms too, like we do with "Custom Fields" now. But this will-be site-wide control, not a multiple "Custom Fields" sections all around the admin console.

You know we have a lot of forms in admin console and then with this new section it will be possible to control them too.

Dmitry A.

unread,
Apr 17, 2011, 10:12:23 PM4/17/11
to in-por...@googlegroups.com
Hi Alex,


Carefully read through your ideas. Very interesting indeed!

I do support this. The only concern I am having is that we are going to have MORE new Events in order to support this, but I guess it just comes with the flexibility we are trying to achieve.


DA

Alexander Obuhovich

unread,
Apr 18, 2011, 3:07:39 AM4/18/11
to in-por...@googlegroups.com
That new events will basically consist of 3-4 lines of code at most.

Now we use OnCreate/OnUpdate all around which is kind of abstract, but OnRegister will surely tell user, who will edit templates what it actually does.

Dmitry A.

unread,
Apr 18, 2011, 11:31:14 PM4/18/11
to in-por...@googlegroups.com
Yes, I follow you on this Alex! Thanks for clarification!

Does anyone else has anything to add here - Ideas, opinions?

Erik, Nikita, Gleb please don't hesitate to share you opinion here since this kind of change will directly reflect on your work too.


Cheers!

DA
Reply all
Reply to author
Forward
0 new messages