PHP - 这是在类内错误处理中正确使用异常吗?


PHP - Is this proper use of exceptions for error handling within classes?

我已经搜索了很多关于这个主题的内容,并得到了很多好(但不同)的结果。有些结果并不完全相关,最终似乎确实是一个偏好问题,但我对我是否遵循良好的设计原则感兴趣

如果这个问题太模糊,请随时删除它,但你能推荐我在哪里发布它吗?

此外,这只是一个例子。这里有很多事情我通常会做不同的事情,但为了简单起见,我这样做了。

代码很长,但您应该能够将其直接复制并粘贴到单个新的PHP文件中,并在您的环境中运行它;不需要设置。

具体问题

  • 这是使用异常并在调用方端处理它们的正确方法吗?
  • 我甚至应该为此使用例外吗?
  • 我的框架自定义异常是否正确?

法典

您可以在此处的单独窗口中查看副本。我将把它粘贴在这里。保存它并在您的环境中运行它,它应该按原样工作而无需任何修改:

当心:前面有很长的代码

<?php
    error_reporting ( E_ALL | E_STRICT );


    class MemberLoginException extends Exception
    {
        public function __construct ( $message = null, $code = 0, Exception $previous = null )
        {
            parent::__construct ( $message, $code, $previous );
        }
    }


    class AccountsInsertException extends Exception
    {
        public function __construct ( $message = null, $code = 0, Exception $previous = null )
        {
            parent::__construct ( $message, $code, $previous );
        }
    }


    class AccountsManager
    {
        protected $_accounts = array ();
        protected $_lcUsernames = array ();     # all usernames in lowercase for checking if username is taken

        public function __construct ( array $accounts = null )
        {
            $this->setAllAccounts ( $accounts );
        }

        public function __destruct ()
        {
            unset ( $this->_accounts, $this->_lcUsernames );
        }

        public function __toString ()
        {
            $return = '';
            if ( count ( $this->_accounts ) > 0 ) :
                $return = '<table>';
                $return .= '<tr><th>Username</th><th>Password</th></tr>';
                foreach ( $this->_accounts as $account ) :
                    $return .= 
                    '<tr>
                        <td>'. htmlentities ( $account['username'], ENT_QUOTES, 'UTF-8' ) . '</td>
                        <td>'. htmlentities ( $account['password'], ENT_QUOTES, 'UTF-8' ) . '</td>
                    </tr>';
                endforeach;
                $return .= '</table>';
                return $return;
            endif;
        }

        public function Clear ()
        {
            $this->_accounts = array ();
            $this->_lcUsernames = array ();
        }

        public function Authenticate ( Member $member )
        {
            $username = strtolower ( $member->getUsername () );
            if ( count ( $this->_accounts ) ) :
                foreach ( $this->_accounts as $account ) :
                    if ( strtolower ( $account['username'] ) == $username )
                        return ( bool ) ( $account['password'] == $member->getPassword () );
                endforeach;
            else :
                return false;
            endif;
        }

        public function getAllAccounts ()
        {
            return $this->_accounts;
        }

        public function setAllAccounts ( array $newValue = null )
        {
            if ( is_null ( $newValue ) )
                $this->_accounts = array ();
            else
                $this->_accounts = $newValue;
                $this->_lcUsernames = array ();
                foreach ( $this->_accounts as $account )
                    $this->_lcUsernames[] = strtolower ( $account['username'] );
            return $this;
        }

        public function hasAccount ( $username )
        {
            return in_array ( strtolower ( $username ), $this->_lcUsernames, false );
        }

        public function AddAccount ( $username, $password )
        {
            /*
            Faster to be redundant by storing a lowercase copy of the username for comparison
            if ( array_key_exists ( strtolower ( $username ), array_change_key_case ( $this->_accounts ) ) )
                throw new AccountsInsertException ( 'Unable to create account; account already exists.' );
            */
            if ( $this->hasAccount ( $username ) )
                throw new AccountsInsertException ( 'Unable to create account; account already exists.' );
            $this->_accounts[] = array (
                'username' => $username,
                'password' => $password,
            );
            $this->_lcUsernames[] = strtolower ( $username );
            return $this;
        }

        public function RemoveAccount ( $username )
        {
            if ( $this->hasAccount ( $username ) ) :
                unset ( $this->_accounts[$username] );
                unset ( $this->_lcUsernames [ strtolower ( $username ) ] );
            endif;
            return $this;
        }

        public function __Debug ()
        {
            echo "'r<pre>'r";
            print_r ( $this->_accounts );
            echo "'r</pre>'r'r'r<pre>'r";
            print_r ( $this->_lcUsernames );
            echo "'r</pre>'r'r";
        }
    }


    class Member
    {
        protected $_username = '';
        protected $_password = '';

        public function __construct ( $username, $password )
        {
            $this->setUsername ( $username );
            $this->setPassword ( $password );
        }

        public function getUsername ()
        {
            return $this->_username;
        }

        public function setUsername ( $newValue )
        {
            $this->_username = ( string ) $newValue;
            return $this;
        }

        public function getPassword ()
        {
            return $this->_password;
        }

        public function setPassword ( $newValue )
        {
            $this->_password = ( string ) $newValue;
            return $this;
        }
    }

    # create a new accounts manager which stores all accounts and handles authentication
    # the Member class would be responsible for setting session variables, etc. Manager just checks user/pass.
    $manager = new AccountsManager ();
?><!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <style>
            *
            {
                font-family: "Segoe UI", "Trebuchet MS", Tahoma, Arial, Helvetica, sans-serif;
            }
            body
            {
                margin: 4em 6em;
                line-height: 1.6em;
                font-size: smaller;
            }
            header
            {
                border-bottom: 2px solid #efefef;
                margin-bottom: 3em;
                padding-bottom: 1em;
            }
            h1, h2, h3, h4, h5, h6
            {
                font-weight: normal;
                letter-spacing: 1px;
                color: royalblue;
            }
            h5, h6
            {
                font-weight: bold;
            }
            header h1 sub, header h1 sup
            {
                font-size: small;
                color: #FF4400;
                letter-spacing: 2px;
            }
            section
            {
                border-bottom: 1px dotted #ccc;
                padding-bottom: 2em;
                margin-bottom: 3em;
            }
            table
            {
                border: 1px solid #eee;
                padding: 1em;
                border-right-width: 2px;
                border-bottom-width: 2px;
            }
            th
            {
                text-align: left;
                font-variant: small-caps;
                border-bottom: 1px dotted #ccc;
                padding-bottom: .75em;
                margin-bottom: .75em;
                letter-spacing: 1px;
                color: #FF4400;
            }
            td:hover
            {
                background-color: skyblue;
            }
            td
            {
                margin: 0;
                display: table-cell;
                padding: .5em;
            }
            pre
            {
                font-family: "Droid Sans Mono", Consolas, "Courier New", Courier, monospaced;
                border: 1px solid #E4E4E4;
                padding: 1em;
                line-height: 1em;
            }
            .error
            {
                color: red;
                border: 1px dotted #ccc;
            }
            .success
            {
                color: forestgreen;
                border: 1px dotted #e0e0e0;
            }
            .error, .success
            {
                padding: .75em;
                background-color: #FFFFCC;
                border: 1px solid #E4E4E4;
            }
        </style>
        <title>Sample Login System - Test Exceptions</title>
    </head>
    <body>
        <header>
            <h1>Simple Login System <sup>demonstrating exceptions&hellip;</sup></h1>
        </header>

        <section>
            <h2>No database required</h2>
            <p>To avoid time setting up your environment, this test simply uses a class that stores an array of accounts.
            Obviously, this isn't persistent (at this time) and it doesn't actually save anything anywhere except in the
            array during the script's lifetime. Upon the next request, the previous accounts will be erased.</p>
        </section>

        <section>
            <h2>Creating accounts...</h2>
            <?php
                $createList =
                    array (
                        array (
                            'username' => 'Daniel Elkins',
                            'password' => 'delkins[not-pass-for-anything]',
                        ),
                        array (
                            'username' => 'Jennifer Lynn',
                            'password' => 'lilJenn',
                        ),
                        array (
                            'username'=> 'Charlie Dog',
                            'password'=> 'grrrrr',
                        ),
                    );
                if ( $manager->setAllAccounts ( $createList ) instanceof AccountsManager ) : ?>
                    <p><strong>Accounts were created successfully!</strong> They should be listed in
                    a table below.</p>
                <?php
                else :
                ?>
                    <p class="error">There was an error creating your accounts...</p>
                <?php
                endif;
            ?>
        </section>

        <section>
            <h2>List of accounts</h2>
            <?php echo $manager; ?>
        </section>

        <section>
            <h2>Trying to create one that already exists...</h2>
            <?php
            try
            {
                $manager->AddAccount ( 'Daniel Elkins', 'delkins[not-pass-for-anything]'); ?>
                <p class="success">Account created successfully!</p>
                <?php
            }
            catch ( AccountsInsertException $exception )
            {
                ?>
                <p class="error"><?= $exception->getMessage (); ?></p>
                <?php
            }
            ?>
        </section>

        <section>
            <h2>Showing accounts again</h2>
            <?php echo $manager; ?>
        </section>

        <section>
            <h2>Valid login test</h2>
            <p>Logging in user `Daniel Elkins`&hellip;</p>
            <?php
            if ( $manager->Authenticate ( new Member ( 'Daniel Elkins', 'delkins[not-pass-for-anything]' ) ) ) : ?>
                <p class="success">Authentication successful!</p>
                <?php
            else :
            ?>
                <p class="error">Unable to login; invalid username or password!</p>
                <?php
            endif;
            ?>
        </section>

        <section>
            <h2><strong>Invalid</strong> login test</h2>
            <p>Logging in user `Doesnt_Exist`&hellip;</p>
            <?php
            if ( $manager->Authenticate ( new Member ( 'Doesnt_Exist', '1234' ) ) ) : ?>
                <p class="success">Authentication successful!</p>
                <?php
            else :
            ?>
                <p class="error">Unable to login; invalid username or password!</p>
                <?php
            endif;
            ?>
        </section>

        <section>
            <h2>Debug information</h2>
            <?php $manager->__Debug (); ?>
        </section>
    </body>
</html>

这是使用异常并在 来电方?

对我来说似乎是一个合理的方法。您正在抛出特定于类的异常,因此很容易捕获或传播它们,并且只捕获或传播特定的异常,而不必捕获所有内容并进行过滤。

我甚至应该为此使用例外吗?

如果您认为存在帐户是特殊情况,是的。

我的框架自定义异常是否正确?

是的,尽管您可以考虑将元数据添加到其中,例如正在创建到AccountInsertException中的帐户的名称。这可能没有必要,但如果您发现自己处于有用的情况,这只是需要考虑的事情。

否则,代码在某些地方有点混乱,但我假设部分原因是该示例。