Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Interfaces in Kohana 3.1.3

I'm trying to build a form wizard in Kohana and am learning a bit as I go. One of the things that I've learn might work best is utilizing a state pattern in my class structure to manage the different steps a user can be in during the form process.

After doing some research, I've been thinking that the best approach may be to use an interface and have all of the steps act as states that implement the interface. After a state validates, it will change a session variable to the next step, which can be read upon the initial load of the interface and call the correct state to use.

Does this approach make sense? If so, how the heck do I make it happen (how do I best structure the filesystem?)

Here is the rough start I've been working on:

<?php defined('SYSPATH') or die('No direct script access.');

/**
* Project_Builder @state
* Step_One @state
* Step_Two @state
**/

interface Project_Builder 
{
    public function do_this_first();
    public function validate();
    public function do_this_after();
}

class Step_One implements Project_Builder {

    public function __construct
    {
        parent::__construct();

        // Do validation and set a partial variable if valid

    }

    public function do_this_first() 
    {
        echo 'First thing done';
        // This should be used to set the session step variable, validate and add project data, and return the new view body.
            $session->set('step', '2');


    }
    public function do_this_after()
    {
        throw new LogicException('Have to do the other thing first!');
    }

}

class Step_Two implements Project_Builder {
    public function do_this_first()
    {
        throw new LogicException('Already did this first!');
    }
    public function do_this_after()
    {
        echo 'Did this after the first!';
        return $this;
    }
}

class Project implements Project_Builder {
    protected $state;
    protected $user_step;
    protected $project_data

    public function __construct()
    {
        // Check the SESSION for a "step" entry. If it does not find one, it creates it, and sets it to "1".
        $session = Session::instance('database');

        if ( ! $session->get('step'))
        {
            $session->set('step', '1');
        }

        // Get the step that was requested by the client.
        $this->user_step = $this->request->param('param1');

        // Validate that the step is authorized by the session.
        if ($session->get('step') !== $this->user_step)
        {
            throw new HTTP_Exception_404('You cannot skip a step!');
        }

        // Check if there is user data posted, and if so, clean it.
        if (HTTP_Request::POST == $this->request->method())
        {
            foreach ($this->request->post() as $name => $value)
            {
                $this->project_data["$name"] = HTML::chars($value);
            }
        }   

        // Trigger the proper state to use based on the authorized session step (should I do this?)
        $this->state = new Step_One;
    }
    public function doThisFirst()
    {
        $this->state = $this->state->do_this_first();
    }
    public function doThisAfter()
    {
        $this->state = $this->state->do_this_after();
    }
}

$project = new Project;
try
{
    $project->do_this_after(); //throws exception
}
catch(LogicException $e)
{
    echo $e->getMessage();
}

$project = new Project;
$project->do_this_first();
$project->validate();
$project->do_this_after();
//$project->update();
like image 259
Dave Kiss Avatar asked May 28 '11 22:05

Dave Kiss


1 Answers

Your way certainly looks possible, however I would be tempted to keep it simpler and use some of Kohanas build in features to take care of what you want. For example, I would use Kostache (mustache) and have separate View classes (and potentially templates) for each step. Then the controller becomes quite simple. See the example below (missing session stuff and validation of the step_number). All of the validation is handled in the model. If there is a validation error, an exception can be thrown which can then pass error messages back to the View.

<?php

class Wizard_Controller {

    function action_step($step_number = 1)
    {
        $view = new View_Step('step_' + $step_number);

        if ($_POST)
        {
            try
            {
                $model = new Model_Steps;
                $model->step_number = $step_number;
                if ($model->save($_POST))
                {
                    // Go to the next step
                    $step_number++;
                    Request::current()->redirect('wizard/step/'.$step_number);    
                }
            }
            catch (Some_Kind_Of_Exception $e)
            {
                $view->post = $_POST;
                $view->errors = $e->errors();
            }
        }

        $view->render();
    }
}
?>

Hope this makes sense.

like image 103
ae. Avatar answered Oct 08 '22 13:10

ae.