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
of MVC pattern, where most of processing (except of form validation and database interactions) is moved away
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.
- 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.
- 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.
- Move all form-specific processing (all items listed in previous section) into a form definition.
- 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:
- set form name from kDBEventHandler::dbBuild method (default will be empty form)
- when form name is empty, then get validation options from Fields array
- 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.