thomasgreen/amp-toolbox

这是一个PHP 5.5.9下更容易发布和托管AMP页面的AMP工具集合。

0.2.9 2021-01-26 16:50 UTC

README

AMP logo

PHP AMP Toolbox

Build Status

这是一个AMP工具集合,使您更容易使用PHP发布和托管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优化器是PHP AMP Toolbox库的一部分,您可以通过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\Configuration::KEY_TRANSFORMERS键的数组。

use AmpProject\Optimizer\Configuration;
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 Configuration($configurationData)
);

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

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

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

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

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

$transformationEngine = new TransformationEngine(
	new Configuration($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\TransformationEngine;
use MyProject\MyCustomTransformer;

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

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

使自定义转换器可配置

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

与此库一起提供的转换器的配置对象已经默认注册。但如果您添加第三方或自定义转换器,您需要首先将它们可能需要的配置对象注册到主要的AmpProject\Optimizer\Configuration对象中。

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

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

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

$configuration = new Configuration($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\Configuration;
use AmpProject\Optimizer\TransformationEngine;

$transformationEngine = new TransformationEngine(
	new Configuration(),

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

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

已提供其他实现,可能很有用

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

use AmpProject\Optimizer\Configuration;
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 Configuration(), $remoteRequest);

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