Maison >développement back-end >tutoriel php >Écriture de bibliothèques asynchrones - Soit convertir HTML en PDF
Points clés
Cet article a été évalué par des pairs par Thomas Punt. Merci à tous les pairs examinateurs de SitePoint pour avoir obtenu le contenu de SitePoint à son meilleur!
Le sujet de la programmation asynchrone PHP est discuté presque toutes les réunions. Je suis content que ce soit mentionné si souvent maintenant. Cependant, ces orateurs n'ont pas révélé de secret ...
Création d'un serveur asynchrone, résoudre les noms de domaine et interagir avec le système de fichiers: ce sont toutes des choses simples. Créer votre propre bibliothèque asynchrone est difficile. Et c'est exactement là que vous passez la plupart de votre temps!
Ces choses simples sont simples car elles sont une preuve de concept - la fabrication de PHP asynchrones rivalise avec les NodeJS. Vous pouvez voir à quel point leurs premières interfaces étaient similaires:
<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>
Ce code est testé à l'aide du nœud 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>
Ce code est testé à l'aide de PHP 7.1 et React / Http: 0.4.2
Aujourd'hui, nous examinerons certaines méthodes pour que votre code d'application s'exécute bien dans une architecture asynchrone. Ne vous inquiétez pas - votre code peut toujours fonctionner dans une architecture synchrone, vous n'avez donc rien à abandonner pour apprendre cette nouvelle compétence. En plus de passer du temps ...
Vous pouvez trouver le code de ce tutoriel sur GitHub. Je l'ai testé avec PHP 7.1 et les dernières versions de ReactPHP et AMP.
théorie de l'espoir
Le code asynchrone a quelques abstractions courantes. Nous en avons vu un: des rappels. Les rappels, comme son nom l'indique, décrivent comment ils gèrent les opérations lentes ou de blocage. Le code de synchronisation est plein d'attente. Demandez quelque chose et attendez que quelque chose se passe.
Par conséquent, les frameworks et bibliothèques asynchrones peuvent utiliser des rappels. Demandez quelque chose, lorsque cela se produit: le framework ou la bibliothèque rappellera votre code.
Dans le cas du serveur HTTP, nous ne traiterons pas toutes les demandes de manière préventive. Nous n'attendrons pas non plus la demande. Nous décrivons simplement le code qui doit être appelé si la demande se produit. La boucle d'événements s'occupe du reste du travail.
La deuxième abstraction commune est promesse. Les rappels sont des crochets en attente d'événements futurs, et la promesse est une référence aux valeurs futures. Ils ressemblent à ceci:
<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>
Cela a un peu plus de code que d'utiliser des rappels seuls, mais c'est une façon intéressante de le faire. Nous attendons que quelque chose se passe et en faisons ensuite un autre. Si quelque chose ne va pas, nous prendrons l'erreur et répondrons raisonnablement. Cela semble simple, mais n'est pas entièrement discuté.
Nous utilisons toujours des rappels, mais nous les avons enveloppés dans une abstraction, ce qui nous aide à d'autres égards. Un avantage est qu'ils autorisent plusieurs rappels d'analyse ...
<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>
Je veux que nous nous concentrions sur une autre chose. Autrement dit, la promesse fournit une langue commune - une abstraction courante - pour réfléchir à la façon dont le code synchrone devient un code asynchrone.
Obtenez un code d'application et rendons-le asynchrone, utilisez la promesse ...
Créer des fichiers PDF
Il est courant que les applications générent une sorte de documents sommaires - qu'il s'agisse d'une liste de factures ou d'inventaire. Supposons que vous ayez une application de commerce électronique qui traite les paiements via Stripe. Lorsqu'un client achète un article, vous souhaitez qu'ils puissent télécharger un reçu PDF pour la transaction.
Vous pouvez le faire de plusieurs façons, mais un moyen très simple consiste à générer le document à l'aide de HTML et CSS. Vous pouvez le convertir en document PDF et permettre aux clients de le télécharger.
J'ai besoin de faire quelque chose de similaire récemment. J'ai trouvé qu'il n'y a pas beaucoup de bonnes bibliothèques pour soutenir cette opération. Je ne trouve pas une seule abstraction qui me permet de basculer entre différents moteurs HTML → PDF. J'ai donc commencé à en construire un moi-même.
J'ai commencé à réfléchir à ce que mon abstraction devait faire. J'ai choisi une interface très similaire:
<code class="language-php">readFile() ->then(function(string $content) { print "content: " . $content; }) ->catch(function(Exception $e) { print "error: " . $e->getMessage(); });</code>
Pour plus de simplicité, j'espère que toutes les méthodes, à l'exception de la méthode de rendu, peuvent agir comme des getters et des setters. Compte tenu de cet ensemble de méthodes attendues, la prochaine chose à faire est de créer une implémentation, en utilisant un moteur possible. J'ai ajouté le DOMPDF à mon projet et j'ai commencé à l'utiliser:
<code class="language-php">$promise = readFile(); $promise->then(...)->catch(...); // ...让我们向现有代码添加日志记录 $promise->then(function(string $content) use ($logger) { $logger->info("file was read"); });</code>
Je n'entrerai pas dans les détails sur la façon d'utiliser DOMPDF. Je pense que la documentation est assez bien fait pour que je puisse me concentrer sur la partie asynchrone de cette implémentation.
Nous allons vérifier les données et les méthodes parallèles plus tard. La chose importante à propos de cette implémentation du pilote est qu'elle collecte des données (si elle est définie, sinon la valeur par défaut) et des options personnalisées ensemble. Il les transmet aux rappels que nous voulons courir de manière asynchrone.
DOMPDF n'est pas une bibliothèque asynchrone, la conversion de HTML en PDF est un processus très lent. Alors, comment le rendre asynchrone? Eh bien, nous pourrions écrire un convertisseur complètement asynchrone, ou nous pourrions utiliser un convertisseur synchrone existant; mais l'exécutez dans un thread ou un processus parallèle.
C'est ce que j'ai fait pour la méthode parallèle:
<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>
Ici, j'ai implémenté la méthode Getter-Setter et j'ai pensé que je pouvais les réutiliser pour la prochaine implémentation. La méthode de données agit comme un raccourci pour collecter divers attributs de documents dans un tableau, ce qui les rend plus faciles à passer aux fonctions anonymes.
La méthode parallèle commence à devenir intéressante:
<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>
J'aime vraiment le projet AMP. Il s'agit d'une collection de bibliothèques qui prennent en charge les architectures asynchrones, et ce sont des partisans clés du projet Async-Interop.
L'une de leurs bibliothèques est appelée AMPHP / parallèle, qui prend en charge le code multi-thread et multi-processus (étendu via Pthreads et Contrôle de processus). Ces méthodes d'apparition renvoient la mise en œuvre des promesses d'AMP. Cela signifie que la méthode de rendu peut être utilisée comme toute autre méthode qui renvoie une promesse:
<code class="language-php">readFile() ->then(function(string $content) { print "content: " . $content; }) ->catch(function(Exception $e) { print "error: " . $e->getMessage(); });</code>
Ce code est un peu compliqué. AMP fournit également une implémentation de boucle d'événements et tout le code auxiliaire pour pouvoir convertir un générateur PHP normal en coroutines et promesses. Vous pouvez lire dans un autre article que j'ai écrit comment cela est même possible et comment cela se rapporte au générateur de PHP.
La promesse retournée est également standardisée. AMP renvoie la mise en œuvre de la spécification de promesse. Il est légèrement différent du code que j'ai montré ci-dessus, mais exécute toujours la même fonction.
Le générateur fonctionne comme une coroutine dans une langue avec des coroutines. Les coroutines sont des fonctions qui peuvent être interrompues, ce qui signifie qu'elles peuvent être utilisées pour effectuer des opérations à court terme, puis s'arrêter en attendant quelque chose. Pendant la pause, d'autres fonctions peuvent utiliser des ressources système.
En fait, cela ressemble à ceci:
<code class="language-php">$promise = readFile(); $promise->then(...)->catch(...); // ...让我们向现有代码添加日志记录 $promise->then(function(string $content) use ($logger) { $logger->info("file was read"); });</code>
Cela semble beaucoup plus compliqué que d'écrire du code synchrone au début. Mais ce que cela permet, c'est que quelque chose d'autre peut se produire lorsque nous attendons que FunCreturnSpromise se termine.
La promesse de génération est exactement ce que nous appelons l'abstraction. Il nous fournit un cadre par lequel nous pouvons créer des fonctions qui renvoient la promesse. Le code peut interagir avec ces promesses de manière prévisible et compréhensible.
Regardez à quoi il ressemble de rendre des documents PDF en utilisant notre pilote:
<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>
Ce n'est pas aussi utile que la génération de PDF dans un serveur HTTP asynchrone. Il existe une bibliothèque AMP appelée Aerys qui facilite la création de ces types de serveurs. À l'aide d'Aerys, vous pouvez créer le code de serveur HTTP suivant:
<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>
De même, je n'entrerai pas dans Aerys en détail maintenant. Il s'agit d'un logiciel impressionnant qui vaut bien la peine d'avoir son propre article. Vous n'avez pas besoin de comprendre comment Aerys fonctionne pour voir à quel point notre code de convertisseur est naturel.
Mon patron a dit "n'utilisez pas asynchrone!"
Si vous ne savez pas combien de temps il faudra pour construire une application asynchrone, pourquoi cela prend-il autant d'efforts? L'écriture de ce code nous permet de mieux comprendre les nouveaux paradigmes de programmation. Et, ce n'est pas parce que nous écrivons ce code asynchrone qu'il ne fonctionnera pas dans un environnement synchrone.
Pour utiliser ce code dans une application synchrone, nous devons simplement déplacer un code asynchrone à l'intérieur:
<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>
Avec ce décorateur, nous pouvons écrire du code qui ressemble à un code synchrone:
<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>
Il exécute toujours le code de manière asynchrone (au moins en arrière-plan), mais tout cela n'est pas exposé au consommateur. Vous pouvez l'utiliser dans une application de synchronisation et vous ne saurez jamais ce qui se passe dans les coulisses.
Soutenez les autres frameworks
AMP a des exigences spécifiques qui le rendent inadapté à tous les environnements. Par exemple, la bibliothèque Basic AMP (Event Loop) nécessite PHP 7.0. La bibliothèque parallèle nécessite une extension Pthreads ou une extension de contrôle de processus.
Je ne veux pas imposer ces restrictions à tout le monde et je veux savoir comment je peux prendre en charge un système plus large. La réponse est de résumer le code d'exécution parallèle dans un autre système de pilote:
<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>
Je peux également l'implémenter pour AMP (moins restreint, mais plus ancien) reactphp:
<code class="language-php">readFile() ->then(function(string $content) { print "content: " . $content; }) ->catch(function(Exception $e) { print "error: " . $e->getMessage(); });</code>
J'ai l'habitude de passer des fermetures à un travailleur multi-thread et multi-processus, car c'est ainsi que fonctionnent les pthreads et le contrôle des processus. L'utilisation d'objets ReactPHP Process est complètement différent car ils s'appuient sur EXEC pour l'exécution multi-processus. J'ai décidé d'implémenter la même fonction de fermeture que j'ai l'habitude d'utiliser. Ce n'est pas nécessaire pour le code asynchrone - c'est purement une question de goût.
La bibliothèque de superclosur sérialise les fermetures et leurs variables liées. La plupart du code ici est le code que vous attendez dans le script de travail. En fait, la seule façon d'utiliser la bibliothèque de processus enfants de ReactPHP (outre les fermetures de sérialisation) est d'envoyer des tâches aux scripts de travailleur.
Maintenant, nous ne chargeons plus nos pilotes avec $ this- & gt; parallèle et un code spécifique AMP, mais pouvons passer l'implémentation du programme RUN. En tant que code asynchrone, cela est similaire à:
<code class="language-php">$promise = readFile(); $promise->then(...)->catch(...); // ...让我们向现有代码添加日志记录 $promise->then(function(string $content) use ($logger) { $logger->info("file was read"); });</code>
Ne soyez pas choqué par la différence entre le code ReactPHP et le code AMP. ReactPHP n'implémente pas la même base de coroutine que l'ampli. Au lieu de cela, ReactPHP préfère utiliser des rappels pour gérer la plupart des choses. Ce code exécute toujours la conversion PDF en parallèle et renvoie les données PDF générées.
En exécutant le programme dans Résumé, nous pouvons utiliser n'importe quel cadre asynchrone que nous voulons, et nous pouvons nous attendre à ce que le pilote que nous utiliserons renvoie l'abstraction de ce cadre.
Puis-je utiliser ceci?
Initialement, ce n'était qu'une expérience, et il est devenu une bibliothèque HTML → PDF avec plusieurs pilotes et plusieurs programmes de course; C'est comme l'équivalent FlySystem de HTML → PDF, mais c'est aussi un excellent exemple de la façon d'écrire une bibliothèque asynchrone.
Lorsque vous essayez de créer une application PHP asynchrone, vous trouverez des lacunes dans l'écosystème de la bibliothèque. Ne soyez pas intimidé par ceux-ci! Au lieu de cela, profitez-en pour réfléchir à la façon dont vous utiliserez les abstractions fournies par ReactPHP et AMP pour créer vos propres bibliothèques asynchrones.
Avez-vous récemment construit une application ou une bibliothèque PHP asynchrones intéressante? Veuillez nous faire savoir dans les commentaires.
FAQ sur la conversion asynchrone HTML en pdf
La programmation asynchrone joue un rôle crucial dans la conversion du HTML en PDF. Il permet d'effectuer des opérations non bloquantes, ce qui signifie que le moteur s'exécute en arrière-plan, permettant au reste de votre code de continuer l'exécution lorsque l'opération asynchrone est terminée. Cela conduit à une utilisation plus efficace des ressources et à des performances améliorées, en particulier dans les applications impliquant un grand nombre d'opérations d'E / S, telles que la conversion du HTML en PDF.
ReactPHP est une bibliothèque de bas niveau pour la programmation axée sur les événements en PHP. Il fournit l'infrastructure centrale pour créer des bibliothèques asynchrones dans PHP. Avec ReactPHP, vous pouvez écrire du code non bloquant en utilisant la syntaxe familière de PHP, ce qui facilite la création d'applications haute performance.
Le processus de conversion asynchrone de HTML en PDF implique plusieurs étapes. Tout d'abord, vous devez configurer un modèle HTML qui définit la structure et le contenu du PDF. Ensuite, vous utilisez des bibliothèques asynchrones comme ReactPHP pour gérer le processus de conversion. Cela comprend la lecture du fichier HTML, le convertir en un PDF, puis l'enregistrement du fichier PDF généré. La nature asynchrone de ce processus signifie que votre application peut continuer à effectuer d'autres tâches pendant que la transformation est en cours.
Oui, vous pouvez programmer de manière asynchrone dans d'autres langues. Par exemple, Node.js est un choix populaire pour construire des applications asynchrones en raison de son architecture axée sur les événements. Cependant, si vous connaissez déjà PHP, des bibliothèques comme ReactPHP vous permettent de profiter facilement de la programmation asynchrone sans avoir à apprendre de nouvelles langues.
La gestion des erreurs est un aspect important de la programmation asynchrone. Dans ReactPHP, vous pouvez gérer les erreurs en fixant un gestionnaire d'événements d'erreur à un objet Promise. Si une erreur se produit pendant le processus de conversion, ce gestionnaire sera appelé, vous permettant de enregistrer l'erreur ou de prendre d'autres mesures appropriées.
Il y a de nombreux avantages à convertir le HTML en PDF. Il vous permet de créer une version statique et portable d'une page Web qui peut être affichée facilement hors ligne, imprimée ou partagée. Le PDF conserve également le format et la mise en page du HTML d'origine, garantissant que le contenu est le même quel que soit le périphérique ou la plate-forme affichée.
Il existe plusieurs façons d'optimiser les performances d'une application PHP asynchrone. Une approche consiste à utiliser des bibliothèques comme ReactPHP, qui fournit une interface de bas niveau pour la programmation axée sur les événements. Cela vous permet d'écrire du code non bloquant, ce qui peut améliorer considérablement les performances des opérations à forte intensité d'E / O telles que la conversion de HTML en PDF.
Oui, le HTML peut être converti en PDF de manière synchrone. Cependant, cette approche peut bloquer l'exécution de votre application jusqu'à ce que le processus de conversion soit terminé, ce qui peut entraîner des problèmes de performances pour les applications à forte intensité d'E / S. D'un autre côté, la conversion asynchrone permet à votre application de continuer à effectuer d'autres tâches pendant que la conversion est en cours, ce qui entraîne une meilleure performance et une meilleure utilisation des ressources.
La programmation asynchrone dans PHP peut être difficile en raison des caractéristiques de synchronisation de PHP. Cependant, les bibliothèques comme ReactPHP fournissent l'architecture requise pour écrire du code non bloquant dans PHP. Comprendre les modèles de programmation axés sur les événements et la maîtrise de l'utilisation de la promesse peut également être difficile, mais ils sont essentiels pour tirer parti des avantages de la programmation asynchrone.
Tester les performances d'une application PHP asynchrone comprend la mesure des mesures clés dans différentes conditions de charge telles que le temps de réponse, l'utilisation de la mémoire et l'utilisation du processeur. Des outils comme Apache JMeter ou Siege peuvent être utilisés pour simuler la charge sur une application et collecter des données de performances. De plus, des outils d'analyse comme XDebug peuvent vous aider à identifier les goulots d'étranglement dans votre code et à optimiser leurs performances.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!