ampproject/amp-toolbox

一组使使用PHP发布和托管AMP页面更加容易的AMP工具集合。

0.11.5 2024-02-05 19:10 UTC

README

AMP logo

PHP AMP Toolbox

Build Status Coverage

一组使使用PHP发布和托管AMP页面更加容易的AMP工具集合。

以下工具是此库的一部分:

PHP AMP优化器

AMP优化器是一个库,通过自动应用AMP性能最佳实践和启用AMP服务器端渲染,对AMP标记进行服务器端优化。

目录(点击展开)

概念概述

AMP优化器是一个AmpProject\Optimizer\TransformationEngine对象,它设置了一系列连续的AmpProject\Optimizer\Transformer对象。该引擎以HTML标记字符串或AmpProject\Dom\Document对象的形式接受未优化的输入,并将其转换为优化的HTML标记字符串。

在过程中,可能会发生错误,使得优化的部分变得不可能。这些错误被收集在一个AmpProject\Optimizer\ErrorCollection对象中,您可以迭代它以了解更多信息并提供所需反馈。

Diagram showing the conceptual parts of the AMP Optimizer

安装

AMP优化器是AMP Toolbox for PHP库的一部分,您可以通过Composer PHP包管理器将其拉入项目。

composer require ampproject/amp-toolbox

基本用法

以下代码片段显示了使用AMP优化器的最基本方式:

use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\TransformationEngine;

$transformationEngine = new TransformationEngine();    // 1.
$errorCollection      = new ErrorCollection;           // 2.

$optimizedHtml = $transformationEngine->optimizeHtml(  // 3.
    $unoptimizedHtml,                                  // 4.
    $errorCollection                                   // 5.
);
  1. 首先,我们实例化转换引擎本身。
  2. 然后,我们实例化一个AmpProject\Optimizer\ErrorCollection对象,因为我们需要一个“袋子”来收集错误并在周围传递。
  3. 最后一步,我们存储调用转换引擎的optimizeHtml()方法的结果,该方法需要...
  4. ...未优化的输入HTML标记字符串,以及...
  5. ...我们已实例化的空错误收集。转换引擎运行后,此集合将包含在转换过程中遇到的全部错误。

使用DOM表示形式

如果您已经有了DOM表示,就没有必要将其首先保存为HTML,然后再使用转换引擎。转换引擎直接通过其optimizeDom()方法接受AmpProject\Dom\Document对象[^1]。

如果您有一个普通的PHP内置的 DOMDocument,可以使用 AmpProject\Dom\Document::fromNode() 方法将其转换为 AmpProject\Dom\Document

use AmpProject\Dom\Document;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\TransformationEngine;

if (! $dom instanceof Document) {
    $dom = Document::fromNode($dom);
}

$transformationEngine = new TransformationEngine();
$errorCollection      = new ErrorCollection;
$transformationEngine->optimizeDom($dom, $errorCollection);

请注意,optimizeDom() 没有返回值,因为它会就地修改提供的 AmpProject\Dom\Document

处理错误

您传递给转换引擎的 optimizeHtml()optimizeDom() 方法的 AmpProject\Optimizer\ErrorCollection 在优化过程中应保持为空。

要检查是否找到错误,您可以遍历集合,这将为您提供 0 个或多个 AmpProject\Optimizer\Error 对象。

$errorCollection = new ErrorCollection;

// Do the transformation here, while passing in the $errorCollection object.

foreach ($errorCollection as $error) {
	printf(
	    "Error code: %s\nError Message: %s\n",
	    $error->getCode(),
	    $error->getMessage()
	);
}

为了提前返回,可以进行错误数量的快速计数。

if ($errorCollection->count() > 0) {
	$this->log('The AMP serverside optimization process produced one or more errors.');
}

您还可以检查错误集合是否包含具有特定代码的错误。当前约定是,所有错误都有其类短名(不带命名空间)作为错误代码。

if ($errorCollection->has('CannotRemoveBoilerplate')) {
	$this->log('The boilerplate was not removed by the Optimizer.');
}

请注意,这仅允许您检查是否出现错误“类别”。这可能是一个或多个具有相同代码的错误。如果您需要进行更详细的检查,则应遍历集合。

包含的转换器

配置转换器

您可以将配置对象注入到 AmpProject\Optimizer\TransformationEngine 中以覆盖默认配置。

AmpProject\Optimizer\Configuration 接口及其默认实现 AmpProject\Optimizer\DefaultConfiguration 将提供要使用的转换器的列表,并允许访问它们存储的特定于转换器的配置对象。

要覆盖要使用的转换器列表,您可以提供一个包含 AmpProject\Optimizer\Configuration::KEY_TRANSFORMERS 键的数组。

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use AmpProject\Optimizer\Transformer;

$configurationData = [
	Configuration::KEY_TRANSFORMERS => [
		Transformer\ServerSideRendering::class,
		Transformer\AmpRuntimeCss::class,
		Transformer\TransformedIdentifier::class,
	],
];

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration($configurationData)
);

转换器的配置值可以存储在这些转换器的完全限定类名下。这可以通过使用它们的 ::class 常量轻松完成。

它们通常还会提供公开可访问的常量,作为已知配置键。

在以下示例中,我们通过将 'canary' 选项设置为 true(默认为 false)来配置 AmpProject\Optimizer\Transformer\AmpRuntimeCss 转换器。

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use AmpProject\Optimizer\Transformer;

$configurationData = [
	Transformer\AmpRuntimeCss::class => [
		Configuration\AmpRuntimeCssConfiguration::CANARY => true,
	],
];

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration($configurationData)
);

创建自定义转换器

自定义转换器在最低层面上是一个实现 AmpProject\Optimizer\Transformer 接口的对象。

这意味着它至少需要以下方法

public function transform(Document $document, ErrorCollection $errors)
{
	// Apply transformations to the provided $document and ...

	// ... add any encountered errors to the $errors collection.
}

要使此转换器对转换引擎已知,您需要将其添加到传递给它的 AmpProject\Optimizer\Configuration 对象的 AmpProject\Optimizer\Configuration::KEY_TRANSFORMERS 键中。

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use MyProject\MyCustomTransformer;

$configurationData = [
	Configuration::KEY_TRANSFORMERS => array_merge(
		Configuration::DEFAULT_TRANSFORMERS,
		[
			MyCustomTransformer::class
		],
	),
];

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration($configurationData)
);

使自定义转换器可配置

对于单个转换器的配置对象,需要使用其 registerConfigurationClass() 方法将其注册到主 AmpProject\Optimizer\Configuration 对象中,该方法接受转换器的完全限定类名以及相应配置对象的完全限定类名作为两个参数。

本库附带变压器的配置对象默认已经注册。但如果您添加第三方或自定义变压器,则需要首先将它们可能需要的配置对象注册到主要的 AmpProject\Optimizer\Configuration 对象中。

在以下示例中,我们在默认集合并之外添加了一个新的 MyProject\MyCustomTransformer 变压器,并使用默认值对其进行配置,然后注册其相应的配置对象,以确保配置可以正确验证并传递。

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use MyProject\MyCustomTransformer;
use MyProject\MyCustomTransformerConfiguration;

$configurationData = [
	Configuration::KEY_TRANSFORMERS => array_merge(
		Configuration::DEFAULT_TRANSFORMERS,
		[
			MyCustomTransformer::class
		],
	),
	MyCustomTransformer::class => [
		MyCustomTransformerConfiguration::SOME_CONFIG_KEY => 'some value',
	],
];

$configuration = new DefaultConfiguration($configurationData);

$configuration->registerConfigurationClass(
	MyCustomTransformer::class,
	MyCustomTransformerConfiguration::class
);

$transformationEngine = new TransformationEngine($configuration);

为了正确连接,MyProject\MyCustomTransformer 类需要在构造函数中接受一个实现 AmpProject\Optimizer\TransformerConfiguration 接口的对象。然后,转换引擎将在变压器实例化时注入适当的实现。

MyProject\MyCustomTransformerConfiguration 类应实现相同的 AmpProject\Optimizer\TransformerConfiguration 接口。为了方便起见,它可以通过扩展抽象的 AmpProject\Optimizer\Configuration\BaseTransformerConfiguration 基类来实现这一点。

配置对象将根据需要自动注入到变压器的构造函数中。

以下是我们的自定义 MyProject\MyCustomTransformer 变压器的一个示例配置类

namespace MyProject;

use AmpProject\Optimizer\Configuration\BaseTransformerConfiguration;

final class MyCustomTransformerConfiguration extends BaseTransformerConfiguration
{
	const SOME_CONFIG_KEY = 'some_config_key';

	protected function getAllowedKeys()
	{
		return [
			self::SOME_CONFIG_KEY => 'default value',
		];
	}

	protected function validate($key, $value)
	{
		switch ($key) {
			case self::SOME_CONFIG_KEY:
				// Validate configuration value here.
		}

		return $value;
	}
}

以下是变压器本身如何接受并使用配置对象的示例

namespace MyProject;

use AmpProject\Dom\Document;
use AmpProject\Optimizer\Configurable;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\TransformerConfiguration;
use AmpProject\Optimizer\Transformer;

final class MyCustomTransformer implements Transformer
{
	private $configuration;

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

	public function transform(Document $document, ErrorCollection $errors)
	{
		// Bogus transformer logic that adds the configuration value as a body attribute.
		$document->body->setAttribute(
			'data-my-custom-transformer-body-attribute,
			$this->configuration->get(
				MyCustomTransformerConfiguration::SOME_CONFIG_KEY
			)
		);
	}
}

请求外部数据的转换器

如果您的变压器需要执行远程请求以获取外部数据(例如,AmpProject\Optimizer\Transformer\AmpRuntimeCss 用于获取内联CSS的最新版本),则需要将一个 AmpProject\RemoteGetRequest 对象作为参数传递给构造函数。然后,转换引擎将在变压器实例化时注入适当的实现。

这层抽象允许转换引擎之外的代码控制远程请求的具体条件和限制,例如,例如限制它们或与框架的缓存子系统集成。

namespace MyProject;

use AmpProject\Dom\Document;
use AmpProject\RemoteGetRequest;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\Transformer;
use Throwable;

final class MyCustomTransformer implements Transformer
{
	const END_POINT = 'https://example.com/some_endpoint/';

	private $remoteRequest;

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

	public function transform(Document $document, ErrorCollection $errors)
	{
		try {
			$response = $this->remoteRequest->get(self::END_POINT);
		} catch (Throwable $exception) {
			// Add error handling here.
		}

		$statusCode = $response->getStatusCode();

		if (200 < $statusCode || $statusCode >= 300) {
			// Add error handling here.
		}

		$content = $response->getBody();

		// Make use of the $content you've just retrieved from an external source.
	}
}

适应远程请求的处理

用于满足通过 AmpProject\RemoteGetRequest 接口发出的请求的实现可以注入到 AmpProject\Optimizer\TransformationEngine 的第二个可选参数中。

use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration(),

	// A custom implementation that lets you control how remote requests are handled.
	new MyCustomRemoteGetRequestImplementation()
);

如果实例化转换引擎时未提供此可选第二个参数,则使用默认的 AmpProject\RemoteRequest\CurlRemoteGetRequest 实现使用。

已提供其他有用实现

以下代码显示了如何使用 cURL 进行远程请求的示例,并在外部请求失败(可能由于网络问题)时回退到存储在磁盘上的文件。

use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use AmpProject\RemoteRequest\CurlRemoteGetRequest;
use AmpProject\RemoteRequest\FallbackRemoteGetRequest;
use AmpProject\RemoteRequest\FilesystemRemoteGetRequest;

const FALLBACK_MAPPING = [
	'https://example.com/some_endpoint/' => __DIR__ . '/../fallback_files/some_endpoint.json',
];

$remoteRequest = new FallbackRemoteGetRequest(
	new CurlRemoteGetRequest(true, 5, 0),                  // 5 second timeout with no retries, and ...
	new FilesystemRemoteGetRequest(self::FALLBACK_MAPPING) // ... fall back to shipped files.
);

$transformationEngine = new TransformationEngine(new DefaultConfiguration(), $remoteRequest);

要构建自己的传输,您需要实现 AmpProject\RemoteGetRequest 接口。有关自定义传输的更复杂示例或与您选择的堆栈集成,请参阅 Amp for WordPress WordPress 插件提供的两个实现