强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 9 章 — 字符串

第 9 章 — 字符串:Heredoc、正则、多字节与 Unicode

9.1 字符串基础

PHP 中的字符串是一系列字符的集合,最大长度取决于系统内存。

<?php
// 单引号 — 不解析变量和大部分转义序列
$name = 'World';
echo 'Hello, $name!';        // Hello, $name!
echo 'It\'s OK';             // It's OK
echo 'Backslash: \\';        // Backslash: \

// 双引号 — 解析变量和转义序列
echo "Hello, $name!";        // Hello, World!
echo "Sum: " . (3 + 4);     // Sum: 7
echo "New line: \n";         // 换行
echo "Tab: \t";              // 制表符

// 花括号语法解析复杂变量
$user = (object)['name' => 'Alice'];
echo "Hello, {$user->name}!";
echo "Price: \${$price}";
echo "Count: {${$countVar}}";
echo "Items: {$arr['key']}";
echo "Count: " . count($items);

9.2 Heredoc 与 Nowdoc

9.2.1 Heredoc(双引号行为)

<?php
$name = 'Alice';
$age = 30;

// Heredoc — 解析变量(PHP 7.3+ 支持缩进结束标记)
$html = <<<HTML
<div class="user-card">
    <h2>{$name}</h2>
    <p>Age: {$age}</p>
    <p>Full name: {$user->getFullName()}</p>
</div>
HTML;

// SQL 语句
$sql = <<<SQL
    SELECT u.name, u.email, COUNT(o.id) AS order_count
    FROM users u
    LEFT JOIN orders o ON o.user_id = u.id
    WHERE u.status = 'active'
    GROUP BY u.id
    HAVING order_count > {$minOrders}
    ORDER BY {$orderBy}
    LIMIT {$limit}
SQL;

// JavaScript 嵌入
$script = <<<JS
document.addEventListener('DOMContentLoaded', function() {
    const name = '{$name}';
    console.log('Hello, ' + name);
});
JS;

9.2.2 Nowdoc(单引号行为)

<?php
$name = 'Alice';

// Nowdoc — 不解析变量
$template = <<<'TEMPLATE'
Hello, {$name}!
This is a template with {$variables} that won't be parsed.
Use {{double braces}} for template syntax.
TEMPLATE;

// 正则表达式模式
$pattern = <<<'REGEX'
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
REGEX;

9.3 字符串函数

9.3.1 查找与搜索

<?php
$str = "Hello, World! Hello, PHP!";

// 位置查找(返回位置或 false)
strpos($str, 'Hello');      // 0
strrpos($str, 'Hello');     // 14(最后一次出现)
stripos($str, 'hello');     // 0(不区分大小写)

// 存在性检查(PHP 8.0+,推荐)
str_contains($str, 'World');     // true
str_contains($str, 'world');     // true(区分大小写!)
str_starts_with($str, 'Hello');  // true
str_ends_with($str, 'PHP!');     // true

// 替换
str_replace('World', 'PHP', $str);
str_ireplace('hello', 'Hi', $str);  // 不区分大小写
substr_replace($str, '***', 7, 5);  // 位置替换

// 子串
substr($str, 0, 5);          // "Hello"
substr($str, -3);            // "PHP"(从末尾)
strstr($str, 'World');       // "World! Hello, PHP!"
strtok($str, '!,');          // "Hello"(按分隔符切分)

9.3.2 格式化

<?php
// 大小写转换
strtolower('HELLO');         // "hello"
strtoupper('hello');         // "HELLO"
ucfirst('hello');            // "Hello"
lcfirst('HELLO');            // "hELLO"
ucwords('hello world');      // "Hello World"

// 填充与裁剪
str_pad('42', 5, '0', STR_PAD_LEFT);  // "00042"
str_repeat('=-', 20);                   // 分割线
trim('  hello  ');                      // "hello"
ltrim('  hello  ');                     // "hello  "
rtrim('  hello  ');                     // "  hello"
trim("{user}", "{}");                   // "user"

// 截断
wordwrap($longText, 80, "\n", true);

// 格式化输出
printf("Name: %-20s | Age: %3d\n", 'Alice', 30);
sprintf("Price: $%.2f", 99.9);    // "Price: $99.90"
number_format(1234567.89, 2, '.', ',');  // "1,234,567.89"

// HTML 相关
htmlspecialchars('<script>alert(1)</script>');  // 转义 HTML
html_entity_decode('&lt;div&gt;');              // 反转义
strip_tags('<p>Hello <b>World</b></p>');        // "Hello World"
nl2br("Line 1\nLine 2");                       // 换行转 <br>

9.3.3 分割与合并

<?php
// 按分隔符分割
$csv = "apple,banana,cherry";
$fruits = explode(',', $csv);      // ['apple', 'banana', 'cherry']

// 按正则分割
$words = preg_split('/\s+/', 'Hello   World  PHP');

// 合并
implode(', ', $fruits);             // "apple, banana, cherry"
join(' | ', $fruits);              // "apple | banana | cherry"

// chunk_split
chunk_split(base64_encode($data), 76, "\n");

9.4 正则表达式

PHP 使用 PCRE(Perl Compatible Regular Expressions)库。

9.4.1 基本用法

<?php
// preg_match — 检查是否匹配
$pattern = '/^[\w.-]+@[\w.-]+\.\w+$/';
$email = '[email protected]';
if (preg_match($pattern, $email)) {
    echo "有效的邮箱地址";
}

// preg_match_all — 查找所有匹配
$text = '价格: ¥100, ¥200, ¥300';
preg_match_all('/¥(\d+)/', $text, $matches);
// $matches[0] = ['¥100', '¥200', '¥300']
// $matches[1] = ['100', '200', '300']

// preg_replace — 替换
$clean = preg_replace('/\s+/', ' ', $text);

// preg_replace_callback — 回调替换
$markdown = '**bold** and *italic*';
$html = preg_replace_callback('/\*{1,2}(.+?)\*{1,2}/', function($m) {
    return strlen($m[0]) === 4 ? "<b>{$m[1]}</b>" : "<i>{$m[1]}</i>";
}, $markdown);

// preg_split — 按正则分割
$tokens = preg_split('/[,;|]/', 'a,b;c|d');

9.4.2 常用正则模式

模式说明
/^...$/完整匹配
\d+一个或多个数字
\w+单词字符(字母、数字、下划线)
\s空白字符
[a-z]字符类
[^...]否定字符类
(?:...)非捕获组
(?P<name>...)命名捕获组
(?=...)正向前瞻
(?!...)负向前瞻
{n,m}量词:n 到 m 次
*? +?非贪婪匹配
<?php
// 命名捕获组
$pattern = '/(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})/';
preg_match($pattern, '2026-05-10', $m);
echo "{$m['year']}{$m['month']}{$m['day']}日";

// 提取 URL 组件
$urlPattern = '/^(?P<scheme>https?):\/\/(?P<host>[^\/]+)(?P<path>\/.*)?$/';
preg_match($urlPattern, 'https://example.com/path', $m);

9.4.3 Unicode 正则修饰符

<?php
// /u 修饰符 — 启用 Unicode 模式
preg_match('/\p{Han}+/u', '你好世界', $m);  // 匹配中文
echo $m[0];  // 你好世界

preg_match('/\p{L}+/u', 'Café', $m);       // 匹配任何 Unicode 字母

// 中文验证
$isChinese = preg_match('/^[\p{Han}]+$/u', $name);

9.5 多字节字符串(mbstring)

PHP 原生字符串函数按字节操作,处理 UTF-8 中文必须使用 mb_* 系列函数。

<?php
$str = "Hello, 你好世界!";

// 长度
strlen($str);         // 22(字节数)⚠️
mb_strlen($str);      // 13(字符数)✓
mb_strlen($str, 'UTF-8');  // 显式指定编码

// 截取
substr($str, 0, 7);         // "Hello, "(可能截断多字节字符)⚠️
mb_substr($str, 0, 7);      // "Hello, 你" ✓
mb_strimwidth($str, 0, 10, '...');  // 按显示宽度截断

// 大小写
mb_strtoupper('hello');     // "HELLO"
mb_strtolower('HELLO');     // "hello"

// 查找
mb_strpos($str, '你好');    // 7
mb_strrpos($str, '你好');   // 7

// 替换
mb_ereg_replace('你好', 'Hi', $str);  // POSIX 正则
// 推荐用 preg_replace + /u 修饰符

// 分割
mb_str_split('你好世界');   // ['你', '好', '世', '界']

// 编码转换
$utf8 = mb_convert_encoding($gbkStr, 'UTF-8', 'GBK');
$detected = mb_detect_encoding($str, ['UTF-8', 'GBK', 'BIG5']);

字符串函数 vs 多字节函数

函数多字节版本说明
strlen()mb_strlen()长度
substr()mb_substr()截取
strpos()mb_strpos()查找
strtolower()mb_strtolower()小写
strtoupper()mb_strtoupper()大写
strstr()mb_strstr()子串搜索

9.6 Unicode 与国际化

9.6.1 Intl 扩展

<?php
// 排序(考虑语言规则)
$collator = new Collator('zh_CN');
$items = ['中国', '美国', '英国', '日本'];
$collator->sort($items);  // 按拼音排序

// 格式化数字
$fmt = new NumberFormatter('zh_CN', NumberFormatter::DECIMAL);
echo $fmt->format(1234567.89);  // "1,234,567.89"

$currency = new NumberFormatter('zh_CN', NumberFormatter::CURRENCY);
echo $currency->formatCurrency(99.99, 'CNY');  // "¥99.99"

// 日期格式化
$intlDate = IntlDateFormatter::create(
    'zh_CN',
    IntlDateFormatter::LONG,
    IntlDateFormatter::SHORT,
    'Asia/Shanghai'
);
echo $intlDate->format(new DateTime());  // "2026年5月10日 14:30"

// 首字母提取
$grapheme = grapheme_extract('你好世界', 1, GRAPHEME_EXTRACT_UNIT);
// "你"

9.6.2 文本处理工具类

<?php
declare(strict_types=1);

class Str
{
    public static function truncate(string $text, int $length, string $suffix = '...'): string
    {
        if (mb_strlen($text) <= $length) return $text;
        return mb_substr($text, 0, $length) . $suffix;
    }

    public static function slug(string $text): string
    {
        // 转换中文为拼音(需要 intl 扩展)
        $text = transliterator_transliterate('Any-Latin; Latin-ASCII; Lower()', $text);
        $text = preg_replace('/[^a-z0-9-]/', '-', $text);
        return trim(preg_replace('/-+/', '-', $text), '-');
    }

    public static function excerpt(string $text, int $length = 200): string
    {
        $text = strip_tags($text);
        $text = preg_replace('/\s+/', ' ', $text);
        return self::truncate(trim($text), $length);
    }

    public static function wordCount(string $text): int
    {
        // 中文按字符计算,英文按单词计算
        $chinese = preg_match_all('/\p{Han}/u', $text);
        $english = str_word_count(preg_replace('/\p{Han}/u', ' ', $text));
        return $chinese + $english;
    }
}

// 使用
echo Str::truncate('这是一个很长的中文文本内容...', 10);  // "这是一个很长的中文文本内容..."
echo Str::slug('PHP 完全指南');  // "php-wan-quan-zhi-nan"
echo Str::wordCount('PHP 是最好的 语言,没有之一');  // 9

9.7 JSON 处理

<?php
// 编码
$data = ['name' => 'Alice', 'age' => 30, 'tags' => ['PHP', 'Laravel']];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
echo $json;

// 解码(返回对象)
$obj = json_decode($json);
echo $obj->name;

// 解码(返回关联数组)
$arr = json_decode($json, true);
echo $arr['name'];

// 错误处理
$result = json_decode($invalidJson);
if (json_last_error() !== JSON_ERROR_NONE) {
    throw new RuntimeException('JSON error: ' . json_last_error_msg());
}

// PHP 8.3+ json_validate — 验证 JSON 是否合法
if (json_validate($input)) {
    $data = json_decode($input, true);
}

9.8 业务场景:模板引擎

<?php
declare(strict_types=1);

class SimpleTemplate
{
    private array $variables = [];

    public function assign(string $key, mixed $value): self
    {
        $this->variables[$key] = $value;
        return $this;
    }

    public function render(string $template): string
    {
        // 替换 {{ variable }}
        $result = preg_replace_callback('/\{\{\s*(.+?)\s*\}\}/', function($m) {
            $key = trim($m[1]);
            if (str_contains($key, '|')) {
                [$key, $filter] = array_map('trim', explode('|', $key, 2));
                return match ($filter) {
                    'upper'  => mb_strtoupper($this->resolve($key)),
                    'lower'  => mb_strtolower($this->resolve($key)),
                    'escape' => htmlspecialchars($this->resolve($key)),
                    default  => $this->resolve($key),
                };
            }
            return htmlspecialchars($this->resolve($key));
        }, $template);

        // 处理 {% for item in items %} ... {% endfor %}
        $result = preg_replace_callback(
            '/\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}(.+?)\{%\s*endfor\s*%\}/s',
            function($m) {
                $varName = $m[1];
                $listKey = $m[2];
                $template = $m[3];
                $items = $this->resolve($listKey);
                $output = '';
                foreach ($items as $item) {
                    $temp = new self();
                    $temp->variables = $this->variables;
                    $temp->variables[$varName] = $item;
                    $output .= $temp->render($template);
                }
                return $output;
            },
            $result
        );

        return $result;
    }

    private function resolve(string $key): mixed
    {
        if (str_contains($key, '.')) {
            $parts = explode('.', $key);
            $value = $this->variables;
            foreach ($parts as $part) {
                $value = $value[$part] ?? null;
            }
            return $value;
        }
        return $this->variables[$key] ?? '';
    }
}

// 使用
$tpl = new SimpleTemplate();
$tpl->assign('title', '用户列表');
$tpl->assign('users', [
    ['name' => 'Alice', 'age' => 30],
    ['name' => 'Bob',   'age' => 25],
]);

echo $tpl->render(<<<'HTML'
<h1>{{ title | upper }}</h1>
{% for user in users %}
<div>{{ user.name }} - {{ user.age }}</div>
{% endfor %}
HTML);

9.9 扩展阅读


上一章第 8 章 — 数组 下一章第 10 章 — OOP 基础