Skip to content

while 循环

while 是 PHP 中最基本的循环结构,它在每次迭代开始前检查条件表达式。只要条件为 true,循环体就会持续执行。while 循环适用于迭代次数未知、需要根据运行时条件决定是否继续的场景。

前置知识

基础概念

while 循环的执行流程:

  1. 在每次迭代开始时,对条件表达式求值。
  2. 如果结果为 true,执行循环体中的代码。
  3. 执行完毕后,回到步骤 1,再次检查条件。
  4. 如果结果为 false,跳过循环体,继续执行循环后面的代码。

注意

while 循环可能一次都不执行——如果初始条件就为 false,循环体将直接跳过。这与 do-while 循环的行为不同。

语法结构

基本 while 语法

php
<?php
declare(strict_types=1);

$count = 1;

while ($count <= 5) {
    echo "第 {$count} 次迭代\n";
    $count++;
}
// 输出:
// 第 1 次迭代
// 第 2 次迭代
// 第 3 次迭代
// 第 4 次迭代
// 第 5 次迭代

while 的替代语法

在模板中可以使用替代语法(while:/endwhile;),详见替代语法

php
<?php
declare(strict_types=1);

$items = ['苹果', '香蕉', '橙子'];
$index = 0;

echo "<ul>\n";
while ($index < count($items)):
    echo "<li>{$items[$index]}</li>\n";
    $index++;
endwhile;
echo "</ul>\n";

条件初始就为 false 的情况

php
<?php
declare(strict_types=1);

$counter = 10;

// 条件初始为 false,循环体不会执行
while ($counter < 5) {
    echo "这行不会输出\n";
    $counter++;
}

echo "循环结束,counter = {$counter}\n";
// 输出:循环结束,counter = 10

详细说明

避免死循环

死循环是指条件永远为 true,循环永远不会停止的情况。这是 while 循环最常见的 bug 之一。

php
<?php
declare(strict_types=1);

// 错误示例:忘记递增变量,导致死循环
$counter = 0;
/*
while ($counter < 10) {
    echo $counter . "\n";
    // 忘记 $counter++,条件永远为 true
}
*/

// 正确示例:确保循环变量在每次迭代中更新
$counter = 0;
while ($counter < 10) {
    echo $counter . "\n";
    $counter++;  // 确保变量被更新
}
php
<?php
declare(strict_types=1);

// 使用 while(true) 配合 break 实现可控循环
$attempts = 0;
$maxAttempts = 5;

while (true) {
    $attempts++;
    echo "第 {$attempts} 次尝试\n";

    // 模拟操作成功
    $success = ($attempts === 3);

    if ($success) {
        echo "操作成功!\n";
        break;
    }

    if ($attempts >= $maxAttempts) {
        echo "达到最大尝试次数,放弃操作。\n";
        break;
    }
}

while vs for

whilefor 都可以用来实现循环,但它们的设计目的不同。

特性whilefor
迭代次数通常未知通常已知
初始化在循环外部在循环声明中
条件每次迭代前检查每次迭代前检查
递增/更新在循环体中在循环声明中
适用场景读取文件、等待事件、轮询遍历固定范围、计数循环
php
<?php
declare(strict_types=1);

// for:适合已知迭代次数
for ($i = 0; $i < 10; $i++) {
    echo $i . " ";
}
echo "\n";

// while:适合未知迭代次数(如读取文件直到结束)
$lines = ["line1", "line2", "line3"];
$index = 0;
while ($index < count($lines)) {
    echo $lines[$index] . "\n";
    $index++;
}

条件判断时机

while 在每次迭代开始前判断条件。这意味着循环体中的变量修改会在下一次条件判断时生效。

php
<?php
declare(strict_types=1);

// 条件判断时机演示
$data = [10, 20, 30, 40, 50];
$total = 0;
$index = 0;

while ($index < count($data) && $total < 60) {
    $total += $data[$index];
    echo "添加 {$data[$index]},总计 {$total}\n";
    $index++;
}
// 输出:
// 添加 10,总计 10
// 添加 20,总计 30
// 添加 30,总计 60(此时 total < 60 不再满足,循环停止)

实战示例

文件逐行读取

while 循环的经典应用场景之一是逐行读取文件内容。

php
<?php
declare(strict_types=1);

function readFileLineByLine(string $filePath): array
{
    $lines = [];
    $handle = fopen($filePath, 'r');

    if ($handle === false) {
        return $lines;
    }

    // feof() 检查是否到达文件末尾
    // fgets() 逐行读取
    while (($line = fgets($handle)) !== false) {
        $lines[] = trim($line);
    }

    fclose($handle);
    return $lines;
}

// 模拟使用
$tempFile = tempnam(sys_get_temp_dir(), 'php');
file_put_contents($tempFile, "第一行\n第二行\n第三行\n");

$lines = readFileLineByLine($tempFile);
foreach ($lines as $line) {
    echo $line . "\n";
}

unlink($tempFile);

数据库结果遍历

php
<?php
declare(strict_types=1);

// 模拟数据库查询结果的遍历
function fetchUsers(): \Generator
{
    yield ['id' => 1, 'name' => '张三', 'email' => 'zhang@example.com'];
    yield ['id' => 2, 'name' => '李四', 'email' => 'li@example.com'];
    yield ['id' => 3, 'name' => '王五', 'email' => 'wang@example.com'];
}

$users = fetchUsers();
$userCount = 0;

while ($users->valid()) {
    $user = $users->current();
    echo "用户 #{$user['id']}:{$user['name']} ({$user['email']})\n";
    $userCount++;
    $users->next();
}

echo "共 {$userCount} 个用户\n";

重试机制

while 循环配合 sleep 可以实现简单的重试机制。

php
<?php
declare(strict_types=1);

function retryOperation(int $maxRetries = 3, int $delaySeconds = 1): bool
{
    $attempt = 0;

    while ($attempt < $maxRetries) {
        $attempt++;
        echo "第 {$attempt} 次尝试...\n";

        // 模拟随机成功/失败
        $success = (random_int(1, 10) > 3);

        if ($success) {
            echo "操作成功!\n";
            return true;
        }

        if ($attempt < $maxRetries) {
            echo "操作失败,{$delaySeconds} 秒后重试...\n";
            sleep($delaySeconds);
        }
    }

    echo "操作失败,已达到最大重试次数 {$maxRetries}。\n";
    return false;
}

// 实际使用时不建议在循环中 sleep,考虑使用异步任务队列

注意事项

  1. 确保循环能终止:始终保证条件最终会变为 false,或在循环体中使用 break 退出。

  2. 避免在条件中调用有副作用的函数:虽然 PHP 允许在条件中调用函数,但这会降低代码的可读性,且每次迭代都会执行。

  3. 小心浮点数比较:使用浮点数作为循环条件时,精度问题可能导致意外的循环行为。

  4. 内存消耗:在处理大量数据时,确保循环体内不会无限积累内存(如不断向数组追加元素)。

最佳实践

  1. 设置超时保护:对于可能长时间运行的 while 循环,添加最大迭代次数限制。

  2. 提取循环条件:如果条件表达式复杂,将其提取为有意义的变量或函数。

  3. 使用 while(true) + break:当退出条件在循环体中间判断时,使用这种模式比把所有条件塞进 while 条件中更清晰。

  4. 考虑使用 for 替代:如果循环有明确的初始化、条件和递增,优先使用 for 循环。

  5. 在 CLI 脚本中注意性能:长时间运行的 while 循环应适当调用 sleep 或处理信号,避免 CPU 占用过高。

下一节

while 的变体 do-while 保证循环体至少执行一次。接下来学习 do-while 循环

进阶用法

调试与测试技巧

php
<?php
declare(strict_types=1);

// 单元测试辅助函数
function createTestResource(): mixed
{
    return match (true) {
        default => new stdClass(),
    };
}

// 调试输出函数
function debugOutput(mixed , string  = ''): void
{
     =  ? ": " : '';
     .= print_r(, true);
    fwrite(STDERR,  . "\n");
}

// 性能基准测试
function benchmark(callable , int  = 1000): float
{
     = hrtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $fn();
    }
    return (hrtime(true) - $start) / 1e9;
}

日志记录实践

php
<?php
declare(strict_types=1);

/**
 * 简易日志记录器
 */
class SimpleLogger
{
    private string $logFile;
    private string $level = 'INFO';

    public function __construct(string $logFile)
    {
        $this->logFile = $logFile;
    }

    public function info(string $message, array $context = []): void
    {
        $this->log('INFO', $message, $context);
    }

    public function warning(string $message, array $context = []): void
    {
        $this->log('WARNING', $message, $context);
    }

    public function error(string $message, array $context = []): void
    {
        $this->log('ERROR', $message, $context);
    }

    private function log(string $level, string $message, array $context): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
        $line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

配置与环境检测

php
<?php
declare(strict_types=1);

// 环境检测工具
class EnvironmentChecker
{
    public static function checkRequirements(array $requirements): array
    {
        $results = [];
        foreach ($requirements as $name => $check) {
            $results[$name] = is_callable($check) ? $check() : false;
        }
        return $results;
    }

    public static function getSystemInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'sapi' => PHP_SAPI,
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
            'loaded_extensions' => get_loaded_extensions(),
        ];
    }
}

常见问题排查

问题可能原因解决方案
连接超时网络问题/配置错误检查配置,增加超时时间
权限不足文件/目录权限使用 chmod/chown 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?php
declare(strict_types=1);

// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
    if (version_compare(PHP_VERSION, $minVersion, '<')) {
        throw new RuntimeException(
            sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
        );
    }
}

ensureVersion('8.1.0');

参考链接