Welcome Guest, Not a member yet? Register   Sign In
Dynamic validation
#1

[eluser]TheFuzzy0ne[/eluser]
Hi everyone. Sorry for the misleading title, if you can suggest a more accurate one, please do so.

What I'm trying to achieve is some seriously dynamic validation, but not like you might think. Below I've attached a screenshot of the edit_forum page from my Web site. Validating the data is easy enough, but the thing is that some things are optional, depending on other things.

For example, each forum has a parent forum. If the parent forum has not changed, then I do not need to move the forum, however, if it does change, I do need to move it. The same applies to moving posts. I don't need to move them unless the forum type changes from "forum" to "container". Now, validating the data is simple enough, what I'd like to cut back on, are calls to the database that have already been made.

My validation rules need to be set dynamically, so I don't validate data that doesn't pertain to the current action. The trouble is that both the controller method and the validation methods need to be aware of what's going on.

Here's my code:
Code:
<?php

class Edit_forum extends MY_Controller
{
    function Edit_forum()
    {
        parent::MY_Controller();
        
        $this->load->helper('mptt');
        $this->load->helper('array');
        $this->load->helper('forums');
        $this->load->model('forums/forum_model');
        $this->load->library('form_validation');
    }
    
    function index($forum_id=0)
    {
        $forum_id = (int)$forum_id;

        # Do we need to process the form?
        if ($this->input->post('submit'))
        {
            $this->_set_validation_rules();
            
            if ($this->form_validation->run())
            {
                # Make the changes (if any)
            }
        }

        $this->load->vars(array(...));
        
        $this->load->view('main_template');
    }
    
    function _set_validation_rules()
    {
        $this->form_validation->set_error_delimiters('', '');
        $rules = array
        (
            array
            (
                'field' => 'parent_id', # check if it's changed, if it has, we need to move this biznitch.
                'rules' => 'required|intval|callback__validate_parent_id',
            ),
            array
            (
                'field' => 'title',
                'rules' => 'required|callback__validate_title',
            ),
            array
            (
                'field' => 'type',
                'rules' => 'required|callback__validate_type',
            ),

            array
            (
                'field' => 'locked',
                'rules' => 'required|callback__validate_locked',
            ),
            array
            (
                'field' => 'description',
                'rules' => 'callback__validate_description',
            ),
        );
        
        $this->form_validation->set_rules($rules);
        
        # This needs to be added dynamically depending on whether or not the forum type has
        # changed from forum to container.
        $rules[] = array
        (
            'field' => 'move_posts_to',
            'rules' => 'callback__validate_move_posts_to',
        );

        return TRUE;
    }
    
    function _validate_description($str="")
    {
        return substr($str, 0, 255);
    }
    
    function _validate_locked($str="")
    {
        return ($str) ? 1 : 0;
    }
    
    function _validate_move_posts_to($str="")
    {
        if ( ! $this->forum_model->get((int)$str))
        {
            $this->form_validation->set_message('_validate_parent_id', 'This forum does not appear to exist in the database');
            return FALSE;
        }
        return TRUE;
    }
    
    function _validate_parent_id($str="")
    {
        if ( ! $this->forum_model->get((int)$str))
        {
            $this->form_validation->set_message('_validate_parent_id', 'This forum does not appear to exist in the database');
            return FALSE;
        }
        return TRUE;
    }
    
    function _validate_title($str="")
    {
        return substr($str, 0, 100);
    }
    
    function _validate_type($str="")
    {
        return (int)$str;
    }
    
    function _remap($fid=0)
    {
        $this->index($fid);
    }
}

The more I think about it, the more complex it seems to become. Should I set the rules in the controller method? Should I just add everything I need as a property of the controller so it can be shared between the validation rules and the controller? Should I just pass another parameter through the certain validation methods? I know this is a big ask and I know that my code is a real mess at the moment, but I'd really appreciate some input on how to get this working like I want it to.

Many thanks in advance.
#2

[eluser]got 2 doodle[/eluser]
Hey, I'm not sure if this applies because I'm not quite sure I understand your question.

A project I am working on now is a volunteer sign up form where certain tasks are only presented if the volunteer's qualifications match. At first the client imposed three restrictions, so that was fairly easy because there were only eight possible combinations from 'zero' qualifications to 'all' qualifications.

My first approach was to use a 'switch case' routine. as below. model function shown to retrieve data.
Code:
/*-----------------------------------------------------------------------*/

function get_tasks_by_qualifications($level) {

/*-----------------------------------------------------------------------*/

    

    $this->db->from('task');
switch ($level) {
    case '0':   // no qualifications
        $this->db->where('v_age !=','YES');
        $this->db->where('v_license !=','YES');
        $this->db->where('food_handler !=','YES');
    break;
    case '1':  // valid age
        $this->db->where('v_license !=','YES');
        $this->db->where('food_handler !=','YES');
    break;
    case '2':  // valid license
        $this->db->where('v_age !=','YES');
        $this->db->where('food_handler !=','YES');
    break;
    case '3':  // valid age + valid license
        $this->db->where('food_handler !=','YES');
    break;
    case '4':  // food handler
        $this->db->where('v_age !=','YES');
        $this->db->where('v_license !=','YES');
    break;
    case '5':  // valid age + food handler
        $this->db->where('v_license !=','YES');
    break;
    case '6':  // valid license + food handler
        $this->db->where('v_age !=','YES');
    break;
    case '7':  // valid age + valid license + food handler
        // all conditions are satisfied
    break;
    default:   // no qualifications
        $this->db->where('v_age !=','YES');
        $this->db->where('v_license !=','YES');
        $this->db->where('food_handler !=','YES');
    break;
}

    $query = $this->db->get();
    return $query->result();

}


and this is how I originally calculated the users qualification level - again a model function
Code:
$this->db->from('applicant');
$this->db->where('id',$id);

$query = $this->db->get();
$atts = $query->row();


$q_age =    ($atts->v_age == 'YES' ? 1 : 0);
$q_license =    ($atts->v_license == 'YES' ? 2 : 0);
$q_food =    ($atts->food_handler == 'YES' ? 4 : 0);
// calculate qualifications level

return $q_age + $q_license + $q_food;

But OH NO! the client came back with three more qualification options now I have 64! possible combinations.

Fortunately I initially approached this with a binary type of logic so I'm thinking bit-wise comparison. And I came up with this new routine.

Code:
$this->db->from('task');  
     $restrictions = array(  'v_age'         =>1,
                            'v_license'     =>2,
                            'food_handler'  =>4,
                            'bartender'     =>8,
                            'first_aid'     =>16,
                            'bondable'      =>32
                           );
    foreach (array_keys($restrictions) as $restriction ) {
     ($restrictions[$restriction] & $level ? null : $this->db->where($restriction.' !=','YES') );        
    }                            

    $this->db->select(    'task.title as title,
                         task.id as id,

                         task.description as description'

                         );


    $query = $this->db->get();
    $tasks = $query->result();

Much easier and to establish a qualification level

Code:
$qualifications = 0;
    $restrictions = array(  'v_age'         =>1,
                            'v_license'     =>2,
                            'food_handler'  =>4,
                            'bartender'     =>8,
                            'first_aid'     =>16,
                            'bondable'      =>32
                           );
    foreach (array_keys($restrictions) as $restriction ) {
     $qualifications += ($atts->$restriction == 'YES' ? $restrictions[$restriction] : 0 );        
    }                            

return $qualifications;

Maybe thinking this way will make your scenario seem less complicated.

doodle
#3

[eluser]TheFuzzy0ne[/eluser]
Thanks for your reply. I can understand your confusion, as it's hard for me to find the words to explain the problem clearly.

I think I might be on to something now, however. Essentially, it's two questions I need answered within my code - Does the forum need moving, and do the posts need moving. The view requires the parent forum and forum to be specified, so I already have this information.

I've added this code:
Code:
# Do we need to process the form?
    if ($this->input->post('submit'))
    {
        # If the parent ID has changed
        if ((int)$parent['id'] != (int)$this->input->post('parent_id'))
        {
            $this->move_forum = TRUE;
        }
        
        # If the forum is a not a container, and is to be changed to a container...
        if ($forum['type'] == FORUM_POST && (int)$this->input->post('type') == FORUM_CONTAINER)
        {
            $this->move_posts = TRUE;
        }

        # Now the _set_validation method can see what needs validating.
        $this->_set_validation_rules();
    ...

Those two properties can be used by the _set_validation_rules() method, and the controller. I think it might do the trick, but I should soon find out if it doesn't.
#4

[eluser]TheFuzzy0ne[/eluser]
I thought I should post my finishing code, in case anyone can make any use of it.

Code:
<?php

class Edit_forum extends MY_Controller
{
    var $move_topics = FALSE,
        $move_forum = FALSE;
        
    function Edit_forum()
    {
        parent::MY_Controller();
        
        ...
    }
    
    function index($forum_id=0)
    {
        $forum_id = (int)$forum_id;
        $forum = ...;
        $parent_forum = ...;
        
        # Do we need to process the form?
        if ($this->input->post('submit'))
        {
            # Do we need to move the forum?
            if ((int)$parent_forum['id'] != (int)$this->input->post('parent_id'))
            {
                $this->move_forum = TRUE;
            }
            
            # Do we need to move the topics for this forum?
            if ($forum['type'] == FORUM_POST && (int)$this->input->post('type') == FORUM_CAT)
            {
                $this->move_topics = TRUE;
            }
            
            $this->_set_validation_rules();
            
            if ($this->form_validation->run())
            {
                # Move any topics if necessary
                if ($this->move_topics)
                {
                    $this->load->model('topic_model');
                    $this->topic_model->move($forum_id, $this->input->post('move_topics_to'));
                }
                
                # Update the forum.
                $forum = array
                (
                    'id' => $forum_id,
                    'title' => $this->input->post('title'),
                    'lft' => $forum['lft'],
                    'description' => $this->input->post('description'),
                    'type' => $this->input->post('type'),
                    'locked' => $this->input->post('locked'),
                );
                
                $this->forum_model->save($forum);
                
                # Move the forum if necessary.
                if ($this->move_forum)
                {
                    $to_forum = $this->forum_model->get($this->input->post('parent_id'));
                    if ($res = $this->forum_model->move($forum['lft'], $to_forum['lft']))
                    {
                        $parent_forum = $this->forum_model->get_parent($res[0], $res[1]);
                    }
                }
                
                redirect('admin/forums/view_forum/'.$parent_forum['id']);
            }
        }

        $this->load->vars(array(...));
        
        $this->load->view('main_template');
    }
    
    function _set_validation_rules()
    {
        $this->form_validation->set_error_delimiters('', '');
        
        $rules = array
        (
            array
            (
                'field' => 'title',
                'rules' => 'required|callback__validate_title',
            ),
            array
            (
                'field' => 'type',
                'rules' => 'required|callback__validate_type',
            ),

            array
            (
                'field' => 'locked',
                'rules' => 'required|callback__validate_locked',
            ),
            array
            (
                'field' => 'description',
                'rules' => 'callback__validate_description',
            ),
        );
        
        if ($this->move_topics)
        {
            $rules[] = array
            (
                'field' => 'move_posts_to',
                'rules' => 'callback__validate_move_topics_to',
            );
        }
        
        if ($this->move_forum)
        {
            $rules[] = array
            (
                'field' => 'parent_id',
                'rules' => 'required|intval|callback__validate_parent_id',
            );
        }
        
        $this->form_validation->set_rules($rules);    

        return TRUE;
    }
    
    function _validate_description($str="")
    {
        return substr($str, 0, 255);
    }
    
    function _validate_locked($str="")
    {
        return ($str) ? 1 : 0;
    }
    
    function _validate_move_topics_to($str="")
    {
        if ( ! $str)
        {
            $this->form_validation->set_message('_validate_move_topics_to', 'You must select a forum to move any posts to.');
            return FALSE;
        }
        
        $forum = $this->forum_model->get((int)$str);
        
        if ( ! $forum)
        {
            $this->form_validation->set_message('_validate_move_topics_to', 'This forum does not appear to exist in the database');
            return FALSE;
        }
        
        if ($forum['type'] == FORUM_CAT)
        {
            $this->form_validation->set_message('_validate_move_topics_to', 'You cannot move posts to a container forum');
            return FALSE;
        }
        return TRUE;
    }
    
    function _validate_parent_id($str="")
    {
        if ( ! $this->forum_model->get((int)$str))
        {
            $this->form_validation->set_message('_validate_parent_id', 'This forum does not appear to exist in the database');
            return FALSE;
        }
        return TRUE;
    }
    
    function _validate_title($str="")
    {
        return substr($str, 0, 100);
    }
    
    function _validate_type($str="")
    {
        return (int)$str;
    }
    
    function _remap($fid=0)
    {
        $this->index($fid);
    }
}

Cheers, doodle!




Theme © iAndrew 2016 - Forum software by © MyBB