在Yii2项目的实际业务中,合同打印、报表导出、订单凭证生成等场景几乎都离不开PDF输出。MPDF作为PHP生态中最流行的PDF生成库之一,最大的优势就是支持大量CSS属性,可以直接把HTML内容转换成带样式的PDF文档。然而很多开发者第一次上手时都会遇到同一个问题:明明HTML页面渲染得好好的,生成的PDF却样式全无,甚至中文显示成乱码方块。这篇文章将系统地讲解MPDF在Yii2中的集成方式以及CSS样式的正确处理方法。

一、在Yii2中安装和配置MPDF
MPDF推荐通过Composer安装,这是最规范也最容易维护版本的方式。在项目根目录执行以下命令即可:
composer require mpdf/mpdf
安装完成后,不建议在控制器里直接new一个Mpdf对象,更好的做法是封装一个独立的组件或助手类。这样做的优点很明显:一是配置集中管理,改字体、改页边距只需要改一个地方;二是方便在多个模块之间复用;三是便于单元测试时进行mock。下面是一个典型的封装示例:
<?php
namespace app\components;
use Mpdf\Mpdf;
class PdfHelper
{
public static function generate($html, $filename = 'document.pdf', $mode = 'D')
{
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'default_font' => 'simpson', // 中文字体,下文详述
'margin_left' => 10,
'margin_right' => 10,
'margin_top' => 15,
'margin_bottom' => 15,
]);
$mpdf->WriteHTML($html);
$mpdf->Output($filename, $mode);
}
}
配置数组中的mode设置为utf-8是处理中文的前提,format定义纸张大小,四个margin参数控制页边距,单位是毫米。Output方法的第二个参数决定输出行为:D表示直接下载,I表示在浏览器内联展示,F表示保存到服务器文件,S则返回PDF字符串内容,可根据业务需要灵活选择。
二、CSS样式集成的三种主流方式
MPDF对CSS的支持方式和浏览器不完全一致,它解析的是写入的HTML内容中包含的样式。也就是说,如果你的CSS写在外部文件里而HTML中只有class名称,MPDF默认是不会自动去加载Yii2视图布局中的样式文件的。理解这一点是解决样式失效问题的关键。实践中主要有三种集成方式。
1. 使用WriteHTML的第二个参数指定样式区块
WriteHTML方法支持传入常量参数,告诉MPDF这段内容是CSS样式表,示例如下:
$css = file_get_contents(\Yii::getAlias('@webroot/css/pdf.css'));
$mpdf->WriteHTML($css, \Mpdf\HTMLParserMode::HEADER_CSS);
$mpdf->WriteHTML($html, \Mpdf\HTMLParserMode::HTML_BODY);
这种方式最推荐用于正式项目。你可以专门准备一个pdf.css文件,只包含PDF需要的样式,避免把整个网站的样式表塞进去。HEADER_CSS模式会把样式当作全局样式表解析,HTML_BODY模式则只处理正文内容。两者配合使用结构清晰,维护成本低。
2. 在HTML字符串中直接嵌入style标签
第二种方式是让渲染出来的HTML本身就带有<style>标签。在Yii2的视图文件中可以这样写:
<?php
// views/report/pdf.php
use yii\helpers\Html;
?>
<style>
body { font-family: sans-serif; font-size: 12px; color: #333; }
table { width: 100%; border-collapse: collapse; }
table td, table th { border: 1px solid #999; padding: 6px; }
.header { background-color: #f5f5f5; font-weight: bold; }
</style>
<div class="header">销售月度报表</div>
<table>
<tr><th>商品</th><th>数量</th><th>金额</th></tr>
<tr><td>键盘</td><td>120</td><td>7200</td></tr>
</table>
控制器中通过renderPartial方法渲染这个视图,注意必须用renderPartial而不是render,否则会把主布局文件也渲染进来,导致布局中的导航栏、页脚等无关内容混入PDF。渲染结果传给WriteHTML后,MPDF会自动解析其中的样式定义。
3. 使用行内样式
行内样式兼容性最好,优先级也最高,适合简单场景:
<div style="font-size:14px; color:#c00; border-bottom:2px solid #c00; padding:5px 0;">
重要提示内容
</div>
行内样式的缺点也很明显:内容与样式耦合,难以复用,样式一旦要调整就得改多处模板。建议只在结构特别简单或某些属性必须强制覆盖时使用,正式项目还是以前两种方式为主。
三、常见问题排查与解决方案
1. 中文乱码问题
这是被问得最多的问题。MPDF从6.x版本开始默认内置了对中文字符集的支持,但前提是mode设置为utf-8,同时default_font要指定一个支持中文的字体。如果内置字体不满足需求,可以通过addCustomFontDirectory方法引入自定义字体目录,把下载好的ttf字体文件放入其中,并在配置中声明字体名。务必保证PHP文件本身和HTML内容的编码都是UTF-8,否则任何字体设置都无济于事。
2. CSS属性支持范围
MPDF并不是完整实现了浏览器的CSS引擎,它对部分属性的支持有限。大致来说,字体、颜色、边框、内边距、表格、背景色这些常用属性都支持良好;但position的absolute和fixed支持不完善,float的支持也远不如浏览器,flex布局则基本不可用。因此在写PDF专用样式时,要尽量用表格布局替代复杂的浮动和弹性布局,这是保证PDF排版稳定的最有效手段。
3. 背景色丢失问题
很多人发现明明设置了background-color,PDF里却看不到。这通常发生在用户使用某些PDF阅读器的夜间模式或者打印预览时。如果业务上必须保证背景色可见,可以在配置中加上'useActiveForms' => true并确保没有禁用打印背景,同时在测试时用不同阅读器交叉验证。另外注意MPDF不支持CSS3渐变背景,复杂的背景图需要预先切成图片再嵌入。
4. 分页控制
长内容PDF经常需要在固定位置分页,或者让表格标题在每页重复显示。MPDF提供了专门的手段:
// 强制分页
$mpdf->AddPage();
// 表头每页重复,写在HTML中
<table repeat_header="1">
<thead>
<tr><th>列一</th><th>列二</th></tr>
</thead>
<tbody>
<!-- 长数据 -->
</tbody>
</table>
表格设置repeat_header属性为1后,表头会在每一页自动重复,这对多页报表非常实用。此外也可以在样式中通过page-break-before: always和page-break-after: always控制元素的换页行为,MPDF对这两个属性的支持是可靠的。
四、一个完整的控制器示例
最后给出一个可直接落地的控制器代码,把前面的要点串起来:
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use Mpdf\Mpdf;
use Mpdf\HTMLParserMode;
class ReportController extends Controller
{
public function actionPdf()
{
// 关用布局,只渲染内容部分
$this->layout = false;
$html = $this->renderPartial('//report/pdf', [
'data' => [/* 业务数据 */],
]);
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'default_font' => 'simpson',
]);
// 加载PDF专用样式表
$css = file_get_contents(Yii::getAlias('@webroot/css/pdf.css'));
$mpdf->WriteHTML($css, HTMLParserMode::HEADER_CSS);
$mpdf->WriteHTML($html, HTMLParserMode::HTML_BODY);
return $this->asPdf? $mpdf->Output('report.pdf', 'D') : null;
}
}
这个示例展示了完整的流程:关闭布局、渲染局部视图、初始化MPDF、注入样式表、写入正文、输出文件。实际项目中还可以在此基础上加入页眉页脚设置、水印、密码保护等MPDF的高级特性。总的来说,MPDF与Yii2的集成并不复杂,核心在于理解它的样式解析机制与浏览器的差异,用独立的CSS文件配合表格布局,就能稳定生成美观的中文PDF文档。