控制结构

到目前为止,我们的文件都是逐行执行的。因此,我们在某些情况下会收到通知,例如当数组不包含我们正在寻找的内容时。如果我们能选择执行哪一行,岂不更好?控制结构来帮忙!

控制结构就像一个交通分流标志。它根据一些预定义的条件来引导执行流。控制结构有很多种,但我们可以将其分为条件式和循环式。条件允许我们选择是否执行一条语句。循环则根据需要多次执行语句。让我们逐一了解一下它们。

条件

条件对布尔表达式(即返回值)进行求值。如果表达式为真,它将执行代码块中的所有内容。代码块是用 {} 括起来的一组语句。让我们看看它是如何工作的:

<?php
echo "Before the conditional.";
if (4 > 3) {
    echo "Inside the conditional.";
}
if (3 > 4) {
    echo "This will not be printed.";
}
echo "After the conditional.";

在前面的代码中,我们使用了两个条件。条件由关键字 if 和括号中的布尔表达式以及代码块定义。如果表达式为真,则执行代码块,否则跳过代码块。

通过添加关键字 else,可以增强条件的功能。它告诉 PHP,如果前面的条件没有满足,就执行某个代码块。我们来看一个例子:

if (2 > 3) {
    echo "Inside the conditional.";
} else {
    echo "Inside the else.";
}

由于 if 的条件未满足,上例将执行 else 内的代码。

最后,您还可以在 elseif 关键字后添加另一个条件和代码块,以继续向 PHP 询问更多条件。您可以根据需要在 if 后添加多个 elseif。如果添加 else,它必须是条件链中的最后一个。还要记住,一旦 PHP 发现一个条件解析为 true,它就会停止评估其余的条件。

<?php
if (4 > 5) {
    echo "Not printed";
} elseif (4 > 4) {
    echo "Not printed";
} elseif (4 == 4) {
    echo "Printed.";
} elseif (4 > 2) {
    echo "Not evaluated.";
} else {
    echo "Not evaluated.";
}
if (4 == 4) {
    echo "Printed";
}

在上一个示例中,第一个求值为 true 的条件就是高亮显示的条件。在此之后,PHP 不会再评估任何条件,直到新的 if 开始。

有了这些知识,让我们试着清理一下应用程序,只在需要时执行语句。将此代码复制到你的 index.php 文件中:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Bookstore</title>
    </head>
    <body>
        <p>
        <?php
            if (isset($_COOKIE[username'])) {
                echo "You are " . $_COOKIE['username'];
            } else {
                echo "You are not authenticated.";
            }
        ?>
        </p>
        <?php
            if (isset($_GET['title']) && isset($_GET['author'])) {
        ?>
            <p>The book you are looking for is</p>
            <ul>
            <li><b>Title</b>: <?php echo $_GET['title']; ?></li>
            <li><b>Author</b>: <?php echo $_GET['author']; ?></li>
            </ul>
        <?php
        } else {
        ?>
            <p>You are not looking for a book?</p>
        <?php
        }
        ?>
    </body>
</html>

在这段新代码中,我们以两种不同的方式混合了条件和 HTML 代码。第一种打开了一个 PHP 标记,并添加了一个 if…​else 子句,该子句将用 echo 来打印我们是否通过了验证。在条件句中没有合并 HTML 代码,这样就一目了然了。

第二个选项—​第二个突出显示的区块—​显示了一个更丑的解决方案,但有时也是必要的。当需要打印大量 HTML 代码时,echo 就不那么方便了,最好是关闭 PHP 标记,打印所有需要的 HTML 代码,然后再打开标记。您甚至可以在 if 子句的代码块中这样做,正如您在代码中看到的那样。

混合使用 PHP 和 HTML

如果您觉得我们编辑的最后一个文件看起来相当难看,那就对了。混合使用 PHP 和 HTML 会造成混乱,应该避免。在第 6 章 "适应 MVC" 中,我们将了解如何正确操作。

让我们也编辑一下我们的 authenticate.php 文件,因为它正在尝试访问可能不存在的 $_POST 条目。文件的新内容如下:

<?php
$submitted = isset($_POST['username']) && isset($_POST['password']);
if ($submitted) {
    setcookie('username', $_POST['username']);
}
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Bookstore</title>
    </head>
    <body>
        <?php if ($submitted): ?>
            <p>Your login info is</p>
            <ul>
                <li><b>username</b>: <?php echo $_POST['username']; ?></li>
                <li><b>password</b>: <?php echo $_POST['password']; ?></li>
            </ul>
        <?php else: ?>
            <p>You did not submit anything.</p>
        <?php endif; ?>
    </body>
</html>

这段代码还包含我们已经知道的条件。我们正在设置一个变量,以了解我们是否提交了登录信息,并在提交后设置 cookie。但高亮显示的几行向您展示了在 HTML 中包含条件的新方法。这种方法避免了使用 {},而是使用 :endif,从而使代码在处理 HTML 代码时更具可读性。这两种语法都是正确的,在每种情况下,您都应该使用您认为更易读的语法。

Switch…​case

if…​else 类似的另一种控制结构是 switch…​case。这种结构只评估一个表达式,并根据其值执行代码块。让我们来看一个例子:

<?php
switch ($title) {
    case 'Harry Potter':
        echo "Nice story, a bit too long.";
        break;
    case 'Lord of the Rings':
        echo "A classic!";
        break;
    default:
        echo "Dunno that one.";
        break;
}

switch 子句接收一个表达式(这里是一个变量),然后定义一系列情况。当 case 与表达式的当前值匹配时,PHP 将执行其中的代码。一旦 PHP 发现 break 语句,就会退出 switch…​case。如果没有一个情况适合表达式,PHP 将执行默认情况(如果有的话),但这是可选的。

您还需要知道,如果要退出 switch…​case,break 是必须的。如果不指定中断,即使遇到新的情况,PHP 也会继续执行语句。让我们看一个类似的例子,但没有断点:

<?php
$title = 'Twilight';
switch ($title) {
    case 'Harry Potter':
        echo "Nice story, a bit too long.";
    case 'Twilight':
        echo 'Uh...';
    case 'Lord of the Rings':
        echo "A classic!";
    default:
        echo "Dunno that one.";
}

如果您在浏览器中测试这段代码,就会发现它打印的是 Uh…​A classic! Dunno that one.。PHP 发现第二种情况有效,因此执行了它的内容。但由于没有中断,它一直执行到最后。有时这可能是我们想要的行为,但通常不是,所以使用时要小心!

循环

循环是一种控制结构,可让您根据需要多次执行某些语句。您可能会在几种不同的情况下使用循环,但最常见的是在与数组交互时。例如,假设你有一个包含元素的数组,但你不知道里面有什么。您想打印其中的所有元素,因此要对所有元素进行循环。

循环有四种类型。每种循环都有自己的用例,但总的来说,你可以将一种类型的循环转换成另一种类型的循环。让我们仔细看看它们。

While

while 循环是最简单的循环。它执行一个代码块,直到要评估的表达式返回 false。让我们来看一个例子:

<?php
$i = 1;
while ($i < 4) {
    echo $i . " ";
    $i++;
}

在上例中,我们定义了一个值为 1 的变量。这个 while 循环执行代码块的内容,直到该表达式为假。正如你所看到的,在循环内部,我们每次都会将 $i 的值递增 1,因此循环在迭代 4 次后结束。检查脚本的输出,你会看到 "0 1 2 3"。最后打印出来的值是 3,所以当时 $i 的值是 3。之后,我们将其值增加到 4,所以当 while 子句评估 $i < 4 时,结果是 false

Whiles 和无限循环

while 循环最常见的问题之一就是产生无限循环。如果不在 while 循环中添加任何代码来更新 while 表达式中的变量,使其在某个时刻为 false,PHP 将永远无法退出循环!

Do…​while

do…​while 循环与 while 非常相似,都是每次评估一个表达式,并执行代码块,直到该表达式为假。唯一不同的是,当对表达式进行求值时,while 子句会在执行代码前对表达式进行求值,因此有时,如果表达式第一次求值为 false,我们甚至可能不会进入循环。另一方面,do…​while 会在执行代码块后对表达式进行评估,因此即使表达式从一开始就是 false,循环也至少会执行一次。

<?php
echo "with while: ";
$i = 1;
while ($i < 0) {
    echo $i . " ";
    $i++;
}
echo "with do-while: ";
$i = 1;
do {
    echo $i . " ";
    $i++;
} while ($i < 0);

前面的代码用相同的表达式和代码块定义了两个循环,但如果执行它们,你会发现只有 do…​while 中的代码被执行。在这两种情况下,表达式从一开始就是 false,因此 while 甚至没有进入循环,而 do…​while 只进入循环一次。

For

for 循环是四个循环中最复杂的一个。它定义了初始化表达式、退出条件和迭代结束表达式。当 PHP 首次遇到循环时,它会执行定义为初始化表达式的内容。然后,对退出条件进行评估,如果评估结果为 true,则进入循环。执行完循环内的所有内容后,执行迭代结束表达式。完成后,它会再次评估结束条件,循环代码和迭代表达式的结束,直到评估结果为假。和往常一样,一个示例就能说明问题:

<?php
for ($i = 1; $i < 10; $i++) {
    echo $i . " ";
}

初始化表达式为 $i = 1,仅在第一次执行。退出条件为 $i < 10,在每次迭代开始时执行。迭代结束表达式为 $i++,在每次迭代结束时执行。这个示例打印了从 1 到 9 的数字。for 循环的另一种更常见的用法是使用数组:

<?php
$names = ['Harry', 'Ron', 'Hermione'];
for ($i = 0; $i < count($names); $i++) {
    echo $names[$i] . " ";
}

在本例中,我们有一个名称数组。由于该数组定义为列表,因此其键分别为 0、1 和 2。在第一次迭代中,$i 为 0,在第二次迭代中,$i 为 1,在第三次迭代中,$i 等于 2。当 $i 为 3 时,它不会进入循环,因为退出条件的值为 false

每次迭代时,我们都会打印数组中 $i 位置的内容,因此这段代码的结果将是数组中的所有三个名称。

小心退出条件

设置的退出条件不完全符合我们的需要是很常见的,尤其是在数组中。请记住,如果数组是一个列表,则以 0 开始,因此一个包含三个元素的数组将有 0、1 和 2 条目。将退出条件定义为 $i <= count($array) 会导致代码出错,因为当 $i 为 3 时,它也满足退出条件,并将尝试访问不存在的键 3。

Foreach

最后一种循环是 foreach 循环。这种循环是数组专用的,它允许你完全遍历一个数组,即使你不知道它的键值。语法有两种选择,请参见下面的示例:

<?php
$names = ['Harry', 'Ron', 'Hermione'];
foreach ($names as $name) {
    echo $name . " ";
}
foreach ($names as $key => $name) {
    echo $key . " -> " . $name . " ";
}

foreach 循环接受一个数组(本例中为 $names),并指定一个变量,该变量将包含数组条目的值。可以看出,我们不需要指定任何结束条件,因为 PHP 会知道数组何时被迭代完。还可以指定一个变量,其中包含每次迭代的键值,如第二个循环。

对于键值不一定是数字的映射,foreach 循环也很有用。PHP 迭代数组的顺序与在数组中插入内容的顺序相同。

让我们在应用程序中使用一些循环。我们想在主页上显示可用的书籍。我们将书籍列表放入一个数组中,因此必须用一个 foreach 循环遍历所有书籍,打印每本书的一些信息。将以下代码添加到 index.phpbody 标签中:

<?php endif;
    $books = [
        [
            'title' => 'To Kill A Mockingbird',
            'author' => 'Harper Lee',
            'available' => true,
            'pages' => 336,
            'isbn' => 9780061120084
        ],
        [
            'title' => '1984',
            'author' => 'George Orwell',
            'available' => true,
            'pages' => 267,
            'isbn' => 9780547249643
        ],
        [
            'title' => 'One Hundred Years Of Solitude',
            'author' => 'Gabriel Garcia Marquez',
            'available' => false,
            'pages' => 457,
            'isbn' => 9785267006323
        ],
    ];
?>
    <ul>
<?php foreach ($books as $book): ?>
    <li>
        <i><?php echo $book['title']; ?></i>
        - <?php echo $book['author']; ?>
    <?php if (!$book['available']): ?>
        <b>Not available</b>
    <?php endif; ?>
    </li>
<?php endforeach; ?>
</ul>

高亮显示的代码还显示了一个使用 : 符号的 foreach 循环,在与 HTML 混合使用时效果更好。它遍历了所有 $books 数组,并以 HTML 列表的形式打印了每本书的一些信息。还要注意的是,我们在循环中加入了一个条件,这完全没有问题。当然,这个条件将针对数组中的每一个条目执行,因此应尽可能简化循环的代码块。