關鍵要點
本文由Thomas Punt同行評審。感謝所有SitePoint的同行評審員,使SitePoint的內容達到最佳狀態!
幾乎在每一次會議上,都會討論到PHP異步編程這個話題。我很高興它現在如此頻繁地被提及。但是,這些演講者沒有透露一個秘密……
創建異步服務器、解析域名、與文件系統交互:這些都是簡單的事情。創建你自己的異步庫很難。而這正是你花費大部分時間的地方!
這些簡單的事情之所以簡單,是因為它們是概念驗證——使異步PHP與NodeJS競爭。你可以看到它們的早期接口有多相似:
<code class="language-javascript">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");</code>
此代碼使用Node 7.3.0測試
<code class="language-php">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();</code>
此代碼使用PHP 7.1和react/http:0.4.2測試
今天,我們將研究一些方法,使你的應用程序代碼在異步架構中良好運行。別擔心——你的代碼仍然可以在同步架構中工作,因此你不必為了學習這項新技能而放棄任何東西。除了花費一些時間……
你可以在Github上找到本教程的代碼。我已經用PHP 7.1和最新版本的ReactPHP和Amp測試過它了。
充滿希望的理論
異步代碼有一些常見的抽象。我們已經看到其中一個:回調。回調顧名思義,描述了它們如何處理緩慢或阻塞操作。同步代碼充滿了等待。請求某些東西,等待事情發生。
因此,異步框架和庫可以使用回調。請求某些東西,當它發生時:框架或庫將回調你的代碼。
在HTTP服務器的情況下,我們不會搶先處理所有請求。我們也不會等待請求發生。我們只是描述應該調用的代碼,如果請求發生的話。事件循環負責其餘的工作。
第二個常見的抽像是Promise。回調是等待未來事件的鉤子,而Promise是對未來值的引用。它們看起來像這樣:
<code class="language-javascript">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");</code>
這比單獨使用回調多了一些代碼,但這是一種有趣的方法。我們等待某些事情發生,然後做另一件事。如果出現問題,我們會捕獲錯誤並做出合理的響應。這看起來很簡單,但並沒有被充分討論。
我們仍在使用回調,但我們已經將它們包裝在一個抽像中,這在其他方面對我們有幫助。一個好處是它們允許多個解析回調……
<code class="language-php">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();</code>
我還想讓我們關注另一件事。那就是Promise提供了一種通用的語言——一種通用的抽象——來思考同步代碼如何變成異步代碼。
讓我們獲取一些應用程序代碼並使其異步,使用Promise……
製作PDF文件
應用程序生成某種摘要文檔是很常見的——無論是發票還是庫存清單。假設你有一個電子商務應用程序,它通過Stripe處理付款。當客戶購買商品時,你希望他們能夠下載該交易的PDF收據。
你可以通過多種方式做到這一點,但一種非常簡單的方法是使用HTML和CSS生成文檔。你可以將其轉換為PDF文檔,並允許客戶下載它。
我最近需要做類似的事情。我發現沒有很多好的庫支持這種操作。我找不到單個抽象可以讓我在不同的HTML→PDF引擎之間切換。所以我開始自己構建一個。
我開始考慮我的抽象需要做什麼。我選擇了一個非常類似的接口:
<code class="language-php">readFile() ->then(function(string $content) { print "content: " . $content; }) ->catch(function(Exception $e) { print "error: " . $e->getMessage(); });</code>
為了簡單起見,我希望除了render方法之外的所有方法都能充當getter和setter。給定這組預期方法,接下來要做的是創建一個實現,使用一個可能的引擎。我將domPDF添加到我的項目中,並開始使用它:
<code class="language-php">$promise = readFile(); $promise->then(...)->catch(...); // ...让我们向现有代码添加日志记录 $promise->then(function(string $content) use ($logger) { $logger->info("file was read"); });</code>
我不會詳細介紹如何使用domPDF。我認為文檔做得足夠好,讓我可以專注於此實現的異步部分。
我們稍後會查看data和parallel方法。關於此Driver實現的重要一點是它將數據(如果已設置,否則為默認值)和自定義選項收集在一起。它將這些傳遞給我們希望異步運行的回調。
domPDF不是異步庫,將HTML轉換為PDF是一個非常緩慢的過程。那麼我們如何使其異步呢?好吧,我們可以編寫一個完全異步的轉換器,或者我們可以使用現有的同步轉換器;但在並行線程或進程中運行它。
這就是我為parallel方法所做的:
<code class="language-javascript">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");</code>
在這裡,我實現了getter-setter方法,認為我可以將它們重用於下一個實現。 data方法充當收集各種文檔屬性到數組中的快捷方式,使它們更容易傳遞給匿名函數。
parallel方法開始變得有趣:
<code class="language-php">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();</code>
我非常喜歡Amp項目。它是一組支持異步架構的庫的集合,它們是async-interop項目的關鍵支持者。
他們的一個庫稱為amphp/parallel,它支持多線程和多進程代碼(通過Pthreads和Process Control擴展)。這些spawn方法返回Amp的Promise實現。這意味著render方法可以像任何其他返回Promise的方法一樣使用:
<code class="language-php">readFile() ->then(function(string $content) { print "content: " . $content; }) ->catch(function(Exception $e) { print "error: " . $e->getMessage(); });</code>
這段代碼有點複雜。 Amp還提供了一個事件循環實現和所有輔助代碼,以便能夠將普通的PHP生成器轉換為協程和Promise。你可以在我寫的另一篇文章中閱讀這甚至是如何可能的,以及它與PHP的生成器有什麼關係。
返回的Promise也正在標準化。 Amp返回Promise規範的實現。它與我上面顯示的代碼略有不同,但仍然執行相同的函數。
生成器的工作方式類似於具有協程的語言中的協程。協程是可以中斷的函數,這意味著它們可以用於執行短時間的操作,然後在等待某些事情時暫停。暫停時,其他函數可以使用系統資源。
實際上,這看起來像這樣:
<code class="language-php">$promise = readFile(); $promise->then(...)->catch(...); // ...让我们向现有代码添加日志记录 $promise->then(function(string $content) use ($logger) { $logger->info("file was read"); });</code>
這看起來比一開始只編寫同步代碼複雜得多。但它允許的是,當我們等待funcReturnsPromise完成時,其他事情可以發生。
生成Promise正是我們所說的抽象。它為我們提供了一個框架,我們可以通過它來創建返回Promise的函數。代碼可以以可預測和可理解的方式與這些Promise交互。
看看使用我們的驅動程序呈現PDF文檔的樣子:
<code class="language-php">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(); }</code>
這不如在異步HTTP服務器中生成PDF那樣有用。有一個名為Aerys的Amp庫,它使創建這些類型的服務器更容易。使用Aerys,你可以創建以下HTTP服務器代碼:
<code class="language-php">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(); } ); } }</code>
同樣,我現在不會詳細介紹Aerys。這是一個令人印象深刻的軟件,非常值得擁有自己的文章。你不需要理解Aerys的工作原理就能看到我們的轉換器代碼在它旁邊看起來有多自然。
我的老闆說“不要用異步!”
如果你不確定你多久才能構建異步應用程序,為什麼要費這麼大的勁?編寫此代碼使我們能夠深入了解新的編程範例。而且,僅僅因為我們正在將此代碼編寫為異步的,並不意味著它不能在同步環境中工作。
要在同步應用程序中使用此代碼,我們只需要將一些異步代碼移到內部:
<code class="language-php">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 } }</code>
使用此裝飾器,我們可以編寫看起來像是同步代碼的代碼:
<code class="language-javascript">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");</code>
它仍然異步運行代碼(至少在後台),但所有這些都不會暴露給消費者。你可以在同步應用程序中使用它,並且永遠不會知道幕後發生了什麼。
支持其他框架
Amp具有一些特定的要求,使其不適合所有環境。例如,基本Amp(事件循環)庫需要PHP 7.0。 parallel庫需要Pthreads擴展或Process Control擴展。
我不想強加這些限制給每個人,並且想知道我如何才能支持更廣泛的系統。答案是將並行執行代碼抽像到另一個驅動程序系統中:
<code class="language-php">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();</code>
我可以為Amp以及(限制較少,但更舊的)ReactPHP實現它:
<code class="language-php">readFile() ->then(function(string $content) { print "content: " . $content; }) ->catch(function(Exception $e) { print "error: " . $e->getMessage(); });</code>
我習慣於將閉包傳遞給多線程和多進程工作程序,因為這就是Pthreads和Process Control的工作方式。使用ReactPHP Process對象完全不同,因為它們依賴於exec進行多進程執行。我決定實現我習慣使用的相同閉包功能。這對於異步代碼來說不是必需的——這純粹是品味的問題。
SuperClosure庫序列化閉包及其綁定的變量。這裡的大部分代碼都是你期望在worker腳本中找到的代碼。事實上,使用ReactPHP的子進程庫的唯一方法(除了序列化閉包之外)是將任務發送到worker腳本。
現在,我們不再使用$this->parallel和Amp特定的代碼加載我們的驅動程序,而是可以傳遞運行程序實現。作為異步代碼,這類似於:
<code class="language-php">$promise = readFile(); $promise->then(...)->catch(...); // ...让我们向现有代码添加日志记录 $promise->then(function(string $content) use ($logger) { $logger->info("file was read"); });</code>
不要被ReactPHP代碼與Amp代碼的不同之處所震驚。 ReactPHP沒有實現與Amp相同的協程基礎。相反,ReactPHP更喜歡使用回調來處理大多數事情。這段代碼仍然只是並行運行PDF轉換,並返回生成的PDF數據。
通過抽象的運行程序,我們可以使用任何我們想要的異步框架,並且我們可以預期我們將使用的驅動程序將返回該框架的抽象。
我可以使用這個嗎?
最初只是一個實驗,變成了一個多驅動程序、多運行程序的HTML→PDF庫;稱為Paper。它就像HTML→PDF的Flysystem等效項,但它也是如何編寫異步庫的一個很好的例子。
當你嘗試製作異步PHP應用程序時,你會發現庫生態系統中的差距。不要被這些嚇倒!相反,抓住機會思考一下你將如何使用ReactPHP和Amp提供的抽象來製作你自己的異步庫。
你最近是否構建了一個有趣的異步PHP應用程序或庫?請在評論中告訴我們。
關於異步轉換HTML為PDF的常見問題解答(FAQ)
異步編程在將HTML轉換為PDF方面起著至關重要的作用。它允許執行非阻塞操作,這意味著引擎在後台運行,允許你的其餘代碼在異步操作完成時繼續執行。這導致更有效地利用資源並提高性能,尤其是在涉及大量I/O操作的應用程序中,例如將HTML轉換為PDF。
ReactPHP是一個用於PHP中事件驅動編程的低級庫。它為在PHP中創建異步庫提供了核心基礎設施。使用ReactPHP,你可以使用PHP熟悉的語法編寫非阻塞代碼,從而更容易創建高性能應用程序。
異步轉換HTML為PDF的過程涉及幾個步驟。首先,你需要設置一個HTML模板,該模板定義PDF的結構和內容。接下來,你使用ReactPHP之類的異步庫來處理轉換過程。這包括讀取HTML文件,將其轉換為PDF,然後保存生成的PDF文件。此過程的異步性質意味著你的應用程序可以在轉換進行時繼續執行其他任務。
是的,你可以使用其他語言進行異步編程。例如,Node.js由於其事件驅動的架構,是構建異步應用程序的流行選擇。但是,如果你已經熟悉PHP,那麼像ReactPHP這樣的庫可以讓你輕鬆利用異步編程的優勢,而無需學習新的語言。
錯誤處理是異步編程的一個重要方面。在ReactPHP中,你可以通過將錯誤事件處理程序附加到Promise對象來處理錯誤。如果在轉換過程中發生錯誤,則將調用此處理程序,允許你記錄錯誤或採取其他適當的操作。
將HTML轉換為PDF有很多好處。它允許你創建一個靜態的、可移植的網頁版本,可以脫機查看、打印或輕鬆共享。 PDF還保留了原始HTML的格式和佈局,確保內容無論在什麼設備或平台上查看都看起來相同。
有幾種方法可以優化異步PHP應用程序的性能。一種方法是使用ReactPHP之類的庫,它為事件驅動編程提供低級接口。這允許你編寫非阻塞代碼,這可以顯著提高I/O密集型操作(如將HTML轉換為PDF)的性能。
是的,可以同步轉換HTML為PDF。但是,這種方法可能會阻塞你的應用程序的執行,直到轉換過程完成,這可能會導致I/O密集型應用程序出現性能問題。另一方面,異步轉換允許你的應用程序在轉換進行時繼續執行其他任務,從而獲得更好的性能和資源利用率。
由於PHP的同步特性,PHP中的異步編程可能具有挑戰性。但是,像ReactPHP這樣的庫提供了在PHP中編寫非阻塞代碼所需的架構。理解事件驅動編程模型並掌握Promise的使用也可能具有挑戰性,但它們是利用異步編程優勢的關鍵。
測試異步PHP應用程序的性能包括在不同的負載條件下測量關鍵指標,例如響應時間、內存使用率和CPU利用率。 Apache JMeter或Siege之類的工具可用於模擬應用程序上的負載並收集性能數據。此外,Xdebug之類的分析工具可以幫助你識別代碼中的瓶頸並優化其性能。
以上是編寫異步庫 - 讓#x27; s將html轉換為pdf的詳細內容。更多資訊請關注PHP中文網其他相關文章!