Asset组件

3.3 version
维护中的版本

Asset组件管理URL的生成以及web assets的版本,诸如CSS样式表,JavaScripts以及图片文件等。

过去,在web程序中写死web assets的链接是很常见的。例如:

1
2
3
4
5
<link rel="stylesheet" type="text/css" href="/css/main.css">
 
<!-- ... -->
 
<a href="/"><img src="/images/logo.png"></a>

这种实践现已不再推荐,除非程序极其简单。写死链接有其不利点,因为:

  • 模板冗长: 你不得不对每个资源写上完整路径。当使用Asset组件时,你可以把资源给打包成群组以避免重复其路径中的通用部分;
  • 版本化极其困难: 不得不对每个程序定制管理。添加一个版本 (如 main.css?v=5) 到asset URLs对某些程序来说是必不可少的,因为它可以让你控制资源(assets)应如何被缓存。Asset组件能让你对不同的包(package)制定不同的版本策略;
  • 移动资源的位置 麻烦且易出错: 它需要你小心地更新全部模板中的全部资源的链接。Asset组件让你毫不费力地通过改变“关联到资源包的基本路径的值”即可实现;
  • 几乎不可能使用多个CDN: 这项技术要求针对每次请求随机地改变资源链接。Asset组件对多CDN提供了完美支持,不管是常规的 (http://) 还是安全的 (https://)。

安装 

你可以通过下述两种方式安装:

然后,包容vendor/autoload.php文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。

用法 

Asset包 

Asset组件通过包(packages)来管理资源(assets)。一个包,集合了全部资源,这些资源共享着相同属性:版本策略,基本路径(base path),CDN主机等。在下面的基本用例中,一个包被创建并管理着没有版本功能的资源:

1
2
3
4
5
6
7
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
 
$package = new Package(new EmptyVersionStrategy());
 
echo $package->getUrl('/image.png');
// result: /image.png

Packages实现的是 PackageInterface接口,它定义了如下两个方法:

getVersion()
返回一个资源的asset版本。
getUrl()
返回绝对路径,或返回相对于根目录的公开路径(public path)。

使用一个包,你可以:

  1. 对资源进行版本化;
  2. 为资源设置一个 常用基本路径 (比如 /css);
  3. 为资源 配置一个CDN

版本化的资源 

Asset组件的一个主要功能,是具有对程序资源的版本进行管理的能力。Asset version常用于控制资源的缓存。

不同于依赖于简单的版本架构,Asset组件允许你通过PHP类定义高级版本策略(strategies)。两个内置的策略分别是 EmptyVersionStrategy,它不对资源添加任何版本,以及 StaticVersionStrategy,它允许你以格式化字符串来设置版本。

在下面例程中, StaticVersionStrategy 用于加挂 v1 后缀到任何意的资源路径:

1
2
3
4
5
6
7
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
 
$package = new Package(new StaticVersionStrategy('v1'));
 
echo $package->getUrl('/image.png');
// result: /image.png?v1

如果你希望调整版本格式,传入一个兼容sprintf的格式化字符串,作为 StaticVersionStrategy 构造器的第二个参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
// put the 'version' word before the version value
// 在版本值的前面放一个 'version' 字样
$package = new Package(new StaticVersionStrategy('v1', '%s?version=%s'));
 
echo $package->getUrl('/image.png');
// result: /image.png?version=v1
 
// put the asset version before its path
// 在路径前面放置资源的版本
$package = new Package(new StaticVersionStrategy('v1', '%2$s/%1$s'));
 
echo $package->getUrl('/image.png');
// result: /v1/image.png

自定义版本策略 

使用 VersionStrategyInterface 来定义你自己的版本策略。例如,你的程序可能需要附加当前日期到所有的web资源,以便在每天销毁缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
 
class DateVersionStrategy implements VersionStrategyInterface
{
    private $version;
 
    public function __construct()
    {
        $this->version = date('Ymd');
    }
 
    public function getVersion($path)
    {
        return $this->version;
    }
 
    public function applyVersion($path)
    {
        return sprintf('%s?v=%s', $path, $this->getVersion($path));
    }
}

资源群组 

一般来讲,很多资源都被存放于常见路径下 (如 /static/images)。如果你也是如此,用 PathPackage 来替换默认的 Package 类,即可避免一次又一次地重复那个路径:

1
2
3
4
5
6
7
use Symfony\Component\Asset\PathPackage;
// ...
 
$package = new PathPackage('/static/images', new StaticVersionStrategy('v1'));
 
echo $package->getUrl('/logo.png');
// result: /static/images/logo.png?v1

知晓“请求上下文”的资源 

如果你在项目中同时使用了 HttpFoundation 组件 (例如,在一套Symfony程序中),那么 PathPackage 可以统管当前请求的上下文:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
// ...
 
$package = new PathPackage(
    '/static/images',
    new StaticVersionStrategy('v1'),
    new RequestStackContext($requestStack)
);
 
echo $package->getUrl('/logo.png');
// result: /somewhere/static/images/logo.png?v1

现在,request context已设置, PathPackage 将准备好当前请求的base URL。所以,举例来说,如果你的整个网站都存放在服务器根目录的 /somewhere 目录下,并且配置好的基本路径是 /static/images,则全部路径都将增加 /somewhere/static/images 前缀。

绝对Assets和CDNs 

那些把资源存放在不同的域名和CDN下的程序 (Content Delivery Networks) 应该使用 UrlPackage 类,以生成各自资源的绝对URL:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\Asset\UrlPackage;
// ...
 
$package = new UrlPackage(
    'http://static.example.com/images/',
    new StaticVersionStrategy('v1')
);
 
echo $package->getUrl('/logo.png');
// result: http://static.example.com/images/logo.png?v1

你也可以传一个schema-agnostic(译注:是否https未知)的URL:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\Asset\UrlPackage;
// ...
 
$package = new UrlPackage(
    '//static.example.com/images/',
    new StaticVersionStrategy('v1')
);
 
echo $package->getUrl('/logo.png');
// result: //static.example.com/images/logo.png?v1

这非常有用,因为如果有用户通过https来访问你的网站,资源将自动地通过HTTPS被请求。只需确保你的CDN托管支持https即可。

如果你把资源托在多个域名上以改进程序性能,传入一个URL的数组到 UrlPackage 构造器的第一个参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\Asset\UrlPackage;
// ...
 
$urls = array(
    '//static1.example.com/images/',
    '//static2.example.com/images/',
);
$package = new UrlPackage($urls, new StaticVersionStrategy('v1'));
 
echo $package->getUrl('/logo.png');
// result: http://static1.example.com/images/logo.png?v1
echo $package->getUrl('/icon.png');
// result: http://static2.example.com/images/icon.png?v1

对于每个资源,链接中的某一个会被随机使用。但是,这种选择是确定了的,也就是说,每一个asset总是会服务于相同的某个域名。这种行为可以简化HTTP缓存的管理。

知晓“请求上下文”的资源 

与相对于程序的资源类似,绝对资源(absolute assets)也能够统管当前请求的上下文。下例中,只有考虑了请求的scheme时,才能选出合适的基本路径(对于HTTPs请求,是HTTPs链接或者“与协议相关”的链接,对于HTTP请求则是任意的base URL)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
// ...
 
$package = new UrlPackage(
    array('http://example.com/', 'https://example.com/'),
    new StaticVersionStrategy('v1'),
    new RequestStackContext($requestStack)
);
 
echo $package->getUrl('/logo.png');
// assuming the RequestStackContext says that we are on a secure host
// 假设RequestStackContext说我们正处于安全连接
// result: https://example.com/logo.png?v1

已命名的包 

管理着许多资源的程序可能需要把它们打包成相同的版本策略和基本路径。Asset组件包含了一个 Packages 的类以简化多个assets的管理。

下例中,所有的包都使用相同的版本策略(versioning strategy),但他们却有着不同的基本路径(base paths):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\Packages;
// ...
 
$versionStrategy = new StaticVersionStrategy('v1');
 
$defaultPackage = new Package($versionStrategy);
 
$namedPackages = array(
    'img' => new UrlPackage('http://img.example.com/', $versionStrategy),
    'doc' => new PathPackage('/somewhere/deep/for/documents', $versionStrategy),
);
 
$packages = new Packages($defaultPackage, $namedPackages)

Packages 类能够定义默认的package,为的是给那些没有定义“包名称”的资源使用。此外,程序还定义了一个名为 img 的包,用于伺服外部域名的图片,以及一个 doc 包,用于在模板中链接到文档时,避免重复使用超长的路径:

1
2
3
4
5
6
7
8
echo $packages->getUrl('/main.css');
// result: /main.css?v1
 
echo $packages->getUrl('/logo.png', 'img');
// result: http://img.example.com/logo.png?v1
 
echo $packages->getUrl('/resume.pdf', 'doc');
// result: /somewhere/deep/for/documents/resume.pdf?v1

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)