利用 Zend_Controller_Action_Helper_FlashMessenger 将 Zend_View_Helper_Action 小部件重构为模型
我尝试创建一个登录小部件,可以随意将其包含在需要登录的应用程序内容之外的任何操作中。 我的想法是,我想要一种 DRY 方法来渲染登录框,其中包含先前登录尝试中的任何错误消息。 为了实现这一点,我使用了 FlashMessenger< /a> 用于保存登录尝试失败的结果。 该小部件还使用 Post/Redirect/Get-pattern 来避免使用浏览器后退按钮时多次发布数据的问题。
现在,问题是我宁愿不使用 Zend_View_Helper_Action 因为该助手克隆了请求并创建了另一次调度循环,这非常消耗资源。 因此,通过查看下面的代码,您能否给我一些有关如何重构代码的建议,例如:
- 小部件可以包含在任意视图脚本中
- 上次登录尝试的结果呈现
- 小部件不会在调度循环中调用运行
目前,登录小部件是通过在视图脚本中调用:
echo $this->action('auth', 'login-widget');
AuthController.php:
class AuthController extends Zend_Controller_Action {
// This method is invoked by Zend_View_Helper_Action to render the
// login widget
public function loginWidgetAction () {
$flashMessenger = $this->_helper->flashMessenger->setNamespace('login');
$this->view->messages = $flashMessenger->getMessages();
}
public function loginAction () {
if($this->getRequest()->isPost()) {
$result = Auth::doLogin($this->getRequest()->getPost());
if($result->isValid()) {
$this->_redirect('user');
}
else {
$flashMessenger = $this->_helper->flashMessenger->
setNamespace('login');
foreach($result->getMessages() as $message) {
$flashMessenger->addMessage($message);
}
}
}
// This will be changed to redirect either to the index page,
// or the page the request originated from if available.
$this->_redirect('');
}
[...]
}
/models/Auth.php 来呈现的:
/**
* Model for encapsulating the actions that deals with authentication,
* such as registering and activating users, as well as logging in and
* logging out.
* @todo: Refactor this to remove static methods
*/
class Auth {
/**
*
* @return Zend_Auth_Result
*/
public static function doLogin ($credentials) {
$authAdapter = new Auth_Adapter_DbTable(
Zend_Db_Table::getDefaultAdapter(),
'Users',
'username',
'pwdHash',
'SHA1(CONCAT(?, salt))'
);
$authAdapter->setIdentity($credentials['username']);
$authAdapter->setCredential($credentials['password']);
$auth = Zend_Auth::getInstance();
return $auth->authenticate($authAdapter);
}
[...]
}
/models/Auth/Adapter/DbTable.php:
class Auth_Adapter_DbTable extends Zend_Auth_Adapter_DbTable {
/**
* authenticate() - defined by Zend_Auth_Adapter_Interface. This method
* is called to attempt an authenication. Previous to this call, this
* adapter would have already been configured with all necessary
* information to successfully connect to a database table and attempt
* to find a record matching the provided identity.
*
* @throws Zend_Auth_Adapter_Exception if answering the authentication
* query is impossible
* @see library/Zend/Auth/Adapter/Zend_Auth_Adapter_DbTable#authenticate()
* @return MedU_Auth_Result
*/
public function authenticate() {
return parent::authenticate();
}
/**
* _authenticateValidateResult() - This method attempts to validate that
* the record in the result set is indeed a record that matched the
* identity provided to this adapter.
*
* Additionally it checks that the user account is activated.
*
* @param array $resultIdentity
* @return MedU_Auth_Result
*/
protected function _authenticateValidateResult($resultIdentity)
{
$result = parent::_authenticateValidateResult($resultIdentity);
if(!$result->isValid()) { return $result; }
$this->_checkAccountIsActivated($resultIdentity);
$this->_checkAccountIsSuspended($resultIdentity);
// Overwrite the username supplied by the user and instead
// use the name supplied upon registration, i.e if the
// user signs in as uSERNAME and registered as Username,
// the identity is Username
$this->_authenticateResultInfo['identity'] =
$resultIdentity[$this->_identityColumn];
return $this->_authenticateCreateAuthResult();
}
protected function _checkAccountIsActivated ($resultIdentity) {
if(!$resultIdentity['activated']) {
$this->_authenticateResultInfo['code'] =
MedU_Auth_Result::FAILURE_NOT_ACTIVATED;
$this->_authenticateResultInfo['messages'] =
array('The account has not yet been activated.
Please click on the link provided in the
activation email.');
}
}
protected function _checkAccountIsSuspended ($resultIdentity) {
if($resultIdentity['suspended']) {
$this->_authenticateResultInfo['code'] =
MedU_Auth_Result::FAILURE_SUSPENDED;
$this->_authenticateResultInfo['messages'] =
array('The account has been suspended.
If you feel this is a mistake,
please contact our support: [email protected]');
}
}
/**
* _authenticateCreateAuthResult() - This method creates a
* MedU_Auth_Result object from the information that has
* been collected during the authenticate() attempt.
*
* @return MedU_Auth_Result
*/
protected function _authenticateCreateAuthResult()
{
return new MedU_Auth_Result(
$this->_authenticateResultInfo['code'],
$this->_authenticateResultInfo['identity'],
$this->_authenticateResultInfo['messages']
);
}
}
/views/scripts/auth/partials/login-widget.phtml
// The fetchForm-view helper returns a Zend_Form object.
// The form definition (xml) is attached below in the
// code box below this one.
<div class="box">
<h3>Login</h3>
<?php if($this->messages) : ?>
<ul class="errors">
<?php foreach($this->messages as $message) : ?>
<li><?php echo $message ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div>
<?php echo $this->fetchForm('user', 'login') ?>
</div>
</div>
/application/configs/forms.xml
// Configuration of the Login form. 'user' and 'login' are
// just keys for the view helper to return the correct form
<forms>
<user>
<login>
<action value="/auth/login" />
<method value="post" />
<elements>
<username>
<type value="text"></type>
<options>
<label value="Username" />
<required value="true" />
<validators>
<alnum validator="alnum" />
<strlen validator="StringLength">
<options min="6" max="45" />
</strlen>
</validators>
</options>
</username>
<password>
<type value="password" />
<options>
<label value="Password" />
<required value="true" />
<validators>
<strlen validator="StringLength">
<options min="6" max="20" />
</strlen>
</validators>
</options>
</password>
<submit>
<type value="submit" />
<options>
<label value="Log in" />
</options>
</submit>
</elements>
</login>
I've tried to create a login widget that can be included at will in any action that is outside the content of the application that requires login. My thought here was that I wanted a DRY-approach for rendering the login box with any error messages from a previous login attempt. To enable this I utilized the FlashMessenger for saving the result of a failed login-attempt. The widget also uses the Post/Redirect/Get-pattern to avoid the problem of posting data multiple times when using the browser back-button.
Now, the problem is that I would rather not use the Zend_View_Helper_Action since that helper clones the request and creates another run of the dispatch-loop, which is pretty resource intensive. So, by looking at the code below, could you give me some advice on how to refactor the code such that:
- The widget can be included in an arbitrary view script
- Results from a previous login attempt is rendered
- The widget does not invoke a run in the dispatch-loop
Currently, the login widget is rendered by calling, in the view scripts:
echo $this->action('auth', 'login-widget');
AuthController.php:
class AuthController extends Zend_Controller_Action {
// This method is invoked by Zend_View_Helper_Action to render the
// login widget
public function loginWidgetAction () {
$flashMessenger = $this->_helper->flashMessenger->setNamespace('login');
$this->view->messages = $flashMessenger->getMessages();
}
public function loginAction () {
if($this->getRequest()->isPost()) {
$result = Auth::doLogin($this->getRequest()->getPost());
if($result->isValid()) {
$this->_redirect('user');
}
else {
$flashMessenger = $this->_helper->flashMessenger->
setNamespace('login');
foreach($result->getMessages() as $message) {
$flashMessenger->addMessage($message);
}
}
}
// This will be changed to redirect either to the index page,
// or the page the request originated from if available.
$this->_redirect('');
}
[...]
}
/models/Auth.php:
/**
* Model for encapsulating the actions that deals with authentication,
* such as registering and activating users, as well as logging in and
* logging out.
* @todo: Refactor this to remove static methods
*/
class Auth {
/**
*
* @return Zend_Auth_Result
*/
public static function doLogin ($credentials) {
$authAdapter = new Auth_Adapter_DbTable(
Zend_Db_Table::getDefaultAdapter(),
'Users',
'username',
'pwdHash',
'SHA1(CONCAT(?, salt))'
);
$authAdapter->setIdentity($credentials['username']);
$authAdapter->setCredential($credentials['password']);
$auth = Zend_Auth::getInstance();
return $auth->authenticate($authAdapter);
}
[...]
}
/models/Auth/Adapter/DbTable.php:
class Auth_Adapter_DbTable extends Zend_Auth_Adapter_DbTable {
/**
* authenticate() - defined by Zend_Auth_Adapter_Interface. This method
* is called to attempt an authenication. Previous to this call, this
* adapter would have already been configured with all necessary
* information to successfully connect to a database table and attempt
* to find a record matching the provided identity.
*
* @throws Zend_Auth_Adapter_Exception if answering the authentication
* query is impossible
* @see library/Zend/Auth/Adapter/Zend_Auth_Adapter_DbTable#authenticate()
* @return MedU_Auth_Result
*/
public function authenticate() {
return parent::authenticate();
}
/**
* _authenticateValidateResult() - This method attempts to validate that
* the record in the result set is indeed a record that matched the
* identity provided to this adapter.
*
* Additionally it checks that the user account is activated.
*
* @param array $resultIdentity
* @return MedU_Auth_Result
*/
protected function _authenticateValidateResult($resultIdentity)
{
$result = parent::_authenticateValidateResult($resultIdentity);
if(!$result->isValid()) { return $result; }
$this->_checkAccountIsActivated($resultIdentity);
$this->_checkAccountIsSuspended($resultIdentity);
// Overwrite the username supplied by the user and instead
// use the name supplied upon registration, i.e if the
// user signs in as uSERNAME and registered as Username,
// the identity is Username
$this->_authenticateResultInfo['identity'] =
$resultIdentity[$this->_identityColumn];
return $this->_authenticateCreateAuthResult();
}
protected function _checkAccountIsActivated ($resultIdentity) {
if(!$resultIdentity['activated']) {
$this->_authenticateResultInfo['code'] =
MedU_Auth_Result::FAILURE_NOT_ACTIVATED;
$this->_authenticateResultInfo['messages'] =
array('The account has not yet been activated.
Please click on the link provided in the
activation email.');
}
}
protected function _checkAccountIsSuspended ($resultIdentity) {
if($resultIdentity['suspended']) {
$this->_authenticateResultInfo['code'] =
MedU_Auth_Result::FAILURE_SUSPENDED;
$this->_authenticateResultInfo['messages'] =
array('The account has been suspended.
If you feel this is a mistake,
please contact our support: [email protected]');
}
}
/**
* _authenticateCreateAuthResult() - This method creates a
* MedU_Auth_Result object from the information that has
* been collected during the authenticate() attempt.
*
* @return MedU_Auth_Result
*/
protected function _authenticateCreateAuthResult()
{
return new MedU_Auth_Result(
$this->_authenticateResultInfo['code'],
$this->_authenticateResultInfo['identity'],
$this->_authenticateResultInfo['messages']
);
}
}
/views/scripts/auth/partials/login-widget.phtml
// The fetchForm-view helper returns a Zend_Form object.
// The form definition (xml) is attached below in the
// code box below this one.
<div class="box">
<h3>Login</h3>
<?php if($this->messages) : ?>
<ul class="errors">
<?php foreach($this->messages as $message) : ?>
<li><?php echo $message ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div>
<?php echo $this->fetchForm('user', 'login') ?>
</div>
</div>
/application/configs/forms.xml
// Configuration of the Login form. 'user' and 'login' are
// just keys for the view helper to return the correct form
<forms>
<user>
<login>
<action value="/auth/login" />
<method value="post" />
<elements>
<username>
<type value="text"></type>
<options>
<label value="Username" />
<required value="true" />
<validators>
<alnum validator="alnum" />
<strlen validator="StringLength">
<options min="6" max="45" />
</strlen>
</validators>
</options>
</username>
<password>
<type value="password" />
<options>
<label value="Password" />
<required value="true" />
<validators>
<strlen validator="StringLength">
<options min="6" max="20" />
</strlen>
</validators>
</options>
</password>
<submit>
<type value="submit" />
<options>
<label value="Log in" />
</options>
</submit>
</elements>
</login>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
据我了解您的解决方案,您当前对
Zend_View_Helper_Action
帮助器的调用只不过是渲染登录表单。 为什么不编写视图助手来从 flash-messenger 检索与登录相关的消息并呈现登录表单?编辑:
您可以从其静态代理检索 flash-messenger:
您不需要在视图助手中实例化新的 flash-messenger,您只需检索它的当前实例(参见 1.)。 从设计的角度来看,我想说在视图助手中检索应用程序组件是绝对可行的(例如,
Zend_View_Helper_Url
从静态前端控制器检索路由器)。 为了减少耦合并实现 setter-dependency-injection-pattern,我建议采用以下方法(Zend_View_Helper_Translate
使用类似的模式来检索翻译器):当 flash-messenger 提供您需要的功能。 例如,如果您需要区分错误消息、信息消息和成功消息,那么本地解决方案可能是更好的方法。
As far as I understood your solution, your current call to the
Zend_View_Helper_Action
helper does nothing more than rendering the login form. Why don't you write your on view helper that retrieves the login-related messages from the flash-messenger and renders the login form?EDIT:
You can retrieve the flash-messenger from its static broker:
You don't need to instatiate a new flash-messenger in your view helper, you just can retrieve the current instance of it (see 1.). From a design point of view I'd say that it's absolutely feasible to retrieve application components within your view helpers (the
Zend_View_Helper_Url
for example retrieves the router from the static front-controller). To reduce coupling and to implement a setter-dependency-injection-pattern I'd suggest the following (Zend_View_Helper_Translate
uses a similar pattern to retrieve the translator):Not when the flash-messenger provides the functionality you need. If you need to distinguish between error, informational and success messages for example a home-grown solution would perhaps be the better way.
另一个补充是您可以使用 FlashMessenger 存储任何数据。 所以你可以这样做
然后在 foreach 使用
Another addition is that you can use FlashMessenger to store ANY data. So you can do
and then in foreach use
也许您应该使用 ViewHelper 来呈现表单并使用 ActionController 进行身份验证?
这使您摆脱了第二个调度循环。
如果你愿意的话,我可以给你看一些小例子。
PS 您确定需要表单验证器吗? 我在你的例子中没有看到任何用处。
Maybe you should use ViewHelper to render form and ActionController for authentication?
This frees you from second dispatch-loop.
I can show you some little example, if you want.
P.S. You sure need validators for form? I don`t see any use in your example.