Parte 6 – WordPress e programmazione orientata agli oggetti: un esempio di WordPress – Implementazione: registrazione delle sezioni

Pubblicato: 2022-02-04

Bentornati alla nostra serie sulla programmazione orientata agli oggetti.

Come abbiamo spiegato nella parte Design della serie, una pagina di amministrazione è composta da sezioni . Ogni sezione contiene uno o più campi e ciascuno di questi campi contiene uno o più elementi .

Come apparirebbe nel codice?

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

Va bene, sembra facile da usare e possiamo già dire che probabilmente dovremo creare tre nuove classi: Section , Field ed Element .

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

Prendiamoci un momento e chiediamoci cosa sappiamo finora di queste classi.

  • $my_section->add_field() → La classe Section dovrebbe essere in grado di aggiungere (e memorizzare) un nuovo oggetto Field
  • $my_field->add_element() → La classe Field dovrebbe essere in grado di aggiungere (e memorizzare) un nuovo oggetto Element .

Iniziamo memorizzando i nostri oggetti Field in un array, come faremmo normalmente:

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

Questa variabile $fields è un membro della classe ed è ciò che chiamiamo una proprietà . Le proprietà sono variabili PHP, che vivono in una classe e possono essere di qualsiasi tipo di dati ( string , integer , object , ecc.).

Scriveremo anche il metodo add_field() per creare e aggiungere un nuovo campo.

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

Questo metodo crea un nuovo oggetto Field , lo aggiunge alla proprietà fields e restituisce l'oggetto appena creato. Abbastanza diretto.

Ripetiamo lo stesso processo anche per la classe Field .

 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; } }

Questo è un inizio! Qual è il prossimo?

La classe di sezione

Dobbiamo chiamare add_settings_section(), quando viene creata una nuova sezione. Ancora una volta, il metodo del costruttore è un ottimo modo per eseguire la nostra inizializzazione. Aggiungiamolo nella classe:

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

Sembra che una sezione abbia bisogno di uno slug-name per identificarla (usato nell'attributo id dei tag). Può anche avere un titolo, una descrizione e appartiene a una pagina specifica.

 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;

Potremmo impostare il titolo della sezione, facendo qualcosa del genere:

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

Beh, non è del tutto corretto. Anche se il codice sopra è perfettamente valido, in realtà non fa ciò che ci aspettiamo che faccia.

Il metodo del costruttore viene eseguito quando viene creato un nuovo oggetto Section. Quindi add_settings_section() verrà chiamato prima ancora che abbiamo la possibilità di impostare il titolo. Di conseguenza, la sezione non avrà un titolo.

Ospita il tuo sito web con Pressidium

GARANZIA DI RIMBORSO DI 60 GIORNI

GUARDA I NOSTRI PIANI

Il titolo deve essere disponibile durante l'inizializzazione del nostro oggetto, quindi dobbiamo farlo nel costruttore.

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

Fai attenzione che $this->title si riferisce alla proprietà della classe title, dove $title si riferisce all'argomento del costruttore.

Qui sfruttiamo anche la visibilità . Poiché la nostra proprietà $title sarà accessibile solo dalla classe che l'ha definita, possiamo dichiararla private . Pertanto, impediamo l'accesso al di fuori della classe.

Oh, e dobbiamo anche aggiungere un metodo print_description() che stamperà la descrizione della sezione.

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

Mettendo tutto insieme, la nostra classe Sezione si presenta così.

 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; } }

La classe sul campo

In modo simile a Section , ora possiamo procedere e costruire la classe Field , che utilizzerà la add_settings_field() di WordPress.

 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 ); } }

Qui, vorremmo anche fornire i valori predefiniti per l'ID, l'etichetta e la descrizione del campo. Possiamo farlo passando un array di opzioni al costruttore e utilizzare la funzione wp_parse_args() di WordPress per analizzare quelle opzioni.

 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 ); } }

La funzione wp_parse_args() ci consentirà di unire i valori definiti dall'utente (l'array $options ) con i valori predefiniti.

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

Dobbiamo anche impostare etichette univoche per ogni campo. Possiamo gestirlo impostando l'etichetta su un prefisso ( 'field_' ) seguito da un numero, che verrà aumentato ogni volta che viene creato un nuovo oggetto Field. Memorizziamo questo numero nella proprietà statica $number_of_fields .

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

È possibile accedere direttamente a una proprietà statica senza dover prima creare un'istanza di una classe.

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

La parola chiave self è usata per fare riferimento alla classe corrente e, con l'aiuto dell'operatore di risoluzione scope :: (comunemente chiamato "doppio due punti"), possiamo accedere alla nostra proprietà statica.

In questo modo, nel costruttore, accediamo sempre alla stessa proprietà $number_of_fields , aumentandone il valore ogni volta che viene creato un oggetto, il che si traduce in un'etichetta univoca allegata a ciascun campo.

Andando avanti, il metodo render() , dopo aver stampato la descrizione (se presente), scorre tutti gli elementi e ne esegue il rendering.

 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(); } }

Mettere tutto insieme…

 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(); } } }

La classe degli elementi

Andando avanti, costruiremo la classe Element in modo simile!

Inizieremo a scrivere la classe in questo modo:

 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 } }

Assicurati di eseguire l'escape del tuo output, come stiamo facendo qui, utilizzando le funzioni esc_attr() ed esc_html() di WordPress, per prevenire qualsiasi attacco di scripting tra siti. Anche se stiamo eseguendo il rendering dei nostri elementi solo nelle pagine di amministrazione, è comunque una buona idea sfuggire sempre a qualsiasi dato di output.

NOTA: il cross-site scripting (o XSS) è un tipo di vulnerabilità di sicurezza che si trova in genere nelle applicazioni web. XSS consente agli aggressori di iniettare codice lato client nelle pagine Web visualizzate da altri utenti. Una vulnerabilità di cross-site scripting può essere utilizzata dagli aggressori per aggirare i controlli di accesso come il criterio della stessa origine.

Durante la raccolta dei requisiti del plug-in, abbiamo notato che esistono più tipi di elementi: caselle di controllo, pulsanti di opzione, campi numerici, ecc. Quando abbiamo ideato il nostro progetto, abbiamo deciso di creare una classe Element destinata ad essere estesa. Quindi, sappiamo che finiremo con una classe figlia per ogni tipo di elemento.

L'output dovrebbe differire a seconda del tipo di elemento, quindi trasformeremo render() in un metodo astratto. Ciò significa, ovviamente, che anche la classe stessa dovrebbe essere astratta.

 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(); }

Ad esempio, una classe Number_Element sarebbe simile a questa:

 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 } }

Allo stesso modo, possiamo costruire una classe Checkbox_Element , Radio_Element e persino Custom_Element per il resto dei nostri elementi.

Nota che stiamo costruendo le nostre classi in modo che possano essere usate tutte allo stesso modo . Chiamare il metodo render() su qualsiasi figlio di Element produrrà del codice HTML.

Questo è un esempio di polimorfismo , uno dei concetti fondamentali della programmazione orientata agli oggetti.

Polimorfismo

“Polimorfismo” significa letteralmente “molte forme” (dal greco “poly” che significa “molte”, e “morphe” che significa “forma”). Una classe figlia Element può avere molte forme , poiché può assumere qualsiasi forma di classe nella sua gerarchia padre.

Possiamo usare un Number_Element , un Checkbox_Element , o qualsiasi altro sottotipo in qualsiasi posto ci si aspetta un oggetto Element , poiché tutti gli oggetti figli possono essere usati esattamente allo stesso modo (cioè chiamando il loro metodo render() ), pur essendo in grado di comportarsi in modo diverso (l'output sarà diverso per ogni tipo di elemento).

Come probabilmente puoi dire, polimorfismo ed ereditarietà sono concetti strettamente correlati.

sostituibilità

Il principio di sostituzione di Liskov (o LSP) , la "L" in SOLID, afferma:

"In un programma per computer, se S è un sottotipo di T, allora gli oggetti di tipo T possono essere sostituiti con oggetti di tipo S (cioè, un oggetto di tipo T può essere sostituito con qualsiasi oggetto di un sottotipo S) senza alterare nessuno dei le proprietà desiderabili del programma”.

In parole povere, dovresti essere in grado di utilizzare qualsiasi classe figlia al posto della sua classe madre senza alcun comportamento imprevisto.

Fabbriche

Torniamo alla nostra classe Field , dove attualmente abbiamo un metodo create_element() che crea un nuovo Element .

 /** * 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; }

Un metodo che restituisce un nuovo oggetto è spesso chiamato factory semplice (da non confondere con il “metodo factory”, che è un design pattern).

Sapendo che qualsiasi sottotipo è utilizzabile al posto della classe genitore Element , andremo avanti e modificheremo questa factory, così sarà in grado di creare oggetti di qualsiasi classe figlia.

 /** * 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 } }

Iniziamo anteponendo al tipo di elemento il nome corrente:

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

La costante magic __NAMESPACE__ contiene il nome dello spazio dei nomi corrente.

Quindi, ci assicuriamo che ci sia una classe per il tipo di elemento specificato:

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

Successivamente, creiamo un nuovo oggetto:

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

Infine, ci assicuriamo che l'oggetto appena creato sia effettivamente un'istanza di Element:

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

Estendere

Vale la pena sottolineare che abbiamo creato il nostro plugin per essere estensibile. Aggiungere diversi tipi di pagine, sezioni, elementi è facile come creare una nuova classe che estenda Admin_Page , Section , Element ecc. Queste classi base non includono alcun codice che deve essere modificato per aggiungere una nuova pagina, sezione o elemento.

Il principio di apertura/chiusura (o OCP), la "O" in SOLID, afferma:

"Le entità software (classi, moduli, funzioni, ecc.) dovrebbero essere aperte per l'estensione, ma chiuse per la modifica."

Ciò significa che dovremmo essere in grado di estendere una classe come Admin_Page e riutilizzarla, ma non dovremmo modificarla per farlo.

Conclusione

In questo articolo, abbiamo registrato le nostre sezioni, campi ed elementi. Durante l'implementazione, abbiamo esaminato più da vicino cos'è il polimorfismo e perché è utile. Abbiamo anche esaminato un paio di principi SOLID, il "Principio di sostituzione di Liskov" e il "Principio di apertura/chiusura".

Resta con noi per la prossima parte di questo viaggio, dove daremo un'occhiata più da vicino a come possiamo migliorare il modo in cui gestiamo i nostri hook di WordPress.

Fare clic qui per leggere la parte 7 della nostra serie di programmazione orientata agli oggetti

Guarda anche

  • WordPress e programmazione orientata agli oggetti: una panoramica
  • Parte 2 – WordPress e programmazione orientata agli oggetti: un esempio del mondo reale
  • Parte 3 – WordPress e programmazione orientata agli oggetti: Α Esempio di WordPress – Definizione dell'ambito
  • Parte 4 – WordPress e programmazione orientata agli oggetti: un esempio di WordPress – Design
  • Parte 5 – WordPress e la programmazione orientata agli oggetti: un esempio di WordPress – Implementazione: il menu di amministrazione