Form
class is used to create forms and to aslo handle all form data requests sent through the server.
This class along with the Request
class, are both able to set form validations, restrictions and saving of
validated request data into the database. An introduction into the generation of forms through Form
class
using predefined methods was already discussed in Form helper class.
Here, we will focus more on form validations in relation with the Request
class.
use spoova/mi/core/class/Request; $Request = new Request;
$Request->data()
method. This method ensures
that only request data forwarded with a CSRF request token are obtainable. If a request data is fowarded without a request
token, an empty array will be returned. When working within the template files, the @csrf
template directive
is used to add CSRF tokens input fields to forms. The sample below reflects the syntax of obtaining request data:
$Request = new Request;
$Formdata = $Request->data(); // returns the request data sent
$Request->data()
is expected to return the form data sent from request by default,
if the @csrf
directive is not supplied, the data returned will be an empty array. In order
to ensure that all data supplied are accepted, each form must have a @csrf
directive attached to it.
strict()
method. If a token is rejected, rather than for data()
method to return
an empty array, an error page will be displayed instead based on the type of error that occured.
$Request = new Request; $Request->strict(); //set request data as strict type $Formdata = $Request->data(); //display error page if data was sent without valid CSRF
$Request->data()
is expected to return form data sent from request or an empty array if
no csrf token is set, if the strict method is applied, the request page will return a csrf default error page preventing any further action.
has()
method allows the checking of data forwarded before it is returned.
This is useful in cases when we may want to detect the type of button clicked. An example is shown below:
$Request = new Request; if($Request->has('submit')){ $Request->strict(); $Formdata = $Request->data(); }
strict()
method comes after the has()
function.
This is because setting the strict type affects the has()
method too and prevents it from checking
any data if the token is not valid.
expires()
method. This tells the request the
number of seconds required in order for a data to be sent. If a request data is not sent within the
required number of seconds, the data sent is not accepted hence, it returns an empty array or displays error message. It should
be noted that even if a csrf
token has expired leading to an empty data, the
has()
method will still allow the checking of data forwarded as long as the strict()
method is not applied before it.
However, unlike the strict()
method, the expires()
method alone
has no effect on the has()
method if used before it unless a strict()
method is declared along with it.
The example below is the best way to declare the strict type along with the expires()
method:
$Request = new Request; if($Request->has('submit')){ $Request->strict()->expires(5); $Formdata = $Request->data(); }
Model
class. The Model
class
not only enables us to authenticate form data but it also allows us to save the data into the database. Other features of this class
include input-column mapping which allows the form input name to properly select its relative database column field where the data is expected to
be inserted. Consider the following Model class structure:
<?php namespace spoova\mi\windows\Models; use spoova\mi\core\classes\Model; class Signup extends Model { protected $firstname; protected $lastname; protected $usermail; protected $password; public function __construct(){ //your code here... } /** * Validation rules * * @return array */ public function rules(): array { return [ 'firstname' => [SELF::RULE_REQUIRED, SELF::RULE_MIN => 2, SELF::RULE_MAX => 20], 'lastname' => [SELF::RULE_REQUIRED, SELF::RULE_MIN => 2, SELF::RULE_MAX => 20], 'usermail' => [SELF::RULE_REQUIRED, SELF::RULE_EMAIL] 'password' => [SELF::RULE_REQUIRED, SELF::RULE_MIN => 2] ]; } /** * Determines if a form authentication is completed * * @return bool */ public static function isAuthenticated(): bool { return true; //some validation test code is expected here. } /** * set table name where data is inserted * * @return string */ public static function tablename(): string { return 'users'; //default table name } /** * input field names mapped with relative database column name * * @return string */ public static function mapform(): array { return [ 'usermail' => 'email', 'password' => 'pass' ]; } }
The code above simulates a Signup model format that validates a firstname, lastname, usermail and password form field. When a form request data is expected to be authenticated, each request data attribute expected to be authenticated must be added as a property into the Model defined. This makes it easier for the Model class to pull out only needed data from the request data. The following list explains each method and their functions
rules()
method defines the authentication needed for each field. The Model class ensures that only
defined authentication rules are applied on the relative property defined.
mapform()
maps the input field names with their respective field names. For example, in the Model above,
the request data attribute name of "usermail" will be mapped to "email" field in database. This means that if the input field
name sent in request is "usermail", when inserting data, the data will be inserted into the "email" field in the database table.
This makes it easier to protect database names.
tableName()
method is used to set a database table name where authenticated data is expected to be inserted.
isAuthenticated()
method by default only returns true if all authentication rules applied to a form request are successfully passed and no
error was found. Redefining this method above in Signup
provides an enviroment to apply more custom rules we require our form data to match.
For example, if all authentication rules applied was met by a request data, then the root Model::isAuthenticated()
method will return true which means that
Signup::isAuthenticated()
will also return true by default. However, if more
tests are done within the child Signup::isAuthenticated()
above and the test fails, then Signup::isAuthenticated()
will return false.
Note that in Form
class, when Form::isValidated()
is called, it automatically calls the Signup::isAuthenticated()
method.
This process is explained below
Form
class is used for further processing and submission request data after csrf validation. This class performs its internal
validation using the instance of a Model class. Once the Model class is validated, then data can be submitted into the database table defined.
The sample of this is shown below:
<?php
use spoova\mi\core\classes\Request;
use Form;
$Request = new Request;
if($Request->has('submit')){
$Request->strict();
$Request->expires(30);
Form::model(new Signup);
Form::loadData($Request->data());
(Form::isValidated() && Form::isSaved());
}
$errors = Form::errors($inputErrors);
$Request->has('submit')
: This checks if the request data forwarded has a "submit" key$Request->strict()
: This ensures that an error page is shown if csrf token is rejected$Request->expires(30)
: This ensures the csrf token generated can only be valid for 30 secondForm::model(new Signup)
: Sets up a model class used for form data authenticationForm::loadData($Request->data())
: Sets data to validated by the form and the supplied modelForm::isValidated()
: By default, this calls the Signup::isAuthenticated()
method to check if data is validForm::isSaved()
: This method saves data into the database.Form::errors()
: This returns all errors encountered during data validation including those related with csrf token. When a variable is supplied, only errors relating to
form inputs will be saved into the variable.
The Signup
model defines the database table and columns the validated data is inserted in the database. In the above,
the request data returned by $Request->data()
is loaded directly into
the Form
class using Form::loadData()
method. The Form::isValidated()
will validate each property using their respective
keys defined within the model's rule()
method and return true
by default. If more custom tests are
done within the supplied model's isAuthenticated()
method, this will further determine if the Form::isAuthenticated()
will return a true
or
false
value. Lastly, the Form::isSaved()
will try to save the data the database table "users" defined within the tableName()
method of Signup
model.
When an error occurs in the execution of these process, we can obtain the all errors using the Form::errors()
method which allows us to fetch all required errors
depending on the stage where the error occurred. This method also makes it easier to fetch only errors that occur in input validation by supplying a variable e.g $inputErrors
, which acts as reference
for errors that occurs for a property when its specified validation rule is not passed.
To obtain the entire errors, the Form::errors()
method returns the entire error that occured which may be from database connection, operation or input validation.
Lastly, since the Form::isAuthenticated()
can naturally call the model's isAuthenticated()
method, we can easily add the Form::isSaved()
into the
current model's isAuthenticated()
method. Hence, The code line
(Form::isValidated() && Form::isSaved())
can both be replaced with a single Form::isAuthenticated()
. The Signup
model's isAuthenticated()
method
will resemble the format below:
/** * Determines if a form authentication is completed * * @return bool */ public static function isAuthenticated(): bool { return Form::isSaved(); //save and return true of data is saved }
<?php use spoova\mi\core\classes\Request; use Form; $Request = new Request; if($Request->has('submit')){ $Request->strict(); $Request->expires(30); Form::model(new Signup); Form::loadData($Request->data()); if(Form::isAuthenticated()) { if(Form::errors()) var_dump(Form::errors); } } $errors = Form::errors($inputErrors);
Form
class after the
Form::loadData()
has been used to load a request data.
Form::datakey($key)
where $key: a key in the request form data
Form::datakey()
method. as it returns the value of a
key in database but it can also be made to throw an error if the specified key does not exist.
Form::dataval($key, $strict)
where:
$key : a key in the request form data
$strict : when set as true throws an error if key does not exist.
Form::loadData()
method. It takes
no argument and returns an array.
Form::loadData(['name' => 'foo']);
prnt_r( Form::loadedData() ); // ['name' => 'foo']
Request::has()
method. The only difference is that the
Form::haskey()
method check the key from the loaded form data.
Form::loadData(['name' => 'foo']);
vdump( Form::haskey('name') ); // true
Form::onpost(function(){ echo "request is post"; });
Form::onpost('login', function(){ echo "request is post"; });
strict
before this method is used.
Form::register()
method. The syntax of this method is
displayed below:
Form::register($userid_key, $callback);
where:
$userid_key : user id key in request data
$callback : callback function
Form::register()
method is used, the first argument $userid_key
that is expected
to be supplied is usually a key in the form request data that corresponds to the configured database USER_ID_FIELDNAME.
For example, if the database table column where the userid is stored is email
, then the
$userid_key
should also be email and it should exist as a key in the form request data. Since the mapform()
method can hide database field names, it does not necessarily mean that the form input field should have the "email" field, we just have to
specify the input field which is a placeholder for the real userid column in the user database table. Assuming we have a signup form as shown below
<form method="post">
@csrf <!--apply csrf-->
<div> @error(':mod', 'signup-failed') </div>
<div> @error('firstname') </div>
<div> <input name="firstname"> <br> </div>
<div> @error('lastname') </div>
<div> <input name="lastname"> <br> </div>
<div> @error('usermail') </div>
<div> <input type="email" name="usermail"> <br> </div>
<div> @error('userpass') </div>
<div> <input type="password" name="userpass"> <br> </div>
<div> <button name="signup">button<button> </div>
</form>
usermail
field corresponds to the database column "email"
where the
collected input will be inserted in the predefined user database table, we can continue to create our form authentication
model as shown below
<?php namespace spoova\mi\windows\Models; class SignupModel { function rules(): array { //set form validation rules return [ 'firstname' => [SELF::RULE_REQUIRED, SELF::RULE_MIN => 2], 'lastname' => [SELF::RULE_REQUIRED, SELF::RULE_MIN => 2], 'usermail' => [SELF::RULE_REQUIRED, SELF::RULE_EMAIL], 'userpass' => [SELF::RULE_REQUIRED, SELF::RULE_MIN => 8], ]; } static function mapform() : array { return [ 'usermail' => 'email' //set the usermail field in form data back to email ]; } static function tablename() { return "users"; } static function isAuthenticated() : bool { return self::isSaved(); //try to save data into database } }
usermail
field was internally renamed back to email
which is
expected to be where the data is inserted in the database table "users"
. Since we intend to use the "usermail"
field's
value as the userid
, we can set up the form authentication frame as shown below:
<?php namespace spoova\mi\windows\Frames\AccessFrame; class AccessFrame extends Windows{ static function onSubmit($Request Request) { if($Request->isPost()) { Form::model(new SignupModel); if(Form::haskey('signup')) { Form::loadData($Request->data()); Form::register('usermail', function($data){ if($data !== false) { // form saved successfully User::login($data); }else{ // set error that something is wrong ... Form::setError('signup-failed', 'signup failed due to some error'); } }) } } } }
AccessFrame
will be used as a middleware to validate the form data. In the above,
the Form::haskey('signup')
checks that a the request data has a signup key equivalent to the signup button.
The Form::loadData()
is used to load the request form data into the Form
class for authentication.
Lastly, the Form::register
assumes that the "usermail" key in request data contains the value intended to be used for
userid. The callback function will return the userid
data if the form is successfully authenticated and saved. This array
data can then be used to login (i.e. create the user's new session). In order to apply this middleware, we can use any shutter method
in our route but we need to declare a session first which can be used for logging in. Let's create a new root session frame below:
<?php namespace spoova\mi\windows\Frames; use Window; class SessionFrame extends Window { static function frame() { new Session('user', 'usercookie'); } }
<?php
namespace spoova\mi\windows\sessions;
use spoova\mi\windows\Frames\SessionFrame;
class GuestSession extends SessionFrame {
static function frame() {
Session::auto('login', 'home'); redirect after the session becomes active
}
}
GuestSession
above will
be used for all guest pages that requires the use of session auto redirection. Now, we can proceed to create the
Signup
route as shown below:
<?php namespace spoova\mi\windows\Routes; use spoova\mi\windows\sessions; class Signup extends GuestSession { function __construct() { self::call($this, [ window() => 'root' ]); } function root() { Accessframe::onSubmit(); // apply middleware self::load('signup', fn() => compile() ); // load template } }
rules()
method. Once these rules are defined within a Model, the model uses such rules
to validate form inputs based on their attributes. The following rules can be applied on form inputs:
RULE_REQUIRED // Defines an attribute that must be filled RULE_NOSPACE // Defines an attribute that cannot contain spaces RULE_TEXT // Defines an attribute that can only contain alphabets RULE_MIN // Sets a minimum length of characters accepted on a form input RULE_MAX // Sets a maximun length of characters accepted on a form input RULE_UNIQUE // Sets an attribute that must not exist more than once in the database RULE_MATCHES // Sets an attribute that must match another attribute and must not be empty RULE_ISOLATE // Sets an attribute that cannot be closely matched by any other attribute's value RULE_EMAIL // Defines an attribute and must be of email format RULE_NOT // Defines an attribute that must not be exactly the same as another form attribute(s) value(s) RULE_UNLIKE // Defines an attribute that must not look like another attribute(s) whose names must be set RULE_NUMBER // Defines an attribute that must be numeric RULE_INTEGER // Defines an attribute that must be a valid integer RULE_PHONE // Defines an attribute that must have a phone numer format RULE_URL // Defines an attribute that must have a url address format RULE_RANGE // Defines an attribute that must have its value within a specified range or list only RULE_NOT_CHARS // Defines an attribute that must not have a character exisiting in a list of defined characters that is set
... public function rules(): array { return [ 'field1' => [ SELF::RULE_REQUIRED, // field is required SELF::RULE_NOSPACE, // allow no space character SELF::RULE_TEXT, // allow only alphabets [A-Z] SELF::RULE_MIN => '10', // allow only a minimum of 10 characters SELF::RULE_MAX => '12', // allow only a maximum of 12 characters SELF::RULE_UNIQUE, // value must not exist more than once in database SELF::RULE_EMAIL, //value must resemble email format SELF::RULE_PHONE, //value must resemble phone number format SELF::RULE_NUMBER //value must be a numeric value SELF::RULE_URL //value should be a url format SELF::RULE_MATCH => 'field2', // field1 value must match field2 value SELF::RULE_UNLIKE => ['field3', 'field4'], // field1 must not resemble field3 and field4 SELF::RULE_NOT_CHARS => ['*', ':'], // value must not contain character*
and:
SELF::RULE_RANGE => ['yes', 'no'], // value must be within the range of optionsyes
orno
SELF::RULE_PATTERN => "A-Za-z0-9", // value must match the specified pattern ] ]; }
Form
class are classified
into four forms:
Form::errors()
into an array
key specified as :csrf
which contains only two subkeys title
and info
which contains the type of error and
the description of that error respectively. The title
and info
returned is determined by the kind of error that occured.
Under this subheading, errors are classified as default, invalid and session. When a token is missing, a default error is returned. When a token does not
match an invalid error is returned while the session error only returnes when a token has expired. In order to access the :csrf
errors, an
helper function error()
makes this easy shown below:
<?php error(':csrf', 'title'); // return the csrf last error title error(':csrf', 'info'); // return the csrf last error info
<?php use spoova\mi\core\classes\Request; use Form; use Csrf; $Request = new Request; if($Request->has('submit')){ Csrf::setError('default', 'csrf request failed'); // set custom message Csrf::setError('invalid', 'csrf token mismatched'); // set custom message Csrf::setError('session', 'csrf session expired'); // set custom message Csrf::setError('default', ['title' => 'some title', 'info' => 'some info']);// set custom title and info using array $Request->strict()->expires(5); Form::loadData($Request->data()); (Form::isValidated() && Form::isSaved()); } $errors = Form::errors(); //return all validation errors print_r( $errors[':csrf'] ); // return only csrf last error (both title and info) print_r( error(':csrf') ); // same as above print error(':csrf', 'title'); // return only csrf last error title print error(':csrf', 'info'); // return only csrf last error info
Csrf::setError()
is used to set a custom modified message. It stores its keys and value into a ":mod"
space
where the values can be retrieved later. Once a key and a message value is defined,
we can access the custom message through its key. For example, error(':mod','invalid')
will return a message of "csrf token mismatched".
The second argument of Csrf::setError()
sets a custom error message.
Also, The error()
helper function by default has internal access to Form::errors()
method which usually returns the error encountered
after a form validation fails. Rather than use a variable to obtain the form errors, we can easily use the error()
helper function to pull the error
specific error from the Form::errors()
. The errors accessible by the error()
function are discussed below:
error()
helper function only returns the first error encountered by each attribute, that is
, the topmost error index. An example of this is shown below:
<?php
error('username'); // return first error encountered for a username field
error()
function is the @error()
template directive. When no errors exists for the defined
attribute error()
function just returns empty without throwing any errors.
Form::error()
also allows fetching of these errors using reserved key names such as :dbi
, :dbe
and :dbm
The named keys can be supplied into error()
to obtain their respective values.
<?php error(':dbm'); // something is wrong error(':dbe'); // database error: something is wrong error(':dbi'); // sql error (fully stated according to type of error)
:dbi
is the only key that displays the full information about the type of database error that occurred. Other keys are just a shorthened form of message to keep the message
simple and easy to read. In template engines, the @error()
directive can be used to obtain these errors.
Form::setError()
method. This method
stores the last defined error into an array key ":mod"
. Hence, by calling the error(':mod')
function, we can retrieve the last defined error. Example is shown below: <?php Form::setError('something is wrong'); echo error(':mod'); // something is wrong Form::setError('name', 'foo'); echo error(':mod', 'name'); // foo
error()
function can access a message defined by Form::setError()
.
It is mostly important that the value returned by the error()
function is a string rather than an array for an easy access
through template files.
core/init
file, we configured
the user table along with user id field (or column) name . Assuming a user session id was roughly
set which does not exist in the column "USER_ID_FIELDNAME" of the "USER_TABLE_NAME" defined, such an invalid id will be nullified.
This means that if the user id detected does not exist in the configured column, then even if a session id is faked, because that id does not exist
in the database, such session will automatically be rejected. This behaviour only works under a secured session defined by setting the third argument of Session
class to the value of true
during initialization. Once a session is nullified, a flash message is usually stored with the reserved key ":user-error"
. This means
that if we call the function flash(':user-error')
immediately the session is nullified, we will get a response of
User error: user id mismatch . This makes it easier to understand why a
session was nullified even if a session id was supplied.
formerror()
function. This function was introduced
to separate error encountered where there are multiple forms on a page which may result in conflicting errors most especially when a form may have
an input field with a name that that exists in another form's input field. An example is shown below:
<form method="post" class="form1"> <input name="email" > <input name="password" > <button name="login">Login</button> </form> <form method="post" class="form2"> <input name="firstname" > <input name="lastname" > <input name="email" > <input name="password" > <button name="login">Signup</button> </form>
email
and password
field. This can result
in conflicting error message because the Form::error()
method is not aware of the form whose error is returned.
With can however, specify the field whose error is returned through a new method Form::castError()
. This new method
takes an error key which is used to store the error. Once the error is stored, we can then obtain the error back through the use of
Form::castedError()
method, formerror()
helper function or the @formerror()
template directive.
In order to fetch a particular error, the cast name must first be supplied into as first argument of the formerror()
function
This is shown below:
<?php use spoova\mi\core\classes\Request; use Form; use Csrf; $Request = new Request; if($Request->isPost()){ if($Request->has('submit')){ Form::model(new SomeModel); // add a model to be used for authentication Form::loadData($Request->data()); if(!Form::isValidated()){ if($Request->has('login')){ // if login button was clicked Form::castError('login'); print_r(formerror('login')) // return errors stored in login }else{ Form::castError('signup'); print_r(formerror('signup')) // return errors stored in signup } } } }
Form::error()
has no idea of the form which error is returned,
we took the advantage of the form buttons to cast the errors into their own unique space which can then be accessed through
formerror('login')
or formerror('signup')
, depending on the unique name used for storing the data.
Aside from the form input errors The Form::castError()
stores other special error in a slightly different way through the use of special access keys.
These keys include flash:
, csrf:
, index:
, any:
and mod:
. These keys are explained below with the
assumption that castname
key is used to store each data.
flash:
key is used to obtain flash notices. In most cases, it is used to obtain the :user-error
message returned
when a session id is nullified. For example, the formerror('flash:user-error')
is equivalent to flash(':user-error')
Other flash errors can be obtained by first calling the flash:
access key identifier followed by the key of the flash name. An example is shown
below:
<?php
if(!Res::hasFlash('flash_name')){
Res::setFlash('flash_name', 'flash message');
} else {
echo formerror('castname','flash:flash_name'); // flash message
}
formerror()
function to print the flash message. This is similarly done in template files
through the use of @formerror('castname','flash:flash_name')
.
csrf:
key is used to obtain errors that may occur when a form that does not have csrf token is submitted.
The formerror('castname','csrf:title')
is equivalent to error(':csrf', 'title')
while the formerror('castname','csrf:info')
is similary equivalent to the error(':csrf', 'info')
. We can also use the relative template directive to
fetch the value of such errors.
mod:
key is used to obtain custom errors defined by the Form::setError()
method just like the
error(':mod')
helper function. A sample of this is shown below
<?php
Form::setError('name', 'foo');
echo formerror('castname', ':mod', 'name'); // foo
index:
key is used to obtain the first error encountered in a form validation. This is mostly
dependent on the request data sent. The first error encountered from the the list input fields data is returned after
input authentication. In most cases, this is useful at the topmost part of
a form field in template files.
any:
key is similar to the index:
key. The only difference is that when this key is used,
errors returned will include all errors from flash notices, csrf token validation, custom errors and any input validation error.
The first error encountered is usually returned. This key is also useful at the top of the form field. Any error encountered after
the form is