Symfony4+Webpack Encore版本化后,PHP中如何访问静态资源生成PDF?
嘿,这个场景我太熟悉了——用Webpack Encore做资源版本化确实能解决前端缓存的大问题,但碰到PHP端(比如TCPDF这种需要直接读取本地文件的工具)就容易卡壳。不过别担心,有几个完美的解决方案,既能保留版本化的优势,又能让PHP顺利拿到正确的图片路径:
方案1:利用Webpack Encore的Manifest文件(最通用)
Encore在构建时会生成一个manifest.json文件,里面存着所有资源原文件名和版本化后文件名的映射关系。PHP只要读取这个文件,就能拿到正确的路径。
步骤1:确保Encore配置正确
首先在webpack.config.js里开启版本化和manifest生成,注意setManifestKeyPrefix要和你的public路径匹配:
// webpack.config.js const Encore = require('@symfony/webpack-encore'); Encore // ... 其他基础配置(比如设置output路径、启用babel等) .setPublicPath('/build') .setManifestKeyPrefix('build/') // 关键:确保manifest里的路径前缀正确 .enableVersioning(Encore.isProduction()) // 只在生产环境开启版本化(可选,但推荐) // 如果你的图片不需要在JS里引用,用copyFiles让Encore处理它们 .copyFiles({ from: './assets/images', to: 'images/[path][name].[hash:8].[ext]', // 给图片加上hash版本号 pattern: /\.(png|jpg|jpeg|svg)$/ }) ; module.exports = Encore.getWebpackConfig();
步骤2:PHP读取Manifest获取路径
你可以自己写个简单的服务,或者直接在需要的地方读取manifest。这里推荐用Symfony的依赖注入写个服务,复用性更高:
// src/Service/AssetResolver.php namespace App\Service; use Symfony\Component\HttpKernel\KernelInterface; class AssetResolver { private $manifestData; private $projectDir; public function __construct(KernelInterface $kernel) { $this->projectDir = $kernel->getProjectDir(); $manifestPath = $this->projectDir.'/public/build/manifest.json'; // 读取manifest内容(生产环境可以缓存这个数据,避免每次读取文件) if (file_exists($manifestPath)) { $this->manifestData = json_decode(file_get_contents($manifestPath), true); } } public function getVersionedPath(string $originalPath): string { // 如果manifest不存在(比如开发环境没构建),直接返回原路径 if (!$this->manifestData) { return '/build/'.$originalPath; } $manifestKey = 'build/'.$originalPath; // 从manifest里拿版本化后的路径,找不到就 fallback 到原路径 return isset($this->manifestData[$manifestKey]) ? '/'.$this->manifestData[$manifestKey] : '/build/'.$originalPath; } }
步骤3:在TCPDF里使用
在生成PDF的代码里注入这个服务,拿到正确的绝对路径(TCPDF需要服务器本地绝对路径):
// 比如在某个Controller或者PDF生成服务里 use App\Service\AssetResolver; use TCPDF; public function generatePdf(AssetResolver $assetResolver) { $pdf = new TCPDF(); // 获取版本化后的相对路径 $logoRelativePath = $assetResolver->getVersionedPath('images/logo.png'); // 转成服务器绝对路径 $logoAbsolutePath = $this->projectDir.'/public'.$logoRelativePath; // 或者用$_SERVER['DOCUMENT_ROOT']拼接: // $logoAbsolutePath = $_SERVER['DOCUMENT_ROOT'].$logoRelativePath; // 插入图片到PDF $pdf->Image($logoAbsolutePath, 10, 10, 30, 0, 'PNG', '', 'T', false, 300, '', false, false, 0, false, false, false); // ... 其他PDF生成逻辑 return $pdf->Output('document.pdf', 'I'); }
方案2:用Symfony官方Asset组件(更省心)
Symfony的Asset组件已经原生支持读取manifest文件,不用自己写服务,直接用官方封装好的工具:
步骤1:安装Asset组件(如果没装)
composer require symfony/asset
步骤2:配置manifest路径
在config/packages/framework.yaml里添加Asset的配置:
# config/packages/framework.yaml framework: assets: json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
步骤3:在PHP里使用
直接注入Package类或者用AssetInterface来获取路径:
use Symfony\Component\Asset\PackageInterface; use TCPDF; public function generatePdf(PackageInterface $assetPackage) { // 获取版本化后的路径 $logoRelativePath = $assetPackage->getUrl('build/images/logo.png'); $logoAbsolutePath = $_SERVER['DOCUMENT_ROOT'].$logoRelativePath; $pdf = new TCPDF(); $pdf->Image($logoAbsolutePath, 10, 10, 30); // ... 其他逻辑 }
如果是从Twig模板生成PDF内容,还可以直接用Twig的asset()函数拿到正确路径,比如:
{# 在Twig模板里 #} <img src="{{ asset('build/images/logo.png') }}" />
然后把渲染后的HTML传给TCPDF即可。
开发环境小技巧
开发环境下如果不想每次修改图片都重新构建,可以直接用Encore dev server的路径,比如:
if ($this->getParameter('kernel.environment') === 'dev') { // 用dev server的地址,不用版本化 $logoAbsolutePath = 'http://localhost:8080/build/images/logo.png'; } else { // 生产环境用manifest获取版本化路径 $logoAbsolutePath = $_SERVER['DOCUMENT_ROOT'].$assetPackage->getUrl('build/images/logo.png'); }
内容的提问来源于stack exchange,提问作者Kristen Joseph-Delaffon

