利用 Zend_Controller_Action_Helper_FlashMessenger 将 Zend_View_Helper_Action 小部件重构为模型

发布于 2024-07-23 19:42:28 字数 9110 浏览 14 评论 0原文

我尝试创建一个登录小部件,可以随意将其包含在需要登录的应用程序内容之外的任何操作中。 我的想法是,我想要一种 DRY 方法来渲染登录框,其中包含先前登录尝试中的任何错误消息。 为了实现这一点,我使用了 FlashMessenger< /a> 用于保存登录尝试失败的结果。 该小部件还使用 Post/Redirect/Get-pattern 来避免使用浏览器后退按钮时多次发布数据的问题。

现在,问题是我宁愿不使用 Zend_View_Helper_Action 因为该助手克隆了请求并创建了另一次调度循环,这非常消耗资源。 因此,通过查看下面的代码,您能否给我一些有关如何重构代码的建议,例如:

  1. 小部件可以包含在任意视图脚本中
  2. 上次登录尝试的结果呈现
  3. 小部件不会在调度循环中调用运行

目前,登录小部件是通过在视图脚本中调用:

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:

  1. The widget can be included in an arbitrary view script
  2. Results from a previous login attempt is rendered
  3. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

老街孤人 2024-07-30 19:42:28

据我了解您的解决方案,您当前对 Zend_View_Helper_Action 帮助器的调用只不过是渲染登录表单。 为什么不编写视图助手来从 flash-messenger 检索与登录相关的消息并呈现登录表单?

编辑:

  1. 您可以从其静态代理检索 flash-messenger:

    $flashMessenger = Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger'); 
      $flashMessenger->setNamespace('login'); 
      $messages = $flashMessenger->getMessages(); 
      
  2. 您不需要在视图助手中实例化新的 flash-messenger,您只需检索它的当前实例(参见 1.)。 从设计的角度来看,我想说在视图助手中检索应用程序组件是绝对可行的(例如,Zend_View_Helper_Url 从静态前端控制器检索路由器)。 为了减少耦合并实现 setter-dependency-injection-pattern,我建议采用以下方法(Zend_View_Helper_Translate 使用类似的模式来检索翻译器):

    class My_View_Helper_Login 扩展了 Zend_View_Helper_Abstract 
      { 
          // [...] 
          /** 
           * @var Zend_Controller_Action_Helper_FlashMessenger 
           */ 
          受保护的$_flash; 
          /** 
           * @param Zend_Controller_Action_Helper_FlashMessenger $flash 
           * @return My_View_Helper_Login 提供流畅的界面 
           */ 
          公共函数 setFlashMessenger(Zend_Controller_Action_Helper_FlashMessenger $flash) 
          { 
              $this->_flash = $flash; 
              返回$这个; 
          } 
          /** 
           * @return Zend_Controller_Action_Helper_FlashMessenger 
           */ 
          公共函数 getFlashMessenger() 
          { 
              if ($this->_flash === null) { 
                  $this->_flash = Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger'); 
              } 
              返回 $this->_flash; 
          } 
          /** 
           * 
           */ 
          公共函数登录() 
          { 
              $flashMessenger = $this->getFlashMessenger(); 
              $flashMessenger->setNamespace('login'); 
              $messages = $flashMessenger->getMessages(); 
              // [...] 
           } 
      } 
      
  3. 当 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:

  1. You can retrieve the flash-messenger from its static broker:

    $flashMessenger = Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger');
    $flashMessenger->setNamespace('login');
    $messages = $flashMessenger->getMessages();
    
  2. 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):

    class My_View_Helper_Login extends Zend_View_Helper_Abstract
    {
        // [...]
        /**
         * @var Zend_Controller_Action_Helper_FlashMessenger
         */
        protected $_flash;
        /**
         * @param Zend_Controller_Action_Helper_FlashMessenger $flash
         * @return My_View_Helper_Login Provides a fluent interface
         */
        public function setFlashMessenger(Zend_Controller_Action_Helper_FlashMessenger $flash)
        {
            $this->_flash = $flash;
            return $this;
        }
        /**
         * @return Zend_Controller_Action_Helper_FlashMessenger
         */
        public function getFlashMessenger()
        {
            if ($this->_flash === null) {
                $this->_flash = Zend_Controller_Action_HelperBroker::getStaticHelper('FlashMessenger');
            }
            return $this->_flash;
        }
        /**
         *
         */
        public function login()
        {
            $flashMessenger = $this->getFlashMessenger();
            $flashMessenger->setNamespace('login');
            $messages = $flashMessenger->getMessages();
            // [...]
         }
    }
    
  3. 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.

别闹i 2024-07-30 19:42:28

另一个补充是您可以使用 FlashMessenger 存储任何数据。 所以你可以这样做

$flash->addMessage(array('status'=>'error','message'=> 'oops'));

然后在 foreach 使用

foreach($messages as $message){
echo '<div class="message '. $message['status'] .'">' . $message['message'] . '</div>'
}

Another addition is that you can use FlashMessenger to store ANY data. So you can do

$flash->addMessage(array('status'=>'error','message'=> 'oops'));

and then in foreach use

foreach($messages as $message){
echo '<div class="message '. $message['status'] .'">' . $message['message'] . '</div>'
}
ぃ弥猫深巷。 2024-07-30 19:42:28

也许您应该使用 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文