Zend Navigation: Where should I load the ACL 'Role' in a private application
I am working on a 'private' application, you must be logged in to do anything at all. This gives me a bit of a an issue loading the role for Zend Navigation. Currently I am 'init'ing Zend Navigation in my bootstrap; that was fine until I added ACL to Zend Nav. The issue is that I want to load the 'userRole' from my auth storage, but there will not be storage yet until the user logs in, so that gets me a 'Trying to get property of non-object' warning at the login page. This is because before login, there is nothing in auth's storage, so auth->userRole
is 'nothing' because auth->getInstance()->getIdentity()->???
would be empty, until a user logs in and the auth is configured.
I'm not using Zend Nav at the login page, in fact I have an alternate layout for the login page (no开发者_如何学C nav at all); 'if !$auth->hasIdentity'
(false) use the login layout, only show the login page for that matter system-wide, as I said the application is completely private so defaulting to a 'guest' role or something like that seems like a 'dirty' approach, because once a user logs in it will have to reconfigure auth anyhow. It just don't seem right to set a generic auth identity just to please the login page.
What I am getting at is, where is a good place to move the 'init' of Zend Nav, or at least move the configure ACL part? May as well move the whole thing?
This is what I have in my bootstrap.php for Zend Navigation:
protected function _initNavigation() {
$this->_logger->info('Bootstrap ' . __METHOD__);
$this->bootstrap('layout');
$layout = $this->getResource('layout');
$view = $layout->getView();
$config = new Zend_Config_Xml(APPLICATION_PATH . '/configs/navigation.xml', 'nav');
$container = new Zend_Navigation($config);
$acl = new Application_Model_Acl_Acl();
$role = Zend_Auth::getInstance()->getIdentity()->userRole;
$view->navigation($container)->setAcl($acl)->setRole($role);
}
It is the '$role = Zend_Auth::getInstance()->getIdentity()->userRole'
that is empty (or a non-object) at the time bootstrap runs.
All of my ACL takes place either in the controller (at the action) or some may take place in the models at some point, although I don't anticicpate web service or anything, so maybe they will stay in the controller, but I have the flexibility because I have the ACL in the models (that would be the 'domain' right?
I am only using ACL on Zend Nav for layout, user experience purposes; The menu and links I get from Zend Nav will be 'greyed', non-existant, or active (and visible) according to the user role, for example a 'user' role will not get many 'admin' options, and this is re-enforced by ACL in the controller so one could not simply type the url and get to an area either.
My other thought was that maybe I should be thinking of moving my login into the bootstrap for this situation, but I'm not sure that is a good idea either?
EDIT: This is the code that I put into a Front Controller Plugin:
class Plugins_Controller_ZendNavInit extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$config = new Zend_Config_Xml(APPLICATION_PATH . '/configs/navigation.xml', 'nav');
$container = new Zend_Navigation($config);
$acl = new Application_Model_Acl_Acl();
$layout = Zend_Layout::getMvcInstance();
$view = $layout->getView();
$role = Zend_Auth::getInstance()->getIdentity()->userRole;
$view->navigation($container)->setAcl($acl)->setRole($role);
}
}
}
Works great; I of course, had to register my plugin just like any other. I also moved my Auth checking to a front-controller plugin too.
You could build an anonymous role before login and handle a very short ACL policy for anonymous users. Check this response for example on how to catch unauthentified users on PreDispatch with an Auth Plugin.
I would not pull it directly from the Auth storage. If you pull it from Zend_Auth and during that users session the user's role is changed, you will either have to force a logout/login or update the info in the Zend_Auth storage. I find it easier to setup a Role class and perform the logic to determine the role there or set a default (guest) role. The User role class extends my User model and implements Zend_Acl_Role_Interface. I can then query the db from within the Role class. I started implementing this a day or so ago but here is an example:
<?php
class Zfcms_Acl_Role_User extends Zfcms_Model_Users implements Zend_Acl_Role_Interface {
/**
* Unique id of Role
*
* @var string
*/
protected $_roleId;
public $_inheritsFrom = 'guest';
public $_defaultRole = 'user';
/**
* Sets the Role identifier
*
* @param string $id
* @return void
* Defined by Zend_Acl_Role_Interface; returns the Role identifier
*
* @return string
*/
public function getRoleId()
{
//$auth = Zend_Auth::getInstance();
if(Zend_Auth::getInstance()->hasIdentity())
{
$this->_roleId = self::getUserRoleById(Zend_Auth::getInstance()->getIdentity()->user_id);
return $this->_roleId;
}
elseif(!Zend_Auth::getInstance()->hasIdentity() && !isset($this->role))
{
//return 'guest';
return (string) new Zfcms_Acl_Role_Guest();
} else {
throw new Zend_Controller_Action_Exception('Invalid user roleId', 110);
}
}
public function getDefaultRole()
{
return $this->_defaultRole;
}
public function getPrivsByRole($role)
{
//TODO: Design db table to hold privs
}
private function setPrivs()
{
//TODO: implement
}
/**
* Defined by Zend_Acl_Role_Interface; returns the Role identifier
* Proxies to getRoleId()
*
* @return string
*/
public function __toString()
{
return $this->getRoleId();
}
public function getUserRoleById($id)
{
$this->_id = $id;
$query = $this->select()
->from('users', array('user_id', 'role'))
->where('user_id = ?', $this->_id);
$result = $this->fetchAll($query);
foreach($result as $userRole)
{
$role = $userRole->role;
}
return $role;
}
}
精彩评论