Fuel Forms & Sessions

Installation

Make sure you have the required plugins installed in NetBeans: PHP, Smarty, FuelPHP. Also make sure that Apache rewrite is enabled (it is by default in XAMPP).

Download the source archive FuelFormsSessions.zip. Extract the archive into the folder which holds your "web projects," which we will assume corresponds to the "/default" URL. Windows users are advised to download the archive and extract it using 7-zip.

Understand exactly what is the "php" command shell command on your system.
  1. Create FuelFormsSessions as a NetBeans Php Project from Existing Sources. On the run configuration step, make the Project URL be:
    http://localhost/default/FuelFormsSessions/public
    
  2. On MAC/Linux, open a command shell, navigate to the FuelFormsSessions directory and run:
    $ php oil r install
    
  3. Double-check the RewriteBase in public/.htaccess. It is currently:
    RewriteBase  /default/FuelFormsSessions/public
    It is meant to work as is if you use the "/default" URL as suggested.
  4. Enable the FuelPhp features for the project:
    • Right-click on the FuelFormsSessions project in the Projects window and select Properties.
    • Locate FuelPhp in Frameworks check the enabled checkbox.
  5. Enable FuelPHP code completion in NetBeans. Right-click on the FuelFormsSessions project and select from the menu:
    FuelPHP ⇾ generate auto-completion file

Check the installation

Double-check the validity of the "/" and "/home/index" URLs, assumed here to be:

Forms

We need to use action URLs that are suitable for use in FuelPhp. In particular, you want your application to be portable, meaning that, for development sake, it should work in any location subject to changing the RewriteBase in the public/.htaccess file

The FuelPhp form-generating function pair is:
Form::open(attrs=[],hidden=[]);  // generates <form ... > tag
Form::close();                   // generates </form> tag
We will, instead, use the Smarty/FuelPhp form extension like this:
{form attrs=[...] hidden=[...]}
 ...
{/form}
See FuelPhp Smarty Extensions and relevant FuelPhp doc page for full details. Both attrs and hidden parameters are optional, allowing the following usage which gives a directly reentrant form (calling the the URL which activated it) using the POST method:
{form}
 ...
{/form}
If the form is not directly reentrant, you can specify an alternative URL through the attrs parameter like this:
{form attrs=['action' => '/controller/action']}
 ...
{/form}
The attrs argument appears in a number of Smarty/FuelPhp functions such asset_img, html_anchor. The usage is as an array map assigning attribute names to their values. For the form extension, the action attribute is transformed into its full FuelPhp URL. The hidden argument is not necessary, and, in my opinion, should be avoided. You can make the generated form use the GET method by setting this as an attribute:
{form attrs=[ ..., 'method' => 'GET' ]}
 ...
{/form}

Form Basics

This section describes the creation of the three basic forms (text, select, checkbox) matching what was created in the Php Forms document. The menu access to these forms are available under the Basic Forms menu. The hyperlinks and controller class are these:

fuel/app/views/links.tpl (excerpt)
<li class='no-action arrow'>
  {html_anchor href='#' text='Basic Forms' attrs=[class => 'no-action']}
  <ul>
    <li>{html_anchor href='/formbasic/text' text='Text Form'}</li>
    <li>{html_anchor href='/formBasic/SELECT' text='Selection Form'}</li>
    <li>{html_anchor href='/FORMBASIC/checkBox' text='CheckBox Form'}</li>
  </ul>
</li>

fuel/app/classes/controller/formbasic.php
<?php
 
class Controller_FormBasic extends Controller {
 
  // the before function is called before any action
  // in this case it is just a convenience for presenting the inputs
  // instead of having it in each action function
  public function before() {
    parent::before();
    print_r(Input::post());
  }
 
  public function action_TEXT() {
    $data = [
        'tf' => Input::post('tf'),
        'ta' => Input::post('ta'),
    ];
    return View::forge('forms/text.tpl', $data);
  }
 
  private function getEntries() {
    return ["aa" => "Choice1", "bb" => "Choice2", "cc" => "Choice3",];
  }
 
  public function action_select() {
    $data = [
        'entries' => $this->getEntries(),
        'ddl' => Input::post('ddl'),
        'rgp' => Input::post('rgp'),
        //'rgp' => Input::post('rgp', 'aa'),  // use a preset value
    ];
    return View::forge('forms/select.tpl', $data);
  }
 
  public function action_CheckBox() {
    $data = [
        'entries' => $this->getEntries(),
        'cbg' => Input::post('cbg'),
        //'cbg' => Input::post('cbg', ['aa', 'cc']),
    ];
    return View::forge('forms/checkbox.tpl', $data);
  }
}

Letter case issues

I've gone to lengths to make a point about the usage of uppercase and lowercase strings in the controller and action names. In particular, it doesn't matter what case you use. This fact is due do Php's case insensitivity in entity names, i.e., we could just a well use ACTION_TEXT as the name of the member function in the controller.

What, however, does matter is that the name of the controller file. It must be all lower case to function correctly on case-sensitive Linux systems.

The before function

We've introduced the FuelPhp-specific before which is called prior to any action function. The before function performs the necessary initializations with the super-class call:
parent::before();
This initial statement is required for correct functioning. The usage of before in this situation is a convenience to help identify the initial/reentrant form activations without having to place the code within each action function.

The Input class

The FuelPhp Input class provides the means to access GET and POST input parameters. Look at the relevant FuelPhp doc page. The one function used in these examples is:
Input::post(...)
Without any arguments it is the map of all the post parameters. With a single string argument it is the value of that parameter, and NULL if there is no such parameter, e.g.,
$tf = Input::post('tf')
A second argument provides a default value if the parameter does not exist, e.g.:
$rgp = Input::post('rgp', 'aa');
Without any arguments, we get an array map of all post parameters, i.e., what $_POST gives:
$all_post = Input::post();
We can retrieve GET (query string) parameters by:
$specific = Input::get('param');
$specific = Input::get('param', 'default');
$all = Input::get();
There is also a version which can retrieve a parameter either by GET or POST:
$specific = Input::param('param');
$specific = Input::param('param', 'default');
$all = Input::param();

Text Form

class Controller_Basic extends Controller {
  ...
  public function action_TEXT() {
    $data = [
        'tf' => Input::post('tf'),
        'ta' => Input::post('ta'),
    ];
    return View::forge('forms/text.tpl', $data);
  }
  ...
}

fuel/app/views/forms/text.tpl
{extends file="layout.tpl"}
 
{block name="localstyle"}
{asset_css refs='tables.css'}
<style>
  table {
    width: 100%;
  }
  td:first-child {
    width: 20px;
  }
  input[type='text'] {
    width: 80%;
  }
  textarea {
    height: 150px;
    width: 80%;
  }
</style>
{/block}
 
{block name="content"}
  <h2>Text Form</h2>
  {form}
    <table>
      <tr>
        <td>tf: </td>
        <td><input type="text" name="tf" value="{$tf}" /></td>
      </tr>
      <tr>
        <td>ta:</td>
        <td><textarea name="ta">{$ta}</textarea></td>
      </tr>
      <tr>
        <td></td>
        <td><button type="submit" name="doit">Submit</button></td>
      </tr>
    </table>
  {/form}
{/block}
Comparisons to the Php/Smarty version:

Select Form

class Controller_Basic extends Controller {
  ...
  private function getEntries() {
    return ["aa" => "Choice1", "bb" => "Choice2", "cc" => "Choice3",];
  }
 
  public function action_select() {
    $data = [
        'entries' => $this->getEntries(),
        'ddl' => Input::post('ddl'),
        'rgp' => Input::post('rgp'),
        //'rgp' => Input::post('rgp', 'aa'),  // use a preset value
    ];
    return View::forge('forms/select.tpl', $data);
  }
  ...
}

fuel/app/views/forms/select.tpl
{extends file="layout.tpl"}
 
{block name="localstyle"}
{asset_css refs='tables.css'}
{/block}
 
{block name="content"}
  <h2>Smarty Selection Form</h2>
  {form}
    <table>
      <tr><td>ddl:</td>
        <td><select name="ddl">
             {html_options options=$entries selected=$ddl}
            </select></td>
      </tr>
      <tr><td>rgp:</td>
          <td>{html_radios name="rgp" options=$entries selected=$rgp}</td>
      </tr>
      <tr>
        <td></td><td><button type="submit" name="doit">Submit</button></td>
      </tr>
    </table>
  {/form}
{/block}
Observe that we must reference the getEntries member function through the object $this:
$this->getEntries()
The reason is because Php, by default, scopes references to variables locally, meaning that without the "$this->" prefix, the getEntries() call would be looking for a function defined within the action_select member function.

Checkbox Form

class Controller_Basic extends Controller {
  ...
  public function action_CheckBox() {
    $data = [
        'entries' => $this->getEntries(),
        'cbg' => Input::post('cbg'),
        //'cbg' => Input::post('cbg', ['aa', 'cc']),
    ];
    return View::forge('forms/checkbox.tpl', $data);
  }
  ...
}

fuel/app/views/forms/checkbox.tpl
{extends file="layout.tpl"}
 
{block name="content"}
  <h2>Checkbox Group Form</h2>
  {form}
    <strong>cbg:</strong> 
    <p>
    {html_checkboxes name='cbg' options=$entries selected=$cbg
                     separator="<br />"
    }
    </p>
    <button type="submit" name="doit">Submit</button>
  {/form}
{/block}
An important observation is that the multi-valued cbg parameter is treated like the single-valued parameters, in contrast to the Smarty/Php code in which the Php filter_input function must be used differently for multi-valued parameters.

FuelPhp Validation

FuelPhp, like other Web Frameworks, provides its own validation scheme for field validation. Look at the
FuelPhp validation documentation
The validation is uniform for all fields and all validation errors can be presented at once instead of having the user deal with them one at a time. Hopefully you will appreciate that this solution is quite a bit better than the ad-hoc style of our previous Php/Smarty approach.

In our example, access to the form validation examples is through Validated Forms menu.

Form1


Controller_Validate1
<?php
 
class Controller_Validate1 extends Controller {
 
  private function form1Validator() {
    $validator = Validation::forge();
 
    $validator->add('name', 'name') // field, label
        ->add_rule('trim')
        ->add_rule('required')
        ->add_rule('valid_string', 
            ['alpha', 'numeric', 'spaces', 'punctuation'])
        ->add_rule('min_length', 3)
    ;
    $validator->add('num', 'num')
        ->add_rule('trim')
        ->add_rule('required')
        ->add_rule('valid_string', ['numeric'])
    ;
    $validator->add('email', 'email')
        ->add_rule('trim')
        ->add_rule('required')
        ->add_rule('valid_email')
    ;
    // add ALL form fields, even if no validation takes place
    $validator->add('ta');
 
    // specifiy error messages to override rule defaults
    $validator
        ->set_message('required', ':label cannot be empty')
        ->set_message('min_length', 'at least :param:1 char(s)')
        ->set_message('valid_email', 'invalid email')
    ;
 
    // specify error messages per field to override other messages
    $validator->field('num')
        ->set_error_message('valid_string', 
            'must be non-negative integer')
    ;
 
    return $validator;
  }
 
  public function action_form1() {
    $validator = $this->form1Validator();
 
    $doit = Input::post('doit');
    $message = "";
    if (!is_null($doit)) {
      $validated = $validator->run(Input::post());
      if ($validated) {
        $validData = $validator->validated();
        $message = var_export($validData, true);
      }
    }
    $data = [
        'name' => Input::post('name'),
        'num' => Input::post('num', 557),
        'email' => Input::post('email'),
        'ta' => Input::post('ta'),
        'message' => $message,
    ];
    $view = View::forge('forms/form1.tpl', $data);
    $view->set_safe('validator', $validator);
    return $view;
  }
 
}

form1.tpl
{extends file="layout.tpl"}
 
{block name="localstyle"}
  <style type='text/css'>
    td:first-child { width: 10px; }
    td { border: none ! important; }
    textarea { resize: vertical; }
    .error { color: red; font-size: 80%; font-weight:bold; }
  </style>
{/block}
 
{block name="content"}
  <h2>Validated Form 1</h2>
 
  {form}  
  <table class="table table-condensed">
    <tr>
      <td>name:</td>
      <td>
        <input class='form-control' type="text" 
               name="name" value="{$name}" />
        <span class="error">{$validator->error_message('name')}</span>
      </td>
    </tr>
    <tr>
      <td>num:</td>
      <td>
        <input class='form-control' type="text" 
               name="num" value="{$num}" />
        <span class="error">{$validator->error_message('num')}</span>
      </td>
    </tr>
    <tr>
      <td>email:</td>
      <td>
        <input class='form-control' type="text" 
               name="email" value="{$email}" />
        <span class="error">{$validator->error_message('email')}</span>
      </td>
    </tr>
    <tr>
      <td>ta:</td>
      <td>
        <textarea class='form-control' name="ta">{$ta}</textarea>
      </td>
    </tr>
    <tr>
      <td></td>
      <td><button type="submit" name="doit">Do It</button></td>
    </tr>
  </table>
  {/form}
 
  <h4 id='message'>{$message|default}</h4>
 
  <pre>{$validator->error_message()|var_export}</pre>
 
{/block}

Defining the validator

The demo form1 uses the fields with these names:
name, num, email, ta
There are several ways to create validation rules; we'll focus on one way to do so. Start by creating the validator object:
$validator = Validation::forge();
With this in place let's use this rule for reference:
$validator->add('name', 'name') // field, label
  ->add_rule('trim')
  ->add_rule('required')
  ->add_rule('valid_string', ['alpha', 'numeric', 'spaces', 'punctuation'])
  ->add_rule('min_length', 3)
;
The field to be validated is added to the validator by:
$validator->add(fieldName, fieldLabel)
The fieldLabel argument is optional, but it is useful to be able refer to this field in a parametric way when setting the per-rule error message. Following this are the validation rules to be applied to the field, added one-by-one like this:
->add_rule(ruleName, argument)
The trim and required rules are very common. The trim rule should be applied first because it forces trimming of the field value before the other rules are applied.

The argument is required for some rules, e.g., for the min_length rule. The valid_string rule is very general rule which forces matching against common letter groups, obviating the necessity of regular expression matching in many cases. Refer to the documentation for available rules.

Using the validator

The actual validation takes place in the form action function by running:
$validator->run(Input::post())
A boolean valud is returned, and true means the validation succeeded for all fields. If validation succeeds, we can retrieve the "validated" field/values in the map
$validData = $validator->validated();
The importance of using this $validData is that the values of fields which employ the trim rule will be trimmed. It is important to add all fields in the form to the validator, whether any rules are used or not. In the case here, the ta field is added:
$validator->add('ta');
Doing so makes all fields uniformly appear in the $validData map.

Errors

If validation fails for one or more field, the field errors are available in a number of different ways. What is used in our examples is the error member function:
$validator->error_message(fieldName)
which returns null if there is no error for that field. Called without any arguments,
$validator->error_message()
returns an array map of all fieldName/errorMessage pairs.

Controlling Error Messages

Each rule has its own default error message, but these can be overridden in one of two ways when the $validator is initialized. Let's refer to the portion of the validator creation which modifies the error messages:
// specifiy error messages to override rule defaults
$validator
  ->set_message('required', ':label cannot be empty')
  ->set_message('min_length', 'at least :param:1 char(s)')
  ->set_message('valid_email', 'invalid email')
;
 
// specify error messages per field to override other messages
$validator
  ->field('num')
  ->set_error_message('valid_string', 'must be non-negative integer')
;
The set_message member function changes the error of a rule for all of its usages:
$validator->set_message(ruleName, new_message)
The message can be parameterized. For example we use:
->set_message('required', ':label cannot be empty')
and
->set_message('min_length', 'at least :param:1 char(s)')
In the first case, the ":label" refers to the fieldLabel set in the initial add call: this is why we need the label. In the second case, the ":param:1" refers to the first argument in the min_length per-field rule usage.

The error messages can be targetted to rule usages for specific fields only like this:
$validator->field(fieldName)->set_error_message(ruleName, message);
For example, this sets the error message for the valid_string rule only when used for the num field:
$validator->field('num')
          ->set_error_message('valid_string', 'must be non-neg. integer');

Using the validator in the view script

Our field validation error message presentation is achieved entirely through the $validator object passed to the view. Unlike other data passed to the view, the $validator may not be "sanitized," and so it must be passed separate from the data:
$data = [
 ...
];
$view = View::forge('validate/form1.tpl', $data);
$view->set_safe('validator', $validator);  // do not sanitize
return $view;
Within the form1.tpl view script, we see the per-field error message presentation, for example:
<tr>
  <td>name:</td>
  <td>
    <input class='form-control' type="text" 
           name="name" value="{$name}" />
    <span class="error">{$validator->error_message('name')}</span>
  </td>
</tr>
At the bottom of the script, we can generate all validation errors:
<pre>{$validator->error_message()|var_export}</pre>
This is only for debugging, but it is useful in case the validator thinks there is an error for a field not presented in the form.

Second Validation Example

Here is the relevant controller code plus the view script. In this case we split up the initial and reentrant activations into different URLs, i.e., different member functions.

Controller_Validate2
<?php
 
class Controller_Validate2 extends Controller {
 
  private function form2Validator() {
    $validator = Validation::forge();
 
    $validator->add('name', 'name') // field, label
        ->add_rule('trim')
        ->add_rule('required')
        ->add_rule('valid_string', ['alpha', 'spaces', 'punctuation'])
        ->add_rule('min_length', 3)
    ;
    $validator->add('num', 'num')
        ->add_rule('trim')
        ->add_rule('required')
        ->add_rule('match_pattern', '/^[1-9]\d*$/')  // positive integer
    ;
    $validator->add('pwd', 'password')
        ->add_rule('required')
    ;
    $validator->add('pwdConfirm', 'confirm')
        ->add_rule('required')
        ->add_rule('match_field', 'pwd') // nust match the pwd field
    ;
 
    $validator->add('ddl', 'ddl')
        ->add_rule('numeric_min', 1)
    ;
 
    // specifiy error messages to override rule defaults
    $validator
        ->set_message('required', ':label cannot be empty')
        ->set_message('min_length', 'at least :param:1 char(s)')
        ->set_message('valid_email', 'invalid email')
        ->set_message('match_field', 
            ':label does not match :param:1 field')
    ;
 
    // specify error messages per field to override other messages
    $validator->field('num')
        ->set_error_message('match_pattern', 
            'must be a positive integer')
    ;
    $validator->field('ddl')
        ->set_error_message('numeric_min', 'must choose something')
    ;
 
    return $validator;
  }
 
  private function form2Entries() {
    return [
        0 => "-- select --",
        1 => "Choice1", 2 => "Choice2", 3 => "Choice3",
    ];
  }
 
  public function action_form2() {
 
    // When initial and reentrant calls are separate, no validation
    // is done on initial entry; we just need a "placeholder"
    $validator = Validation::forge();
 
    $data = [
        'name' => 'Mr. Cool', // preset value
        'ddl' => 0,
        'entries' => $this->form2Entries(),
    ];
    $view = View::forge('forms/form2.tpl', $data);
    $view->set_safe('validator', $validator); 
    return $view;
  }
 
  public function action_form2Reentrant() {
    // return "form2Reentrant";
    // here we need the real validator
    $validator = $this->form2Validator();
 
    $validated = $validator->run(Input::post());
 
    $message = "";
    if ($validated) {
      $message = "OK";
    }
 
    $data = [
        'entries' => $this->form2Entries(),
        'name' => Input::post('name'),
        'num' => Input::post('num'),
        'ddl' => Input::post('ddl'),
        'message' => $message,
    ];
    $view = View::forge('forms/form2.tpl', $data);
    $view->set_safe('validator', $validator);
    return $view;
  }
 
}

form2.tpl
{extends file="layout.tpl"}
 
{block name="localstyle"}
  <style type='text/css'>
    td:first-child { width: 10px; }
    td { border: none ! important; }
    textarea { resize: vertical; }
    .error { color: red; font-size: 80%; font-weight:bold; }
  </style>
{/block}
 
{block name="content"}
  <h2>Validated Form 2</h2>
 
  {form attrs=['action' => '/validate2/form2Reentrant']}
 
  <table class='table table-condensed'>
    <tr>
      <td>name:</td>
      <td>
        <input class='form-control' type="text" 
               name="name" value="{$name}" />
        <span class="error">{$validator->error_message('name')}</span>
      </td>
    </tr>
    <tr>
      <td>num:</td>
      <td>
        <input class='form-control' type="text" 
               name="num" value="{$num|default}" />
        <span class="error">{$validator->error_message('num')}</span>
      </td>
    </tr>
    <tr>
      <td>password:</td>
      <td>
        <input class='form-control' type="password" 
               name='pwd' value="" />
        <span class="error">{$validator->error_message('pwd')}</span>
      </td>
    </tr>
    <tr>
      <td>confirm:</td>
      <td>
        <input class='form-control' type="password" 
               name='pwdConfirm' value="" />
        <span class="error">{$validator->error_message('pwdConfirm')}</span>
      </td>
    </tr>
    <tr>
      <td>ddl:</td>
      <td>
        <select class='form-control' name="ddl">
          {html_options options=$entries selected=$ddl}
        </select>
        <span class="error">{$validator->error_message('ddl')}</span>
      </td>
    </tr>
    <tr>
      <td></td>
      <td><button type="submit" name="doit">Do It</button></td>
    </tr>
  </table>
  {/form}
 
  <h4 id='message'>{$message|default}</h4>
 
  <pre>{$validator->error_message()|var_export}</pre>
 
{/block}
The new features presented here are:
  1. The match_pattern rule validates the num field by comparison with a regular expression:
    $validator->add('num', 'num')
        ->add_rule('match_pattern', '/^[1-9]\d*$/') 
    ;
  2. The valid_email rule validates an email field.
  3. The match_field rule compares content with another field. In this case the pwdConfirm field must match the pwd field.
    $validator->add('pwd', 'password')
        ->add_rule('required')
    ;
    $validator->add('pwdConfirm', 'password confirmation')
        ->add_rule('match_field', 'pwd') // nust match the pwd field
    ;
    The argument in the match_field rule is used to identify the field, pwd which is supposed to match the pwdConfirm field so that we can set a generic message applicable to every usage of this rule:
    $validator
        ->set_message('match_field', ':label does not match :param:1 field')
    ;
  4. The numeric_min rule for the ddl field ensures that a selection has been made with the assumption that "no selection" has the value 0 and other choices have a positive integer value.
    $validator->add('ddl', 'ddl')
        ->add_rule('numeric_min', 1)
    ;
    ...
    $validator
        ->field('ddl')
        ->set_error_message('numeric_min', 'must choose something')
    ;

Custom Rules

This section introduces the usage of a custom rule, something which does not fit (at least easily) one of the standard rules.

In these examples, we want to validate that the text typed into a textarea contains two or more "special" words of our choice. There is a single form, form34.tpl, and two ways to access it.

Controller_Validate34
<?php
 
class Controller_Validate34 extends Controller {
 
  //=========================== form 3
 
  private function form3Validator() {
 
    // create a function of one param to be used as field input
    $hasSpecialWords = function($text) {
      $specialWords = ["east", "north", "state", "city"];
      $text_words = preg_split("/\W+/", $text);
      $count = 0;
      foreach ($text_words as $word) {
        if (in_array(strtolower($word), $specialWords)) {
          ++$count;
        }
      }
      return $count >= 2;
    };
 
    $validator = Validation::forge();
    $validator->add('wordtext', 'wordtext')
        // custom rule: first argument assigns the name to a function
        ->add_rule(['has_special_words' => $hasSpecialWords])
    ;
    $validator->set_message('has_special_words', 
            'must use two of the special words')
    ;
 
    return $validator;
  }
 
  public function action_form3() {
    $validator = $this->form3Validator();
 
    $doit = Input::post('doit');
    $message = "";
    if (!is_null($doit)) {
      $validated = $validator->run(Input::post());
      if ($validated) {
        $message = "OK";
      }
    }
    $data = [
        'form_num' => 3,
        'wordtext' => Input::post('wordtext'),
        'message' => $message,
    ];
    $view = View::forge('forms/form34.tpl', $data);
    $view->set_safe('validator', $validator);
    return $view;
  }
 
  //=========================== form 4
 
  private function form4Validator() {
 
    // a function of 2 params, second passed in from rule assignment
    $hasSpecialWords = function($text, $specialWords) {
      $text_words = preg_split("/\W+/", $text);
      $count = 0;
      foreach ($text_words as $word) {
        if (in_array(strtolower($word), $specialWords)) {
          ++$count;
        }
      }
      return $count >= 2;
    };
 
    $myWords = ["east", "north", "state", "city"];
 
    $validator = Validation::forge();
    $validator->add('wordtext', 'wordtext')
        // custom rule: the 2nd arg. becomes the function's 2nd arg.
        ->add_rule(['has_special_words' => $hasSpecialWords], $myWords)
    ;
    $validator
        // now we can capture the second argument as ":param:1"
        ->set_message('has_special_words', 
            'must use two or more words from [:param:1]')
    ;
 
    return $validator;
  }
 
  public function action_form4() {
    $validator = $this->form4Validator();
 
    $doit = Input::post('doit');
    $message = "";
    if (!is_null($doit)) {
      $validated = $validator->run(Input::post());
      if ($validated) {
        $message = "OK";
      }
    }
    $data = [
        'form_num' => 4,
        'wordtext' => Input::post('wordtext'),
        'message' => $message,
    ];
    $view = View::forge('forms/form34.tpl', $data);
    $view->set_safe('validator', $validator);
    return $view;
  }
 
}

form34.tpl
{extends file="layout.tpl"}
 
{block name="localstyle"}
  <style type='text/css'>
    td:first-child { width: 10px; }
    td { border: none ! important; }
    textarea { resize: vertical; }
    .error { color: red; font-size: 80%; font-weight:bold; }
  </style>
{/block}
 
{block name="content"}
  <h2>Validated Form {$form_num}: use custom rule</h2>
 
  {form}
  <table class="table table-condensed">
    <tr>
      <td>wordtext:</td>
      <td>
        <textarea class="form-control" 
                  name="wordtext">{$wordtext}</textarea>
        <span class="error">{$validator->error_message('wordtext')}</span>
      </td>
    </tr>
    <tr>
      <td></td>
      <td><button type="submit" name="doit">Do It</button></td>
    </tr>
  </table>
  {/form}
 
  <h4 id='message'>{$message|default}</h4>
 
  <pre>{$validator->error_message()|var_export}</pre>
 
{/block}

Form 3

The custom rule for the wordtext field is established by:
$validator->add('wordtext', 'wordtext')
    ->add_rule(['has_special_words' => $hasSpecialWords])
;
$validator->set_message('has_special_words', 
            'must use two of the special words')
;
The first argument of add_rule, instead of a string representing a known rule, is a map associating a custom rule name, has_special_words, to a function which defines how the rule behaves. Ignoring the content, the format of the function is this:
$hasSpecialWords = function($text) {
  // take $text as value of the field and return a 
  // boolean value indicating whether there are present
  // 2 or more words in the field belonging to a
  // list, $specialWords, defined within the function
};
As describe in the documentation, this rule function is expected to return true or false based on some feature of the content of the textfield to which it is applied.

Form4

This is a variation on the custom rule usage in Form3. The only real difference is in the creation of the validation rule. We want to be able tell the user what are the special words in the validation error message. The list of special words must be removed from the rule function and passed in as a parameter:
$hasSpecialWords = function($text, $specialWords) {
  // same content, except missing definition of $specialWords
};
We can pass the list of chosen words, $myWords, to the add_rule definition like this:
$myWords = ["east", "north", "state", "city"];
 
$validator->add('wordtext', 'wordtext')
    ->add_rule( ['has_special_words' => $hasSpecialWords], $myWords )
;
The $myWords list becomes the second parameter, $specialWords, of the rule function. Then $myWords can be presented in the error message as ":param:1"
$validator->set_message('has_special_words',		 
        'must use two or more words from [:param:1]' );

FuelPhp Sessions

Session control in FuelPhp is built into the framework via the Session class. Refer to the
FuelPhp doc page
FuelPhp has a rich set of configuration options about where to put the sessions, when to expire them, etc. For simplicity, we will use the defaults.

When sessions are used, FuelPhp needs special application-specific keys stored in the file
fuel/app/config/crypt.php
FuelPhp specifically wants to generate this file for your application. On Mac or Linux, if the folder fuel/app/config is not server-writable, FuelPhp will instruct you to create the crypt.php file by hand.

There are three key functions to manage persistent session data. The function
Session::set('sess_variable', $SOME_VALUE);
associates the key sess_variable to a persistent value held in the session. To retrieve this value, use:
$val = Session::get('sess_variable');
A null value is returned if the key has not yet been set. Testing for presence of the key is just like testing for presence of an input parameter. A second argument provides a default value if the session variable is not set:
$val = Session::get('sess_variable', defaultValue);
Finally, to remove a session variable use:
Session::delete('sess_variable');

Flash variables

The other important feature of sessions are for so-called flash memory, represented by a read-once usage. As before, the function
Session::set_flash('sess_variable', $SOME_VALUE);
associates the key sess_variable with the value. This flash session memory association is separate from those regular sessions. The function
$val = Session::get_flash('sess_variable');
reads the value and then deletes the key from the flash memory. Flash memory is commonly used to send information across a redirection. The target script gets the value, but does not hold it when entered subsequent times.

By default, the flash memory is maintained only across a single redirection, but it can be configured to be held until it is actually read.

Smarty session/flash access

As in our Php/Smarty applications, it is useful to have access to the session object within the Smarty template without having to have it passed in from the controller code. We can do something similar to what we had before. The key is forcing Smarty to load the following class:

fuel/app/classes/myextensions.php
<?php
class MyExtensions {
  public function __construct(\Smarty $smarty) {
    $session = Session::forge();
    $smarty->assign('session', $session);
  }
}
The loading of this file is forced by configuration setting here:

fuel/app/config/parser.php
<?php
 
return array(
  'extensions' => array( 'tpl' => 'View_Smarty' ),
  'View_Smarty' => array(
     'extensions' => array(
        'Smarty_Fuel_Extension',
        'MyExtensions',
    ),
  )
);
The explanation of why this works is based on FuelPhp conventions. The MyExtensions usage implies the existence of "class MyExtensions." By default, FuelPhp assumes this to be in a file myextensions.php (converted to lower case) which is made available in one of class hierarchies searched, in particular, within
fuel/app/classes
Within class MyExtensions, the constructor does all the work in accessing the Session class by a secondary means, through instantiating an object of this class:
$session = Session::forge();
This object is assigned to the Smarty session variable just as before. The only difference in usage is that the session keys are accessed via the get method, i.e.,
$session->get('sess_variable')
instead of what we used previously: $session->variable.

Accessing a session flash variable is typically done in the Smarty view code, and, fortunately, there is a useful session_get_flash extension. The extension code is:
/**
 * Usage: {session_get_flash var='' default='' expire=false}
 * Required: var
 *
 * @return mixed
 */
public function session_get_flash($params) {
  if (!isset($params['var'])) {
    throw new \UnexpectedValueException("The var parameter is required.");
  }
  $default = isset($params['default']) ? $params['default'] : null;
  $expire = isset($params['expire']) ? $params['expire'] : false;
  return Session::get_flash($params['var'], $default, $expire);
}
The typical usages are:
{session_get_flash var='session_variable'}
{* and *}
{session_get_flash var='session_variable' default='a default value'}

Basic Session Examples

The group of URLs defined within the sessionexamples controller are meant to be imitations of what was done in Php Sessions. The differences with the session usage from the previous Php/Smarty approach are these:

fuel/app/classes/controller/sessionexamples.php
<?php
 
class Controller_SessionExamples extends Controller {
 
  public function action_index() {
    $data = [];
    return View::forge('sessionexamples/index.tpl', $data);
  }
 
  public function action_setfoo() {
    print_r(Input::post());
 
    $doit = Input::post('doit');
 
    if (!is_null($doit)) {
       Session::set('foo', Input::post('foo'));
    }
    $data = [
       'foo' => Session::get('foo'),
    ];
    return View::forge('sessionexamples/setfoo.tpl', $data);
  }
 
  public function action_setbar() {
    $doit = Input::post('doit');
 
    if (!is_null($doit)) {
      Session::set_flash('bar', Input::post('bar'));
      return Response::redirect('sessionexamples');
    }
 
    $data = [
        'choices' => ['aaa' => 'aaa', 'bbb' => 'bbb', 'ccc' => 'ccc'],
    ];
 
    return View::forge('sessionexamples/setbar.tpl', $data);
  }
}

fuel/app/views/sessionexamples/index.tpl
{extends file="layout.tpl"}
 
{block name="content"}
 
<h2>Session Home</h2>
 
value of <tt>foo</tt>: <tt>{$session->get('foo')}</tt>
<br />
<br />
value of <tt>bar</tt>: {session_get_flash var='bar'}
 
{/block}

fuel/app/views/sessionexamples/setfoo.tpl
{extends file="layout.tpl"}
 
{block name="content"}
<h2>SetFoo</h2>
  {form}
     foo: <input type="text" name="foo" value="{$foo}" />
     <button type="submit" name="doit">Submit</button>
  {/form}
{/block}

fuel/app/views/sessionexamples/setbar.tpl
{extends file="layout.tpl"}
 
{block name="content"}
<h2>SetBar</h2>
  {form}
    <select name="bar">
      {html_options options=$choices}
    </select>
    <button type="submit" name="doit">Submit</button>
  {/form}
{/block}

FuelPhp Redirection

We also make the first usage of redirection in FuelPhp via:
return Response::redirect('/controller/action');
In this case we just use the controller part, sessionexamples, which will call the index action within.

Simple Password Authentication

As in the previous section, these URLs are meant to be imitations of what was done in Php Sessions. The three login, validate, and logout URLs are all located within the authenticate controller. The authenticate/login.tpl view script activates the validation by:
{form attrs=['action' => '/authenticate/validate']}
An unsuccessful login attempt sets the flash message variable which is read and displayed by in authenticate/login.tpl as:
<h3 id="message">{session_get_flash var='message'}</h3>

fuel/app/classes/controller/authenticate.php
<?php
 
class Controller_Authenticate extends Controller {
 
  public function action_login() {
    if ( !is_null(Session::get('valid')) ) {
      return Response::redirect("/");
    }
    $data = [];
    return View::forge('authenticate/login.tpl', $data);
  }
 
  public function after($response) {
    $response = parent::after($response);
    $response->set_header(
      'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
    return $response;
  }
 
  public function action_validate() {
    define("PASSWORD", "foobar");
 
    $password = Input::post('password');
 
    if ($password === PASSWORD) {
      Session::set('valid', (object) ['user' => 'admin']);
      return Response::redirect("/");
    }
    Session::set_flash('message', "Invalid");
    return Response::redirect("/authenticate/login");
  }
 
  public function action_logout() {
    Session::delete('valid');
    return Response::redirect("/");
  }
}

fuel/app/views/authenticate/login.tpl
{extends file="layout.tpl"}
 
{block name="content"}
   <h2>Login</h2>
   {form attrs=['action' => '/authenticate/validate']}
     <input type="password" name="password" autofocus />
     <button type="submit">Submit</button>
   {/form}
  <h3 id="message">{session_get_flash var='message'}</h3>
{/block}
Successful validation means setting the valid session variable which then gives a non-null value to this within a controller:
Session::get('valid');
and this within a view script:
{... $session->get('valid') ...}
The most important usage is to reveal hidden links:

fuel/app/views/links.tpl
...
{if $session->get('valid')}
  <li>{html_anchor href='/admin/protected' text='Protected'}</li>
{/if}
 
{if $session->get('valid')}
  <li>{html_anchor href='/authenticate/logout' text='Logout'}</li>
{else}
  <li>{html_anchor href='/authenticate/login' text='Login'}</li>
{/if}
Another effect is to give us access to the protected controller. In this case we are protecting access to the entire controller by testing validation within the before function and redirecting to the login if not authenticated.

fuel/app/classes/controller/admin.php
<?php
 
class Controller_Admin extends Controller {
  public function before() {
    parent::before();
    if ( is_null(Session::get('valid')) ) {
      return Response::redirect("/authenticate/login");
    }
  }
 
  public function after($response) {
    $response = parent::after($response);
    $response->set_header(
      'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
    return $response;
  }
 
  public function action_protected() {
    $data = [];
    return View::forge('admin/protected.tpl', $data);
  }
}

fuel/app/views/admin/protected.tpl
{extends file="layout.tpl"}
 
{block name="content"}
 
<h2>Protected</h2>
 
You shouldn't see this unless you have validated.
 
{/block}

Dealing with the back button

As long as we keep moving "forward" in accessing URLs, there is no problem. However when the back button is used we want to avoid having the browser use cached pages and make sure to only see the login page if we have not authenticated. There are several features introduced to ensure the behavior we want.


© Robert M. Kline