Form Validation

Phad has a built-in form validation feature where validation rules are written in your form's html, with defaults based on html input type.

New Validation methods can be written in code/Controller/FormValidator.php with the definition function validatePropAttributename(string $attribute_value, mixed $user_input_value): bool. New attributes should use p- prefix. Definitions should be like validatePropPType for p-type attribute.

Available Validations

Every value runs through mulitple validation tests, and ALL of them must pass.

  • validator attribute: Execute a custom validator. If a custom validator is provided, NO OTHER VALIDATION IS PERFORMED. (See Below)
  • input tagName - Automatic validation based upon the input's tag name
    • input: no validation
    • select: no validation (does validate options, but that's a different validations step)
    • textarea: is_string($value)
    • Any other tagname: always fails to validate.
  • input type - Automatic validation based upon the input's type (other validation is still applied!)
    • hidden, backend: no validation
    • radio: no validation
    • text: is_string($value)
    • number: is_numeric($value)
    • date: date_create($value) MUST NOT return false
    • time: h:m, matching integer ranges [0-23]:[0-59]
    • url: filter_var($value,FILTER_VALIDATE_URL);
    • phone: is_numeric() && (is 10 characters || is 11 characters, starting with a 1)
    • email: filter_var($value, FILTER_VALIDATE_EMAIL);
    • checkbox: ($value=='on'||$value=='1')
  • required attribute: Value must be present and non-empty. If value not required, and value is empty string or null, then no further validation is done & the value is considered valid.
    • You may omit required or set required="false" for values that are not required.
  • maxlength attribute: $value <= max length
  • minlength attribute: $value >= min length
  • p-type attribute - validate the php data type. Attribute's value can be one of: (@TODO test p-type validation)
    • array
    • number
    • int
    • string
  • p-nohtml attribute - if present and not "false", value cannot contain html. Tests input string against strip_tags($input_string), and if they're not identical, then validation fails.
  • <select> options: $value must be present on one of the option tags. if $value=='something' AND there is an <option value="something">, then it passes.
  • radio: the submitted value MUST be one of the options defined on the same-"name"d radio inputs. (@todo write tests for radio validation)
  • checkbox: @todo add checkbox validation, similar to select & radio

Sample Form

Submitting this form will fail if any of the html-defined requirements are not met. So if title is 76+ characters or is not submitted, it'll fail.

Note: The requirements are always loaded server-side by parsing the server-side copy of the form. The requirements are never submitted by the user.

<form item="Blog" target="/blog/{slug}/">
    <onsubmit><?php $BlogRow['slug'] = generate_slug($BlogRow['title']); ?></onsubmit>

    <input type="text" name="title" maxlength="75" required>
    <textarea name="body" maxlength="2000" minlength="10" required></textarea>

    <!-- backend properties are also validated -->
    <input type="backend" name="slug" minlength=4 maxlength=150 />
</form>

Note: generate_slug() is NOT a built-in function.

Manual Validation From Form

Data can also be manually validated by scanning a form & calling the validator api. (Validation in pure php is available too. See below)

Note: You can alternatively use your Phad instance to laod/compile the item.

Let's say the above form is saved at $dir/form/Blog.php

<?php
$fake_phad = new class { public function __call($mthd,$args){}} // required because of bad code design.
$dir = __DIR__; // depends on your setup
$item = new \Phad\Item('form/Blog', $dir, ['phad'=>$fake_phad]);

$item_info = $item->info();
$validation_expectations = $item_info->properties;


$submitter = new \Phad\FormValidator($expectations);

// $is_valid is true here
$is_valid = $submitter->validate(
    [ 'title' => 'Are cats happy?',
      'body' => 'Cats are probably happy when they\'re well taken care of',
      'slug' => 'are-cats-happy',
    ]

Manual validation Without a Form

You can define your validation requirements in php & directly use Phad's Validator without any complicated dependencies or html parsing.

<?php
// These are the same expectations defined in the form above. 
// Most forms will also have an id as a hidden input, but not this example.
$expectations = [
    'title' => 
        [
        'type' => 'text',
        'maxlength' => '75',
        'tagName' => 'input',
        ],
    'body' => 
        [
        'maxlength' => '2000',
        'minlength' => '50',
        'tagName' => 'textarea',
        ],
    'slug' =>
        [
            'type'=>'backend',
            'minlength' => '4',
            'maxlength' => '150',
            'tagName'=>'input',
        ]
];

$submitter = new \Phad\FormValidator($expectations);

// $is_valid is true here
$is_valid = $submitter->validate(
    [ 'title' => 'Are cats happy?',
      'body' => 'Cats are probably happy when they\'re well taken care of',
      'slug' => 'are-cats-happy',
    ]
);

Failed Validation

Continuing the example above, let's see how a failed validation works. Firstly, validate() will return false. Secondly, you'll get an array describing which validations failed, and another array of error messages.

<?php
// now let's fail a validation!
$is_valid = $submitter->validate(
    [ 'title' => 'Are cats happy?',
      'body' => 'Cats are probab', // body is < 50 chars (the minlength), so this will fail
      'slug' => 'are-cats-happy',
    ],
    $error_messages_array,
    $failed_columns_array,
);
// $error_messages_array & $failed_columns_array are both passed by-reference, and will be filled as below
$failed_columns_array === [
    [
        'body'=> [
            'failed_value' => 'Cats are probab', // the value that failed
            'minlength' => 50, // the validation that caused the failure
        ]
    ]
];

$error_messages_array === [
    'msg' => "'body' failed validation for 'minlength:50'",
];

Notes

If a validation fails, but no error messages are added, a generic message is added saying "[number_of] fields failed validation. Cause unkown." (Yes, the typo is in the code lol. Yes I should fix it lol.)

If extra keys/values are passed, and $submitter->allow_extra_fields == false (the default), and all other validations were successful, then a generic error message is added: "[number_of] unexpected fields were submitted. They were: [list of fields]", and validation fails.

If full form submission fails due to a validation error, but no error messages were generated, then this generic message is used: "Validation failed. Reason unkown. 'validation' error"

Custom Validator

Custom validators can be written.

This specifies that the 'tag' data should be an array of ints, using the user-defined validator u-contains-ints. Note that undefined validators always return true.

<input prop="tag" type="hidden" p-type="array" u-contains-ints />

Example php: To make this work, we have to add a custom validator.

<?php
$validator = new \Phad\FormValidator($expectations);
$validator->addAttributeValidator('u-contains-ints',
    /** Ensure the value is an array containing only int values. */
    function(string $attribute_name, string $attribute_value, mixed $user_input_value){
        // $attribute_name is passed just in case you want to use the same callable for multiple validators.
        if (!is_array($user_input_value))return false;
        foreach ($user_input_value as $string_value){
            $intval = (int)$string_value;
            $intstring = (string)$intval; // because user-submitted data is always a string
            if ($intstring!==$string_value)return false; 
        }
        return true;
    }
);