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();
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.
Related
I'm working on breaking up a large, monolithic class into several subclasses, but it's too much to do all at once so I'm looking to split them out one by one over several releases as time permits. It's an authentication class that authorizes some channel, so currently it looks like this:
$auth = new Auth($user, $data);
$output = $auth->authChannel($channelName);
Inside Auth, it basically looks like this:
public function __construct($user, $data)
{
$this->user = $user;
$this->data = $data;
}
public function authChannel($channel)
{
$this->setUserData();
if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
return $this->$authFunction();
} else {
// invalid channel
}
}
So self::CHANNEL_AUTH_FUNCTIONS is basically ['channelA' => 'authChannelA', 'channelB' => 'authChannelB'], etc., and all those functions are in this one class.
Now what I want to do, one at a time, is if $legacyChannel => callLegacyFunction() / else $newChannel => instantiate its own class and call auth().
So I put Auth.php into its own namespace and have the new Channel.php class in that same namespace. And Channel extends Auth.
Currently I have this:
public function authChannel($channel)
{
$this->setUserData();
if (isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel];
if ($authFunction) {
return $this->$authFunction();
} else {
$authClassName = __NAMESPACE__ . '\\' . ucwords($channel);
$authClass = new $authClassName($user, $data);
return $authClass->auth();
}
} else {
// invalid channel
}
}
Is there a better way to do this? Currently it seems a bit wasteful since two different objects are created and the setUserData() function for example would need to be called again I believe. I'm also wondering if there's a better way to get the dynamic class name other than through __NAMESPACE__ . / . $className.
You'll have to work quite a bit until that code starts looking better. I'll try to suggest as few changes as possible, to make "migration" as painless as possible, although you are a few steps removed from a clean design.
To start with, you can create an AuthStrategyInterface for your new authentication classes.
interface AuthStrategyInterface
{
public function supports(string $channel): bool;
public function auth($user, $data);
}
Each of your new authentication classes should implement this interface. The method supports($channel) is easy enough to understand: if a authentication class can deal with certain channel, it should return true.
Your Auth class would need a way to get these strategies injected. Usually you would do that in the constructor... but to leave your API unchanged we'll just create a setter method for that.
When executing authChannel(), it will first check on the injected strategies to see if any supports the used $channel, and use that if possible. If not, goes back to check your old implementations.
This way you do not need to touch any of the old code as you add new authentication strategies. As you add new implementations, you are gradually strangling the legacy system. At one point no of the old implementations are used, and you can move on to a new code refactoring phase.
class Auth {
private iterable $strategies = [];
public function __construct($user, $data)
{
$this->user = $user;
$this->data = $data;
}
public function setAuthStrategies(iterable $strategies)
{
$this->strategies = $strategies;
}
public function authChannel($channel)
{
$this->setUserData();
// check if any of the new strategies supports
foreach ($this->strategies as $strategy) {
if ($strategy->supports($channel) {
return $strategy->auth($this->user, $this->data);
}
}
// check "legacy" authentication methods.
if (isset(self::CHANNEL_AUTH_FUNCTIONS[$channel])) {
$authFunction = self::CHANNEL_AUTH_FUNCTIONS[$channel];
return $this->$authFunction($this->user, $this->data);
}
// no valid authentication method
return false;
}
}
To use it, you would do something like this:
$fooAuthStrategy = new FooAuthStrategy();
$barAuthStrategy = new BarAuthStrategy();
$bazAuthStrategy = new BazAuthStrategy();
$auth = new Auth($user, $data);
$auth->setAuthStrategies(
[
$fooAuthStrategy,
$barAuthStrategy,
bazAuthStrategy
]
);
$auth->authChannel($channel);
The specifics would change according to how exactly your application is set-up, but something like this would take you further in a good direction than your current approach.
I don't know if I understood the question correctly, but you couldn't do it like that?
public function authChannel($channel)
{
$this->setUserData();
if (!isset(self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel])) {
// Invalid channel
return;
}
return self::CHANNEL_AUTH_LEGACY_FUNCTIONS[$channel]
? $this->$authFunction()
: parent::auth();
}
I'm wondering is there a better way of "braking out" of method under some conditions. Let me better explain this with code:
function execute($context)
{
// some init actions
$event = new BeforeOperationOne();
$this->dispatch($event);
if ($event->accessGranted()) {
$context->setUser($this->user);
// other repeated code
return;
}
$result = $this->operationOne();
// some other code
$event = new BeforeOperationTwo();
$this->dispatch($event);
if ($event->accessGranted()) {
$context->setUser($this->user);
// other repeated code
return;
}
// this is not important what is access checker,
// this is just to show that all following code uses data
// computed in previous steps
$accessChecker = new AccessChecker($result);
$this->operationTwo(accessChecker);
// some other code
$event = new BeforeOperationThree();
$this->dispatch($event);
if ($event->accessGranted()) {
$context->setUser($this->user);
// other repeated code
return;
}
$this->operationThree();
// some other code
}
We have repeated here the condition, setting user in context when user has access from event. What options I can think about is:
The ugly do-while(false) or goto (I better leave it this way as it is now)
Extract this to method and change the condition to if (!$this->handleEvent($event, $context)) { return; }- This doesn't help to much and cannot think a better name handle doesn't say it's returning something
Build array of closures for operations and loop it through checking. We can assume that all event classes are derived from common class with accessGranted methods. This can be ugly as some operations need data from previous "steps", I would have to keep them outside and pass them.
Throw and catch exceptions that user has access - another bad solution.
Do you have any ideas how to write it better?
#Greg i was thinking about something like that:
abstract class Handler
{
protected $nextHandler = null;
abstract public function Request($request);
public function setNextHandler(Handler $handler)
{
$this->nextHandler = $handler;
}
protected function someOperations($event)
{
//i copied this section, so you must shape that
$this->dispatch($event);
if ($event->accessGranted()) {
$context->setUser($this->user);
// other repeated code
return true;
}
return false;
}
}
class BeforeOperationOneHandler extends Handler
{
public function Request($request)
{
if ($this->someOperations(new BeforeOperationOne())) {
return;
}
$result = $this->operationOne(); // shape this too
return $this->nextHandler->Request($result);
}
}
class BeforeOperationTwoHandler extends Handler
{
public function Request($request)
{
if ($this->someOperations(new BeforeOperationTwo())) {
return;
}
$accessChecker = new AccessChecker($result); // shape this too
$result = $this->operationTwo(accessChecker);
return $this->nextHandler->Request($result);
}
}
class BeforeOperationThreeHandler extends Handler
{
public function Request($request)
{
if ($this->someOperations(new BeforeOperationThree())) {
return;
}
$result = $this->operationThree(); // shape this too
return $this->nextHandler->Request($result);
}
}
class DefaultHandler extends Handler
{
public function Request($request)
{
// this is the last step
}
}
function execute($context)
{
// some init actions
$beforeOperationOneHandler = new BeforeOperationOneHandler();
$beforeOperationTwoHandler = new BeforeOperationTwoHandler();
$beforeOperationThreeHandler = new BeforeOperationThreeHandler();
$defaultHandler = new DefaultHandler();
// set the sequence of the elements
// BeforeOperationOneHandler > BeforeOperationTwoHandler > BeforeOperationThreeHandler> DefaultHandler
$beforeOperationOneHandler->setNextHandler($beforeOperationTwoHandler);
$beforeOperationTwoHandler->setNextHandler($beforeOperationThreeHandler);
$beforeOperationThreeHandler->setNextHandler($defaultHandler);
return $beforeOperationOneHandler->Request($some_init);
}
It's only quickly written shape of "chain of responsibility" pattern so i thoughtlessly copied some of your code fragments
I hope this will lead you to a better solution
I currently have a manual method for registering helpers into my base connection class which goes pretty much as follows:
class db_con
{
// define the usual suspect properties..
public $helpers; // helper objects will get registered here..
public function __construct()
{
// fire up the connection or die trying
$this->helpers = (object) array();
}
public function __destruct()
{
$this->helpers = null;
$this->connection = null;
}
// $name = desired handle for the helper
// $helper = name of class to be registered
public function register_helper($name, $helper)
{
if(!isset($this->helpers->$name, $helper))
{
// tack on a helper..
$this->helpers->$name = new $helper($this);
}
}
// generic DB interaction methods follow..
}
Then a helper class such as..
class user_auth
{
public function __construct($connection){ }
public function __destruct(){ }
public function user_method($somevars)
{
// do something with user details
}
}
So after creating the $connection object, i would then manually register a helper like so:
$connection->register_helper('users', 'user_auth');
Now my question is, could I somehow autoload helper classes inside the base connection class? (within the register_helper() method or similar) Or am I limited to loading them manually or via an external autoloader of some form?
My apologies if this question has been answered elsewhere, but I just haven't found it (not for lack of trying) and I haven't any real experience autoloading anything yet.
Any help or pointers greatly appreciated, thanks in advance! :)
EDIT: As per Vic's suggestion this is the working solution I came up with for the register method..
public function register_handlers()
{
$handler_dir = 'path/to/database/handlers/';
foreach (glob($handler_dir . '*.class.php') as $handler_file)
{
$handler_bits = explode('.', basename($handler_file));
$handler = $handler_bits[0];
if(!class_exists($handler, false))
{
include_once $handler_file;
if(!isset($this->handle->$handler, $handler))
{
$this->handle->$handler = new $handler($this);
}
}
}
}
This appears to include and register the objects absolutely fine for now, whether this solution is a "good" one or not, I can't know without more input or testing.
The code could look something like below, but why would you need this?
public function register_helper($name, $helper)
{
if(!isset($this->helpers->$name, $helper))
{
$this->load_class($helper);
// tack on a helper..
$this->helpers->$name = new $helper($this);
}
}
private function load_class($class)
{
if( !class_exists($class, false) ) {
$class_file = PATH_SOME_WHERE . $class . '.php';
require $class_file;
}
}
I'm trying to figure out how to limit access to specific resources in a PHP project I'm currently working on. I've looked for existing solutions, but none of them really fit what I need (for example, Zend_Acl).
Now I've come up with something like this: (Of course, this is very, very simplified. No exceptions or whatever. Just enough to get the point across)
class Access {
protected $_context;
protected $_handlers;
public function __construct($context) {
$this->_context = $context;
}
public static function registerHandler(Access_Handler $handler) {
$key = $handler->getContextType().'/'.$handler->getResourceType();
self::$_handlers[$key] = $handler;
}
public function isAllowed($resource) {
return $this->getHandler($resource)->isAllowed($this->_context, $resource);
}
public function getHandler($resource) {
// Look for and return the appropriate handler for the combination of
// $context and $resource
}
}
abstract class Access_Handler {
$_contextType;
$_resourceType;
abstract public function isAllowed();
}
class Access_Handler_UserInvoice extends Access_Handler {
$_contextType = 'User';
$_resourceType = 'Invoice';
public function isAllowed($user, $invoice) {
if($invoice->user_id === $user->id) {
return true;
}
return false;
}
}
I would then do something like this in my Application Bootstrap:
protected function $_initAccessHandlers() {
Access::registerHandler(new Access_Handler_UserInvoice());
}
And in my controller (because I've heard that's where you should put your access control) I'd have something like this:
class InvoiceController {
public function viewAction() {
// $this->me is of type User
$access = new Access($this->me);
if($access->isAllowed($this->invoice)) {
// ...
}
}
}
I haven't tested the code, so there might be typos or other errors, but I think you get the gist. Also, in reality I'd probably implement Access as a Singleton or a Multiton, but that's not what my question is about.
Is this the right way to do it? It seems so natural to me, but then I'm wondering why nobody else is doing it in this fashion.
My development stack is PHP/MySQL/Zend Framework/Doctrine.
With Zend_Acl you will perform the basic control, like:
$acl = new Zend_Acl();
$acl->add(new Zend_Acl_Resource('article'));
$acl->addRole(new Zend_Acl_Role('author'));
$acl->deny();
$acl->allow('author', 'article', array('list'));
Then you can use assertions to do what you want:
$user = Zend_Auth::getInstance()->getIdentity();
$assertion = new My_Acl_Assertion_ArticleEditCheck($user);
$acl->allow('author', 'article', 'edit', $assertion);
You can instead of pass the user object to the assertion, implement it as a internal property and also work on the request parameters if necessary.
References:
http://framework.zend.com/manual/en/zend.acl.advanced.html
Dynamic custom ACL in zend framework?
For more advanced use of assertions, look at:
http://www.aviblock.com/blog/2009/03/19/acl-in-zend-framework/
http://ralphschindler.com/2009/08/13/dynamic-assertions-for-zend_acl-in-zf
Example:
class UserStorage {
public function addUser(User $user) { //saves to db }
}
class User {
public function setName($name);
}
What if I add a user to the user storage and later change that user object? In this case you might argue that user objects only should be stored on __destruct. But sometimes this isn't an option (eg imagine the user is displayed and updated afterwards).
I agree with Peter, the above model seems a little quirky to me and I would recommend against implicit save to the datastore.
Additionally, a pattern to use is something like:
class UserStorage {
$_user;
function addUser(User user, commit = true) {
if (commit) {
// save to db
} else {
// populate your internal instance
$_user = user;
}
}
}
So if you have multiple updates of a User object in the execution of your PHP application, you can use
addUser(user,false)
all the way until the very last call to
addUser(user)
This will alleviate the need for multiple inserts/updates to the DB.
However, your problem of where in the application to decide to finally save to db remains, and is more about logical flow than object representation. It may be helpful to have a end() function in the script that persists all your objects to the db.
Implicit writes to the database are probably a bad idea. That should be an explicit, controlled operation.
Your pattern is a little weird to me, but I think this how you'd want to do it
class UserStorage
{
const ACTION_INSERT = 'INSERT';
const ACTION_UPDATE = 'UDPATE';
public function addUser(User $user)
{
$this->saveUser($user, self::ACTION_INSERT);
}
public function updateUser(User $user)
{
$this->saveUser($user, self::ACTION_UPDATE);
}
protected function saveUser(User $user, $action)
{
switch ($action) {
case self::ACTION_INSERT:
// INSERT query
break;
case self::ACTION_UPDATE:
// UPDATE query
break;
default:
throw new Exception('Unsupported action');
}
}
}
class User
{
public function setName($name)
{
// whatever
}
}
$userStorage = new UserStorage();
$user = new User();
$userStorage->addUser($user);
$user->setName('Peter');
try {
$userStorage->updateUser($user);
} catch (Exception $e) {
echo "There was an error saving this user: " . $e->getMessage();
}
But Personally I'm not crazy about this class design. There are some well-established patterns for this that are less confusing, such as ActiveRecord.