
本文详细介绍了如何在 Symfony 3.4 应用程序中,将 Snappy PDF 生成的原始 PDF 字符串保存为服务器上的文件,并利用 `qpdf` 工具通过 Symfony 的 `Process` 组件对其进行密码保护。教程涵盖了文件写入、外部命令执行、错误处理以及临时文件清理等关键步骤,旨在提供一个将无保护 PDF 转换为安全加密 PDF 的完整解决方案。
在使用 Snappy PDF(基于 wkhtmltopdf)生成 PDF 文档时,其通常以字符串形式返回生成的 PDF 内容。然而,wkhtmltopdf 本身不直接提供密码保护功能。为了满足对 PDF 文档进行加密的需求,我们需要将这个 PDF 字符串写入服务器上的临时文件,然后借助外部工具(如 qpdf)对其进行加密处理。在 Symfony 应用程序中,执行此类系统级命令的最佳实践是使用其内置的 Process 组件。
本教程将分步指导您完成以下操作:
首先,您需要将 Snappy PDF 生成的 PDF 内容(一个二进制字符串)写入服务器上的一个临时文件。这可以通过 PHP 的 file_put_contents 函数轻松实现。为了确保文件名的唯一性,可以使用 tempnam 函数生成一个唯一的临时文件名。
<?php
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
// 假设 $pdfString 是 Snappy PDF 返回的原始 PDF 内容
$pdfString = '%PDF-1.4
%âã
1 0 obj
<<
/Title ()
/Creator (??wkhtmltopdf 0.12.5)
/Producer (??Qt 5.11.3)
/CreationDate (D:20211221165455Z)
>>
endobj
2 0 obj
<<
/Type /Catalog
/Pages 3 0 R
...
'; // 实际的 PDF 字符串会更长
// 生成一个唯一的临时文件路径
// sys_get_temp_dir() 获取系统临时目录
$tempDir = sys_get_temp_dir();
$unprotectedPdfPath = tempnam($tempDir, 'unprotected_pdf_');
if ($unprotectedPdfPath === false) {
throw new \RuntimeException('无法创建临时文件用于未保护的 PDF。');
}
// 将 PDF 字符串写入临时文件
$bytesWritten = file_put_contents($unprotectedPdfPath, $pdfString);
if ($bytesWritten === false) {
// 尝试清理可能已创建但写入失败的文件
if (file_exists($unprotectedPdfPath)) {
unlink($unprotectedPdfPath);
}
throw new \RuntimeException('无法将 PDF 字符串写入文件: ' . $unprotectedPdfPath);
}
// 确保文件权限正确,以便 qpdf 可以读取
chmod($unprotectedPdfPath, 0644);
echo "未保护的 PDF 已保存到: " . $unprotectedPdfPath . "\n";
?>注意事项:
接下来,我们将使用 qpdf 工具对刚刚创建的临时 PDF 文件进行密码保护。qpdf 是一个强大的命令行工具,用于转换和检查 PDF 文件。您需要确保 qpdf 已安装在您的服务器上。
我们将使用 Symfony 的 Process 组件来执行 qpdf 命令。
<?php
// ... (接上面的代码)
// 定义加密后的 PDF 文件的临时路径
$protectedPdfPath = tempnam($tempDir, 'protected_pdf_');
if ($protectedPdfPath === false) {
// 清理未保护的 PDF
if (file_exists($unprotectedPdfPath)) {
unlink($unprotectedPdfPath);
}
throw new \RuntimeException('无法创建临时文件用于受保护的 PDF。');
}
$ownerPassword = 'owner_password_here'; // 所有者密码
$userPassword = 'user_password_here'; // 用户密码
$keyLength = 256; // 密钥长度,可选 40, 128, 256
// 构建 qpdf 命令
// --encrypt [用户密码] [所有者密码] [密钥长度] -- [权限] [输入文件] [输出文件]
// 权限可以根据需要调整,例如:
// --print=full --copy=n --modify=n --annotate=n --extract=n --assemble=n --fill-form=n --access-support=y
// 这里我们使用一个简单的权限设置,允许打印和修改
$command = [
'qpdf',
'--encrypt',
$userPassword,
$ownerPassword,
(string)$keyLength, // 密钥长度必须是字符串
'--print=full', // 允许打印
'--modify=full', // 允许修改
'--', // 权限选项和文件路径之间的分隔符
$unprotectedPdfPath, // 输入文件
$protectedPdfPath // 输出文件
];
// 创建 Process 实例
$process = new Process($command);
$process->setTimeout(60); // 设置超时时间,根据 PDF 大小调整
try {
$process->run();
// 检查命令是否成功执行
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
echo "PDF 已成功加密并保存到: " . $protectedPdfPath . "\n";
// 确保加密后的文件权限正确
chmod($protectedPdfPath, 0644);
} catch (ProcessFailedException $exception) {
// 处理命令执行失败的情况
echo "加密 PDF 失败。\n";
echo "错误输出: " . $exception->getMessage() . "\n";
echo "标准输出: " . $process->getOutput() . "\n";
echo "错误输出: " . $process->getErrorOutput() . "\n";
// 清理可能已创建的文件
if (file_exists($unprotectedPdfPath)) {
unlink($unprotectedPdfPath);
}
if (file_exists($protectedPdfPath)) {
unlink($protectedPdfPath);
}
throw $exception; // 重新抛出异常
} finally {
// 无论成功与否,都尝试删除未保护的临时文件
if (file_exists($unprotectedPdfPath)) {
unlink($unprotectedPdfPath);
echo "已清理未保护的临时文件: " . $unprotectedPdfPath . "\n";
}
}
?>注意事项:
加密完成后,您可以读取加密后的 PDF 文件内容,以便进行后续处理(例如将其作为 HTTP 响应返回给用户,或上传到云存储)。最后,务必清理所有临时文件,以避免服务器磁盘空间被占用。
<?php
// ... (接上面的代码)
$protectedPdfContent = null;
if (file_exists($protectedPdfPath)) {
$protectedPdfContent = file_get_contents($protectedPdfPath);
if ($protectedPdfContent === false) {
echo "无法读取加密后的 PDF 文件内容。\n";
// 尝试清理文件
unlink($protectedPdfPath);
throw new \RuntimeException('无法读取加密后的 PDF 文件: ' . $protectedPdfPath);
}
echo "已成功读取加密后的 PDF 内容。\n";
}
// 在完成所有操作后,清理加密后的临时文件
if (file_exists($protectedPdfPath)) {
unlink($protectedPdfPath);
echo "已清理加密后的临时文件: " . $protectedPdfPath . "\n";
}
// 现在 $protectedPdfContent 包含了密码保护的 PDF 文件的二进制内容
// 您可以将其返回给浏览器,或者进行其他处理
// 例如:
// return new Response($protectedPdfContent, 200, [
// 'Content-Type' => 'application/pdf',
// 'Content-Disposition' => 'attachment; filename="protected_document.pdf"',
// ]);
?>将上述所有步骤整合到一个完整的 PHP 脚本中,您可以在 Symfony 控制器或服务中调用此逻辑。
<?php
namespace AppBundle\Service; // 假设在一个 Symfony 服务中
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\HttpFoundation\Response; // 如果要直接返回给浏览器
class PdfProtectorService
{
public function protectPdfString(string $pdfString, string $userPassword, string $ownerPassword): ?string
{
$tempDir = sys_get_temp_dir();
$unprotectedPdfPath = false;
$protectedPdfPath = false;
try {
// 1. 保存 PDF 字符串到临时文件
$unprotectedPdfPath = tempnam($tempDir, 'unprotected_pdf_');
if ($unprotectedPdfPath === false) {
throw new \RuntimeException('无法创建临时文件用于未保护的 PDF。');
}
if (file_put_contents($unprotectedPdfPath, $pdfString) === false) {
throw new \RuntimeException('无法将 PDF 字符串写入文件: ' . $unprotectedPdfPath);
}
chmod($unprotectedPdfPath, 0644);
// 2. 使用 qpdf 进行密码保护
$protectedPdfPath = tempnam($tempDir, 'protected_pdf_');
if ($protectedPdfPath === false) {
throw new \RuntimeException('无法创建临时文件用于受保护的 PDF。');
}
$keyLength = 256;
$command = [
'qpdf',
'--encrypt',
$userPassword,
$ownerPassword,
(string)$keyLength,
'--print=full',
'--modify=full',
'--',
$unprotectedPdfPath,
$protectedPdfPath
];
$process = new Process($command);
$process->setTimeout(120); // 增加超时时间以适应大型 PDF
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
chmod($protectedPdfPath, 0644);
// 3. 读取加密后的 PDF 内容
$protectedPdfContent = file_get_contents($protectedPdfPath);
if ($protectedPdfContent === false) {
throw new \RuntimeException('无法读取加密后的 PDF 文件: ' . $protectedPdfPath);
}
return $protectedPdfContent;
} catch (\Exception $e) {
// 记录错误或进行其他错误处理
error_log("PDF 加密失败: " . $e->getMessage());
if ($e instanceof ProcessFailedException) {
error_log("QPDF 错误输出: " . $e->getProcess()->getErrorOutput());
error_log("QPDF 标准输出: " . $e->getProcess()->getOutput());
}
return null; // 或者抛出自定义异常
} finally {
// 清理所有临时文件
if ($unprotectedPdfPath && file_exists($unprotectedPdfPath)) {
unlink($unprotectedPdfPath);
}
if ($protectedPdfPath && file_exists($protectedPdfPath)) {
unlink($protectedPdfPath);
}
}
}
}
// 如何在控制器中使用:
// class MyController extends Controller
// {
// public function generateProtectedPdfAction()
// {
// // 假设您已经从 Snappy PDF 获取了 $originalPdfString
// $originalPdfString = $this->get('snappy.pdf')->getOutputFromHtml('<h1>Hello World</h1>');
//
// $pdfProtectorService = $this->get('app.pdf_protector_service'); // 假设服务已注册
// $protectedPdfContent = $pdfProtectorService->protectPdfString(
// $originalPdfString,
// 'my_user_password',
// 'my_owner_password'
// );
//
// if ($protectedPdfContent) {
// return new Response($protectedPdfContent, 200, [
// 'Content-Type' => 'application/pdf',
// 'Content-Disposition' => 'attachment; filename="protected_document.pdf"',
// ]);
// } else {
// // 处理加密失败的情况
// return new Response('PDF 加密失败。', 500);
// }
// }
// }
?>通过利用 Symfony 的 Process 组件,我们能够有效地在 PHP 应用程序中执行外部命令行工具(如 qpdf),从而实现了将 Snappy PDF 生成的原始 PDF 字符串转换为密码保护的 PDF 文件。这个过程涉及临时文件管理、外部命令的构建与执行、以及健壮的错误处理。遵循这些步骤和最佳实践,可以确保您的应用程序能够安全、可靠地处理 PDF 加密需求。
以上就是将 Snappy PDF 生成的字符串转换为服务器上的加密 PDF的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号