Mit der Transients API WP_Queries zwischenspeichern bzw. cachen

Neulich habe ich für Helpful eine Caching-Lösung gesucht, damit die Statistiken für die Gesamtstimmen nicht jedes Mal neu gezogen werden müssen. Da ich dort mit dem WP Cache Object gearbeitet habe und der nur semi gut dafür geeignet ist, gibt es hier eine kurze Anleitung dafür, wie man seinen WP_Query mit der Transients API von WordPress zwischenspeichern bzw. cachen kann.

Was sind in WordPress Transients?

Transients sind im Grunde Optionen mit einem Ablauftermin. Diesen Ablauftermin kann man beim Festlegen dieser Transients bestimmen. Hier in unserem Beispiel werden also die Ergebnisse unseres Queries in einer Option gespeichert und müssen nicht jedes Mal neu generiert werden. Das spart Zeit und entlastet den Server ein wenig.

Wie nutze ich Transients um meinen WP_Query zu beschleunigen?

Dafür legst du für jedes Ergebnis deiner WP_Query einen Transient fest und speicherst eben diese Ergebnisse dort ab. Damit neue Beiträge auch angezeigt werden, muss dieser Transient bei jeder Beitragsveröffentlichung wieder gelöscht werden. Im Anschluss wird dann automatisch ein neuer Transient gespeichert. Das Ganze läuft noch besser, wenn du die Ausgabe auf die Beitrags-ID beschränkst. Wie das funktioniert wird hier beschrieben.

Hier der Code zum Festlegen des Transient für einen unserer Queries:

/* Unser transient, wie er in der wp_options Datenbanktabelle gespeichert wird. */
$query = get_transient( 'my_custom_query' );

/**
 * Hier prüfen wir ob wir einen transient für my_custom_query angelegt haben
 * und wenn wir keinen angelegt haben, packen wir die Ergebnisse unseres Queries
 * in den Transient 'my_custom_query'.
 */
if ( false === $query ) {

	$args = [
		'post_type'      => 'post',
		'posts_per_page' => -1,
		'fields'         => 'ids',
	];

	$query = new WP_Query( $args );

	/* $query für eine Woche als transient speichern */
	set_transient( 'my_custom_query', maybe_serialize( $query ), WEEK_IN_SECONDS );

}

$query = maybe_unserialize( $query );

if ( $query->found_posts ) {

	foreach ( $query->posts as $post_id ) :

		printf( '%d <br>', $post_id );

	endforeach;

} else {
	esc_html_e( 'Keine Beiträge gefunden.', 'textdomain' );
}Code-Sprache: PHP (php)

Und jetzt noch der Code zum Löschen des Transient, bei Veröffentlichung eines Beitrages:

/**
 * Diese Funktion wird immer beim Veröffentlichen von Beiträgen
 * aufgerufen. Wenn das passiert, wird der transient gelöscht,
 * damit auch der neue Beitrag im eigenen Query zu sehen ist.
 *
 * @param integer $post_id ID des aktuellen Beitrags.
 * @param object  $post    der aktuelle Beitrag als Object.
 *
 * @return void
 */
function remove_transient_on_publish_post( $post_id, $post ) {

	/* Unser transient für den obigen Query */
	$query = get_transient( 'my_custom_query' );

	/* Hier prüfen wir ob wir einen transient für my_custom_query angelegt haben */
	if ( false !== $query ) {

		/* Hier löschen wir unseren transient */
		delete_transient( 'my_custom_query' );
	}
}

/**
 * Hier geben wir unsere Funktion an publish_post weiter,
 * sprich die Funktion wird immer dann ausgeführt, wenn etwas
 * veröffentlicht wird. Das gilt auch für den Wechsel von Entwurf
 * auf Veröffentlicht.
 */
add_action( 'publish_post', 'remove_transient_on_publish_post', 10, 2 );Code-Sprache: PHP (php)

Den Code für das Festlegen musst du unmittelbar bei deinem Query nutzen. Den anderen Code kannst du in die funtions.php oder in dein Plugin packen. Du kannst die einzelnen Transients nennen wir du magst und musst dich nicht auf my_custom_query festlegen. Du kannst die transients auch für Queries verwenden, die mittels $wpdb erstellt hast. Eigentlich kannst du dort alles abspeichern, ich würde es aber auf größere Queries beschränken, oder zumindest auf Queries bei denen du einschätzen kannst, dass sie größer werden.

WP_Query mit Pagination

Wenn du eine Pagination im Einsatz hast, musst du die Seitenzahl mitgeben und selbst dann gibt es Probleme, weil man auf der zweiten Seite möglicherweise eine andere Ablaufzeit hat. Du kannst das lösen in dem du die Queries für jede einzelne Seite in der Pagination selbst, einmal komplett ausführst und vollständig abwickelst. Hier ein Beispiel:

/**
 * Hier legen wir unsere neue Funktion fest, die den Inhalt jeder
 * einzelnen Seite im Query in einen eigenen Transient speichert
 * und dabei beachtet, dass alle den gleichen Ablauftermin haben.
 * Je nach dem wie viele Seiten der Query hat, kann das etwas
 * dauern. Eine bessere Lösung ist mir jetzt gerade aber nicht
 * eingefallen.
 *
 * @param integer $post_id ID des aktuellen Beitrags.
 * @param object  $post    Der aktuelle Beitrag als Object.
 *
 * @return void
 */
function my_transients( $post_id, $post ) {

	$args = [
		'post_type'      => 'post',
		'posts_per_page' => 5,
		'fields'         => 'ids',
		'paged'          => 1,
	];

	$query = new WP_Query( $args );

	if ( $query->max_num_pages > 1 ) {

		$pages  = $query->max_num_pages;
		$expire = WEEK_IN_SECONDS;

		for ( $page = 1; $page <= $pages; $page++ ) :

			/**
			 * Der Name unseres Transient inkl. der aktuellen Seitenzahl,
			 * der dann so in der Datenbank abgespeichert wird.
			 */
			$transient = 'my_custom_query_page_' . $page;

			/**
			 * Erst einmal löschen wir den aktuellen Transient,
			 * damit wir ihn aktualisieren können.
			 */
			delete_transient( $transient );

			$query = get_transient( $transient );

			/* Hier prüfen wir ob es den Transient gibt */
			if ( false === $query ) {

				/**
				 * Hier verändern wir die Seite im Query, damit wir den
				 * Inhalt der anderen Seiten erhalten.
				 */
				$args['paged'] = $page;

				$query = new WP_Query( $args );

				/**
				 * Hier speichern wir unseren transient inkl. der Ergebnisse
				 * aus unserem Query
				 */
				set_transient( $transient, maybe_serialize( $query ), $expire );
			}

		endfor;
	}
}

/**
 * Unsere Funktion wird erst dann ausgeführt, wenn wir
 * einen Beitrag veröffentlichen.
 */
add_action( 'publish_post', 'my_transients', 10, 2 );Code-Sprache: PHP (php)

Das war es eigentlich auch schon. Jetzt werden unsere Ergebnisse für eine Woche in der Datenbank gespeichert. Damit sparst du dem Server die Arbeit jedes Mal einen kompletten Query auszuführen, weil die Ergebnisse ja bereits im Transient gespeichert sind. Wenn dann ein Beitrag veröffentlicht wird, wird automatisch der Transient gelöscht und beim Aufrufen des Queries wieder gespeichert. Ein endloser Kreislauf der dir hoffentlicht dabei hilft, schnellere Queries zu schreiben.