Die WordPress Nav Walker Klasse: Eine geführte var_dump()

Avatar of Scott Fennell
Scott Fennell am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der folgende Text ist ein Gastbeitrag von Scott Fennell. Scott ist ein WordPress Theme & Plugin-Entwickler in Anchorage, Alaska. Hier passt er die Standard-HTML-Ausgabe des WordPress-Menüsystems an seine Bedürfnisse an, ohne die nützlichen Funktionen, die es bereits bietet, zu beschädigen.

Es gibt viele Dinge am WordPress Nav-Menüsystem, die ich mag, und eine Handvoll Dinge, die ich wirklich nicht mag. Die Dinge, die ich nicht mag, sind meine persönlichen Dorn im Auge: Markup-Bloat und nicht-SMACCS-konforme Klassennamen. Für mich ist es eine tiefere Betrachtung wert, um diese Situation zu korrigieren. Ich möchte jedoch die Integration mit der Admin-Oberfläche nicht verlieren und ich möchte die dynamischen Klassennamen, die der Core uns gibt, wie current_page_item oder current-menu-ancestor, nicht verlieren. Deshalb werde ich nichts ersetzen: Ich werde die PHP-Klasse erweitern, die die Nav-Menüs zeichnet: Die Walker_Nav_Menu Klasse.

Dies werde ich tun, indem ich ein Plugin erstelle, das Navigationsmenüs mit dem gewünschten Markup und den gewünschten Klassen ausgibt. Unterwegs werde ich innehalten undam Rosenduft schnuppern var_dump() der PHP-Variablen, die WordPress uns zur Verfügung stellt. Dieses Plugin wird die folgenden Komponenten haben

  1. Eine Hauptplugin-Datei zur Registrierung des Plugins und zum Aufruf anderer Dateien
  2. Ein Shortcode zur Ausgabe des Menüs
  3. Etwas CSS, JS & SVG, um Dinge wie das Ein-/Ausblenden von Untermenüs zu erledigen
  4. Eine benutzerdefinierte Walker-Klasse, die die Core-Klasse Walker_Nav_Menu erweitert

Von diesen Komponenten werden alle bis auf die letzte hauptsächlich als Platzhalter dienen. Sie werden den minimalen Code enthalten, um ein minimal lebensfähiges Produkt zu erreichen, und ich werde sie nicht im Detail untersuchen. Sie bieten gerade genug Grundlage, damit ich eine benutzerdefinierte Walker-Klasse erstellen kann, die im Mittelpunkt dieses Artikels steht.

Annahmen

  • Machen wir das mit dem twentyfifteen Theme
  • Wenn andere Plugins aktiv sind, stellen Sie sicher, dass sie keine JS- oder PHP-Fehler verursachen. Im Zweifelsfall deaktivieren Sie sie
  • Ich verwende zum Zeitpunkt der Erstellung dieses Artikels WordPress 4.3.1.

Das Plugin

Ich werde Blöcke aus dem fertigen Plugin zitieren, während wir fortfahren. Sie können es von meinem GitHub-Repo herunterladen, wenn Sie das fertige Produkt einsehen oder es sogar auf einer WordPress-Testseite installieren möchten.

Die Ausgabe des fertigen Plugins

Der Shortcode

Das Plugin funktioniert, indem es einen Shortcode registriert, [csst_nav]. Der Shortcode nimmt ein Argument entgegen, which_menu, mit dem Sie auswählen können, welches Navigationsmenü ausgegeben werden soll, indem Sie den Slug, die ID oder den Titel eines Navigationsmenüs angeben. Hier sind einige Beispiele, bei denen ich zufällig ein Menü namens "Legal Links" mit dem Slug legal-links und der ID 5 habe

  • [csst_nav]
  • [csst_nav which_menu='legal-links']
  • [csst_nav which_menu='Legal Links']
  • [csst_nav which_menu='5']
Verwendung des Shortcodes im Editor

Der Shortcode ist ein Wrapper für die Funktion wp_nav_menu(), die eine Menge von Argumenten entgegennimmt.

Hier weiche ich von den Standardwerten ab und mache *was ich bevorzuge* stattdessen

  • menu: Ich möchte angeben können, welches Menü abgerufen werden soll.
  • container: Ich möchte weniger Markup, daher ist kein Container-Element erforderlich.
  • menu_class: Ich liebe Klassennamen. Ich werde diesem einige Klassennamen geben, die für mein Plugin und für das Menü, das ich abrufe, benannt sind.
  • echo: Nein danke. Ich gebe das Menü zurück, anstatt es auszugeben.
  • items_wrap: Ich werde die Elemente in einem <nav> statt der Standard-Ungeordnete Liste verpacken.
  • before: Ich werde jedes Menüelement als <span> öffnen und auch die fest codierten <li> des Cores entfernen.
  • after: Ich werde jedes Menüelement mit einem schließenden </span> schließen und auch die fest codierten </li> des Cores entfernen.
  • before_submenu: Ich werde jedes Untermenü als <span> statt als <ul> öffnen.
  • after_submenu: Ich werde jedes Untermenü mit einem schließenden </span> schließen, anstatt mit einem schließenden </ul>.
  • walker: **Deshalb lesen Sie diesen Artikel.** Ich werde WordPress anweisen, unsere benutzerdefinierte Walker-Klasse zu verwenden.

Einige dieser Argumente, wie before_submenu und after_submenu, werden nicht tatsächlich mit wp_nav_menu() ausgeliefert. Das ist aber in Ordnung, da sie trotzdem an die Walker-Klasse weitergegeben werden, wo ich sie nach Belieben verwenden kann.

Hier sieht das alles im Code aus

<?php

  /**
   * The main template tag for this class.  Get a custom menu via our walker.
   * 
   * @return string A WordPress custom menu, passed through our walker class.
   */
  public function get() {
    
    // The CSS class for our shortcode.
    $class = strtolower( __CLASS__ );

    // Get a menu from the db.
    $which_menu = $this -> which_menu;

    /**
     * Args for a call to wp_nav_menu().
     * 
     * Some of these args don't get used by wp_nav_menu() per se,
     * but we're able to pass them through to our walker class, which does use them.
     */ 
    $menu_args = array(

      // Instead of wrapping each menu item as list item, let's do a span.
      'after' => '',

      // The closing markup after a submenu.
      'after_submenu' => '',

      // Instead of wrapping each menu item as list item, let's do a span.
      'before' => '',

      // The opening markup before a submenu.
      'before_submenu' => '',

      // Nope, we don't need extra markup wrapping our menu.
      'container' => FALSE,

      // Nope, let's return instead of echo.
      'echo' => FALSE,

      // Let's use a <nav> instead of a nested list.
      'items_wrap' => '<nav role="navigation" class="%2$s">%3$s</nav>',

      // Which menu to grab?  Takes ID, name, or slug.
      'menu' => $which_menu,

      // CSS classes for our menu.
      'menu_class' => "$class $class-$which_menu",

      // Our custom walker.
      'walker' => new CSST_Nav_Walker(),

    );

    // The main content of the shortcode is in fact a call to wp_nav_menu().
    $out = wp_nav_menu( $menu_args );

    return $out;

  }

?>

Okay, genug der Einleitung. Es ist Zeit, in die benutzerdefinierte Walker-Klasse einzutauchen. Ich liebe eine erschöpfende Detailtiefe!

Die benutzerdefinierte Walker-Klasse

Hier gibt es eine Art Hierarchie

  1. Core definiert eine extrem generische Klasse: Walker. Ihr Zweck ist es, durch komplexe Strukturen wie mehrdimensionale Arrays zu iterieren und bei jedem Element dieser Struktur *Dinge zu tun*.
  2. Core definiert dann eine spezifischere Erweiterung von Walker, die speziell für die Navigation durch Menüs entwickelt wurde: Walker_Nav_Menu.
  3. Schließlich definiere ich meine eigene Erweiterung von Walker_Nav_Menu und nenne sie CSST_Nav_Walker.

Meine benutzerdefinierte Walker-Klasse wird die folgenden Methoden von Core's Walker_Nav_Menu erweitern

  • start_el(), die das öffnende Markup für Menüelemente und die Menüelemente selbst anhängt.
  • end_el(), die das schließende Markup von Menüelementen anhängt.
  • start_lvl(), die das öffnende Markup für Untermenüs anhängt.
  • end_lvl(), die das schließende Markup für Untermenüs anhängt.

Das sind doch ziemlich generische Namen, oder? Das ist irgendwie der Punkt: Wir erben von Walker, das dazu gedacht ist, jede Art von Struktur aus beliebigen Gründen durchlaufen zu können. In diesem Kontext ist Spezifität der Feind. Schneiden wir durch die abstrakte Nomenklatur und finden heraus, was jede Methode für uns tut!

start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 )

Diese Methode zeichnet das öffnende HTML für ein Menüelement und das Menüelement selbst. Sie hat fünf Parameter

  1. &$output, das ist das gesamte HTML für das Menü, bis zum „aktuellen“ Menüelement. Wenn ich „aktuell“ sage, verstehen Sie, dass diese Methode einmal für jedes Menüelement aufgerufen wird.
  2. $item, das ist das WP Post-Objekt für dieses Menüelement (Menüelemente sind tatsächlich Posts vom Post-Typ nav_menu_item), plus einige zusätzliche Daten, die spezifisch für Navigationsmenüs sind.
  3. $depth, die verfolgt, wie viele Ebenen tief wir im Menü sind – also bei verschachtelten Untermenüs.
  4. $args, das ist hauptsächlich ein Array von Argumenten für wp_nav_menu(). Es enthält die Argumente, die wir in unserem Shortcode-Callback übergeben haben, plus alle Standardwerte, die wir weggelassen haben.
  5. $id, das in der Core-Dokumentation als ID des aktuellen Menüelements aufgeführt ist, obwohl ich nicht sicher bin, ob es noch unterstützt wird.

Die meisten dieser Parameter sind etwas unspektakulär, aber einige von ihnen enthalten eine Menge nützlicher Informationen. Erlauben Sie mir, var_dump() zu verwenden!

&$output

Beachten Sie, dass diese Variable mit einem kaufmännischen Und beginnt, was bedeutet, dass sie per Referenz übergeben wird. Das bedeutet, dass die Methode nichts zurückgeben muss, da alles, was dieser Variable innerhalb der Methode passiert, auch die Variable außerhalb der Methode beeinflusst. Dies ist auch der Grund, warum der var_dump() sehr schnell sehr groß wird

var_dump( esc_html( $output ) );

liefert uns

<?php

string(0) ""

string(274) "
  
    Front Page
  
"

string(1066) "
  
    Front Page
  
  
    [...] (truncated)

Dies ergibt etwa 35 KB an var_dump()-Text, daher habe ich ihn stark gekürzt. Ich zeige nur Teile der ersten drei Menüelemente. Das ist das Markup für die vorhergehenden Menüelemente, bei jedem Menüelement, weshalb wir das aktuelle Menüelement anhängen.

$item

Dieser Parameter gibt uns das WP Post-Objekt für das aktuelle Menüelement und macht es damit zum bei weitem interessantesten Argument in dieser Methode.

wp_die( var_dump( $item ) )

liefert uns

<?php

object(WP_Post)#358 (40) {
  ["ID"]                    => int(68)
  ["post_author"]           => string(1) "1"
  ["post_date"]             => string(19) "2015-10-07 01:05:49"
  ["post_date_gmt"]         => string(19) "2015-10-07 01:05:49"
  ["post_content"]          => string(1) " "
  ["post_title"]            => string(0) ""
  ["post_excerpt"]          => string(0) ""
  ["post_status"]           => string(7) "publish"
  ["comment_status"]        => string(6) "closed"
  ["ping_status"]           => string(6) "closed"
  ["post_password"]         => string(0) ""
  ["post_name"]             => string(2) "68"
  ["to_ping"]               => string(0) ""
  ["pinged"]                => string(0) ""
  ["post_modified"]         => string(19) "2015-10-07 01:05:49"
  ["post_modified_gmt"]     => string(19) "2015-10-07 01:05:49"
  ["post_content_filtered"] => string(0) ""
  ["post_parent"]           => int(0)
  ["guid"]                  => string(33) "https:///wp/csstnav/?p=68"
  ["menu_order"]            => int(1)
  ["post_type"]             => string(13) "nav_menu_item" 
  ["post_mime_type"]        => string(0) ""
  ["comment_count"]         => string(1) "0" 
  ["filter"]                => string(3) "raw"
  ["db_id"]                 => int(68)
  ["menu_item_parent"]      => string(1) "0"
  ["object_id"]             => string(2) "50"
  ["object"]                => string(4) "page"
  ["type"]                  => string(9) "post_type"
  ["type_label"]            => string(4) "Page"
  ["url"]                   => string(28) "https:///wp/csstnav/"
  ["title"]                 => string(10) "Front Page"
  ["target"]                => string(0) ""
  ["attr_title"]            => string(0) ""
  ["description"]           => string(0) ""
  ["classes"]               => array(8) {
    [0]=> string(0) ""
    [1]=> string(9) "menu-item" 
    [2]=> string(24) "menu-item-type-post_type" 
    [3]=> string(21) "menu-item-object-page" 
    [4]=> string(17) "current-menu-item" 
    [5]=> string(9) "page_item"
    [6]=> string(12) "page-item-50"
    [7]=> string(17) "current_page_item"
  }
  ["xfn"]                   => string(0) "" 
  ["current"]               => bool(true)
  ["current_item_ancestor"] => bool(false)
  ["current_item_parent"]   => bool(false)
}

Ziemlich clever, oder? Wir könnten in dieses Post-Objekt eintauchen und eine Menge cooler Sachen wie den Auszug, das Datum, Taxonomien abrufen. Verdammt, vielleicht könnten wir einen Weg entwickeln, um Beitragsbilder für Navigationsmenüelemente zu erstellen! Zusätzlich zu diesen Werten, die wir normalerweise für Posts sehen, gibt es ein paar neue Elemente, wie z. B. classes. Dort finden wir das fantastische Array dynamischer CSS-Klassen: Dinge wie current-menu-item. Auch erwähnenswert ist object, das uns Details darüber gibt, worauf dieses Menüelement verlinkt: Vielleicht eine Seite oder ein Term-Archiv.

$depth

Depth zählt mit, wie viele Untermenü-„Ebenen“ tief wir sind. Ich habe keinen Nutzen dafür, aber ich bin bereit anzuhalten und zu bewundern, was core damit macht: Sie verwenden es, um Tabulatorzeichen (also buchstäblich „\t“) voranzustellen, damit der Quellcode lesbarer ist. Zumindest nehme ich das an. Gut gemacht, Core, gut gemacht.

Anstatt var_dump() $depth zu verwenden, ist es lehrreicher, es einfach für jeden Eintrag an &amp;$output anzuhängen. Sie können sehen, wie es die Ebene für jeden Eintrag verfolgt

Eine Demonstration von $depth aus der Methode start_el().

$args

$args sollte vertraut aussehen: Es ist größtenteils das Array von Werten, die ich in unserer Shortcode-Funktion an wp_nav_menu() übergeben habe. Plus die Standardwerte für alle Argumente, die ich weggelassen habe.

var_dump( esc_html( $args ) );

liefert uns

<?php

object(stdClass)#341 (16) {
	["menu"]            => string(13) "a-nested-menu"
	["container"]       => bool(false)
	["container_class"] => string(0) "" 
	["container_id"]    => string(0) "" 
	["menu_class"]      => string(31) "csst_nav csst_nav-a-nested-menu" 
	["menu_id"]         => string(0) "" 
	["echo"]            => bool(false) 
	["fallback_cb"]     => string(12) "wp_page_menu" 
	["before"]          => string(0) "" 
	["after"]           => string(0) "" 
	["link_before"]     => string(0) "" 
	["link_after"]      => string(0) "" 
	["items_wrap"]      => string(46) "%3$s"
	["depth"]           => int(0) 
	["walker"]          => object( CSST_Nav_Walker )#339 (5) {
		["icon"]     => string(96) "
			<svg class='csst_nav_svg-icon'>
				<use xmlns:xlink='http://www.w3.org/1999/xlink' xlink:href='#csst_nav_svg-icon'></use>
			</svg>
		" 
		["tree_type"]=> array(3) {
			[0] => string(9) "post_type" 
			[1] => string(8) "taxonomy"
			[2] => string(6) "custom"
		}
		["db_fields"] => array(2) {
			["parent"] => string(16) "menu_item_parent" 
			["id"]     => string(5) "db_id"
		} 
		["max_pages"]    => int(1) 
		["has_children"] => bool(false)
	}
	["theme_location"] => string(0) ""
}

Erwähnenswert ist das Argument walker. Sie sehen, dass es unseren Walker-Klassenamen angibt und sogar das SVG-Symbol erfasst, das wir als Klassenmitglied gespeichert haben! Die anderen Elemente unter dem Argument walker sind entweder ungenutzt oder für unseren Zweck, ein Navigationsmenü anzupassen, uninteressant.

$id

$id scheint eine große Enttäuschung zu sein. Es ist immer 0. Ich werde es nicht einmal dumpen.

Praktische Anwendungen für die start_el() Args

Beginnen wir damit, was core in Walker_Nav_Menu -> start_el() macht. Wie oben erwähnt, verwenden sie $depth, um Tabs voranzustellen, scheinbar auf der Suche nach besser lesbarem Quellcode. Solche Handwerkskunst! Außerdem, glauben Sie mir, sie greifen sich die CSS-Klassen aus $item.

In meiner benutzerdefinierten Version habe ich zwei Mehrwerte. Erstens habe ich die Möglichkeit, das Menüelement nach meinen eigenen Programmierpräferenzen zu erstellen. Ich hasse zum Beispiel ternäre Operatoren. Zweitens habe ich die Möglichkeit, alle von WordPress für das Menüelement generierten CSS-Klassen zu benennen. current-menu-item würde zu csst_nav-current_menu_item werden. Dies geschieht, indem die CSS-Klassen an eine benutzerdefinierte Methode übergeben werden, die die Klassen umbenennt und zurückgibt. Sie kommen mit dem Präfix für unser Projekt und einer konsistenteren Formatierung bei Dingen wie Bindestrichen und Unterstrichen zurück.

Das war's für start_el()! Ich habe nichts mehr über das öffnende HTML für ein Menüelement zu sagen. Aber jetzt, wo es offen ist, sollten wir es besser schließen.

end_el( &$output, $item, $depth = 0, $args = array() )

Die end_el() ist “>eine sehr kurze Methode: Sie hängt nur das schließende HTML für ein Menüelement an. Sie hat dieselben Argumente wie start_el(), außer $id, das weggelassen wird. Außerdem wird &amp;$output größer sein als bei unserem Treffen in start_el(), da das aktuelle $item angehängt wurde. Diese Argumente werden in meiner Diskussion über start_el() var_dump()ed, daher werde ich sie nicht noch einmal durchgehen.

Was die praktische Anwendung betrifft, ist es interessant festzustellen, dass core einfach ein schließendes li ausgibt. Stattdessen greife ich in $args zurück, um das Element mit dem Markup zu schließen, das ich über das Argument after beim Erstellen unseres Shortcodes angegeben habe.

start_lvl( &$output, $depth = 0, $args = array() )

Der Zweck dieses seltsam benannten Freundes ist es, eine neue "Ebene" in der Struktur zu starten, die wir durchsuchen. Das klingt ziemlich abstrakt, aber glücklicherweise haben wir ein sehr vertrautes Beispiel zur Hand: In einem Navigationsmenü ist eine neue Ebene einfach ein Untermenü!

Diese Methode hat drei Parameter: &$output, $depth und $args, die alle oben var_dump()ed wurden. Was die Verwendung angeht, nutzt core diese Gelegenheit, um eine neue ul für das Untermenü zu öffnen, komplett mit eingerücktem Quellcode. Sehr schön. Allerdings habe ich mich oft unzufrieden mit der Behandlung von Untermenüs gezeigt. Zum Beispiel möchte ich ein Toggle-Icon hinzufügen, um anzuzeigen, dass ein Untermenü vorhanden ist. Ich möchte, dass das Untermenü mein Markup und meine CSS-Klassen verwendet. Und ich möchte, dass das Untermenü als ein- und ausblendbar reagiert, wenn das Toggle geklickt wird. Dies ist der perfekte Zeitpunkt, um diese Anpassungen vorzunehmen.

Gute Zeiten: Unser Untermenü ist geöffnet und Untermenüelemente werden über start_el() und end_el() daran angehängt. Wenn es Untermenüs innerhalb dieses Untermenüelements gibt, kein Problem. Diese werden ebenfalls über start_lvl() angehängt. Sobald all das erledigt ist, müssen wir unser Untermenü schließen.

end_lvl( &$output, $depth = 0, $args = array() )

Diese Methode ist sehr ähnlich zu end_el(), nur dass sie anstatt ein Menüelement zu schließen, ein Untermenü schließt. Für core ist das ein schließendes ul. Für mich ist es ein schließendes span.

Andere Elemente

Meine benutzerdefinierte Walker-Klasse hat einige andere Elemente: einen Konstruktor und ein paar Attribute. Ich verwende den Konstruktor, um meine SVG-Icon-Klasse aufzurufen und ein Toggle-Icon für die Untermenüs abzurufen. Ich speichere das Icon als Attribut der Klasse, damit meine anderen Methoden es einfach verwenden können.

Core's Walker_Nav_Menu Klasse hat ebenfalls einige andere Elemente

  • Ein mysteriöses Attribut namens $tree_type, das selbst core nicht verwendet. Die Quelle dokumentiert es als „Was die Klasse behandelt“, und ein var_dump() liefert
    <?php
    
    	array(3) {
    		[0]=> string(9) "post_type"
    		[1]=> string(8) "taxonomy"
    		[2]=> string(6) "custom"
    	}
    	
    ?>

    Was, naja, egal.

  • Ein Attribut namens $db_fields, das etwas undurchsichtig ist. Ein var_dump() liefert
    <?php
    
    	array(2) {
        
    		["parent"] => string(16) "menu_item_parent"
    		["id"]     => string(5) "db_id"
        
    	}
    
    ?>

    Dem ich nachgebe. Wenn Sie herausfinden können, wie diese verwendet werden und wie wir sie für etwas Interessantes nutzen könnten, hinterlassen Sie es in den Kommentaren!

Ressourcen und nächste Schritte

Walker und seine Erben werden nicht so intensiv diskutiert oder dokumentiert wie andere Teile von WordPress, was einer der Gründe ist, die mich zu diesem Artikel inspiriert haben. Es gibt jedoch einige Vorarbeiten. Ich interessierte mich zum ersten Mal für tiefe Einblicke in Walker, als ich diesen Port eines BootStrap-Navigationsmenüs sah. Und, wie erwartet, gibt der Codex auch ein paar Beispiele.

Die Hauptsache, die ich in diesem Artikel thematisiert habe, war die Kontrolle über meine Klassennamen und das Markup rund um Navigationspunkte und Untermenüs, aber es gibt viele andere Möglichkeiten. Vielleicht könnten wir in $item eintauchen und das Beitragsbild oder Meta-Informationen abrufen, wenn $item auf einen Post verweist. Wenn es auf ein Term-Archiv verweist, möchten wir vielleicht etwas aus dem kommenden term_meta-System abrufen. Sie könnten sogar etwas völlig anderes tun, wie z. B. Menüpunkte mit dem Markup und den Klassen ausgeben, die Ihr bevorzugtes jQueryUI-Widget oder Ihr Bild-Slider erwartet. Probieren Sie es aus und viel Spaß beim var_dump()en!