Dynamisches Generieren von CSS-Dateien mithilfe von PHP-Skripten?

7

Im Rahmen meiner Bemühungen, meine Themen für meine Kunden zu beschleunigen, stelle ich mein CSS über eine dynamische PHP-Datei bereit. Am Ende des aufgerufenen Beispiels my_theme_css.php:

Auf diese Weise kann ich meinem CSS und JavaScript Expiry-Header wie folgt hinzufügen:

<?php
header("Expires: Thu, 31 Dec 2020 20:00:00 GMT");
header('Content-type: text/css');
?>

Ich möchte dem Benutzer dann erlauben, sein eigenes benutzerdefiniertes CSS hinzuzufügen, damit ich Folgendes eingerichtet habe:

<?php
header("Expires: Thu, 31 Dec 2020 20:00:00 GMT");
header('Content-type: text/css');
?>

p{color:blue;}

/** Custom CSS **/
<?php 
if(get_theme_mod('my_custom_css') != '') {
  $my_custom_css = get_theme_mod('my_custom_css'); 
  echo $my_custom_css; 
}
?> 

Ich habe dann die entsprechenden Themenoptionen hinzugefügt. Was ich jedoch finde, ist, dass get_theme_mod()dies aufgrund eines Umfangsproblems nicht funktioniert (wie ich mir vorstellen kann). Irgendwelche Vorschläge, wie man das umgehen kann?

Ich könnte den folgenden Code hinzufügen:

<?php 
if(get_theme_mod('my_custom_css') != '') {
  $my_custom_css = get_theme_mod('my_custom_css'); 
  echo $my_custom_css; 
}
?>

<style></style>Erwarten Sie, dass innerhalb von Tags im Header und das würde gut funktionieren, ich möchte das CSS nicht inline, sondern in der Haupt-CSS-Datei mit dem Ablauf-Header.

Ashley G.
quelle
Wäre eine statische CSS-Datei nicht besser für die Leistung? Es gibt auch einige gute Ratschläge zu einer ähnlichen Frage, die ich gestellt habe.
Chris_O
@Chris_O Wenn dies für ein paar Stilregeln wäre, würde ich ja sagen, aber für eine stärkere Inline-Verwendung bedeutet dies, dass jedes Mal, wenn eine Seite geladen wird, Hunderte von zusätzlichen Zeilen heruntergeladen werden, im Vergleich zu einem externen Stylesheet mit einem weit entfernten zukünftigen Ablaufdatum, das zwischengespeichert werden kann
Ashley G.
@Ash G: Stellen Sie eine statische CSS-Datei bereit und richten Sie das Caching im Webserver ordnungsgemäß ein. Das ist mein Vorschlag.
hakre
@hakre Dies wird in Fällen verwendet, in denen der Zugriff auf dieser Ebene auf dem Server nicht verfügbar ist oder der Endbenutzer nicht über ausreichende Kenntnisse verfügt, um diese Änderungen vorzunehmen. Aus diesem Grund muss sich der Code auf Themenebene befinden.
Ashley G
@Ash G: Wenn das der Fall ist, dann ist das auf PHP angewiesen, stimmt.
hakre

Antworten:

15

Hallo @Ash G:

Ich bin nicht 100% gefolgt, wo Sie speziell Probleme hatten, daher bin ich mir nicht sicher, ob ich Ihre Probleme wirklich Punkt für Punkt beantworten kann, aber ich kann erklären, wie dies von Grund auf zu tun ist . Und es sei denn, was Sie tun, ist ein bisschen mehr involviert, als Sie erwähnt haben, dass es ein bisschen mehr Arbeit ist, als Sie erwartet haben, aber es ist immer noch vollständig machbar. Und selbst wenn ich viele Bereiche abdeckte, von denen Sie bereits wissen, dass es eine gute Chance gibt, dass andere mit weniger Wissen oder Erfahrung dies über Google finden und auch davon unterstützt werden.

Bootstrap WordPress mit /wp-load.php

Das erste, was wir in Ihrer my_theme_css.phpDatei tun müssen, ist das Booten der Kernbibliotheksfunktionen von WordPress. Die folgende Codezeile wird geladen /wp-load.php. Es verwendet , $_SERVER['DOCUMENT_ROOT']um die Root - Webseite zu finden , so dass Sie Sorge haben nicht darüber , wo Sie speichern diese Datei auf Ihrem Server; Vorausgesetzt, es DOCUMENT_ROOTist richtig eingestellt, wie es immer für WordPress sein sollte, dann wird dies WordPress booten:

<?php
include_once("{$_SERVER['DOCUMENT_ROOT']}/wp-load.php"); 

Das ist also der einfache Teil. Als nächstes kommt der schwierige Teil ...

PHP-Skripte müssen die gesamte Caching-Logik verarbeiten

Ich wette, Sie sind hier gestolpert, weil ich es wirklich getan habe, als ich versucht habe, herauszufinden, wie ich Ihre Frage beantworten kann. Wir sind so an die Caching-Details gewöhnt, die vom Apache-Webserver verarbeitet werden, dass wir vergessen oder gar nicht erkennen , dass wir beim Laden von CSS oder JS mit PHP das ganze schwere Heben selbst erledigen müssen .

Sicher, der Ablauf-Header ist möglicherweise alles, was wir brauchen, wenn wir einen Proxy in der Mitte haben, aber wenn die Anfrage an den Webserver gelangt und das PHP-Skript wohl oder übel Inhalte und den Statuscode "Ok" zurückgibt, sind Sie im Wesentlichen gut habe kein Caching.

Rückgabe von "200 Ok" oder "304 Not Modified"

Insbesondere unsere PHP - Datei , dass CSS Bedürfnisse kehrt korrekt auf die reagieren Anfrage vom Browser gesendete Header. Unser PHP-Skript muss den richtigen Statuscode basierend auf dem Inhalt dieser Header zurückgeben. Wenn der Inhalt bereitgestellt werden muss, weil es sich um eine erstmalige Anforderung handelt oder weil der Inhalt abgelaufen ist, sollte das PHP-Skript den gesamten CSS-Inhalt generieren und mit "200 Ok" zurückkehren .

Auf der anderen Seite , wenn wir auf den Cache-bezogenen bestimmen basierend Anfrage - Header , dass der Client - Browser bereits die neueste CSS hat , wir sollten nicht jede CSS zurückzukehren und stattdessen zurückkehren mit einem „304 Not Modified“ . Die zu Codezeilen dafür sind jeweils (natürlich würden Sie niemals beide Zeilen nacheinander verwenden, ich zeige dies hier nur der Einfachheit halber) :

<?php 
header('HTTP/1.1 200 Ok');
header('HTTP/1.1 304 Not Modified');

Vier Geschmacksrichtungen des HTTP-Caching

Als nächstes müssen wir uns die verschiedenen Möglichkeiten ansehen, wie HTTP mit dem Caching umgehen kann. Das erste ist, was Sie erwähnen; Expires::

  • Läuft ab : DieserExpiresHeader erwartet ein Datum imFormat( PHP- gmdate()Funktion )'D, d M Y H:i:s'mit einem' GMT'angehängten ( GMT steht für Greenwich Mean Time ). Theoretisch werden der Browser und die nachgeschalteten Proxys zwischengespeichert, bis die angegebene Zeit abgelaufen ist, nach der er gestartet wird die Seite erneut anfordern. Dies ist wahrscheinlich der bekannteste Caching-Header, aber offensichtlich nicht der bevorzugte. Cache-Control ist die bessere . Interessanterweise konnte ich bei meinen Testslocalhostmit Safari 5.0 unter Mac OS XI den Browser nie dazu bringen, denExpiresHeaderzu respektieren. es immerhat die Datei erneut angefordert (wenn jemand dies erklären kann, wäre ich dankbar). Hier ist das Beispiel, das Sie von oben gegeben haben:

header("Expires: Thu, 31 Dec 2020 20:00:00 GMT");

  • Cache-Kontrolle : DerCache-ControlHeader ist einfacher zu bearbeiten als derExpiresHeader, da Sie nur die Anzahl der Sekunden in Sekunden angeben müssen, damitmax-ageSie kein genaues Datumsformat in Zeichenfolgenform erstellen müssen, das leicht falsch zu verstehen ist . Darüber hinausCache-Controlkönnen verschiedene andere Optionen verwendet werden, z. B. die Möglichkeit, den Clientanzuweisen,den Cache mithilfe diesermustrevalidateOptionimmer erneut zu validieren,undpublicwenn Sie das Caching für normalerweise nicht zwischenspeicherbare Anforderungen (dh Anforderungen überHTTPS)erzwingenund sogar nicht zwischenspeichernmöchten,wenn Sie dies benötigen (Das heißt, Sie möchten möglicherweise ein 1x1-Pixel-Anzeigen-Tracking erzwingen. GIF darf nicht zwischengespeichert werden.) Als obExpires ich dies auch beim Testennicht zum Laufen bringen könnte( Hilfe?) Das folgende Beispiel wird für einen Zeitraum von 24 Stunden (60 Sekunden x 60 Minuten x 24 Stunden) zwischengespeichert:

header("Cache-Control: max-age=".60*60*24.", public, must-revalidate");

  • Last-Modified / If-Modified-Since : Dann gibt es dasLast-ModifiedAntwort-Header- und das If-Modified-Since Request-Header-Paar. Diese verwenden ebenfalls dasselbe GMT-Datumsformat wie derExpiresHeader, führen jedoch einen Handshake zwischen Client und Server durch. Das PHP-Skript muss einenLast-ModifiedHeadersenden(den Sie übrigens nur aktualisieren sollten, wenn Ihr Benutzer sein benutzerdefiniertes CSS zuletzt aktualisiert hat). Danach sendet der Browser weiterhin denselben Wert wie einenIf-Modified-SinceHeader zurück und es liegt in der Verantwortung des PHP-Skripts Vergleichen Sie den gespeicherten Wert mit dem vom Browser gesendeten. Hier muss das PHP-Skript die Entscheidung treffen, ob a200 Okoder abereitgestellt werden soll304 Not Modified. Hier ist ein Beispiel für das Bereitstellen desLast-ModifiedHeaders unter Verwendung der aktuellen Zeit (was nicht derFall ist)was wir tun wollen; siehe das spätere Beispiel für das, was wir tatsächlich brauchen):

header("Last-Modified: " . gmdate('D, d M Y H:i:s', time()).'GMT');

Und so würden Sie die Last-Modifiedvom Browser über den If-Modified-SinceHeader zurückgegebenen Informationen lesen :

$last_modified_to_compare = $_SERVER['HTTP_IF_MODIFIED_SINCE'];

  • ETag / If-None-Match : Und schließlich gibt es denETagAntwortheader und dasIf-None-MatchAnforderungsheaderpaar. DasETagist eigentlich nur ein Token, das unser PHP auf einen eindeutigen Wert setzt (normalerweise basierend auf dem aktuellen Datum) und an den Browser sendet und der Browser gibt es zurück. Wenn sich der aktuelle Wert von dem unterscheidet, den der Browser zurückgibt, sollte Ihr PHP-Skript den Inhalt neu200 Okgenerieren,den ein Serversonst nicht generiert und a bereitstellt304 Not Modified. Hier ist ein Beispiel für die Einstellung einer unterETagVerwendung der aktuellen Zeit:

header("ETag: " . md5(gmdate('D, d M Y H:i:s', time()).'GMT'));

Und so würden Sie die ETagvom Browser über den If-None-MatchHeader zurückgegebenen Informationen lesen :

$etag_to_match = $_SERVER['HTTP_IF_NONE_MATCH'];

Nachdem wir alles behandelt haben, schauen wir uns den eigentlichen Code an, den wir brauchen:

Bereitstellen der CSS-Datei über initundwp_enqueue_style()

Sie haben das nicht gezeigt, aber ich dachte, ich würde es zum Nutzen anderer zeigen. Hier ist der Funktionsaufruf, der WordPress anweist, es my_theme_css.phpfür sein CSS zu verwenden. Dies kann in der functions.phpThemendatei oder auf Wunsch auch in einem Plugin gespeichert werden:

<?php
add_action('init','add_php_powered_css');
function add_php_powered_css() {
  if (!is_admin()) {
    $version = get_theme_mod('my_custom_css_version',"1.00");
    $ss_dir = get_stylesheet_directory_uri();
    wp_enqueue_style('php-powered-css', 
        "{$ss_dir}/my_theme_css.php",array(),$version);
  }
}

Es gibt mehrere Punkte zu beachten:

  • Verwendung vonis_admin() , um das Laden des CSS im Administrator zu vermeiden (es sei denn, Sie möchten das ...),
  • Verwendung vonget_theme_mod() , um das CSS mit einer Standardversion von zu laden 1.00(mehr dazu gleich),
  • Verwendung vonget_stylesheet_directory_uri() , um das richtige Verzeichnis für das aktuelle Thema abzurufen, auch wenn das aktuelle Thema ein untergeordnetes Thema ist.
  • Verwendung von,wp_enqueue_style() um das CSS in die Warteschlange zu stellen, damit WordPress es zum richtigen Zeitpunkt laden kann, wobei 'php-powered-css'ein beliebiger Name später ( falls erforderlich ) als Abhängigkeit verwendet werden kann. Leer array()bedeutet, dass dieses CSS keine Abhängigkeiten aufweist ( obwohl dies in der realen Welt häufig der Fall ist eine oder mehrere ) und
  • Verwendung von$version ; Wahrscheinlich die wichtigste, wir empfehlen wp_enqueue_style(), ?ver=1.00der /my_theme_css.phpURL einen Parameter hinzuzufügen , damit der Browser ihn bei einer Änderung der Version als eine völlig andere URL ansieht (viel mehr dazu in Kürze).

Einstellung $versionund Last-Modifiedwann Benutzer CSS aktualisiert

Also hier ist der Trick. Jedes Mal, wenn der Benutzer sein CSS aktualisiert, möchten Sie den Inhalt bereitstellen und nicht warten, bis 2020der Browser-Cache aller Benutzer eine Zeitüberschreitung aufweist, oder? Hier ist eine Funktion, die in Kombination mit meinem anderen Code dies erreicht. Verwenden Sie jedes Mal, wenn Sie vom Benutzer aktualisiertes CSS speichern, diese Funktion oder Funktionalität, die den folgenden Funktionen ähnelt:

<?php
function set_my_custom_css($custom_css) {
  $new_version = round(get_theme_mod('my_custom_css_version','1.00',2))+0.01;
  set_theme_mod('my_custom_css_version',$new_version);
  set_theme_mod('my_custom_css_last_modified',gmdate('D, d M Y H:i:s',time()).' GMT');
  set_theme_mod('my_custom_css',$custom_css);
}

Die set_my_custom_css()Funktion erhöht die aktuelle Version automatisch um 0,01 (was nur ein beliebiger von mir ausgewählter Inkrementwert war) und setzt das Datum der letzten Änderung auf " Jetzt" und speichert schließlich das neue benutzerdefinierte CSS. Das Aufrufen dieser Funktion ist möglicherweise so einfach (wo new_custom_csssie wahrscheinlich über einen Benutzer zugewiesen wird, der übermittelt wird, $_POSTanstatt durch Hardcodierung, wie Sie hier sehen) :

<?php
$new_custom_css = 'body {background-color:orange;}';
set_my_custom_css($new_custom_css);

Das bringt uns zum letzten, wenn auch bedeutenden Schritt:

Generieren des CSS aus dem PHP-Skript

Endlich sehen wir das Fleisch, die eigentliche my_theme_css.phpAkte. Auf einem hohen Niveau testet es sowohl die If-Modifed-Since gegenüber dem gespeicherten Last-ModifiedWert und die If-None-Matchgegen die , ETagdie von der gespeicherten abgeleitet wurde Last-ModifiedWert und wenn weder geändert setzt nur den Header 304 Not Modifedund Zweige bis zum Ende.

Wenn sich jedoch einer von beiden geändert hat, wird der Expires, Cache-Control. Last-Modifiedund EtagÜberschriften sowie ein 200 Okund, das angibt, dass der Inhaltstyp ist text/css. Wir brauchen wahrscheinlich nicht alle, aber angesichts der Tatsache, wie schwierig das Caching mit verschiedenen Browsern und Proxys sein kann, schadet es meiner Meinung nach nicht, alle Basen abzudecken. (Und jeder mit mehr Erfahrung mit HTTP-Caching und WordPress meldet sich bitte, wenn ich irgendwelche Nuancen falsch verstanden habe.)

Der folgende Code enthält einige weitere Details, aber ich denke, Sie können sie wahrscheinlich selbst herausarbeiten:

<?php

  $s = $_SERVER;

  include_once("{$s['DOCUMENT_ROOT']}/wp-load.php");

  $max_age = 60*60*24; // 24 hours
  $now = gmdate('D, d M Y H:i:s', time()).'GMT';
  $last_modified = get_theme_mod('my_custom_css_last_modified',$now);
  $etag = md5($last_modified);

  if (strtotime($s['HTTP_IF_MODIFIED_SINCE']) >= strtotime($last_modified) || $s['HTTP_IF_NONE_MATCH']==$etag) {
    header('HTTP/1.1 304 Not Modified');
  } else {
    header('HTTP/1.1 200 Ok');
    header("Expires: " . gmdate('D, d M Y H:i:s', time()+$max_age.'GMT'));
    header("Cache-Control: max-age={$mag_age}, public, must-revalidate");
    header("Last-Modified: {$last_modified}");
    header("ETag: {$etag}");
    header('Content-type: text/css');
    echo_default_css();
    echo_custom_css();
  }
  exit;

function echo_custom_css() {
  $custom_css = get_theme_mod('my_custom_css');
  if (!empty($custom_css))
    echo "\n{$custom_css}";
}

function echo_default_css() {
  $default_css =<<<CSS
body {background-color:yellow;}
CSS;
  echo $default_css;
}

Also mit diesen drei Hauptcodebits; 1.) die add_php_powered_css()vom initHook aufgerufene Funktion , 2.) die set_my_custom_css()von welchem ​​Code auch immer aufgerufene Funktion ermöglicht es dem Benutzer, sein benutzerdefiniertes CSS zu aktualisieren, und schließlich 3.) das my_theme_css.phpsollten Sie so ziemlich geleckt haben.

Weiterführende Literatur

Abgesehen von den bereits verlinkten Artikeln stieß ich auf einige andere Artikel, die ich für sehr nützlich hielt, und dachte mir, ich sollte sie hier verlinken:

Epilog:

Aber ich würde das Thema nicht verlassen, ohne abschließende Kommentare abzugeben.

Läuft ab in 2020? Wahrscheinlich zu extrem.

Erstens glaube ich nicht, dass Sie Expiresdas Jahr 2020 festlegen möchten . Browser oder Proxys, die dies respektieren, Expireswerden auch nach vielen CSS-Änderungen nicht erneut angefordert. Es ist besser, etwas Vernünftiges wie 24 Stunden festzulegen (wie ich es in meinem Code getan habe), aber selbst das wird die Benutzer für den Tag frustrieren, an dem Sie Änderungen am fest codierten CSS vornehmen, aber die bereitgestellte Versionsnummer vergessen. Moderation in allen Dingen?

Dies könnte sowieso alles übertrieben sein!

Als ich verschiedene Artikel las, um Ihre Frage zu beantworten, stieß ich im Mr. Cache Tutorial selbst, Mark Nottingham, auf Folgendes :

Der beste Weg, um ein Skript cachefreundlich zu machen (und eine bessere Leistung zu erzielen), besteht darin, seinen Inhalt bei jeder Änderung in eine einfache Datei zu kopieren. Der Webserver kann es dann wie jede andere Webseite behandeln und Validatoren generieren und verwenden, was Ihnen das Leben erleichtert. Denken Sie daran, nur Dateien zu schreiben, die sich geändert haben, damit die zuletzt geänderten Zeiten erhalten bleiben.

Während all dieser Code, den ich geschrieben habe, cool war und Spaß gemacht hat (ja, das habe ich tatsächlich zugegeben), ist es vielleicht besser, jedes Mal eine statische CSS-Datei zu generieren, wenn der Benutzer stattdessen sein benutzerdefiniertes CSS aktualisiert , und Apache das ganze schwere Heben zu überlassen wie es geboren wurde, um zu tun? Ich sage ja nur...

Hoffe das hilft!

MikeSchinkel
quelle
Hallo Mike, Danke dafür. Sie geben immer so hervorragende Antworten! Ich habe versucht, die ganze Sache mit wp_load zu vermeiden ( ottopress.com/2010/dont-include-wp-load-please )
Ashley G
@ Ash G: heh. Nun, deshalb hat deins nicht funktioniert! ;-) get_theme_mod()ist Teil der WordPress-API, ohne dass /wp-load.phpdu es nicht bekommst! Und die API ist nicht modular, es ist alles oder nichts. Aber ich werde argumentieren, während Otto im Allgemeinen Recht hat, ist es kein großes Problem, wenn Sie 24 Stunden oder länger mit Expiresoder zwischenspeichern Cache-Control. Und wenn Sie wirklich optimieren möchten, können Sie die Funktion entfernen /wp-load.phpund rohe MySQL-Aufrufe verwenden, um die get_theme_mod()Funktion zu ersetzen , wenn Sie möchten. Es ist trivialer Code. Der einzige Nachteil ist, dass die API die Kapselung der Details des Theme-Mod-Speichers bricht.
MikeSchinkel
@Ash G: Natürlich gibt es immer den letzten Teil meiner Antwort, den ich mit "Das könnte sowieso alles übertrieben sein!" Begonnen habe. ... :-)
MikeSchinkel
@AshG : Oh, und danke für das Kompliment. Ich lerne am besten, indem ich andere unterrichte, und ich versuche, Experte für alles zu werden, was man über das Codieren für WordPress wissen muss. Deshalb habe ich mich um die Antworten bemüht. Anderen zu helfen hilft mir; Das ist eine gute Win-Win- Situation , nein? :-)
MikeSchinkel
1
Hallo @Ash G : Nun könnten Sie file_get_contents()und scannen durch /wp-config.phpsuchen $table_prefixund DB_HOST, DB_NAME, DB_USERund DB_PASSWORD; Das wäre weder schwer zu schreibender Code noch Code mit zu viel Overhead (im Vergleich zu allen /wp-load.php) zu überlegenden Punkten. :) Und danke für die Auswahl und Abstimmung.
MikeSchinkel
0

Versuchen Sie es wie folgt zu codieren:

<?php $my_custom_css = get_theme_mod('my_custom_css');
  if(!empty($my_custom_css)) {
    echo $my_custom_css;
  }
?>

Damit sollten Sie alle Probleme mit der get_theme_mod()Funktion umgehen können.

dgw
quelle
Danke für die Hilfe. Dies funktioniert jedoch nicht. Ich verstehe nicht, warum dies das Problem des Anwendungsbereichs umgehen würde.
Ashley G
Ich verstehe nicht, warum es überhaupt ein Umfangsproblem mit einer Funktion geben würde. Aber ich dachte, ich würde zeigen, wie ich ähnliche Aufgaben in anderen Plugins gesehen habe.
dgw