Partea 6 – WordPress și programare orientată pe obiecte: un exemplu WordPress – Implementare: înregistrarea secțiunilor

Publicat: 2022-02-04

Bine ați revenit la seria noastră de programare orientată pe obiecte.

După cum am explicat în partea Design a seriei, o pagină de administrare este formată din secțiuni . Fiecare secțiune conține unul sau mai multe câmpuri și fiecare dintre acele câmpuri conține unul sau mai multe elemente .

Cum ar arăta asta în cod?

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

Bine, pare ușor de utilizat și putem deja să spunem că probabil va trebui să creăm trei clase noi: Section , Field și Element .

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

Să luăm un moment și să ne întrebăm ce știm până acum despre aceste cursuri.

  • $my_section->add_field() → Clasa Section ar trebui să poată adăuga (și stoca) un nou obiect Field
  • $my_field->add_element() → Clasa Field ar trebui să poată adăuga (și stoca) un nou obiect Element .

Începem prin a stoca obiectele Field într-o matrice, așa cum am face în mod normal:

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

Această variabilă $fields este un membru al clasei și este ceea ce numim o proprietate . Proprietățile sunt variabile PHP, care trăiesc într-o clasă și pot fi de orice tip de date ( string , integer , object etc.).

Vom scrie și metoda add_field() pentru a crea și adăuga un câmp nou.

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

Această metodă creează un nou obiect Field , îl adaugă la proprietatea fields și returnează acel obiect nou creat. Destul de direct.

Să repetăm ​​același proces și pentru clasa 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; } }

Acesta este un început! Ce urmeaza?

Clasa de secție

Trebuie să apelăm add_settings_section(), când este creată o nouă secțiune. Încă o dată, metoda constructorului este o modalitate excelentă de a efectua inițializarea noastră. Să-l adăugăm în clasă:

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

Se pare că o Secțiune are nevoie de un nume slug pentru a o identifica (utilizat în atributul id al etichetelor). De asemenea, poate avea un titlu, o descriere și aparține unei anumite pagini.

 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;

Am putea seta titlul secțiunii, făcând ceva de genul acesta:

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

Ei bine, nu este chiar corect. Chiar dacă codul de mai sus este perfect valid, de fapt nu face ceea ce ne așteptăm să facă.

Metoda constructorului este executată atunci când este creat un nou obiect Section. Așadar, add_settings_section() va fi apelat chiar înainte de a avea șansa de a seta titlul. Ca urmare, secțiunea nu va avea un titlu.

Găzduiește-ți site-ul web cu Pressidium

GARANTIE 60 DE ZILE BANI RAPIS

VEZI PLANUL NOSTRU

Titlul trebuie să fie disponibil în timpul inițializării obiectului nostru, așa că trebuie să facem acest lucru în constructor.

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

Atenție că $this->title se referă la proprietatea clasei titlu, unde $title se referă la argumentul constructorului.

Aici, profităm și de vizibilitate . Deoarece proprietatea noastră $title va fi accesată doar de clasa care a definit-o, o putem declara private . Prin urmare, împiedicăm accesarea acestuia în afara clasei.

Ah, și trebuie să adăugăm și o metodă print_description() care va imprima, ei bine, descrierea secțiunii.

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

Punând toate împreună, clasa noastră Secțiune arată astfel.

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

Clasa de câmp

Într-un mod similar cu Section , acum putem continua și construi clasa Field , care va utiliza funcția add_settings_field() 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 ); } }

Aici, am dori, de asemenea, să furnizăm valori implicite pentru ID-ul, eticheta și descrierea câmpului. Putem face acest lucru prin transmiterea unui tablou de opțiuni către constructor și folosind funcția WordPress wp_parse_args() pentru a analiza acele opțiuni.

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

Funcția wp_parse_args() ne va permite să îmbinăm valorile definite de utilizator (matricea $options ) cu valorile implicite.

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

De asemenea, trebuie să setăm etichete unice pentru fiecare câmp. Ne putem ocupa de acest lucru setând eticheta la un prefix ( 'field_' ) urmat de un număr, care va fi mărit de fiecare dată când este creat un nou obiect Field. Vom stoca acest număr în proprietatea statică $number_of_fields .

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

O proprietate statică poate fi accesată direct fără a fi nevoie să creați mai întâi o instanță a unei clase.

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

Cuvântul cheie self este folosit pentru a face referire la clasa curentă și, cu ajutorul operatorului de rezoluție a domeniului :: (numit în mod obișnuit „duble două puncte”), putem accesa proprietatea noastră statică.

Astfel, în constructor, accesăm întotdeauna aceeași proprietate $number_of_fields , crescând valoarea acesteia de fiecare dată când este creat un obiect, ceea ce are ca rezultat o etichetă unică atașată fiecărui câmp.

Mergând mai departe, metoda render() , după tipărirea descrierii (dacă există una), iterează prin toate elementele și redă fiecare dintre ele.

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

Punând totul împreună...

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

Clasa Elementului

În continuare, vom construi clasa Element într-un mod similar!

Vom începe să scriem clasa astfel:

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

Asigurați-vă că scăpați de rezultatul dvs. - așa cum facem noi aici, folosind funcțiile WordPress esc_attr() și esc_html() - pentru a preveni orice atacuri de scripturi între site-uri. Chiar dacă redăm elementele noastre doar în paginile de administrare, este totuși o idee bună să scăpăm întotdeauna de orice date de ieșire.

NOTĂ: Cross-site scripting (sau XSS) este un tip de vulnerabilitate de securitate întâlnită de obicei în aplicațiile web. XSS le permite atacatorilor să injecteze cod la nivelul clientului în paginile web vizualizate de alți utilizatori. O vulnerabilitate de scripting între site-uri poate fi folosită de atacatori pentru a ocoli controalele de acces, cum ar fi politica de aceeași origine.

Când am adunat cerințele pluginului, am observat că există mai multe tipuri de elemente - casete de selectare, butoane radio, câmpuri numerice etc. Când am venit cu designul nostru, am luat decizia de a construi o clasă Element menită să fie extinsă. Deci, știm că vom ajunge cu o clasă copil pentru fiecare tip de element.

Ieșirea ar trebui să difere în funcție de tipul elementului, așa că vom transforma render() într-o metodă abstractă. Asta înseamnă, desigur, că clasa în sine ar trebui să fie și abstractă.

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

De exemplu, o clasă Number_Element ar arăta astfel:

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

În mod similar, putem construi un Checkbox_Element , un Radio_Element și chiar o clasă Custom_Element pentru restul elementelor noastre.

Observați că ne construim clasele astfel încât să poată fi folosite toate în același mod . Apelarea metodei render() pe orice copil al elementului va scoate niște HTML.

Acesta este un exemplu de polimorfism , unul dintre conceptele de bază ale programării orientate pe obiecte.

Polimorfismul

„Polimorfism” înseamnă literal „multe forme” (din cuvintele grecești „poli” care înseamnă „multe”, iar „morphe” înseamnă „formă”). O clasă element element poate avea mai multe forme , deoarece poate lua orice formă a unei clase în ierarhia părinte.

Putem folosi un Number_Element , un Checkbox_Element sau orice alt subtip în orice loc în care se așteaptă un obiect Element , deoarece toate obiectele copil pot fi utilizate exact în același mod (adică apelând metoda lor render() ), în timp ce se pot comporta . diferit (ieșirea va diferi pentru fiecare tip de element).

După cum probabil vă puteți da seama, polimorfismul și moștenirea sunt concepte strâns legate.

Substituibilitate

Principiul substituției Liskov (sau LSP) , „L” în SOLID, afirmă:

„Într-un program de calculator, dacă S este un subtip de T, atunci obiectele de tip T pot fi înlocuite cu obiecte de tip S (adică, un obiect de tip T poate fi înlocuit cu orice obiect al unui subtip S) fără a modifica niciunul dintre proprietățile dezirabile ale programului.”

În termeni profani, ar trebui să puteți folosi orice clasă copil în locul clasei părinte fără niciun comportament neașteptat.

Fabrici

Să revenim la clasa noastră Field , unde în prezent avem o metodă create_element() care creează un nou 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; }

O metodă care returnează un obiect nou este adesea numită o fabrică simplă (a nu fi confundată cu „metoda fabrică”, care este un model de proiectare).

Știind că orice subtip este utilizabil în locul clasei părinte Element , vom continua și vom modifica această fabrică, astfel încât să poată crea obiecte din orice clasă copil.

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

Începem prin a prefix tipul de element cu numele curent:

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

Constanta magică __NAMESPACE__ conține numele actual al spațiului de nume.

Apoi, ne asigurăm că există o clasă pentru tipul de element specificat:

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

Apoi, creăm un nou obiect:

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

Și, în sfârșit, ne asigurăm că obiectul nou creat este într-adevăr o instanță a Elementului:

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

Extinderea

Merită să subliniem că am creat pluginul nostru pentru a fi extensibil. Adăugarea diferitelor tipuri de pagini, secțiuni, elemente este la fel de ușoară ca și crearea unei noi clase care extinde Admin_Page , Section , Element etc. Aceste clase de bază nu includ niciun cod care trebuie schimbat pentru a adăuga o nouă pagină, secțiune sau element.

Principiul Deschis/Închis (sau OCP), „O” în SOLID, afirmă:

„Entitățile software (clase, module, funcții etc.) ar trebui să fie deschise pentru extindere, dar închise pentru modificare.”

Aceasta înseamnă că ar trebui să putem extinde o clasă precum Admin_Page și să o reutilizam, dar nu ar trebui să o modificăm pentru a face asta.

Concluzie

În acest articol, ne-am înregistrat secțiunile, câmpurile și elementele. În timp ce le-am implementat, ne-am uitat mai atent la ce este polimorfismul și de ce este util. De asemenea, am aruncat o privire peste câteva principii SOLIDE, „Principiul de substituție Liskov” și „Principiul deschis/închis”.

Rămâneți cu noi pentru următoarea parte a acestei călătorii, unde vom arunca o privire mai atentă asupra modului în care putem îmbunătăți modul în care ne gestionăm cârligele WordPress.

Faceți clic aici pentru a citi partea 7 din seria noastră de programare orientată pe obiecte

Vezi si

  • WordPress și programarea orientată pe obiecte – O prezentare generală
  • Partea 2 – WordPress și programarea orientată pe obiecte: un exemplu din lumea reală
  • Partea 3 – WordPress și programare orientată pe obiecte: Un exemplu WordPress – Definirea domeniului de aplicare
  • Partea 4 – WordPress și programare orientată pe obiecte: un exemplu WordPress – Design
  • Partea 5 – WordPress și programare orientată pe obiecte: un exemplu WordPress – Implementare: meniul de administrare