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
- Eine Hauptplugin-Datei zur Registrierung des Plugins und zum Aufruf anderer Dateien
- Ein Shortcode zur Ausgabe des Menüs
- Etwas CSS, JS & SVG, um Dinge wie das Ein-/Ausblenden von Untermenüs zu erledigen
- Eine benutzerdefinierte Walker-Klasse, die die Core-Klasse
Walker_Nav_Menuerweitert
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.

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']

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
- 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*. - Core definiert dann eine spezifischere Erweiterung von
Walker, die speziell für die Navigation durch Menüs entwickelt wurde:Walker_Nav_Menu. - Schließlich definiere ich meine eigene Erweiterung von
Walker_Nav_Menuund nenne sieCSST_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
&$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.$item, das ist das WP Post-Objekt für dieses Menüelement (Menüelemente sind tatsächlich Posts vom Post-Typnav_menu_item), plus einige zusätzliche Daten, die spezifisch für Navigationsmenüs sind.$depth, die verfolgt, wie viele Ebenen tief wir im Menü sind – also bei verschachtelten Untermenüs.$args, das ist hauptsächlich ein Array von Argumenten fürwp_nav_menu(). Es enthält die Argumente, die wir in unserem Shortcode-Callback übergeben haben, plus alle Standardwerte, die wir weggelassen haben.$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) "
"
string(1066) "
“
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 &$output anzuhängen. Sie können sehen, wie es die Ebene für jeden Eintrag verfolgt

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 &$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 einvar_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. Einvar_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!
WordPress's Nav-Walker ist ein Durcheinander. Warum nicht einfach benutzerdefiniertes HTML ohne ihn ausgeben?
Hier ist die Hilfsfunktion
getItems()Die Elemente sind nur WordPress-Posts, und sie enthalten alles, was Sie brauchen könnten.
Danke für den Kommentar!
Ich mag Ihren Ansatz sehr und er erscheint mir völlig vernünftig. Einige Dinge, die man bedenken sollte und die für Ihre spezielle Situation vielleicht überzeugend sind oder auch nicht
1) Ich denke, für Menüs, die keine Untermenüs haben, haben Sie einen guten Punkt. Für Menüs, die Untermenüs haben, bin ich mir nicht sicher, wie Sie das berücksichtigen.
2) Sie verpassen es, Ihre Navigationsmenüpunkte für einige Core-Filter verfügbar zu machen.
3) Ihre Implementierung erfordert die Angabe einer Menüposition, im Gegensatz zur Möglichkeit, einen Menü-Slug, eine ID oder einen Namen zu übergeben.
4) Ihre Implementierung erfordert, dass der Benutzer das Menü jedes Mal verpackt, wenn er die Hilfsfunktion verwendet, anstatt einen Standard-Wrapper annehmen zu können.
Faire Punkte. Lassen Sie mich einige davon ansprechen
1) Die meisten Websites verschachteln typischerweise nicht mehr als ein oder zwei Ebenen tief, was innerhalb derselben Ansicht sauber bleibt. Wenn Sie mehr als zwei Ebenen tief verschachteln müssen, können Sie das Menü in eine rekursive Ansicht packen. Sie benötigen auch eine Schleife, um ein verschachteltes Array aus dem flachen Menüelement-Array zu erstellen.
3)
wp_get_nav_menu_object()nimmt die Menü-ID, den Namen oder den Slug. Sie müssen lediglich diese Standortsuche überspringen.#2 und #4 sind absolut richtig.