Teil 6 – WordPress und objektorientierte Programmierung: Ein WordPress-Beispiel – Implementierung: Registrieren der Abschnitte

Veröffentlicht: 2022-02-04

Willkommen zurück zu unserer Serie über objektorientierte Programmierung.

Wie wir im Design-Teil der Serie erklärt haben, besteht eine Admin-Seite aus Abschnitten . Jeder Abschnitt enthält ein oder mehrere Felder , und jedes dieser Felder enthält ein oder mehrere Elemente .

Wie würde das im Code aussehen?

 public function register_sections() { $my_section = $this->register_section( /* ... */ ); $my_field = $my_section->add_field( /* ... */ ); $my_element = $my_field->add_element( /* ... */ ); }

Okay, das scheint einfach zu verwenden und wir können bereits sagen, dass wir wahrscheinlich drei neue Klassen erstellen müssen: Section , Field und Element .

 class Section {}
 class Field {}
 class Element {}

Nehmen wir uns einen Moment Zeit und fragen uns, was wir bisher über diese Klassen wissen.

  • $my_section->add_field() → Die Section -Klasse sollte in der Lage sein, ein neues Field Objekt hinzuzufügen (und zu speichern).
  • $my_field->add_element() → Die Field Klasse sollte in der Lage sein, ein neues Element -Objekt hinzuzufügen (und zu speichern).

Wir beginnen damit, unsere Field-Objekte in einem Array zu speichern, wie wir es normalerweise tun würden:

 class Section { /** * @var Field[] Section field objects. */ protected $fields = array();

Diese $fields Variable ist ein Klassenmitglied und wird von uns als Eigenschaft bezeichnet. Eigenschaften sind PHP-Variablen, die in einer Klasse leben, und sie können jeden Datentyp haben ( string , integer , object usw.).

Wir werden auch die Methode add_field() schreiben, um ein neues Feld zu erstellen und hinzuzufügen.

 public function add_field() { $field = new Field( /* ... */ ); $this->fields[] = $field; return $field; }

Diese Methode erstellt ein neues Field Objekt, fügt es der Eigenschaft fields hinzu und gibt dieses neu erstellte Objekt zurück. Ziemlich einfach.

Lassen Sie uns den gleichen Vorgang auch für die Field Klasse wiederholen.

 class Field { /** * @var Element[] Field elements. */ private $elements = array(); /** * Create a new element object. * * @return Element */ private function create_element() { return new Element( /* ... */ ); } /** * Add a new element object to this field. */ public function add_element() { $element = $this->create_element(); $this->elements[] = $element; } }

Das ist ein Anfang! Was kommt als nächstes?

Die Sektionsklasse

Wir müssen add_settings_section() aufrufen, wenn ein neuer Abschnitt erstellt wird. Auch hier ist die Konstruktormethode eine großartige Möglichkeit, unsere Initialisierung durchzuführen. Fügen wir es in der Klasse hinzu:

 class Section { // ... public function __construct() { add_settings_section( $this->id, $this->title, array( $this, 'print_description' ), $this->page ); } }

Es scheint, dass ein Abschnitt einen Slug-Namen benötigt, um ihn zu identifizieren (wird im id-Attribut von Tags verwendet). Sie kann auch einen Titel und eine Beschreibung haben und zu einer bestimmten Seite gehören.

 class Section { /** * @var Field[] Section field objects. */ protected $fields = array(); /** * @var string Section title. */ public $title; /** * @var string Section id. */ public $id; /** * @var string Slug-name of the settings page this section belongs to. */ public $page; /** * @var string Section description. */ public $description;

Wir könnten den Titel des Abschnitts festlegen, indem wir etwa so vorgehen:

 $section = new Section(); $section->title = __( 'Hello world', 'prsdm-limit-login-attempts' );

Nun, das ist nicht ganz richtig. Obwohl der obige Code vollkommen gültig ist, tut er nicht das, was wir von ihm erwarten.

Die Konstruktormethode wird ausgeführt, wenn ein neues Section-Objekt erstellt wird. Daher wird add_settings_section() aufgerufen, bevor wir überhaupt die Möglichkeit haben, den Titel festzulegen. Daher hat der Abschnitt keinen Titel.

Hosten Sie Ihre Website mit Pressidium

60- TÄGIGE GELD-ZURÜCK-GARANTIE

SEHEN SIE UNSERE PLÄNE

Der Titel muss während der Initialisierung unseres Objekts verfügbar sein, also müssen wir dies im Konstruktor tun.

 class Section { /** * @var string Section title. */ private $title; public function __construct( $title ) { $this->title = $title; // ... } // ..

Beachten Sie, dass sich $this->title auf die Klasseneigenschaft title bezieht, wobei sich $title auf das Argument des Konstruktors bezieht.

Auch hier nutzen wir die Sichtbarkeit . Da auf unsere Eigenschaft $title nur die Klasse zugreift, die sie definiert hat, können wir sie als private deklarieren. Daher verhindern wir, dass außerhalb der Klasse darauf zugegriffen wird.

Oh, und wir müssen auch eine Methode print_description() hinzufügen, die, nun ja, die Beschreibung des Abschnitts ausdruckt.

 /** * Print the section description. */ public function print_description() { echo esc_html( $this->description ); }

Alles zusammengenommen sieht unsere Section-Klasse so aus.

 class Section { /** * @var Field[] Section field objects. */ protected $fields = array(); /** * @var string Section title. */ private $title; /** * @var string Section id. */ private $id; /** * @var string Slug-name of the settings page this section belongs to. */ private $page; /** * @var string Section description. */ private $description; /** * Section constructor. * * @param string $id Section id. * @param string $title Section title. * @param string $page Slug-name of the settings page. * @param string $description Section description. */ public function __construct( $id, $title, $page, $description ) { $this->id = $id; $this->title = $title; $this->page = $page; $this->description = $description; add_settings_section( $this->id, $this->title, array( $this, 'print_description' ), $this->page ); } /** * Print the section description. */ public function print_description() { echo esc_html( $this->description ); } /** * Create and add a new field object to this section. */ public function add_field() { $field = new Field( /* ... */ ); $this->fields[] = $field; return $field; } }

Die Feldklasse

Ähnlich wie bei Section können wir nun fortfahren und die Klasse Field erstellen, die die WordPress-Funktion add_settings_field() verwenden wird.

 class Field { /** * @var Element[] Field elements. */ private $elements = array(); /** * @var string ID of the section this field belongs to. */ private $section_id; /** * @var string Field description. */ private $description; /** * Field constructor. * * @param string $id Field ID. * @param string $label Field label. * @param string $page Slug-name of the settings page. * @param string $section_id ID of the section this field belongs to. * @param string $description Field description. */ public function __construct( $id, $label, $page, $section_id, $description ) { $this->section_id = $section_id; $this->description = $description; add_settings_field( $id, $label, array( $this, 'render' ), $page, $this->section_id ); } }

Hier möchten wir auch Standardwerte für die ID, Bezeichnung und Beschreibung des Felds bereitstellen. Wir können dies tun, indem wir ein Optionsarray an den Konstruktor übergeben und die WordPress-Funktion wp_parse_args() verwenden, um diese Optionen zu analysieren.

 class Field { /** * @var int Number of fields instantiated. */ private static $number_of_fields = 0; // ... /** * Field constructor. * * @param string $section_id ID of the section this field belongs to. * @param string $page Slug-name of the settings page. * @param array $options Options. */ public function __construct( $section_id, $page, $options = array() ) { self::$number_of_fields++; $options = wp_parse_args( $options, array( 'label' => sprintf( __( 'Field #%s', 'prsdm-limit-login-attempts' ), self::$number_of_fields 'id' => 'field_' . self::$number_of_fields, 'description' => '' ) ); $this->section_id = $section_id; $this->description = $options['description']; add_settings_field( $options['id'], $options['label'], array( $this, 'render' ), $page, $this->section_id ); } }

Die Funktion wp_parse_args() ermöglicht es uns, die benutzerdefinierten Werte (das $options Array) mit den Standardwerten zusammenzuführen.

 array( 'label' => sprintf( __( 'Field #%s', 'prsdm-limit-login-attempts' ), self::$number_of_fields 'id' => 'field_' . self::$number_of_fields, 'description' => '' )

Wir müssen auch eindeutige Labels für jedes Feld festlegen. Wir können dies handhaben, indem wir das Label auf ein Präfix ( 'field_' ) gefolgt von einer Zahl setzen, die jedes Mal erhöht wird, wenn ein neues Field-Objekt erstellt wird. Wir speichern diese Zahl in der statischen Eigenschaft $number_of_fields .

 /** * @var int Number of fields instantiated. */ private static $number_of_fields = 0;

Auf eine statische Eigenschaft kann direkt zugegriffen werden, ohne dass zuerst eine Instanz einer Klasse erstellt werden muss.

 'id' => 'field_' . self::$number_of_fields

Das Schlüsselwort self wird verwendet, um auf die aktuelle Klasse zu verweisen, und mit Hilfe des Bereichsauflösungsoperators :: (allgemein als „doppelter Doppelpunkt“ bezeichnet) können wir auf unsere statische Eigenschaft zugreifen.

Auf diese Weise greifen wir im Konstruktor immer auf dieselbe $number_of_fields Eigenschaft zu und erhöhen ihren Wert jedes Mal, wenn ein Objekt erstellt wird, was zu einer eindeutigen Bezeichnung führt, die jedem Feld zugeordnet ist.

In Zukunft iteriert die Methode render() nach dem Drucken der Beschreibung (falls vorhanden) durch alle Elemente und rendert jedes einzelne von ihnen.

 public function render() { if ( ! empty( $this->description ) ) { printf( '<p class="description">%s</p>', esc_html( $this->description ) ); } foreach ( $this->elements as $key => $element ) { $element->render(); } }

Alles zusammenfügen…

 class Field { /** * @var int Number of fields instantiated. */ private static $number_of_fields = 0; /** * @var Element[] Field elements. */ private $elements = array(); /** * @var string ID of the section this field belongs to. */ private $section_id; /** * @var string Field description. */ private $description; /** * Field constructor. * * @param string $section_id ID of the section this field belongs to. * @param string $page Slug-name of the settings page. * @param array $options Options. */ public function __construct( $section_id, $page, $options = array() ) { self::$number_of_fields++; $options = wp_parse_args( $options, array( 'label' => sprintf( /* translators: %s is the unique s/n of the field. */ __( 'Field #%s', 'prsdm-limit-login-attempts' ), self::$number_of_fields 'id' => 'field_' . self::$number_of_fields, 'description' => '' ) ); $this->section_id = $section_id; $this->description = $options['description']; add_settings_field( $options['id'], $options['label'], array( $this, 'render' ), $page, $this->section_id ); } /** * Create a new element object. * * @return Element */ private function create_element() { return new Element( /* ... */ ); } /** * Add a new element object to this field. */ public function add_element() { $element = $this->create_element(); $this->elements[] = $element; } /** * Render the field. */ public function render() { if ( ! empty( $this->description ) ) { printf( '<p class="description">%s</p>', esc_html( $this->description ) ); } foreach ( $this->elements as $key => $element ) { $element->render(); } } }

Die Elementklasse

In Zukunft werden wir die Element -Klasse auf ähnliche Weise erstellen!

Wir beginnen die Klasse wie folgt zu schreiben:

 class Element { /** * @var int Number of elements instantiated. */ private static $number_of_elements = 0; /** * @var string Element label. */ private $label; /** * @var string Element name. */ private $name; /** * @var mixed Element value. */ private $value; /** * Element constructor. * * @param string $section_id Section ID. * @param array $options Options. */ public function __construct( $section_id, $options = array() ) { self::$number_of_elements++; $options = wp_parse_args( $options, array( 'label' => sprintf( /* translators: %s is the unique s/n of the element. */ __( 'Element #%s', 'prsdm-limit-login-attempts' ), self::$number_of_elements ), 'name' => 'element_' . self::$number_of_elements ) ); $this->label = $options['label']; $this->name = $options['name']; $this->value = ''; } /** * Render the element. */ public function render() { ?> <fieldset> <label> <input type="number" name="<?php echo esc_attr( $this->name ); ?>" value="<?php echo esc_attr( $this->value ); ?>" /> <?php echo esc_html(); ?> </label> </fieldset> <?php } }

Stellen Sie sicher, dass Sie Ihre Ausgabe maskieren – wie wir es hier tun, indem Sie die WordPress-Funktionen esc_attr() und esc_html() verwenden –, um Cross-Site-Scripting-Angriffe zu verhindern. Auch wenn wir unsere Elemente nur auf Admin-Seiten rendern, ist es dennoch eine gute Idee, Ausgabedaten immer mit Escapezeichen zu versehen.

HINWEIS: Cross-Site-Scripting (oder XSS) ist eine Art von Sicherheitslücke, die typischerweise in Webanwendungen zu finden ist. XSS ermöglicht es Angreifern, clientseitigen Code in Webseiten einzuschleusen, die von anderen Benutzern angezeigt werden. Eine Cross-Site-Scripting-Schwachstelle kann von Angreifern genutzt werden, um Zugriffskontrollen wie die Same-Origin-Policy zu umgehen.

Als wir die Anforderungen des Plugins erfassten, stellten wir fest, dass es mehrere Elementtypen gibt – Kontrollkästchen, Optionsfelder, Zahlenfelder usw. Als wir unser Design entwickelten, trafen wir die Entscheidung, eine Element zu erstellen, die erweitert werden soll. Wir wissen also, dass wir am Ende eine untergeordnete Klasse für jeden Elementtyp haben werden.

Die Ausgabe sollte sich je nach Elementtyp unterscheiden, also verwandeln wir render() in eine abstrakte Methode. Das bedeutet natürlich, dass die Klasse selbst auch abstrakt sein sollte.

 abstract class Element { /** * @var int Number of elements instantiated. */ private static $number_of_elements = 0; /** * @var string Element label. */ protected $label; /** * @var string Element name. */ protected $name; /** * @var mixed Element value. */ protected $value; /** * Element constructor. * * @param string $section_id Section ID. * @param array $options Options. */ public function __construct( $section_id, $options = array() ) { self::$number_of_elements++; $options = wp_parse_args( $options, array( 'label' => sprintf( /* translators: %s is the unique s/n of the element. */ __( 'Element #%s', 'prsdm-limit-login-attempts' ), self::$number_of_elements ), 'name' => 'element_' . self::$number_of_elements ) ); $this->label = $options['label']; $this->name = $options['name']; $this->value = ''; } /** * Render the element. */ abstract public function render(); }

Eine Number_Element -Klasse würde beispielsweise so aussehen:

 class Number_Element extends Element { /** * Render the element. */ public function render() { ?> <fieldset> <label> <input type="number" name="<?php echo esc_attr( $this->name ); ?>" value="<?php echo esc_attr( $this->value ); ?>" /> <?php echo esc_html(); ?> </label> </fieldset> <?php } }

Auf ähnliche Weise können wir eine Checkbox_Element , eine Radio_Element und sogar eine Custom_Element Klasse für den Rest unserer Elemente erstellen.

Beachten Sie, dass wir unsere Klassen so aufbauen, dass sie alle auf die gleiche Weise verwendet werden können . Das Aufrufen der render() Methode für ein untergeordnetes Element von Element gibt etwas HTML aus.

Das ist ein Beispiel für Polymorphismus , eines der Kernkonzepte der objektorientierten Programmierung.

Polymorphismus

„Polymorphismus“ bedeutet wörtlich „viele Formen“ (von den griechischen Wörtern „poly“ bedeutet „viele“ und „morphe“ bedeutet „Form“). Eine Element-Kindklasse kann viele Formen haben, da sie jede Form einer Klasse in ihrer Elternhierarchie annehmen kann.

Wir können ein Number_Element , ein Checkbox_Element oder jeden anderen Untertyp an jeder Stelle verwenden, an der ein Element -Objekt erwartet wird, da alle untergeordneten Objekte auf genau die gleiche Weise verwendet werden können (dh ihre render() -Methode aufrufen), sich aber dennoch verhalten können anders (die Ausgabe wird für jeden Elementtyp unterschiedlich sein).

Wie Sie wahrscheinlich erkennen können, sind Polymorphismus und Vererbung eng verwandte Konzepte.

Substituierbarkeit

Das Liskov-Substitutionsprinzip (oder LSP) , das „L“ in SOLID, besagt:

„In einem Computerprogramm, wenn S ein Untertyp von T ist, dann können Objekte des Typs T durch Objekte des Typs S ersetzt werden (dh ein Objekt des Typs T kann durch jedes Objekt eines Untertyps S ersetzt werden), ohne eines von zu ändern die wünschenswerten Eigenschaften des Programms.“

Laienhaft ausgedrückt sollten Sie in der Lage sein, jede untergeordnete Klasse ohne unerwartetes Verhalten anstelle ihrer übergeordneten Klasse zu verwenden.

Fabriken

Kehren wir zu unserer Klasse Field zurück, wo wir derzeit eine Methode create_element() haben, die ein neues Element erstellt.

 /** * Create a new element object. * * @return Element */ private function create_element() { return new Element( /* ... */ ); } /** * Add a new element object to this field. */ public function add_element() { $element = $this->create_element(); $this->elements[] = $element; }

Eine Methode, die ein neues Objekt zurückgibt, wird oft als einfache Factory bezeichnet (nicht zu verwechseln mit „Factory-Methode“, die ein Entwurfsmuster ist).

Da wir wissen, dass jeder Untertyp anstelle der übergeordneten Klasse Element verwendet werden kann, werden wir diese Factory so modifizieren, dass sie Objekte jeder untergeordneten Klasse erstellen kann.

 /** * Create a new element object. * * @throws Exception If there are no classes for the given element type. * @throws Exception If the given element type is not an `Element`. * * @param string $element_type * @param array $options * * @return Element */ private function create_element( $element_type, $options ) { $element_type = __NAMESPACE__ . '\\Elements\\' . $element_type; if ( ! class_exists( $element_type ) ) { throw new Exception( 'No class exists for the specified type' ); } $element = new $element_type( $this->section_id, $options ); if ( ! ( $element instanceof Element ) ) { throw new Exception( 'The specified type is invalid' ); } return $element; } /** * Add a new element object to this field. * * @param string $element_type * @param array $options */ public function add_element( $element_type, $options ) { try { $element = $this->create_element( $element_type, $options ); $this->elements[] = $element; } catch ( Exception $e ) { // Handle the exception } }

Wir beginnen damit, dem Elementtyp den aktuellen Namen voranzustellen:

 $element_type = __NAMESPACE__ . '\\Elements\\' . $element_type;

Die magische Konstante __NAMESPACE__ enthält den aktuellen Namespace-Namen.

Dann stellen wir sicher, dass es eine Klasse für den angegebenen Elementtyp gibt:

 if ( ! class_exists( $element_type ) ) { throw new Exception( 'No class exists for the specified type' ); }

Als nächstes erstellen wir ein neues Objekt:

 $element = new $element_type( $this->section_id, $options );

Und schließlich stellen wir sicher, dass das neu erstellte Objekt tatsächlich eine Instanz von Element ist:

 if ( ! ( $element instanceof Element ) ) { return; }

Verlängerung

Es ist erwähnenswert, dass wir unser Plugin so aufgebaut haben, dass es erweiterbar ist. Das Hinzufügen verschiedener Arten von Seiten, Abschnitten und Elementen ist so einfach wie das Erstellen einer neuen Klasse, die Admin_Page , Section , Element usw. erweitert. Diese Basisklassen enthalten keinen Code, der geändert werden muss, um eine neue Seite, einen neuen Abschnitt oder ein neues Element hinzuzufügen.

Das Open/Closed-Prinzip (oder OCP), das „O“ in SOLID, besagt:

„Softwareentitäten (Klassen, Module, Funktionen usw.) sollten für Erweiterungen offen, aber für Änderungen geschlossen sein.“

Das bedeutet, dass wir in der Lage sein sollten, eine Klasse wie Admin_Page zu erweitern und wiederzuverwenden, aber wir sollten sie dafür nicht ändern müssen.

Fazit

In diesem Artikel haben wir unsere Abschnitte, Felder und Elemente registriert. Bei der Implementierung haben wir uns genauer angesehen, was Polymorphismus ist und warum er nützlich ist. Wir haben uns auch einige SOLID-Prinzipien angesehen, das „Liskov-Substitutionsprinzip“ und das „Offen/Geschlossen-Prinzip“.

Bleiben Sie für den nächsten Teil dieser Reise bei uns, wo wir uns genauer ansehen werden, wie wir die Art und Weise verbessern können, wie wir unsere WordPress-Hooks verwalten.

Klicken Sie hier, um Teil 7 unserer Serie zur objektorientierten Programmierung zu lesen

Siehe auch

  • WordPress und objektorientierte Programmierung – Ein Überblick
  • Teil 2 – WordPress und objektorientierte Programmierung: Ein Beispiel aus der Praxis
  • Teil 3 – WordPress und objektorientierte Programmierung: Α WordPress-Beispiel – Definition des Geltungsbereichs
  • Teil 4 – WordPress und objektorientierte Programmierung: ein WordPress-Beispiel – Design
  • Teil 5 – WordPress und objektorientierte Programmierung: ein WordPress-Beispiel – Implementierung: Das Verwaltungsmenü