6.4 字符串格式化

字符串格式化是将数据按照特定格式转换为字符串的过程,这在Web开发中非常重要。无论是显示价格、格式化日期、生成报告,还是创建用户友好的输出,都离不开字符串格式化技术。

格式化的重要性和应用场景

在Web开发中,字符串格式化广泛应用于:

  • 数据显示:将数字、日期等按照本地化格式显示
  • 报表生成:创建格式化的表格和报告
  • 日志记录:生成结构化的日志信息
  • 用户界面:创建友好的用户界面文本
  • 数据导出:生成CSV、XML等格式化文件
  • 模板系统:动态生成HTML、邮件等

数字格式化

number_format() - 基本数字格式化

<?php
// 基本用法:添加千位分隔符
$number = 1234567.89;
$formatted = number_format($number);
echo "格式化: {$formatted}\n";  // 输出: 1,234,568

// 指定小数位数
$number = 1234.567;
$formatted = number_format($number, 2);
echo "两位小数: {$formatted}\n";  // 输出: 1,234.57

// 指定小数点符号和千位分隔符
$number = 1234.56;
$formatted = number_format($number, 2, ',', '.');  // 德国格式
echo "德国格式: {$formatted}\n";  // 输出: 1.234,56

$formatted = number_format($number, 2, '.', ',');  // 美国格式
echo "美国格式: {$formatted}\n";  // 输出: 1,234.56

// 实用函数:格式化货币显示
function formatCurrency($amount, $currency = '¥', $locale = 'zh_CN') {
    switch ($locale) {
        case 'zh_CN':
            return $currency . number_format($amount, 2);
        case 'en_US':
            return $currency . number_format($amount, 2, '.', ',');
        case 'de_DE':
            return number_format($amount, 2, ',', '.') . ' ' . $currency;
        default:
            return $currency . number_format($amount, 2);
    }
}

echo "中文货币: " . formatCurrency(1234.56, '¥', 'zh_CN') . "\n";
echo "美元货币: " . formatCurrency(1234.56, '$', 'en_US') . "\n";
echo "欧元货币: " . formatCurrency(1234.56, '€', 'de_DE') . "\n";
?>

printf() 和 sprintf() - 格式化输出

<?php
// printf() - 直接输出
$name = "张三";
$age = 25;
$salary = 5000.50;

printf("姓名:%s,年龄:%d岁,工资:%.2f元\n", $name, $age, $salary);

// sprintf() - 返回格式化字符串
$formatted = sprintf("姓名:%s,年龄:%d岁,工资:%.2f元", $name, $age, $salary);
echo "格式化字符串: {$formatted}\n";

// 常用格式说明符
$number = 123.456;
$integer = 42;
$string = "Hello";
$hex = 255;

printf("二进制: %b\n", $hex);        // 二进制
printf("字符: %c\n", 65);            // ASCII字符
printf("十进制: %d\n", $integer);    // 十进制整数
printf("科学计数: %e\n", $number);    // 科学计数法
printf("浮点数: %f\n", $number);     // 浮点数
printf八进制: %o\n", $hex);          // 八进制
printf("字符串: %s\n", $string);     // 字符串
printf("十六进制: %x\n", $hex);      // 十六进制(小写)
printf("十六进制: %X\n", $hex);      // 十六进制(大写)
printf("百分号: %%\n");             // 百分号

// 格式化对齐和填充
printf("%10s | %-10s | %8.2f\n", "产品", "数量", "价格");
printf("%10s | %-10s | %8.2f\n", "苹果", "100", "5.99");
printf("%10s | %-10s | %8.2f\n", "香蕉", "50", "3.50");
printf("%10s | %-10s | %8.2f\n", "橙子", "75", "4.25");
?>

vprintf() 和 vsprintf() - 可变参数格式化

<?php
// vprintf() - 可变参数直接输出
$args = ["张三", 25, "工程师"];
vprintf("姓名:%s,年龄:%d,职业:%s\n", $args);

// vsprintf() - 可变参数返回字符串
$formatted = vsprintf("姓名:%s,年龄:%d,职业:%s", $args);
echo "格式化结果: {$formatted}\n";

// 实际应用:动态格式化表格数据
function formatTableRow($format, ...$data) {
    return vsprintf($format, $data);
}

$products = [
    ["笔记本电脑", 2, 4999.99],
    ["无线鼠标", 5, 89.90],
    ["USB键盘", 3, 159.00]
];

echo str_repeat("-", 50) . "\n";
echo formatTableRow("%-15s | %5s | %10s\n", "产品名称", "数量", "价格");
echo str_repeat("-", 50) . "\n";

foreach ($products as $product) {
    echo formatTableRow("%-15s | %5d | %10.2f\n", ...$product);
}

echo str_repeat("-", 50) . "\n";

// 创建可重用的格式化函数
function createFormatter($template) {
    return function(...$args) use ($template) {
        return vsprintf($template, $args);
    };
}

$userFormatter = createFormatter("用户:%s,邮箱:%s,注册时间:%s");
echo $userFormatter("张三", "zhangsan@example.com", "2024-01-15") . "\n";
echo $userFormatter("李四", "lisi@example.org", "2024-02-20") . "\n";
?>

字符串填充和对齐

str_pad() - 字符串填充

<?php
// 基本填充
$text = "PHP";
$padded = str_pad($text, 10, " ", STR_PAD_RIGHT);
echo "右填充: '{$padded}'\n";  // 输出: 'PHP       '

$padded = str_pad($text, 10, " ", STR_PAD_LEFT);
echo "左填充: '{$padded}'\n";  // 输出: '       PHP'

$padded = str_pad($text, 10, " ", STR_PAD_BOTH);
echo "两边填充: '{$padded}'\n";  // 输出: '   PHP    '

// 使用自定义填充字符
$padded = str_pad($text, 10, "*", STR_PAD_RIGHT);
echo "自定义填充: '{$padded}'\n";  // 输出: 'PHP*******'

// 实际应用:格式化表格对齐
function createTable($headers, $data) {
    // 计算每列的最大宽度
    $columnWidths = [];
    foreach ($headers as $i => $header) {
        $columnWidths[$i] = strlen($header);
        foreach ($data as $row) {
            $columnWidths[$i] = max($columnWidths[$i], strlen($row[$i]));
        }
    }

    // 格式化表头
    $headerRow = "|";
    foreach ($headers as $i => $header) {
        $headerRow .= " " . str_pad($header, $columnWidths[$i]) . " |";
    }
    $headerRow .= "\n";

    // 格式化分隔线
    $separator = "+";
    foreach ($columnWidths as $width) {
        $separator .= "-" . str_repeat("-", $width + 2) . "-+";
    }
    $separator .= "\n";

    // 格式化数据行
    $dataRows = "";
    foreach ($data as $row) {
        $dataRows .= "|";
        foreach ($row as $i => $cell) {
            $dataRows .= " " . str_pad($cell, $columnWidths[$i]) . " |";
        }
        $dataRows .= "\n";
    }

    return $separator . $headerRow . $separator . $dataRows . $separator;
}

$headers = ["姓名", "年龄", "城市", "职业"];
$data = [
    ["张三", "28", "北京", "工程师"],
    ["李四", "25", "上海", "设计师"],
    ["王五", "32", "广州", "产品经理"]
];

echo createTable($headers, $data);
?>

sprintf() 对齐功能

<?php
// 使用sprintf实现字符串对齐
$data = [
    ["产品", "数量", "价格"],
    ["苹果", "100", "¥5.99"],
    ["香蕉", "50", "¥3.50"],
    ["橙子", "75", "¥4.25"]
];

foreach ($data as $row) {
    printf("%-10s | %6s | %10s\n", ...$row);
}

// 创建动态对齐函数
function alignColumns($data, $padding = 2) {
    if (empty($data)) return "";

    // 计算每列最大宽度
    $columnWidths = [];
    $colCount = count($data[0]);

    for ($col = 0; $col < $colCount; $col++) {
        $maxWidth = 0;
        foreach ($data as $row) {
            $maxWidth = max($maxWidth, mb_strwidth($row[$col], 'UTF-8'));
        }
        $columnWidths[] = $maxWidth + $padding;
    }

    // 格式化输出
    $result = "";
    foreach ($data as $row) {
        $formatted = "";
        foreach ($row as $col => $cell) {
            $formatted .= str_pad($cell, $columnWidths[$col]);
        }
        $result .= rtrim($formatted) . "\n";
    }

    return $result;
}

$tableData = [
    ["中文名称", "English Name", "价格"],
    ["苹果", "Apple", "¥5.99"],
    ["香蕉", "Banana", "¥3.50"],
    ["橙子", "Orange", "¥4.25"]
];

echo alignColumns($tableData);
?>

日期和时间格式化

date() - 基本日期格式化

<?php
// 常用日期格式
$timestamp = time();

echo "当前时间戳: {$timestamp}\n";
echo "Y-m-d: " . date('Y-m-d', $timestamp) . "\n";  // 2024-01-15
echo "H:i:s: " . date('H:i:s', $timestamp) . "\n";  // 14:30:25
echo "Y-m-d H:i:s: " . date('Y-m-d H:i:s', $timestamp) . "\n";  // 2024-01-15 14:30:25

// 更多格式选项
echo "年份(Y): " . date('Y') . "\n";           // 2024 (4位)
echo "年份(y): " . date('y') . "\n";           // 24 (2位)
echo "月份(F): " . date('F') . "\n";           // January (英文全称)
echo "月份(M): " . date('M') . "\n";           // Jan (英文缩写)
echo "月份(m): " . date('m') . "\n";           // 01 (数字,带前导零)
echo "月份(n): " . date('n') . "\n";           // 1 (数字,不带前导零)
echo "日期(d): " . date('d') . "\n";           // 15 (带前导零)
echo "日期(j): " . date('j') . "\n";           // 15 (不带前导零)
echo "星期(l): " . date('l') . "\n";           // Monday (英文全称)
echo "星期(D): " . date('D') . "\n";           // Mon (英文缩写)
echo "星期(N): " . date('N') . "\n";           // 1 (ISO-8601数字表示,1-7)

// 时间格式
echo "小时(H): " . date('H') . "\n";           // 14 (24小时制,带前导零)
echo "小时(h): " . date('h') . "\n";           // 02 (12小时制,带前导零)
echo "分钟(i): " . date('i') . "\n";           // 30 (带前导零)
echo "秒(s): " . date('s') . "\n";             // 25 (带前导零)
echo "上午下午(a): " . date('a') . "\n";       // am/pm
echo "上午下午(A): " . date('A') . "\n";       // AM/PM

// 实用函数:友好的时间显示
function friendlyTime($timestamp) {
    $now = time();
    $diff = $now - $timestamp;

    if ($diff < 60) {
        return "刚刚";
    } elseif ($diff < 3600) {
        $minutes = floor($diff / 60);
        return "{$minutes}分钟前";
    } elseif ($diff < 86400) {
        $hours = floor($diff / 3600);
        return "{$hours}小时前";
    } elseif ($diff < 2592000) {  // 30天
        $days = floor($diff / 86400);
        return "{$days}天前";
    } else {
        return date('Y-m-d', $timestamp);
    }
}

$past = time() - 3661;  // 1小时1分钟前
echo "友好时间: " . friendlyTime($past) . "\n";

// 本地化日期格式化
function formatDateLocalized($timestamp, $locale = 'zh_CN') {
    switch ($locale) {
        case 'zh_CN':
            $weekdays = ['日', '一', '二', '三', '四', '五', '六'];
            $weekday = $weekdays[date('w', $timestamp)];
            return date('Y年m月d日 星期' . $weekday, $timestamp);

        case 'en_US':
            return date('l, F j, Y', $timestamp);

        case 'de_DE':
            $germanMonths = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
                           'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
            $month = $germanMonths[date('n', $timestamp) - 1];
            return date('j. ') . $month . date(' Y', $timestamp);

        default:
            return date('Y-m-d', $timestamp);
    }
}

echo "中文日期: " . formatDateLocalized(time(), 'zh_CN') . "\n";
echo "英文日期: " . formatDateLocalized(time(), 'en_US') . "\n";
echo "德文日期: " . formatDateLocalized(time(), 'de_DE') . "\n";
?>

DateTime 类 - 面向对象的日期处理

<?php
// 创建DateTime对象
$now = new DateTime();
echo "当前时间: " . $now->format('Y-m-d H:i:s') . "\n";

// 从字符串创建DateTime
$date = DateTime::createFromFormat('Y-m-d', '2024-01-15');
echo "指定日期: " . $date->format('Y年m月d日') . "\n";

// 日期修改
$date->modify('+1 week');
echo "一周后: " . $date->format('Y-m-d') . "\n";

$date->modify('+2 days');
echo "再两天: " . $date->format('Y-m-d') . "\n";

// 日期比较
$date1 = new DateTime('2024-01-01');
$date2 = new DateTime('2024-01-15');

if ($date1 < $date2) {
    echo "date1 早于 date2\n";
}

$interval = $date1->diff($date2);
echo "相差: " . $interval->days . " 天\n";

// 实用类:日期格式化工具
class DateFormatter {
    private $dateTime;

    public function __construct($date = null) {
        if ($date instanceof DateTime) {
            $this->dateTime = $date;
        } elseif (is_string($date)) {
            $this->dateTime = new DateTime($date);
        } elseif (is_int($date)) {
            $this->dateTime = DateTime::createFromFormat('U', $date);
        } else {
            $this->dateTime = new DateTime();
        }
    }

    public function format($format) {
        return $this->dateTime->format($format);
    }

    public function toChinese() {
        $weekdays = ['日', '一', '二', '三', '四', '五', '六'];
        $weekday = $weekdays[$this->dateTime->format('w')];

        return $this->dateTime->format('Y年m月d日 星期') . $weekday;
    }

    public function toISO() {
        return $this->dateTime->format('c');
    }

    public function toMySQL() {
        return $this->dateTime->format('Y-m-d H:i:s');
    }

    public function relativeTo($otherDate = null) {
        if ($otherDate === null) {
            $otherDate = new DateTime();
        } elseif (!$otherDate instanceof DateTime) {
            $otherDate = new DateTime($otherDate);
        }

        $interval = $this->dateTime->diff($otherDate);

        if ($interval->invert) {
            $prefix = "之后";
        } else {
            $prefix = "之前";
        }

        if ($interval->y > 0) {
            return $interval->y . "年" . $prefix;
        } elseif ($interval->m > 0) {
            return $interval->m . "个月" . $prefix;
        } elseif ($interval->d > 0) {
            return $interval->d . "天" . $prefix;
        } elseif ($interval->h > 0) {
            return $interval->h . "小时" . $prefix;
        } elseif ($interval->i > 0) {
            return $interval->i . "分钟" . $prefix;
        } else {
            return "刚刚";
        }
    }

    public function addDays($days) {
        $this->dateTime->modify("+{$days} days");
        return $this;
    }

    public function addMonths($months) {
        $this->dateTime->modify("+{$months} months");
        return $this;
    }
}

// 使用示例
$formatter = new DateFormatter('2024-01-15');
echo "中文格式: " . $formatter->toChinese() . "\n";
echo "ISO格式: " . $formatter->toISO() . "\n";
echo "MySQL格式: " . $formatter->toMySQL() . "\n";
echo "相对时间: " . $formatter->relativeTo() . "\n";

$formatter->addDays(7);
echo "一周后: " . $formatter->format('Y-m-d') . "\n";
?>

模板和占位符系统

简单模板系统

<?php
class SimpleTemplate {
    private $template;
    private $variables = [];

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

    public function set($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function setMultiple($variables) {
        $this->variables = array_merge($this->variables, $variables);
        return $this;
    }

    public function render() {
        $result = $this->template;

        // 替换简单变量 {{variable}}
        $result = preg_replace_callback('/\{\{(\w+)\}\}/', function($matches) {
            $var = $matches[1];
            return isset($this->variables[$var]) ? $this->variables[$var] : $matches[0];
        }, $result);

        // 替换函数调用 {{func()}}
        $result = preg_replace_callback('/\{\{(\w+)\(\)\}\}/', function($matches) {
            $func = $matches[1];
            if (function_exists($func)) {
                return $func();
            }
            return $matches[0];
        }, $result);

        return $result;
    }

    public function renderAndEcho() {
        echo $this->render();
    }
}

// 使用示例
$template = new SimpleTemplate(<<<TEMPLATE
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    <h1>{{greeting}},{{name}}!</h1>
    <p>当前时间:{{date()}}</p>
    <table border="1">
        <tr><th>产品</th><th>价格</th></tr>
        {{product_rows}}
    </table>
</body>
</html>
TEMPLATE);

$template->setMultiple([
    'title' => '欢迎页面',
    'greeting' => '欢迎',
    'name' => '张三'
]);

// 生成产品行
$products = [
    ['name' => '苹果', 'price' => '¥5.99'],
    ['name' => '香蕉', 'price' => '¥3.50'],
    ['name' => '橙子', 'price' => '¥4.25']
];

$productRows = "";
foreach ($products as $product) {
    $productRows .= "<tr><td>{$product['name']}</td><td>{$product['price']}</td></tr>";
}

$template->set('product_rows', $productRows);
$template->renderAndEcho();
?>

高级模板引擎

<?php
class AdvancedTemplate {
    private $template;
    private $variables = [];
    private $functions = [];

    public function __construct($template) {
        $this->template = $template;
        $this->functions = [
            'upper' => 'strtoupper',
            'lower' => 'strtolower',
            'date' => function($format = 'Y-m-d') { return date($format); },
            'currency' => function($amount, $symbol = '¥') {
                return $symbol . number_format($amount, 2);
            },
            'truncate' => function($text, $length = 50) {
                return strlen($text) > $length ? substr($text, 0, $length) . '...' : $text;
            }
        ];
    }

    public function set($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function setFunction($name, callable $func) {
        $this->functions[$name] = $func;
        return $this;
    }

    public function render() {
        $result = $this->template;

        // 处理条件语句 {% if condition %} ... {% endif %}
        $result = $this->processConditions($result);

        // 处理循环语句 {% for item in items %} ... {% endfor %}
        $result = $this->processLoops($result);

        // 处理变量和函数调用
        $result = $this->processVariables($result);

        return $result;
    }

    private function processConditions($template) {
        $pattern = '/\{%\s*if\s+(\w+)\s*%\}(.*?)\{%\s*endif\s*%\}/s';

        return preg_replace_callback($pattern, function($matches) {
            $condition = $matches[1];
            $content = $matches[2];

            $value = isset($this->variables[$condition]) ? $this->variables[$condition] : null;
            return $value ? $content : '';
        }, $template);
    }

    private function processLoops($template) {
        $pattern = '/\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}(.*?)\{%\s*endfor\s*%\}/s';

        return preg_replace_callback($pattern, function($matches) {
            $itemVar = $matches[1];
            $arrayVar = $matches[2];
            $content = $matches[3];

            $array = isset($this->variables[$arrayVar]) ? $this->variables[$arrayVar] : [];
            $result = '';

            foreach ($array as $item) {
                $tempContent = $content;

                // 替换循环变量
                if (is_array($item)) {
                    foreach ($item as $key => $value) {
                        $tempContent = str_replace('{{' . $itemVar . '.' . $key . '}}', $value, $tempContent);
                    }
                } else {
                    $tempContent = str_replace('{{' . $itemVar . '}}', $item, $tempContent);
                }

                $result .= $tempContent;
            }

            return $result;
        }, $template);
    }

    private function processVariables($template) {
        // 处理函数调用 {{func(arg1, arg2)}}
        $template = preg_replace_callback('/\{\{(\w+)\(([^)]*)\)\}\}/', function($matches) {
            $funcName = $matches[1];
            $argsStr = $matches[2];

            if (!isset($this->functions[$funcName])) {
                return $matches[0];
            }

            // 解析参数
            $args = [];
            if (!empty($argsStr)) {
                $argList = array_map('trim', explode(',', $argsStr));
                foreach ($argList as $arg) {
                    // 如果参数是变量引用
                    if (preg_match('/^\$?\w+$/', $arg)) {
                        $varName = ltrim($arg, '$');
                        $args[] = isset($this->variables[$varName]) ? $this->variables[$varName] : '';
                    } elseif (preg_match('/^["\'].*["\']$/', $arg)) {
                        // 字符串参数
                        $args[] = trim($arg, '"\'');
                    } else {
                        // 数字参数
                        $args[] = is_numeric($arg) ? $arg + 0 : $arg;
                    }
                }
            }

            return call_user_func_array($this->functions[$funcName], $args);
        }, $template);

        // 处理简单变量 {{variable}}
        $template = preg_replace_callback('/\{\{(\w+(?:\.\w+)*)\}\}/', function($matches) {
            $varPath = $matches[1];
            $parts = explode('.', $varPath);

            $value = $this->variables;
            foreach ($parts as $part) {
                if (is_array($value) && isset($value[$part])) {
                    $value = $value[$part];
                } else {
                    return $matches[0];
                }
            }

            return $value;
        }, $template);

        return $template;
    }
}

// 使用示例
$template = new AdvancedTemplate(<<<TEMPLATE
<!DOCTYPE html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
    <h1>{{greeting}},{{user.name}}!</h1>
    {% if show_message %}
    <p>{{user.message | truncate(100) | upper}}</p>
    {% endif %}

    <h2>订单详情</h2>
    <p>订单时间:{{date('Y-m-d H:i:s')}}</p>

    <table border="1">
        <tr>
            <th>产品名称</th>
            <th>数量</th>
            <th>单价</th>
            <th>小计</th>
        </tr>
        {% for item in order.items %}
        <tr>
            <td>{{item.name}}</td>
            <td>{{item.quantity}}</td>
            <td>{{currency(item.price)}}</td>
            <td>{{currency(item.quantity * item.price)}}</td>
        </tr>
        {% endfor %}
        <tr>
            <td colspan="3"><strong>总计</strong></td>
            <td><strong>{{currency(order.total)}}</strong></td>
        </tr>
    </table>

    <p>感谢您的惠顾!</p>
</body>
</html>
TEMPLATE);

// 设置模板变量
$template->set('title', '订单确认');
$template->set('greeting', '尊敬的客户');
$template->set('show_message', true);

$template->set('user', [
    'name' => '李四',
    'message' => '感谢您购买我们的产品,这是您的订单确认信息。希望您对本次购物体验满意,期待您的再次光临!'
]);

$template->set('order', [
    'items' => [
        ['name' => '笔记本电脑', 'quantity' => 1, 'price' => 4999.00],
        ['name' => '无线鼠标', 'quantity' => 2, 'price' => 99.00],
        ['name' => 'USB键盘', 'quantity' => 1, 'price' => 159.00]
    ],
    'total' => 5356.00
]);

// 渲染模板
echo $template->render();
?>

实际应用示例

CSV文件生成器

<?php
class CSVGenerator {
    private $data;
    private $headers;
    private $filename;

    public function __construct($data, $headers = [], $filename = 'export.csv') {
        $this->data = $data;
        $this->headers = $headers;
        $this->filename = $filename;
    }

    public function generate() {
        $csv = '';

        // 添加BOM以支持中文
        $csv = "\xEF\xBB\xBF";

        // 添加表头
        if (!empty($this->headers)) {
            $csv .= $this->formatRow($this->headers);
        }

        // 添加数据行
        foreach ($this->data as $row) {
            $csv .= $this->formatRow($row);
        }

        return $csv;
    }

    private function formatRow($row) {
        $formatted = [];
        foreach ($row as $cell) {
            // 处理包含逗号、引号或换行符的字段
            if (is_string($cell) && (strpos($cell, ',') !== false ||
                strpos($cell, '"') !== false ||
                strpos($cell, "\n") !== false)) {
                $cell = '"' . str_replace('"', '""', $cell) . '"';
            }
            $formatted[] = $cell;
        }

        return implode(',', $formatted) . "\n";
    }

    public function download() {
        $csv = $this->generate();

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename=' . $this->filename);
        header('Content-Length: ' . strlen($csv));

        echo $csv;
        exit;
    }

    public function save($filepath) {
        $csv = $this->generate();
        return file_put_contents($filepath, $csv) !== false;
    }
}

// 使用示例
$users = [
    ['name' => '张三', 'email' => 'zhangsan@example.com', 'age' => 28, 'city' => '北京'],
    ['name' => '李四', 'email' => 'lisi@example.org', 'age' => 25, 'city' => '上海'],
    ['name' => '王五', 'email' => 'wangwu@example.net', 'age' => 32, 'city' => '广州']
];

$headers = ['姓名', '邮箱', '年龄', '城市'];

$csvGenerator = new CSVGenerator($users, $headers, 'users.csv');

// 生成CSV内容
$csvContent = $csvGenerator->generate();
echo $csvContent;

// 或者直接下载
// $csvGenerator->download();

// 或者保存到文件
// $csvGenerator->save('path/to/users.csv');
?>

报表生成器

<?php
class ReportGenerator {
    private $data;
    private $title;
    private $columns;

    public function __construct($title, $data, $columns) {
        $this->title = $title;
        $this->data = $data;
        $this->columns = $columns;
    }

    public function generateText() {
        $report = $this->title . "\n";
        $report .= str_repeat('=', mb_strlen($this->title, 'UTF-8')) . "\n\n";

        // 计算列宽
        $widths = [];
        foreach ($this->columns as $key => $config) {
            $widths[$key] = mb_strlen($config['title'], 'UTF-8');
            foreach ($this->data as $row) {
                $value = $this->formatValue($row[$key] ?? '', $config['format'] ?? 'text');
                $widths[$key] = max($widths[$key], mb_strlen($value, 'UTF-8'));
            }
        }

        // 表头
        $header = '|';
        $separator = '+';
        foreach ($this->columns as $key => $config) {
            $width = $widths[$key];
            $header .= ' ' . str_pad($config['title'], $width) . ' |';
            $separator .= '-' . str_repeat('-', $width + 2) . '-+';
        }

        $report .= $separator . "\n" . $header . "\n" . $separator . "\n";

        // 数据行
        foreach ($this->data as $row) {
            $line = '|';
            foreach ($this->columns as $key => $config) {
                $value = $this->formatValue($row[$key] ?? '', $config['format'] ?? 'text');
                $align = $config['align'] ?? 'left';

                if ($align === 'right') {
                    $line .= ' ' . str_pad($value, $widths[$key], ' ', STR_PAD_LEFT) . ' |';
                } else {
                    $line .= ' ' . str_pad($value, $widths[$key]) . ' |';
                }
            }
            $report .= $line . "\n";
        }

        $report .= $separator . "\n";

        // 添加统计信息
        $report .= "\n统计信息:\n";
        $report .= "总记录数: " . count($this->data) . "\n";
        $report .= "生成时间: " . date('Y-m-d H:i:s') . "\n";

        return $report;
    }

    public function generateHTML() {
        $html = '<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>' . htmlspecialchars($this->title) . '</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        h1 { color: #333; }
        table { border-collapse: collapse; width: 100%; margin: 20px 0; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; font-weight: bold; }
        tr:nth-child(even) { background-color: #f9f9f9; }
        .text-right { text-align: right; }
        .text-center { text-align: center; }
        .summary { background-color: #f0f0f0; padding: 10px; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>' . htmlspecialchars($this->title) . '</h1>

    <table>
        <thead>
            <tr>';

        foreach ($this->columns as $config) {
            $align = $config['align'] ?? 'left';
            $class = $align === 'right' ? 'text-right' : ($align === 'center' ? 'text-center' : '');
            $html .= '<th class="' . $class . '">' . htmlspecialchars($config['title']) . '</th>';
        }

        $html .= '</tr>
        </thead>
        <tbody>';

        foreach ($this->data as $row) {
            $html .= '<tr>';
            foreach ($this->columns as $key => $config) {
                $value = $this->formatValue($row[$key] ?? '', $config['format'] ?? 'text');
                $align = $config['align'] ?? 'left';
                $class = $align === 'right' ? 'text-right' : ($align === 'center' ? 'text-center' : '');
                $html .= '<td class="' . $class . '">' . htmlspecialchars($value) . '</td>';
            }
            $html .= '</tr>';
        }

        $html .= '</tbody>
    </table>

    <div class="summary">
        <p><strong>统计信息:</strong></p>
        <p>总记录数: ' . count($this->data) . '</p>
        <p>生成时间: ' . date('Y-m-d H:i:s') . '</p>
    </div>
</body>
</html>';

        return $html;
    }

    private function formatValue($value, $format) {
        switch ($format) {
            case 'currency':
                return '¥' . number_format($value, 2);
            case 'number':
                return number_format($value);
            case 'percentage':
                return number_format($value * 100, 2) . '%';
            case 'date':
                if (is_numeric($value)) {
                    return date('Y-m-d', $value);
                }
                return $value;
            case 'datetime':
                if (is_numeric($value)) {
                    return date('Y-m-d H:i:s', $value);
                }
                return $value;
            default:
                return (string) $value;
        }
    }
}

// 使用示例
$salesData = [
    ['product' => '笔记本电脑', 'sales' => 120, 'revenue' => 599880, 'date' => '2024-01-01'],
    ['product' => '无线鼠标', 'sales' => 450, 'revenue' => 44550, 'date' => '2024-01-02'],
    ['product' => 'USB键盘', 'sales' => 280, 'revenue' => 44520, 'date' => '2024-01-03'],
    ['product' => '显示器', 'sales' => 95, 'revenue' => 142500, 'date' => '2024-01-04'],
    ['product' => '耳机', 'sales' => 320, 'revenue' => 64000, 'date' => '2024-01-05']
];

$columns = [
    'product' => ['title' => '产品名称', 'format' => 'text', 'align' => 'left'],
    'sales' => ['title' => '销售数量', 'format' => 'number', 'align' => 'right'],
    'revenue' => ['title' => '销售收入', 'format' => 'currency', 'align' => 'right'],
    'date' => ['title' => '日期', 'format' => 'date', 'align' => 'center']
];

$report = new ReportGenerator('销售报表', $salesData, $columns);

echo "文本格式报表:\n";
echo $report->generateText() . "\n\n";

echo "HTML格式报表:\n";
echo $report->generateHTML();
?>

邮件模板系统

<?php
class EmailTemplate {
    private $template;
    private $variables;
    private $layout;

    public function __construct($template, $layout = null) {
        $this->template = $template;
        $this->variables = [];
        $this->layout = $layout;
    }

    public function set($name, $value) {
        $this->variables[$name] = $value;
        return $this;
    }

    public function setMultiple($variables) {
        $this->variables = array_merge($this->variables, $variables);
        return $this;
    }

    public function render() {
        $content = $this->template;

        // 处理条件渲染 {% if variable %}...{% endif %}
        $content = preg_replace_callback(
            '/{%\s*if\s+(\w+)\s*%}(.*?)(?:{%\s*else\s*%}(.*?))?{%\s*endif\s*%}/s',
            function($matches) {
                $condition = $matches[1];
                $ifContent = $matches[2] ?? '';
                $elseContent = $matches[3] ?? '';

                return !empty($this->variables[$condition]) ? $ifContent : $elseContent;
            },
            $content
        );

        // 处理变量替换 {{variable}}
        $content = preg_replace_callback(
            '/\{\{(\w+(?:\.\w+)*)\}\}/',
            function($matches) {
                $path = $matches[1];
                $keys = explode('.', $path);
                $value = $this->variables;

                foreach ($keys as $key) {
                    if (is_array($value) && isset($value[$key])) {
                        $value = $value[$key];
                    } else {
                        return $matches[0];
                    }
                }

                return $value;
            },
            $content
        );

        // 处理格式化函数 {{format:function(args)}}
        $content = preg_replace_callback(
            '/\{\{format:(\w+)\((.*?)\)\}\}/',
            function($matches) {
                $funcName = $matches[1];
                $argsStr = $matches[2];

                $args = [];
                if (!empty($argsStr)) {
                    $argList = array_map('trim', explode(',', $argsStr));
                    foreach ($argList as $arg) {
                        if (preg_match('/^\$?\w+$/', $arg)) {
                            $varName = ltrim($arg, '$');
                            $args[] = $this->variables[$varName] ?? '';
                        } else {
                            $args[] = trim($arg, '"\'');
                        }
                    }
                }

                switch ($funcName) {
                    case 'currency':
                        return '¥' . number_format($args[0] ?? 0, 2);
                    case 'date':
                        return date($args[0] ?? 'Y-m-d', strtotime($args[1] ?? 'now'));
                    case 'upper':
                        return strtoupper($args[0] ?? '');
                    case 'lower':
                        return strtolower($args[0] ?? '');
                    default:
                        return $matches[0];
                }
            },
            $content
        );

        // 应用布局
        if ($this->layout) {
            $this->variables['content'] = $content;
            $layoutTemplate = new EmailTemplate($this->layout);
            $layoutTemplate->variables = $this->variables;
            return $layoutTemplate->render();
        }

        return $content;
    }

    public function send($to, $subject) {
        $headers = [
            'MIME-Version: 1.0',
            'Content-Type: text/html; charset=UTF-8',
            'From: noreply@example.com'
        ];

        $content = $this->render();
        return mail($to, $subject, $content, implode("\r\n", $headers));
    }
}

// 邮件模板示例
$orderConfirmationTemplate = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>订单确认</title>
</head>
<body style="font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5;">
    <div style="max-width: 600px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
        <h1 style="color: #333; text-align: center;">订单确认</h1>

        <p>尊敬的 {{customer.name}}:</p>
        <p>感谢您的订单!我们已经收到您的订单,正在为您准备商品。</p>

        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">订单信息</h3>
            <p><strong>订单号:</strong>{{order.number}}</p>
            <p><strong>下单时间:</strong>{{format:date('Y年m月d日 H:i', order.created_at)}}</p>
            <p><strong>支付方式:</strong>{{order.payment_method}}</p>
        </div>

        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">配送地址</h3>
            <p>{{customer.address}}</p>
            <p>{{customer.city}} {{customer.province}} {{customer.postal_code}}</p>
            <p>电话:{{customer.phone}}</p>
        </div>

        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">订单明细</h3>
            <table style="width: 100%; border-collapse: collapse;">
                <thead>
                    <tr style="background-color: #f8f8f8;">
                        <th style="padding: 10px; text-align: left; border: 1px solid #ddd;">商品</th>
                        <th style="padding: 10px; text-align: center; border: 1px solid #ddd;">数量</th>
                        <th style="padding: 10px; text-align: right; border: 1px solid #ddd;">单价</th>
                        <th style="padding: 10px; text-align: right; border: 1px solid #ddd;">小计</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in order.items %}
                    <tr>
                        <td style="padding: 10px; border: 1px solid #ddd;">{{item.name}}</td>
                        <td style="padding: 10px; text-align: center; border: 1px solid #ddd;">{{item.quantity}}</td>
                        <td style="padding: 10px; text-align: right; border: 1px solid #ddd;">{{format:currency(item.price)}}</td>
                        <td style="padding: 10px; text-align: right; border: 1px solid #ddd;">{{format:currency(item.quantity * item.price)}}</td>
                    </tr>
                    {% endfor %}
                </tbody>
                <tfoot>
                    <tr>
                        <td colspan="3" style="padding: 10px; text-align: right; border: 1px solid #ddd; font-weight: bold;">总计</td>
                        <td style="padding: 10px; text-align: right; border: 1px solid #ddd; font-weight: bold;">{{format:currency(order.total)}}</td>
                    </tr>
                </tfoot>
            </table>
        </div>

        {% if order.note %}
        <div style="margin: 20px 0;">
            <h3 style="color: #555; border-bottom: 2px solid #eee; padding-bottom: 10px;">订单备注</h3>
            <p>{{order.note}}</p>
        </div>
        {% endif %}

        <div style="margin: 30px 0; padding: 20px; background-color: #f0f8ff; border-radius: 5px;">
            <p style="margin: 0; text-align: center;">如有任何问题,请联系我们的客服团队</p>
            <p style="margin: 5px 0; text-align: center;">电话:400-123-4567 | 邮箱:service@example.com</p>
        </div>

        <p style="text-align: center; color: #666; font-size: 12px; margin-top: 30px;">
            此邮件由系统自动发送,请勿回复
        </p>
    </div>
</body>
</html>
HTML;

// 使用示例
$email = new EmailTemplate($orderConfirmationTemplate);

$email->setMultiple([
    'customer' => [
        'name' => '李明',
        'address' => '中关村大街1号',
        'city' => '北京市',
        'province' => '北京',
        'postal_code' => '100080',
        'phone' => '13812345678'
    ],
    'order' => [
        'number' => 'ORD202401150001',
        'created_at' => '2024-01-15 10:30:00',
        'payment_method' => '支付宝',
        'total' => 5356.00,
        'note' => '请在工作日配送',
        'items' => [
            ['name' => '笔记本电脑', 'quantity' => 1, 'price' => 4999.00],
            ['name' => '无线鼠标', 'quantity' => 2, 'price' => 99.00],
            ['name' => 'USB键盘', 'quantity' => 1, 'price' => 159.00]
        ]
    ]
]);

echo $email->render();

// 发送邮件(需要配置邮件服务器)
// $email->send('customer@example.com', '订单确认 - ORD202401150001');
?>

本节练习

基础练习

  1. 使用number_format()格式化不同类型的数字(价格、百分比、科学计数法等)
  2. 使用printf()sprintf()创建格式化的表格输出
  3. 使用str_pad()实现对齐的文本显示
  4. 使用date()函数创建各种日期格式

进阶练习

  1. 创建一个完整的报表生成器,支持多种输出格式
  2. 实现一个简单的模板引擎,支持变量替换和条件渲染
  3. 开发一个CSV导入导出工具
  4. 创建一个邮件模板系统,支持动态内容生成

实战练习

  1. 完善AdvancedTemplate类,添加更多功能(循环、过滤器等)
  2. 扩展EmailTemplate类,支持邮件队列和批量发送
  3. 创建一个数据可视化工具,将数据转换为图表格式
  4. 实现一个多语言支持的格式化系统

总结

本节我们学习了PHP字符串格式化的各种技术:

  • 数字格式化number_format(), printf(), sprintf()
  • 字符串填充对齐str_pad(), sprintf()对齐功能
  • 日期时间格式化date()函数和DateTime类
  • 模板系统:从简单模板到高级模板引擎
  • 实际应用:CSV生成、报表生成、邮件系统等

字符串格式化是Web开发中的重要技能,它能让数据显示更加美观和专业。掌握这些技术将帮助你创建用户友好的界面和专业的报告系统。

💡 学习建议

  1. 熟悉各种格式化函数的参数和用法
  2. 注意本地化和国际化问题
  3. 在实际项目中灵活应用不同的格式化技术
  4. 考虑性能优化,避免过度格式化
  5. 始终考虑安全性,特别是在处理用户输入时

章节总结:通过本章的学习,你已经掌握了PHP字符串处理的全面知识,从基础操作到高级应用,为后续的Web开发打下了坚实的基础。