PHP 完全指南 / 第 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('<div>'); // 反转义
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 基础