PHP-Ausgabepuffer

Ursprünglicher Autor: Julien Pauli
  • Übersetzung
In diesem Artikel möchte ich darüber sprechen, wie die Ebene "Ausgabepufferung" in PHP implementiert ist, wie sie funktioniert und wie man mit ihr von PHP aus interagiert. Diese Ebene ist nicht kompliziert, aber viele Entwickler wissen entweder gar nicht, wie sie damit umgehen sollen, oder sie haben keine vollständige Klarheit. Alles, worüber ich schreiben werde, gehört zu PHP Version 5.4 und höher. Es beginnt damit, dass sich viele Dinge im Zusammenhang mit dem Ausgabepuffer (BV) geändert haben. Tatsächlich wurde diese Funktionalität komplett neu geschrieben, sodass die Kompatibilität mit Version 5.3 nur teilweise erhalten bleibt.

Was ist ein Ausgabepuffer?


Der Ausgabestream in PHP enthält Bytes, normalerweise in Form von Text, die der Entwickler anzeigen muss. Am häufigsten wird dafür das Konstrukt echo oder printf () verwendet . Zunächst müssen Sie verstehen, dass jede Funktion, die etwas ausgibt, BVs aus der PHP-Domäne verwendet. Wenn es sich um PHP-Erweiterungen handelt, können Sie auf Funktionen zugreifen, die direkt in SAPI schreiben, und dabei jede vorgelagerte BV umgehen. Die C-API ist in lxr.php.net/xref/PHP_5_5/main/php_output.h dokumentiert. Hier können viele Informationen gesammelt werden, zum Beispiel über die Standardpuffergröße.

Der zweite wichtige Punkt: Die BV-Schicht ist nicht die einzige Schicht, in der die Ausgabedaten gepuffert werden.

Und drittens: Je nachdem, welche SAPI Sie verwenden (Web oder CLI), kann sich die BV-Schicht unterschiedlich verhalten.

Im Folgenden finden Sie ein Diagramm, das Ihnen hilft, das Obige zu verstehen:



Hier sehen wir, dass zum Verwalten der Ausgabedaten in PHP drei logische Pufferebenen verwendet werden. Zwei von ihnen gehören zum selben "Ausgabepuffer" und der dritte - SAPI. Wenn der Ausgabestream die PHP-Domäne verlässt, um zur unteren Ebene der Architektur zu gelangen, werden möglicherweise neue Puffer angezeigt: Terminalpuffer, FastCGI-Puffer, Webserverpuffer, Betriebssystempuffer, TCP / IP-Stapelpuffer. Vergiss es nicht. Obwohl im Rahmen dieses Artikels nur auf PHP eingegangen wird, befinden sich auf dem Weg zur untersten Ebene und zum Benutzer noch viele Softwaretools im Stapel.

Ein wichtiger Hinweis zur SAPI-CLI: Sie deaktiviert jeden Standard-Ausgabepuffer in PHP, indem Sie den Parameter output_buffering auf 0 setzen. Bis Sie also die Funktion ob_ () manuell in die CLI schreiben, wird die gesamte Ausgabe standardmäßig direkt an die SAPI-Ebene gesendet . Darüber hinaus wird in der CLI implizit der Wert 1 für den Parameter implicit_flush angegeben.Die Entwickler verstehen das Wesentliche dieses Parameters immer falsch, obwohl der Code besagt, dass er völlig eindeutig ist: Wenn implicit_flush 1 ist, wird der SAPI-Ebenenpuffer bei jedem Schreiben zurückgesetzt. Das heißt, jedes Mal, wenn Sie Daten für die Ausgabe mit der CLI SAPI schreiben, werden sie sofort an die niedrigere Ebene gesendet, wo sie als stdout geschrieben und dann zurückgesetzt werden.

Standardmäßige PHP-Ausgabepufferschicht


Wenn Sie SAPI nicht wie die CLI verwenden, sondern beispielsweise PHP-FPM, können Sie mit drei Parametern in ini experimentieren, die sich auf den Puffer beziehen:
  • output_buffering
  • implicit_flush
  • output_handler

Bitte beachten Sie, dass die Verwendung von ini_set () keine Auswirkungen hat, da deren Werte zum Zeitpunkt des Starts von PHP gelesen werden, bevor ein Skript ausgeführt werden kann. Wenn Sie ini_set () mit einem dieser Parameter verwenden, ändert sich der Wert, er wird jedoch nirgendwo verwendet. Zu spät - Die BI-Schicht wird bereits ausgeführt und ist aktiv. Sie können diese Parameter ändern, indem Sie die Datei php.ini bearbeiten oder den Schalter –d auf die PHP-Binärdatei anwenden .

In der mit PHP gelieferten php.ini ist output_buffering standardmäßig auf „4096“ (Bytes) eingestellt. Wenn Sie php.ini nicht verwenden (oder PHP mit dem Schalter –n ausführen)), dann ist der Standardwert "0", dh deaktiviert. Wenn der Hardcode auf „Ein“ gesetzt ist, wird die Standardgröße des Ausgabepuffers (16 KB) zugewiesen.

Wie Sie wahrscheinlich bereits vermutet haben, wirkt sich die Verwendung eines Puffers für die Ausgabe in einer Webumgebung positiv auf die Leistung aus. Die ersten 4 KB reichen aus, da Sie bis zu 4096 ASCII-Zeichen schreiben können, bis PHP mit der zugrunde liegenden SAPI-Schicht interagiert. Im Web wird die Leistung nicht verbessert, wenn Daten byteweise gesendet werden. Es ist viel besser, wenn der Server den gesamten Inhalt in großen Mengen oder in großen Mengen sendet. Je seltener die Ebenen Daten austauschen, desto besser ist die Leistung. Verwenden Sie daher unbedingt den Ausgabepuffer. PHP sendet seinen Inhalt am Ende der Anfrage und Sie müssen nichts dafür tun.

Im vorherigen Kapitel habe ich implicit_flush im Kontext der CLI erwähnt. Bei allen anderen SAPIs ist implicit_flush zunächst deaktiviert. Dies ist insofern gut, als Sie ein Zurücksetzen von SAPI nicht sofort nach dem Schreiben begrüßen werden. Für das FastCGI-Protokoll kann ein Zurücksetzen mit dem Beenden und Senden eines Pakets nach jedem Schreibvorgang verglichen werden. Es ist jedoch besser, zuerst den FastCGI-Puffer vollständig zu füllen und dann erst Pakete zu senden. Wenn Sie den SAPI-Puffer manuell zurücksetzen müssen, verwenden Sie dazu die PHP-Funktion flush () . Um nach jedem Datensatz zurückzusetzen, können Sie, wie oben erwähnt, den implicit_flush- Parameter in der php.ini verwenden. Alternativ ein einzelner Aufruf der PHP-Funktion ob_implicit_flush () .

Callback kann auf den Inhalt des Puffers angewendet werden.output_handler . Im Allgemeinen stehen dank PHP-Erweiterungen viele Rückrufe zur Verfügung (Benutzer können sie auch schreiben, worauf ich im nächsten Kapitel eingehen werde).
  • ob_gzhandler: Komprimierung der Ausgabe mit ext / zlib
  • mb_output_handler: Zeichenkodierung mit ext / mbstring übersetzen
  • ob_iconv_handler: Zeichenkodierung mit ext / iconv übersetzen
  • ob_tidyhandler: HTML-Ausgabe mit ext / tidy leeren
  • ob_ [inflate / deflate] _handler: Ausgabe mit ext / http komprimieren
  • ob_etaghandler: Automatische Generierung von ETag-Headern mit ext / http


Sie können nur einen Rückruf verwenden, der den Inhalt des Puffers empfängt und nützliche Konvertierungen für die Ausgabe vornimmt, was eine gute Nachricht ist. Um die Daten zu analysieren, die PHP an den Webserver sendet und die der Server an den Benutzer sendet, ist es hilfreich, Rückrufe und Ausgabepuffer zu verwenden. Übrigens meine ich mit "Schlussfolgerung" sowohl die Überschrift als auch den Körper. HTTP-Header sind ebenfalls Teil der Ausgabepufferschicht.

Body und Header


Wenn Sie den Ausgabepuffer verwenden (es spielt keine Rolle, ob es sich um einen benutzerdefinierten oder einen der Standardpuffer handelt), können Sie HTTP-Header und -Inhalte nach Belieben senden. Für jedes Protokoll muss zuerst der Header und dann der Body gesendet werden. PHP erledigt dies jedoch für Sie, wenn Sie die BV-Schicht verwenden. Jede PHP-Funktion, die mit headers ( header (), setcookie (), session_start () ) zusammenarbeitet, verwendet tatsächlich die interne Funktion sapi_header_op (), die einfach den Header-Puffer füllt. Schreiben Sie anschließend die Ausgabedaten, z. B. mit printf () , werden sie in einen der entsprechenden Ausgabepuffer geschrieben. Und während der Puffer gesendet wird,

sendet PHP zuerst die Header und erst dann den Body. Wenn Sie diese Bedenken von PHP nicht mögen, müssen Sie die BV-Ebene vollständig deaktivieren.

Benutzerdefinierte Ausgabepuffer


Schauen wir uns Beispiele an, wie dies funktioniert und was Sie tun können. Beachten Sie, dass Sie die CLI nicht verwenden können, wenn Sie die standardmäßige PHP-Pufferebene verwenden möchten, da sie als Ebene deaktiviert ist.

Das folgende Beispiel zeigt die Arbeit mit einer Standard-PHP-Ebene unter Verwendung des internen SAPI-Webservers:

/* запущено так: php -doutput_buffering=32 -dimplicit_flush=1 -S127.0.0.1:8080 -t/var/www */
echo str_repeat('a', 31);
sleep(3);
echo 'b';
sleep(3);
echo 'c';


Wir haben PHP mit einem Standard-32-Byte-Ausgabepuffer gestartet. Danach haben wir sofort 31 Byte in den Ausgabepuffer geschrieben, bis die Ausführungsverzögerung aktiviert wurde. Der Bildschirm ist schwarz, bis nichts gesendet wird. Dann endet die Aktion sleep () und wir schreiben ein weiteres Byte, wodurch der Puffer vollständig gefüllt wird. Danach spült er sich sofort in den Puffer der SAPI-Schicht und spült sich in die Ausgabe, da implicit_flush den Wert 1 hat. Auf dem Bildschirm wird die Zeichenfolge aaaaaaaaa {31 times} b angezeigt , wonach sleep () erneut zu handeln beginnt . Nach Abschluss wird ein leerer 31-Byte-Puffer mit einem einzelnen Byte gefüllt, wonach PHP den Puffer beendet und löscht. Erscheint auf dem Bildschirm mit .

So sieht die Arbeit eines Standard-PHP-Puffers aus, ohne irgendwelche ob-Funktionen aufzurufen. Vergessen Sie nicht, dass dies der Standardpuffer ist, dh er ist bereits verfügbar (nur Sie können die CLI nicht verwenden).

Jetzt können Sie mit ob_start () benutzerdefinierte Puffer und so viele ausführen, wie Sie benötigen, bis der Speicher voll ist . Jeder Puffer wird hinter den vorherigen gelegt und sofort zum nächsten gespült, was nach und nach zu einem Überlauf führt.

ob_start(function($ctc) { static $a = 0; return $a++ . '- ' . $ctc . "\n";}, 10);
ob_start(function($ctc) { return ucfirst($ctc); }, 3);
echo "fo";
sleep(2);
echo 'o';
sleep(2);
echo "barbazz";
sleep(2);
echo "hello";
/* 0- FooBarbazz\n 1- Hello\n */


Ausgabepuffergerät


Wie gesagt, ab Version 5.4 wurde der Ausgabepuffermechanismus komplett neu geschrieben. Davor war der Code sehr unübersichtlich, viele Dinge waren nicht einfach zu tun, oft traten Fehler auf. Mehr dazu können Sie hier lesen . Die neue Codebasis ist viel übersichtlicher, übersichtlicher, neue Funktionen sind erschienen. Die Kompatibilität zu Version 5.3 ist allerdings nur teilweise gegeben.

Eine der vielleicht angenehmsten Neuerungen war, dass Erweiterungen jetzt ihre Rückrufe deklarieren und Puffer ausgeben können, die mit Rückrufen anderer Erweiterungen in Konflikt stehen. Bisher war es nicht möglich, Situationen vollständig zu verwalten, in denen andere Nebenstellen ihre Rückrufe deklarieren konnten.

Hier ist ein kurzes Beispiel, das zeigt, wie ein Rückruf registriert wird, der Daten in Großbuchstaben konvertiert:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "main/php_output.h"
#include "php_myext.h"
static int myext_output_handler(void **nothing, php_output_context *output_context)
{
char *dup = NULL;
dup = estrndup(output_context->in.data, output_context->in.used);
php_strtoupper(dup, output_context->in.used);
output_context->out.data = dup;
output_context->out.used = output_context->in.used;
output_context->out.free = 1;
return SUCCESS;
}
PHP_RINIT_FUNCTION(myext)
{
php_output_handler *handler;
handler = php_output_handler_create_internal("myext handler", sizeof("myext handler") -1, myext_output_handler, /* PHP_OUTPUT_HANDLER_DEFAULT_SIZE */ 128, PHP_OUTPUT_HANDLER_STDFLAGS);
php_output_handler_start(handler);
return SUCCESS;
}
zend_module_entry myext_module_entry = {
STANDARD_MODULE_HEADER,
"myext",
NULL, /* Function entries */
NULL,
NULL, /* Module shutdown */
PHP_RINIT(myext), /* Request init */
NULL, /* Request shutdown */
NULL, /* Module information */
"0.1", /* Replace with version number for your extension */
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_MYEXT
ZEND_GET_MODULE(myext)
#endif


Fallstricke


Zum größten Teil sind sie dokumentiert, einige von ihnen sind ziemlich offensichtlich und andere nicht zu viel. Die offensichtlichen können zum Beispiel der Tatsache zugeschrieben werden, dass Sie keine Pufferfunktionen innerhalb des Rückrufs der BV aufrufen und die von dort ausgegebenen Daten schreiben sollten.

Zu den nicht offensichtlichen Fallstricken gehört die Tatsache, dass einige PHP-Funktionen die interne BV für sich nutzen, sie füllen und dann zurücksetzen oder zurückgeben. Der nächste Puffer wird auf den Stapel geschoben. Zu diesen Funktionen gehören print_r (), highlight_file () und SoapServer :: handle () . Sie sollten sie nicht innerhalb eines Rückrufs einer BV verwenden - dies kann zu unvorhersehbaren Konsequenzen führen.

Fazit


Die Ausgabeschicht kann mit einer Art Netzwerk verglichen werden, das alle möglichen "Lecks" der PHP-Ausgabe auffängt und diese in einem Puffer einer bestimmten Größe speichert. Wenn der Puffer voll ist, wird er, falls vorhanden, in die untere Ebene gespült (geschrieben). Zumindest zum niedrigsten verfügbaren - zum SAPI-Puffer. Benutzer können die Anzahl der Puffer, ihre Größe und die Vorgänge steuern, die in jeder Ebene des Puffers zulässig sind (Leeren, Leeren oder Löschen). Dies ist ein sehr flexibles Tool, mit dem beispielsweise die Ersteller von Bibliotheken und Frameworks den Ausgabestream vollständig steuern, in den globalen Puffer leiten und dort verarbeiten können. In diesem Fall regelt PHP selbst die Reihenfolge des Sendens von Headern und des Ausgabestreams.

Standardmäßig gibt es einen Ausgabepuffer, der von drei Einstellungen in der ini-Datei gesteuert wird. Es wurde entwickelt, um Schreibvorgänge weniger häufig auszuführen und nicht zu oft auf die SAPI-Schicht und damit auf das Netzwerk zuzugreifen. Dies geschieht, um die Gesamtleistung zu verbessern. Außerdem können PHP-Erweiterungen Rückrufe deklarieren, die in jedem Puffer gestartet werden - zum Beispiel zur Datenkomprimierung, zum Ersetzen von Zeichenfolgen, zur Steuerung von HTTP-Headern und für viele andere Vorgänge.

Jetzt auch beliebt: