除了多个 if 语句,我还能做啥? PHP 注册脚本

Posted

技术标签:

【中文标题】除了多个 if 语句,我还能做啥? PHP 注册脚本【英文标题】:What Can I Do Instead Of Multiple If Statements? PHP Register Script除了多个 if 语句,我还能做什么? PHP 注册脚本 【发布时间】:2016-11-26 17:10:34 【问题描述】:

正如您在下面的脚本中看到的,我在检查注册输入时使用了多个 if 语句。有没有更简单、更少意大利面的?

脚本按原样工作,但我希望它更整洁。

<?php

if (isset($_POST['register'])) 

    $uname = trim($_POST['uName']);
    $email = trim($_POST['email']);
    $pass = trim($_POST['pass']);
    $passCon = trim($_POST['passCon']);

    $uname = strip_tags($uname);
    $email = strip_tags($email);
    $pass = strip_tags($pass);
    $passCon = strip_tags($passCon);

    if (!empty($pass)) 
        if (!empty($email)) 
            if (!empty($uname)) 
                if ($pass == $passCon) 

                    $query = "SELECT username FROM users WHERE username='$uname'";
                    $result = mysqli_query($conn, $query);
                    $checkUsername = mysqli_num_rows($result);

                    if ($checkUsername == 0) 

                        $query = "SELECT email FROM users WHERE email='$email'";
                        $result = mysqli_query($conn, $query);
                        $count = mysqli_num_rows($result);

                        if ($count == 0) 

                            $password = hash('sha256', $pass);
                            $queryInsert = "INSERT INTO users(id, username, email, password, date) VALUES('', '$uname', '$email', '$password', '" . time() . "')";
                            $res = mysqli_query($conn, $queryInsert);

                            if ($res) 
                                $errTyp = "success";
                                $errMsg = "successfully registered, you may login now";
                            
                         else 
                            $errTyp = "warning";
                            $errMsg = "Sorry Email already in use";
                        
                     else 
                        $errTyp = "warning";
                        $errMsg = "Sorry Username already in use";
                    
                 else 
                    $errTyp = "warning";
                    $errMsg = "Passwords didn't match";
                
             else 
                $errTyp = "warning";
                $errMsg = "You didn't enter a Username";
            
         else 
            $errTyp = "warning";
            $errMsg = "You didn't enter an email address";
        
     else 
        $errTyp = "warning";
        $errMsg = "You didn't enter a password";
    

谢谢, 周杰伦

【问题讨论】:

这是正确的语法吗?没有适当的缩进很难理解。 是的。这是正确的..因为它的工作原理。 我喜欢“提前返回”的短代码方法,在这里阅读更多信息:programmers.stackexchange.com/questions/18454/… 不相关,但如果我希望我的密码包含&lt;h1&gt;hello world&lt;/h1&gt;,该怎么办? @AlonEitan $pass = strip_tags($pass); - 是的 【参考方案1】:

您面临的问题并不少见。许多程序员都面临过这个问题。让我在重构脚本的过程中帮助您。

首先,让我们摆脱嵌套的if-else 语句。他们混淆和混淆了真正发生的事情。

版本 1:

if (!isset($_POST['register']))
    redirect('register.php'); // Let's assume that redirect() redirects the user to a different web page and exit()s the script.

$uname = $_POST['uName'];
$email = $_POST['email'];
$pass = $_POST['pass'];
$passRepeat = $_POST['passRepeat'];

if (empty($pass)) 
    $errorMessage = "You didn't enter a password";


if (empty($email)) 
    $errorMessage = "You didn't enter an email address";


if (empty($uname)) 
    $errorMessage = "You didn't enter a Username";


if ($pass !== $passRepeat) 
    $errMsg = "Passwords didn't match";


$query = "SELECT username FROM users WHERE username='$uname'";
$result = mysqli_query($conn, $query);
$checkUsername = mysqli_num_rows($result);

if ($checkUsername !== 0) 
    $errMsg = 'Sorry Username already in use';


$query = "SELECT email FROM users WHERE email='$email'";
$result = mysqli_query($conn, $query);
$count = mysqli_num_rows($result);

if ($count !== 0) 
    $errMsg = 'Sorry Email already in use';


$password = hash('sha256', $pass);
$queryInsert = "INSERT INTO users(id, username, email, password, date) VALUES('', '$uname', '$email', '$password', '" . time() . "')";
$res = mysqli_query($conn, $queryInsert);

请注意,虽然这避免了嵌套的 if 语句,但这与原始代码不同,因为错误会消失。让我们解决这个问题。当我们这样做时,为什么我们要在第一个错误发生后返回?让我们一次返回所有错误!

版本 2:

$errors = array();

if (empty($pass)) 
    $errors[] = "You didn't enter a password";


if (empty($email)) 
    $errors[] = "You didn't enter an email address";


if (empty($uname)) 
    $errors[] = "You didn't enter a username";


if ($pass !== $passRepeat) 
    $errors[] = "Passwords didn't match";


$query = "SELECT username FROM users WHERE username='$uname'";
$result = mysqli_query($conn, $query);
$usernameExists = mysqli_num_rows($result) > 0;

if ($usernameExists) 
    $errors[] = 'Sorry Username already in use';


$query = "SELECT email FROM users WHERE email='$email'";
$result = mysqli_query($conn, $query);
$emailExists = mysqli_num_rows($result) > 0;

if ($emailExists) 
    $errors[] = 'Sorry Email already in use';


if (count($errors) === 0) 
    $password = hash('sha256', $pass);
    $queryInsert = "INSERT INTO users(id, username, email, password, date) VALUES('', '$uname', '$email', '$password', '" . time() . "')";
    $res = mysqli_query($conn, $queryInsert);

    redirect('register_success.php');
 else 
    render_errors($errors);

到目前为止相当干净!请注意,我们可以用 for 循环替换 if (empty($var)) 语句。但是,我认为在这种情况下这是矫枉过正的。

附带说明,请记住此代码易受SQL injection 的攻击。解决该问题超出了问题的范围。

【讨论】:

【参考方案2】:

少吃意大利面? 从功能分解开始,然后努力将卫生任务与验证任务分开。我将省略我采取的许多步骤(例如验证表单/$_POST/filter_input_array() 具有正确的输入数量,并且正确的键在 $_POST 超全局/INPUT_POST 等,您可能要考虑那。)。根据您的确切需求更改我的一些技术。之后你的程序应该少一些意大利面。 :-)

消毒然后验证。 你必须让他们分开,可以这么说。 ;-)

用功能分解消毒

使单个任务成为自己的代码块。

如果所有表单字段的所有清理步骤(trim()、strip_tags() 等)都相同,则创建一个清理函数来完成这项工作。请注意,只需使用循环即可改进修剪和剥离标签的一次性方式。将原始值保存在变量中,然后在 while 循环中进行 trim()、strip_tags() 等。 比较结果与原始结果。如果它们相同,则断开。如果它们不同,请再次将表单字段的当前值保存在变量中并让循环再次运行。

function sanitize($formValue)

    $oldValue = $formValue;

    do
    
        $formValue = trim($formValue);
        $formValue = strip_tags($formValue);

        //Anything else you want to do.

        $formValue = trim($formValue);

        if($formValue === $oldValue)
        
            break;
        

        $oldValue = $formValue;
    
    while(1); //Infinite loop

    return $formValue;

然后,简单地循环运行这个函数。

$sanitized = [];

foreach($_POST as $key => $value)

    $sanitized[$key] = sanitize($value);


/* You can keep track your variable anyway you want.*/

再往前看,像这样设计一个输入源($_POST、$_GET、$_SESSION、$_FILES、$_COOKIE 等)基于清理,类层次结构真的派上用场了。此外,基于使用 filter_input_array() 的类层次结构确实让您成为游戏的负责人。验证呢?

使用功能分解验证

您可以将每个表单字段视为需要其自己的验证功能。然后,只有检查一个表单字段所需的逻辑将包含在块中。关键是通过让验证器函数返回测试结果(真/假)来保留您的布尔逻辑。

function uname($uname, &$error)

    if(! /* Some test */)
    
        $error = 'Totally wrong!'
    
    elseif(! /* Another test */)
    
        $error = 'Incredibly wrong!'
    
    else
     
        $error = NULL;
    

    return !isset($error) //If error is set, then the test has failed.


function email($email, &$error)

    if(! /* Some test */)
    
        $error = 'Totally wrong!'
    
    elseif(! /* Another test */)
    
        $error = 'Incredibly wrong!'
    
    else
     
        $error = NULL;
    

    return !isset($error) //If error is set, then the test has failed.


function pass($pass, &$error)

    if(! /* Some test */)
    
        $error = 'Totally wrong!'
    
    elseif(! /* Another test */)
    
        $error = 'Incredibly wrong!'
    
    else
     
        $error = NULL;
    

    return !isset($error) //If error is set, then the test has failed.


function passCon($passCon, &$error)

    if(! /* Some test */)
    
        $error = 'Totally wrong!'
    
    elseif(! /* Another test */)
    
        $error = 'Incredibly wrong!'
    
    else
     
        $error = NULL;
    

    return !isset($error) //If error is set, then the test has failed.

在 PHP 中,您可以使用变量函数将函数命名为与它们正在检查的字段相同的名称。因此,要执行这些验证器,只需执行此操作即可。

$errorMsgs = [];

foreach($sanitized as $key => $value)

    $key($value, $errorMsgs[$key])

然后,一般来说,你只需要看看 $errorMsgs 数组中是否有错误。通过处理 $errorMsgs 数组来做到这一点

$error = false;

foreach($errorMsgs as $key => $value)

    if(isset($value))
    
         //There is an error in the $key field
         $error = true;
    



..and then.

if($error === true)

     //Prompt user in some way and terminate processing.


// Send email, login, etc ....

更进一步,您可以创建一个通用的 Validator 超类。

都说了这么多。我以面向对象的方式进行所有的清理和验证,以减少代码重复。 Sanitizer 超类有子类(PostSanitizer、GetSanitizer ......)。 Validator 超类具有可能对字符串、整数或浮点数执行的所有测试。 Validator 超类的子类是特定于页面/表单的。但是,当需要表单令牌之类的东西时,可以在 Validator 超类中找到它的验证方法,因为它可以用于任何表单。

一个好的验证例程会跟踪:

1) 在关联数组中输入值..

2) 关联数组中的测试结果(布尔值)。测试结果(真/假)可以转换为 CSS 类或 '1' 和 '0' 的 JSON 字符串。

3) 关联数组中的错误消息。

..然后根据测试结果(按键)对如何处理输入值和/或错误消息做出最终决定。如果有错误(假设的测试结果数组中的错误值),请使用具有相应键的错误消息。

我之前的示例将最终的错误检查和错误消息数据结构压缩到一个数组中,但是使用单独的数据结构可以提供更大的灵活性(将错误消息与检测到的错误分离)。只需像这样将每个验证变量函数的结果存储到 $testResults 数组中。

function sanitize($formValue)

    $oldValue = $formValue;

    do
    
        $formValue = trim($formValue);
        $formValue = strip_tags($formValue);

        //Anything else you want to do.

        $formValue = trim($formValue);

        if($formValue === $oldValue)
        
            break;
        

        $oldValue = $formValue;
    
    while(1); //Infinite loop

    return $formValue;


$sanitized = [];

foreach($_POST as $key => $value)

    $sanitized[$key] = sanitize($value);


$testResults = [];
$errorMsgs = [];

foreach($sanitized as $key => $value)

    $testResults[$key] = $key($value, $errorMsgs[$key])


if(!in_array(false, $testResults, true))

     return true  //Assuming that, ultimately, you need to know if everything worked or not, and will take action on this elsewhere. It's up to you to make the correct functions/methods, but this general foundation can get you going.


return false; //Obviously. Do not submit the form. Show the errors (CSS and error messages).

然后,只需检查false 是否存在于$testResults 数组中。使用适当的$key 从 $errorMsgs 获取相应的错误消息。使用这个通用的最终存根,您可以创建强大的清理和验证例程,尤其是在您使用面向对象的情况下。

最终,您将开始看到在各种验证变量函数中重复进行相同类型的测试:数据类型、长度、正则表达式、完全匹配、必须是集合中的值等。因此,验证变量函数之间的主要区别将是最小和最大字符串长度、正则表达式模式等...如果您精明,您可以创建一个关联数组,用于“编程”每个变量函数及其验证参数集.这有点超出了范围,但这就是我所做的。

因此,我所有的变量函数都使用称为validateInput() 的类Validator 方法通过分解出的逻辑执行相同的基本测试。该方法接收以下参数

1) 要测试的值。 2)测试参数的关联数组(可以指定数据类型) 3) 一个数组元素,作为变量(通过引用)传入,对应于被测试的字段,该字段将保存错误消息(如果有)。

有趣的是,我使用了两步清理和两步验证。我使用使用 PHP 函数的自定义过滤器算法,然后使用 PECL 过滤器函数 (filter_input_array())。如果在这些步骤中出现任何问题,我会抛出 SecurityException(因为我扩展了 RuntimeException)。

只有在这些过滤器通过后,我才会尝试使用 PHP/PECL 过滤器验证函数。然后,我使用验证变量函数运行我自己的验证例程。是的,这些只有在之前的测试通过为真时才会运行(以避免覆盖之前的失败和相应的错误消息)。

这完全是面向对象的。 希望我能帮上忙。

【讨论】:

以上是关于除了多个 if 语句,我还能做啥? PHP 注册脚本的主要内容,如果未能解决你的问题,请参考以下文章

启用僵尸对象不足以帮助调试我的问题 - 我还能做啥?

当 sleep() 不能很好地与警报一起工作时,我还能做啥“睡眠”?

引入 git switch 后 git checkout 还能做啥?

除了“和”我还能用啥吗?[重复]

DevOps能做啥?

flex能做啥