搜索
首页后端开发php教程编写异步库 - 让#x27; s将html转换为pdf

Writing Async Libraries - Let's Convert HTML to PDF

关键要点

  • PHP异步编程,例如HTML转PDF,允许非阻塞操作,通过同时执行其他代码来提高性能。
  • 在异步框架中使用Promise和回调可以简化延迟操作和潜在错误的处理,使代码更健壮、更易于维护。
  • 开发自定义异步库(如本文讨论的HTML转PDF转换器)涉及创建抽象,使用ReactPHP和Amp等工具有效地管理异步任务。
  • 异步代码可以适应同步执行,确保不同应用程序架构之间的兼容性和灵活性,而不会牺牲异步编程的优势。
  • 通过将并行执行逻辑抽象到通用的驱动程序系统中,可以支持多个框架和环境,该系统可以与各种异步库接口。
  • 本文阐述了PHP中异步HTML转PDF转换的实际实现,强调了理解和利用现代编程范例对高效应用程序开发的重要性。

本文由Thomas Punt同行评审。感谢所有SitePoint的同行评审员,使SitePoint的内容达到最佳状态!


几乎在每一次会议上,都会讨论到PHP异步编程这个话题。我很高兴它现在如此频繁地被提及。但是,这些演讲者没有透露一个秘密……

创建异步服务器、解析域名、与文件系统交互:这些都是简单的事情。创建你自己的异步库很难。而这正是你花费大部分时间的地方!

Writing Async Libraries - Let's Convert HTML to PDF

这些简单的事情之所以简单,是因为它们是概念验证——使异步PHP与NodeJS竞争。你可以看到它们的早期接口有多相似:

var http = require("http");
var server = http.createServer();

server.on("request", function(request, response) {
    response.writeHead(200, {
        "Content-Type": "text/plain"
    });

    response.end("Hello World");
});

server.listen(3000, "127.0.0.1");

此代码使用Node 7.3.0测试

require "vendor/autoload.php";

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$server = new React\Http\Server($socket);

$server->on("request", function($request, $response) {
    $response->writeHead(200, [
        "Content-Type" => "text/plain"
    ]);

    $response->end("Hello world");
});

$socket->listen(3000, "127.0.0.1");
$loop->run();

此代码使用PHP 7.1和react/http:0.4.2测试

今天,我们将研究一些方法,使你的应用程序代码在异步架构中良好运行。别担心——你的代码仍然可以在同步架构中工作,因此你不必为了学习这项新技能而放弃任何东西。除了花费一些时间……

你可以在Github上找到本教程的代码。我已经用PHP 7.1和最新版本的ReactPHP和Amp测试过它了。

充满希望的理论

异步代码有一些常见的抽象。我们已经看到其中一个:回调。回调顾名思义,描述了它们如何处理缓慢或阻塞操作。同步代码充满了等待。请求某些东西,等待事情发生。

因此,异步框架和库可以使用回调。请求某些东西,当它发生时:框架或库将回调你的代码。

在HTTP服务器的情况下,我们不会抢先处理所有请求。我们也不会等待请求发生。我们只是描述应该调用的代码,如果请求发生的话。事件循环负责其余的工作。

第二个常见的抽象是Promise。回调是等待未来事件的钩子,而Promise是对未来值的引用。它们看起来像这样:

var http = require("http");
var server = http.createServer();

server.on("request", function(request, response) {
    response.writeHead(200, {
        "Content-Type": "text/plain"
    });

    response.end("Hello World");
});

server.listen(3000, "127.0.0.1");

这比单独使用回调多了一些代码,但这是一种有趣的方法。我们等待某些事情发生,然后做另一件事。如果出现问题,我们会捕获错误并做出合理的响应。这看起来很简单,但并没有被充分讨论。

我们仍在使用回调,但我们已经将它们包装在一个抽象中,这在其他方面对我们有帮助。一个好处是它们允许多个解析回调……

require "vendor/autoload.php";

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$server = new React\Http\Server($socket);

$server->on("request", function($request, $response) {
    $response->writeHead(200, [
        "Content-Type" => "text/plain"
    ]);

    $response->end("Hello world");
});

$socket->listen(3000, "127.0.0.1");
$loop->run();

我还想让我们关注另一件事。那就是Promise提供了一种通用的语言——一种通用的抽象——来思考同步代码如何变成异步代码。

让我们获取一些应用程序代码并使其异步,使用Promise……

制作PDF文件

应用程序生成某种摘要文档是很常见的——无论是发票还是库存清单。假设你有一个电子商务应用程序,它通过Stripe处理付款。当客户购买商品时,你希望他们能够下载该交易的PDF收据。

你可以通过多种方式做到这一点,但一种非常简单的方法是使用HTML和CSS生成文档。你可以将其转换为PDF文档,并允许客户下载它。

我最近需要做类似的事情。我发现没有很多好的库支持这种操作。我找不到单个抽象可以让我在不同的HTML→PDF引擎之间切换。所以我开始自己构建一个。

我开始考虑我的抽象需要做什么。我选择了一个非常类似的接口:

readFile()
    ->then(function(string $content) {
        print "content: " . $content;
    })
    ->catch(function(Exception $e) {
        print "error: " . $e->getMessage();
    });

为了简单起见,我希望除了render方法之外的所有方法都能充当getter和setter。给定这组预期方法,接下来要做的是创建一个实现,使用一个可能的引擎。我将domPDF添加到我的项目中,并开始使用它:

$promise = readFile();
$promise->then(...)->catch(...);

// ...让我们向现有代码添加日志记录

$promise->then(function(string $content) use ($logger) {
    $logger->info("file was read");
});

我不会详细介绍如何使用domPDF。我认为文档做得足够好,让我可以专注于此实现的异步部分。

我们稍后会查看data和parallel方法。关于此Driver实现的重要一点是它将数据(如果已设置,否则为默认值)和自定义选项收集在一起。它将这些传递给我们希望异步运行的回调。

domPDF不是异步库,将HTML转换为PDF是一个非常缓慢的过程。那么我们如何使其异步呢?好吧,我们可以编写一个完全异步的转换器,或者我们可以使用现有的同步转换器;但在并行线程或进程中运行它。

这就是我为parallel方法所做的:

var http = require("http");
var server = http.createServer();

server.on("request", function(request, response) {
    response.writeHead(200, {
        "Content-Type": "text/plain"
    });

    response.end("Hello World");
});

server.listen(3000, "127.0.0.1");

在这里,我实现了getter-setter方法,认为我可以将它们重用于下一个实现。data方法充当收集各种文档属性到数组中的快捷方式,使它们更容易传递给匿名函数。

parallel方法开始变得有趣:

require "vendor/autoload.php";

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$server = new React\Http\Server($socket);

$server->on("request", function($request, $response) {
    $response->writeHead(200, [
        "Content-Type" => "text/plain"
    ]);

    $response->end("Hello world");
});

$socket->listen(3000, "127.0.0.1");
$loop->run();

我非常喜欢Amp项目。它是一组支持异步架构的库的集合,它们是async-interop项目的关键支持者。

他们的一个库称为amphp/parallel,它支持多线程和多进程代码(通过Pthreads和Process Control扩展)。这些spawn方法返回Amp的Promise实现。这意味着render方法可以像任何其他返回Promise的方法一样使用:

readFile()
    ->then(function(string $content) {
        print "content: " . $content;
    })
    ->catch(function(Exception $e) {
        print "error: " . $e->getMessage();
    });

这段代码有点复杂。Amp还提供了一个事件循环实现和所有辅助代码,以便能够将普通的PHP生成器转换为协程和Promise。你可以在我写的另一篇文章中阅读这甚至是如何可能的,以及它与PHP的生成器有什么关系。

返回的Promise也正在标准化。Amp返回Promise规范的实现。它与我上面显示的代码略有不同,但仍然执行相同的函数。

生成器的工作方式类似于具有协程的语言中的协程。协程是可以中断的函数,这意味着它们可以用于执行短时间的操作,然后在等待某些事情时暂停。暂停时,其他函数可以使用系统资源。

实际上,这看起来像这样:

$promise = readFile();
$promise->then(...)->catch(...);

// ...让我们向现有代码添加日志记录

$promise->then(function(string $content) use ($logger) {
    $logger->info("file was read");
});

这看起来比一开始只编写同步代码复杂得多。但它允许的是,当我们等待funcReturnsPromise完成时,其他事情可以发生。

生成Promise正是我们所说的抽象。它为我们提供了一个框架,我们可以通过它来创建返回Promise的函数。代码可以以可预测和可理解的方式与这些Promise交互。

看看使用我们的驱动程序呈现PDF文档的样子:

interface Driver
{
    public function html($html = null);
    public function size($size = null);
    public function orientation($orientation = null);
    public function dpi($dpi = null);
    public function render();
}

这不如在异步HTTP服务器中生成PDF那样有用。有一个名为Aerys的Amp库,它使创建这些类型的服务器更容易。使用Aerys,你可以创建以下HTTP服务器代码:

class DomDriver extends BaseDriver implements Driver
{
    private $options;

    public function __construct(array $options = [])
    {
        $this->options = $options;
    }

    public function render()
    {
        $data = $this->data();
        $custom = $this->options;

        return $this->parallel(
            function() use ($data, $custom) {
                $options = new Options();

                $options->set(
                    "isJavascriptEnabled", true
                );

                $options->set(
                    "isHtml5ParserEnabled", true
                );

                $options->set("dpi", $data["dpi"]);

                foreach ($custom as $key => $value) {
                    $options->set($key, $value);
                }

                $engine = new Dompdf($options);

                $engine->setPaper(
                    $data["size"], $data["orientation"]
                );

                $engine->loadHtml($data["html"]);
                $engine->render();

                return $engine->output();
            }
        );
    }
}

同样,我现在不会详细介绍Aerys。这是一个令人印象深刻的软件,非常值得拥有自己的文章。你不需要理解Aerys的工作原理就能看到我们的转换器代码在它旁边看起来有多自然。

我的老板说“不要用异步!”

如果你不确定你多久才能构建异步应用程序,为什么要费这么大的劲?编写此代码使我们能够深入了解新的编程范例。而且,仅仅因为我们正在将此代码编写为异步的,并不意味着它不能在同步环境中工作。

要在同步应用程序中使用此代码,我们只需要将一些异步代码移到内部:

abstract class BaseDriver implements Driver
{
    protected $html = "";
    protected $size = "A4";
    protected $orientation = "portrait";
    protected $dpi = 300;

    public function html($body = null)
    {
        return $this->access("html", $html);
    }

    private function access($key, $value = null)
    {
        if (is_null($value)) {
            return $this->$key;
        }

        $this->$key = $value;
        return $this;
    }

    public function size($size = null)
    {
        return $this->access("size", $size);
    }

    public function orientation($orientation = null)
    {
        return $this->access("orientation", $orientation);
    }

    public function dpi($dpi = null)
    {
        return $this->access("dpi", $dpi);
    }

    protected function data()
    {
        return [
            "html" => $html,
            "size" => $this->size,
            "orientation" => $this->orientation,
            "dpi" => $this->dpi,
        ];
    }

    protected function parallel(Closure $deferred)
    {
        // TODO
    }
}

使用此装饰器,我们可以编写看起来像是同步代码的代码:

var http = require("http");
var server = http.createServer();

server.on("request", function(request, response) {
    response.writeHead(200, {
        "Content-Type": "text/plain"
    });

    response.end("Hello World");
});

server.listen(3000, "127.0.0.1");

它仍然异步运行代码(至少在后台),但所有这些都不会暴露给消费者。你可以在同步应用程序中使用它,并且永远不会知道幕后发生了什么。

支持其他框架

Amp具有一些特定的要求,使其不适合所有环境。例如,基本Amp(事件循环)库需要PHP 7.0。parallel库需要Pthreads扩展或Process Control扩展。

我不想强加这些限制给每个人,并且想知道我如何才能支持更广泛的系统。答案是将并行执行代码抽象到另一个驱动程序系统中:

require "vendor/autoload.php";

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$server = new React\Http\Server($socket);

$server->on("request", function($request, $response) {
    $response->writeHead(200, [
        "Content-Type" => "text/plain"
    ]);

    $response->end("Hello world");
});

$socket->listen(3000, "127.0.0.1");
$loop->run();

我可以为Amp以及(限制较少,但更旧的)ReactPHP实现它:

readFile()
    ->then(function(string $content) {
        print "content: " . $content;
    })
    ->catch(function(Exception $e) {
        print "error: " . $e->getMessage();
    });

我习惯于将闭包传递给多线程和多进程工作程序,因为这就是Pthreads和Process Control的工作方式。使用ReactPHP Process对象完全不同,因为它们依赖于exec进行多进程执行。我决定实现我习惯使用的相同闭包功能。这对于异步代码来说不是必需的——这纯粹是品味的问题。

SuperClosure库序列化闭包及其绑定的变量。这里的大部分代码都是你期望在worker脚本中找到的代码。事实上,使用ReactPHP的子进程库的唯一方法(除了序列化闭包之外)是将任务发送到worker脚本。

现在,我们不再使用$this->parallel和Amp特定的代码加载我们的驱动程序,而是可以传递运行程序实现。作为异步代码,这类似于:

$promise = readFile();
$promise->then(...)->catch(...);

// ...让我们向现有代码添加日志记录

$promise->then(function(string $content) use ($logger) {
    $logger->info("file was read");
});

不要被ReactPHP代码与Amp代码的不同之处所震惊。ReactPHP没有实现与Amp相同的协程基础。相反,ReactPHP更喜欢使用回调来处理大多数事情。这段代码仍然只是并行运行PDF转换,并返回生成的PDF数据。

通过抽象的运行程序,我们可以使用任何我们想要的异步框架,并且我们可以预期我们将使用的驱动程序将返回该框架的抽象。

我可以使用这个吗?

最初只是一个实验,变成了一个多驱动程序、多运行程序的HTML→PDF库;称为Paper。它就像HTML→PDF的Flysystem等效项,但它也是如何编写异步库的一个很好的例子。

当你尝试制作异步PHP应用程序时,你会发现库生态系统中的差距。不要被这些吓倒!相反,抓住机会思考一下你将如何使用ReactPHP和Amp提供的抽象来制作你自己的异步库。

你最近是否构建了一个有趣的异步PHP应用程序或库?请在评论中告诉我们。

关于异步转换HTML为PDF的常见问题解答(FAQ)

异步转换HTML为PDF的意义是什么?

异步编程在将HTML转换为PDF方面起着至关重要的作用。它允许执行非阻塞操作,这意味着引擎在后台运行,允许你的其余代码在异步操作完成时继续执行。这导致更有效地利用资源并提高性能,尤其是在涉及大量I/O操作的应用程序中,例如将HTML转换为PDF。

ReactPHP如何在创建异步库方面提供帮助?

ReactPHP是一个用于PHP中事件驱动编程的低级库。它为在PHP中创建异步库提供了核心基础设施。使用ReactPHP,你可以使用PHP熟悉的语法编写非阻塞代码,从而更容易创建高性能应用程序。

异步转换HTML为PDF涉及哪些步骤?

异步转换HTML为PDF的过程涉及几个步骤。首先,你需要设置一个HTML模板,该模板定义PDF的结构和内容。接下来,你使用ReactPHP之类的异步库来处理转换过程。这包括读取HTML文件,将其转换为PDF,然后保存生成的PDF文件。此过程的异步性质意味着你的应用程序可以在转换进行时继续执行其他任务。

我可以使用PHP以外的其他语言进行异步编程吗?

是的,你可以使用其他语言进行异步编程。例如,Node.js由于其事件驱动的架构,是构建异步应用程序的流行选择。但是,如果你已经熟悉PHP,那么像ReactPHP这样的库可以让你轻松利用异步编程的优势,而无需学习新的语言。

如何在异步转换HTML为PDF期间处理错误?

错误处理是异步编程的一个重要方面。在ReactPHP中,你可以通过将错误事件处理程序附加到Promise对象来处理错误。如果在转换过程中发生错误,则将调用此处理程序,允许你记录错误或采取其他适当的操作。

将HTML转换为PDF的好处是什么?

将HTML转换为PDF有很多好处。它允许你创建一个静态的、可移植的网页版本,可以脱机查看、打印或轻松共享。PDF还保留了原始HTML的格式和布局,确保内容无论在什么设备或平台上查看都看起来相同。

如何优化我的异步PHP应用程序的性能?

有几种方法可以优化异步PHP应用程序的性能。一种方法是使用ReactPHP之类的库,它为事件驱动编程提供低级接口。这允许你编写非阻塞代码,这可以显著提高I/O密集型操作(如将HTML转换为PDF)的性能。

我可以同步转换HTML为PDF吗?

是的,可以同步转换HTML为PDF。但是,这种方法可能会阻塞你的应用程序的执行,直到转换过程完成,这可能会导致I/O密集型应用程序出现性能问题。另一方面,异步转换允许你的应用程序在转换进行时继续执行其他任务,从而获得更好的性能和资源利用率。

PHP中异步编程的挑战是什么?

由于PHP的同步特性,PHP中的异步编程可能具有挑战性。但是,像ReactPHP这样的库提供了在PHP中编写非阻塞代码所需的架构。理解事件驱动编程模型并掌握Promise的使用也可能具有挑战性,但它们是利用异步编程优势的关键。

如何测试异步PHP应用程序的性能?

测试异步PHP应用程序的性能包括在不同的负载条件下测量关键指标,例如响应时间、内存使用率和CPU利用率。Apache JMeter或Siege之类的工具可用于模拟应用程序上的负载并收集性能数据。此外,Xdebug之类的分析工具可以帮助你识别代码中的瓶颈并优化其性能。

以上是编写异步库 - 让#x27; s将html转换为pdf的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
使用数据库存储会话的优点是什么?使用数据库存储会话的优点是什么?Apr 24, 2025 am 12:16 AM

使用数据库存储会话的主要优势包括持久性、可扩展性和安全性。1.持久性:即使服务器重启,会话数据也能保持不变。2.可扩展性:适用于分布式系统,确保会话数据在多服务器间同步。3.安全性:数据库提供加密存储,保护敏感信息。

您如何在PHP中实现自定义会话处理?您如何在PHP中实现自定义会话处理?Apr 24, 2025 am 12:16 AM

在PHP中实现自定义会话处理可以通过实现SessionHandlerInterface接口来完成。具体步骤包括:1)创建实现SessionHandlerInterface的类,如CustomSessionHandler;2)重写接口中的方法(如open,close,read,write,destroy,gc)来定义会话数据的生命周期和存储方式;3)在PHP脚本中注册自定义会话处理器并启动会话。这样可以将数据存储在MySQL、Redis等介质中,提升性能、安全性和可扩展性。

什么是会话ID?什么是会话ID?Apr 24, 2025 am 12:13 AM

SessionID是网络应用程序中用来跟踪用户会话状态的机制。1.它是一个随机生成的字符串,用于在用户与服务器之间的多次交互中保持用户的身份信息。2.服务器生成并通过cookie或URL参数发送给客户端,帮助在用户的多次请求中识别和关联这些请求。3.生成通常使用随机算法保证唯一性和不可预测性。4.在实际开发中,可以使用内存数据库如Redis来存储session数据,提升性能和安全性。

您如何在无状态环境(例如API)中处理会议?您如何在无状态环境(例如API)中处理会议?Apr 24, 2025 am 12:12 AM

在无状态环境如API中管理会话可以通过使用JWT或cookies来实现。1.JWT适合无状态和可扩展性,但大数据时体积大。2.Cookies更传统且易实现,但需谨慎配置以确保安全性。

您如何防止与会议有关的跨站点脚本(XSS)攻击?您如何防止与会议有关的跨站点脚本(XSS)攻击?Apr 23, 2025 am 12:16 AM

要保护应用免受与会话相关的XSS攻击,需采取以下措施:1.设置HttpOnly和Secure标志保护会话cookie。2.对所有用户输入进行输出编码。3.实施内容安全策略(CSP)限制脚本来源。通过这些策略,可以有效防护会话相关的XSS攻击,确保用户数据安全。

您如何优化PHP会话性能?您如何优化PHP会话性能?Apr 23, 2025 am 12:13 AM

优化PHP会话性能的方法包括:1.延迟会话启动,2.使用数据库存储会话,3.压缩会话数据,4.管理会话生命周期,5.实现会话共享。这些策略能显着提升应用在高并发环境下的效率。

什么是session.gc_maxlifetime配置设置?什么是session.gc_maxlifetime配置设置?Apr 23, 2025 am 12:10 AM

thesession.gc_maxlifetimesettinginphpdeterminesthelifespanofsessiondata,setInSeconds.1)它'sconfiguredinphp.iniorviaini_set().2)abalanceIsiseededeedeedeedeedeedeedto to to avoidperformance andununununununexpectedLogOgouts.3)

您如何在PHP中配置会话名?您如何在PHP中配置会话名?Apr 23, 2025 am 12:08 AM

在PHP中,可以使用session_name()函数配置会话名称。具体步骤如下:1.使用session_name()函数设置会话名称,例如session_name("my_session")。2.在设置会话名称后,调用session_start()启动会话。配置会话名称可以避免多应用间的会话数据冲突,并增强安全性,但需注意会话名称的唯一性、安全性、长度和设置时机。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境