Validation framework

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Validation framework

Post by Christopher »

The base Skeleton tag renderer is essentially identical, but a class (there are classes for all the other HTML tags that inherit this).

Code: Select all

class A_Html_Tag {
 
    /*
     * $tag - name of tag
     * $attr - array of attributes 
     * $content - determines if the tag has a closing tag - defined yes, null no
     *          - a string, an object with a render() method or an array containing any mix of those
     * e.g. render('div', array('id'=>'foo'), 'bar') generates <div id="foo">bar</div>
     * e.g. render('img', array('src'=>'foo.jpg', 'alt'=>'bar')) generates <img src="foo.jpg" alt="bar"/>
     */
    public function render($tag, $attr=array(), $content=null) {
    }
 
    public function __toString() {
        $this->render();
    }
}
I think the main difference is your selfClosing vs just passing content to determine when to generate a closing tag. I think you can pass strings or objects as content. I also think you can pass content in the attributes array as 'content' like your 'innerhtml' (better name!)
(#10850)
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Validation framework

Post by josh »

VirtuosiMedia wrote:jshpro2, how do you generate your HTML? I've been using this simple XML function that I wrote and it's been working well for me, but I'm curious to see how you do it.
In Zend?

Controller:

Code: Select all

 
$form = new Zend_Form( $options );
$this->view->form = $form;
 

View:

Code: Select all

 
echo $this->form;
 
It probably uses something like what arborint just posted internally in the view helpers

Edit: here you go

Code: Select all

 
class Zend_View_Helper_FormText extends Zend_View_Helper_FormElement
{
    /**
     * Generates a 'text' element.
     *
     * @access public
     *
     * @param string|array $name If a string, the element name.  If an
     * array, all other parameters are ignored, and the array elements
     * are used in place of added parameters.
     *
     * @param mixed $value The element value.
     *
     * @param array $attribs Attributes for the element tag.
     *
     * @return string The element XHTML.
     */
    public function formText($name, $value = null, $attribs = null)
    {
        $info = $this->_getInfo($name, $value, $attribs);
        extract($info); // name, value, attribs, options, listsep, disable
 
        // build the element
        $disabled = '';
        if ($disable) {
            // disabled
            $disabled = ' disabled="disabled"';
        }
        
        // XHTML or HTML end tag?
        $endTag = ' />';
        if (($this->view instanceof Zend_View_Abstract) && !$this->view->doctype()->isXhtml()) {
            $endTag= '>';
        }
 
        $xhtml = '<input type="text"'
                . ' name="' . $this->view->escape($name) . '"'
                . ' id="' . $this->view->escape($id) . '"'
                . ' value="' . $this->view->escape($value) . '"'
                . $disabled
                . $this->_htmlAttribs($attribs)
                . $endTag;
 
        return $xhtml;
    }
}
 
class Zend_View_Helper_Form extends Zend_View_Helper_FormElement
{
    /**
     * Render HTML form
     *
     * @param  string $name Form name
     * @param  null|array $attribs HTML form attributes
     * @param  false|string $content Form content
     * @return string
     */
    public function form($name, $attribs = null, $content = false)
    {
        $info = $this->_getInfo($name, $content, $attribs);
        extract($info);
 
        if (!empty($id)) {
            $id = ' id="' . $this->view->escape($id) . '"';
        } else {
            $id = '';
        }
 
        if (array_key_exists('id', $attribs) && empty($attribs['id'])) {
            unset($attribs['id']);
        }
 
        $xhtml = '<form'
               . $id
               . $this->_htmlAttribs($attribs)
               . '>';
 
        if (false !== $content) {
            $xhtml .= $content
                   .  '</form>';
        }
 
        return $xhtml;
    }
}
 
abstract class Zend_View_Helper_HtmlElement extends Zend_View_Helper_Abstract
{
    /**
     * EOL character
     */
    const EOL = "\n";
 
    /**
     * The tag closing bracket
     *
     * @var string
     */
    protected $_closingBracket = null;
 
    /**
     * Get the tag closing bracket
     *
     * @return string
     */
    public function getClosingBracket()
    {
        if (!$this->_closingBracket) {
            if ($this->_isXhtml()) {
                $this->_closingBracket = ' />';
            } else {
                $this->_closingBracket = '>';
            }
        }
 
        return $this->_closingBracket;
    }
 
    /**
     * Is doctype XHTML?
     *
     * @return boolean
     */
    protected function _isXhtml()
    {
        $doctype = $this->view->doctype();
        return $doctype->isXhtml();
    }
 
    /**
     * Converts an associative array to a string of tag attributes.
     *
     * @access public
     *
     * @param array $attribs From this array, each key-value pair is
     * converted to an attribute name and value.
     *
     * @return string The XHTML for the attributes.
     */
    protected function _htmlAttribs($attribs)
    {
        $xhtml = '';
        foreach ((array) $attribs as $key => $val) {
            $key = $this->view->escape($key);
 
            if (('on' == substr($key, 0, 2)) || ('constraints' == $key)) {
                // Don't escape event attributes; _do_ substitute double quotes with singles
                if (!is_scalar($val)) {
                    // non-scalar data should be cast to JSON first
                    require_once 'Zend/Json.php';
                    $val = Zend_Json::encode($val);
                }
                $val = preg_replace('/"([^"]*)":/', '$1:', $val);
            } else {
                if (is_array($val)) {
                    $val = implode(' ', $val);
                }
                $val = $this->view->escape($val);
            }
 
            if ('id' == $key) {
                $val = $this->_normalizeId($val);
            }
 
            $xhtml .= " $key=\"$val\"";
        }
        return $xhtml;
    }
 
    /**
     * Normalize an ID
     * 
     * @param  string $value 
     * @return string
     */
    protected function _normalizeId($value)
    {
        if (strstr($value, '[')) {
            if ('[]' == substr($value, -2)) {
                $value = substr($value, 0, strlen($value) - 2);
            }
            $value = trim($value, ']');
            $value = str_replace('][', '-', $value);
            $value = str_replace('[', '-', $value);
        }
        return $value;
    }
}
 
User avatar
VirtuosiMedia
Forum Contributor
Posts: 133
Joined: Thu Jun 12, 2008 6:16 pm

Re: Validation framework

Post by VirtuosiMedia »

Thanks to both of you for posting examples. I always enjoy seeing different implementations. Mine is actually a method, but I just didn't post the whole class.
arborint wrote:I think you can pass strings or objects as content.
I'm not quite sure from your comment as to whether or not you think that's good.

I haven't done it, but do you think it's worth writing an extending class for each type of element if you already have a generic constructor like this? I'm leaning toward not doing it.

Going back to the validation topic, a simple usage of my validate class looks like:

Code: Select all

$form = new Vm_Validate();
 
//Add a single validation
$form->addValidator('email', $_POST['email'], 'email', 'You must enter a valid email address');
 
//Add multiple validations
$form->addValidators('username', $_POST['username'], array(
    'required'=>'A username is required',
    'alphanum' //The default error is used
));
 
//Get all the errors for a single field
if ($form->errorsExist('username')){
    $getErrors = $form->getErrors('username');
 
    foreach($getErrors as $error){
        $errorAttributes = array(
            'class'=>'errorListItem', 
            'innerHtml'=>$error
        );
        $errors .= Vm_Xml::createTag('li', $errorAttributes)."\n";
    }
 
    $errorListAttributes = array(
        'id'=>'usernameErrorList', 
        'class'=>'errorListClass', 
        'innerHtml'=>$errors
    );
    $errorList = Vm_Xml::createTag('ul', $errorListAttributes)."\n";
    echo $errorList;    
}
 
//Get all the errors for the form
if ($form->errorsExist()){
    $getErrors = $form->getAllErrors();
 
    foreach($getErrors as $error){
        $errorAttributes = array(
            'class'=>'errorListItem', 
            'innerHtml'=>$error
        );
        $errors .= Vm_Xml::createTag('li', $errorAttributes)."\n";
    }
 
    $errorListAttributes = array(
        'id'=>'errorList', 
        'innerHtml'=>$errors
    );
    $errorList = Vm_Xml::createTag('ul', $errorListAttributes)."\n";
    echo $errorList;    
}
However, if I use my form class, I could write the above (plus the entire form code and labels) using just this code:

Code: Select all

$form = new VM_Form(array(), array('errorPosition'=>'beforeForm'));
$form->text('email', array(
    'validators' => array(
        'email', 
        'required'=>'An email is required'
    ),  
    'label' => array(
        'innerHtml'=>'Email',
        'class'=>'label'
    ),
    'attributes' => array('class'=>'input')
));
$form->text('username', array(
    'attributes' => array('class'=>'input'), 
    'label' => array(
        'innerHtml'=>'Username',
        'class'=>'label'
    ),  
    'validators' => array(
        'required'=>'A username is required',
        'alphanum'
    )
));
$form->submit(array(
    'value'=>'Submit',
    'id'=>'submit'
));
 
//If you were to save the form data or do anything else with it, it would go here
 
echo $form->render();
Filters can be added the same way as validators and you have access to both the raw and filtered data.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Validation framework

Post by Christopher »

VirtuosiMedia wrote:I'm not quite sure from your comment as to whether or not you think that's good.
Yes, I think it is a good idea to be able to pass either strings or objects as 'innerhtml'. A big part of the decoration ability is that you can pass a whole tree of HTML rendering objects to any tag.
VirtuosiMedia wrote:I haven't done it, but do you think it's worth writing an extending class for each type of element if you already have a generic constructor like this? I'm leaning toward not doing it.
And yes I think extending makes sense, but the Skeleton solution is a mostly OO one. With your validation rules (I didn't see filters) you pass a string name. With Skeleton everything as an object. There are no built-in rules and custom rules are trivial to build.

I can post example code or you can just look at the code and examples on the Google Code project (it's all public code). But it sounds like you are on the way to building a QuickForm style solution, so good luck.
(#10850)
wei
Forum Contributor
Posts: 140
Joined: Wed Jul 12, 2006 12:18 am

Re: Validation framework

Post by wei »

A particular interesting way of handling forms and validation is the following (gleaned from http://www.yiiframework.com/doc/guide/form.overview)

Code: Select all

 
class LoginForm extends CFormModel
{
    public $username;
    public $password;
    public $rememberMe=false;
 
    public function rules()
    {
        return array(
            array('username, password', 'required'),
            array('password', 'authenticate'),
        );
    }
 
    public function authenticate($attribute,$params)
    {
        //...
    }
}
 
In that the class LoginForm defines the form input parameters as properties, and also contains validation logic. The validation seems to be defined in the return of the rules() function. Where the 2nd array element is a validation class name (or alias) or a method name within the form class. Validation seems to be triggered by the CFormModel::validate() method.

Upon a post action, it does something like

Code: Select all

 
$form= new LoginForm($_POST['LoginForm']);
if($form->validate()) {
    //do something with form.
}
 
The html seems to be just

Code: Select all

 <input type="text" name="LoginForm[username]" /><input type="text" name="LoginForm[password]" />... 
I think this is rather elagant as it encapsulate the form data and logic in one place, not aware of the "controller" and view.
Post Reply