Bagian 6 – WordPress dan Pemrograman Berorientasi Objek: Contoh WordPress – Implementasi: Mendaftarkan Bagian
Diterbitkan: 2022-02-04Selamat datang kembali ke seri kami tentang Pemrograman Berorientasi Objek.
Seperti yang kami jelaskan di bagian Desain seri, halaman admin terdiri dari bagian . Setiap bagian berisi satu atau lebih bidang , dan masing-masing bidang tersebut berisi satu atau lebih elemen .

Bagaimana itu terlihat dalam kode?
public function register_sections() { $my_section = $this->register_section( /* ... */ ); $my_field = $my_section->add_field( /* ... */ ); $my_element = $my_field->add_element( /* ... */ ); }
Baiklah, sepertinya mudah digunakan dan kita sudah tahu bahwa kita mungkin perlu membuat tiga kelas baru: Section
, Field
, dan Element
.
class Section {}
class Field {}
class Element {}
Mari luangkan waktu sejenak dan tanyakan pada diri kita apa yang kita ketahui sejauh ini tentang kelas-kelas ini.
-
$my_section->add_field()
→ KelasSection
harus dapat menambahkan (dan menyimpan) objekField
baru -
$my_field->add_element()
→ KelasField
harus dapat menambahkan (dan menyimpan) objekElement
baru.
Kita mulai dengan menyimpan objek Field kita dalam sebuah array, seperti yang biasa kita lakukan:
class Section { /** * @var Field[] Section field objects. */ protected $fields = array();
Variabel $fields
ini adalah anggota kelas dan itulah yang kami sebut properti . Properti adalah variabel PHP, tinggal di kelas, dan dapat berupa tipe data apa pun ( string
, integer
, object
, dll.).
Kami juga akan menulis metode add_field()
untuk membuat dan menambahkan bidang baru.
public function add_field() { $field = new Field( /* ... */ ); $this->fields[] = $field; return $field; }
Metode ini membuat objek Field
baru, menambahkannya ke properti bidang dan mengembalikan objek yang baru dibuat itu. Cukup mudah.
Mari kita ulangi proses yang sama untuk kelas Field
juga.
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; } }
Itu adalah permulaan! Apa berikutnya?
Kelas Bagian
Kita perlu memanggil add_settings_section(), ketika bagian baru dibuat. Sekali lagi, metode konstruktor adalah cara yang bagus untuk melakukan inisialisasi kita. Mari kita tambahkan di kelas:
class Section { // ... public function __construct() { add_settings_section( $this->id, $this->title, array( $this, 'print_description' ), $this->page ); } }
Tampaknya Bagian membutuhkan nama siput untuk mengidentifikasinya (digunakan dalam atribut id dari tag). Itu juga dapat memiliki judul, deskripsi, dan milik halaman tertentu.
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;
Kita dapat mengatur judul bagian, dengan melakukan sesuatu seperti ini:
$section = new Section(); $section->title = __( 'Hello world', 'prsdm-limit-login-attempts' );
Nah, itu kurang tepat. Meskipun kode di atas benar-benar valid, itu tidak benar-benar melakukan apa yang kita harapkan.
Metode konstruktor dijalankan ketika objek Bagian baru dibuat. Jadi add_settings_section()
akan dipanggil bahkan sebelum kita mendapatkan kesempatan untuk menetapkan judul. Akibatnya, bagian tersebut tidak akan memiliki judul.
Judul harus tersedia selama inisialisasi objek kita, jadi kita perlu melakukan ini di konstruktor.
class Section { /** * @var string Section title. */ private $title; public function __construct( $title ) { $this->title = $title; // ... } // ..
Hati-hati bahwa $this->title
mengacu pada properti kelas judul, di mana $title
mengacu pada argumen konstruktor.

Di sini, kami juga memanfaatkan visibilitas . Karena properti $title
kami hanya akan diakses oleh kelas yang mendefinisikannya, kami dapat mendeklarasikannya sebagai private
. Oleh karena itu, kami mencegahnya diakses di luar kelas.
Oh, dan kita juga harus menambahkan metode print_description()
yang akan, baik, mencetak deskripsi bagian.
/** * Print the section description. */ public function print_description() { echo esc_html( $this->description ); }
Menyatukan semuanya, kelas Bagian kami terlihat seperti ini.
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; } }
Kelas Lapangan
Dengan cara yang mirip dengan Section
, sekarang kita dapat melanjutkan dan membangun kelas Field
, yang akan menggunakan fungsi 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 ); } }
Di sini, kami juga ingin memberikan nilai default untuk ID, label, dan deskripsi bidang. Kita dapat melakukan ini dengan meneruskan array opsi ke konstruktor dan menggunakan fungsi wp_parse_args() WordPress untuk mengurai opsi tersebut.
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 ); } }
Fungsi wp_parse_args() akan memungkinkan kita untuk menggabungkan nilai yang ditentukan pengguna (array $options
) dengan nilai default.
array( 'label' => sprintf( __( 'Field #%s', 'prsdm-limit-login-attempts' ), self::$number_of_fields 'id' => 'field_' . self::$number_of_fields, 'description' => '' )
Kami juga harus menetapkan label unik untuk setiap bidang. Kita dapat menangani ini dengan menyetel label ke awalan ( 'field_'
) diikuti dengan angka, yang akan ditingkatkan setiap kali objek Field baru dibuat. Kami akan menyimpan nomor ini di properti static $number_of_fields
.
/** * @var int Number of fields instantiated. */ private static $number_of_fields = 0;
Properti statis dapat diakses secara langsung tanpa harus membuat instance kelas terlebih dahulu.
'id' => 'field_' . self::$number_of_fields
Kata kunci self
digunakan untuk merujuk ke kelas saat ini dan, dengan bantuan operator resolusi lingkup ::
(biasa disebut "titik dua"), kita dapat mengakses properti statis kita.
Dengan begitu, dalam konstruktor, kami selalu mengakses properti $number_of_fields
yang sama , meningkatkan nilainya setiap kali sebuah objek dibuat, yang menghasilkan label unik yang dilampirkan ke setiap bidang.
Ke depan, metode render()
, setelah mencetak deskripsi (jika ada), mengulangi semua elemen dan merender masing-masing elemen.
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(); } }
Menyatukan semuanya…
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(); } } }
Kelas Elemen
Ke depan, kita akan membangun kelas Element
dengan cara yang sama!

Kami akan mulai menulis kelas seperti ini:
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 } }
Pastikan Anda keluar dari output—seperti yang kami lakukan di sini, menggunakan fungsi WordPress esc_attr() dan esc_html()—untuk mencegah serangan skrip lintas situs. Meskipun kami merender elemen kami hanya di halaman admin, tetap merupakan ide bagus untuk selalu menghindari data keluaran apa pun.
CATATAN: Skrip lintas situs (atau XSS) adalah jenis kerentanan keamanan yang biasanya ditemukan di aplikasi web. XSS memungkinkan penyerang untuk menyuntikkan kode sisi klien ke halaman web yang dilihat oleh pengguna lain. Kerentanan skrip lintas situs dapat digunakan oleh penyerang untuk melewati kontrol akses seperti kebijakan asal yang sama.
Ketika kami mengumpulkan persyaratan plugin, kami memperhatikan bahwa ada beberapa jenis elemen—kotak centang, tombol radio, bidang angka, dll. Ketika kami membuat desain kami, kami membuat keputusan untuk membangun kelas Element
yang dimaksudkan untuk diperluas. Jadi, kita tahu bahwa kita akan berakhir dengan kelas anak untuk setiap tipe elemen.
Outputnya harus berbeda tergantung pada tipe elemen, jadi kita akan mengubah render()
menjadi metode abstrak. Itu berarti, tentu saja, kelas itu sendiri juga harus abstrak.
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(); }
Misalnya, kelas Number_Element
akan terlihat seperti ini:
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 } }
Demikian pula, kita dapat membangun sebuah Checkbox_Element
, Radio_Element
, dan bahkan kelas Custom_Element
untuk elemen kita yang lain.
Perhatikan bahwa kita sedang membangun kelas kita sehingga semuanya dapat digunakan dengan cara yang sama . Memanggil metode render()
pada anak Elemen mana pun akan menampilkan beberapa HTML.
Itulah contoh polimorfisme , salah satu konsep inti pemrograman berorientasi objek.
Polimorfisme
"Polimorfisme" secara harfiah berarti "banyak bentuk" (dari kata Yunani "poli" yang berarti "banyak", dan "morphe" yang berarti "bentuk"). Kelas anak Elemen dapat memiliki banyak bentuk , karena ia dapat mengambil bentuk apa pun dari kelas dalam hierarki induknya.
Kita dapat menggunakan Number_Element
, a Checkbox_Element
, atau subtipe lainnya di tempat mana pun yang diharapkan objek Element
, karena semua objek anak dapat digunakan dengan cara yang sama persis (yaitu memanggil metode render()
mereka), sambil tetap dapat berperilaku berbeda (output akan berbeda untuk setiap jenis elemen).
Seperti yang mungkin Anda ketahui, polimorfisme dan pewarisan adalah konsep yang terkait erat.
Substitusi
Prinsip Substitusi Liskov (atau LSP) , "L" dalam SOLID, menyatakan:
Dalam program komputer, jika S adalah subtipe T, maka objek tipe T dapat diganti dengan objek tipe S (yaitu, objek tipe T dapat diganti dengan objek subtipe S) tanpa mengubah salah satu dari sifat program yang diinginkan.”
Dalam istilah awam, Anda harus dapat menggunakan kelas anak apa pun sebagai pengganti kelas induknya tanpa perilaku yang tidak terduga.
Pabrik
Mari kembali ke kelas Field
, di mana saat ini kita memiliki metode create_element()
untuk membuat Element
baru.
/** * 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; }
Metode yang mengembalikan objek baru sering disebut pabrik sederhana (jangan dikelirukan dengan "metode pabrik", yang merupakan pola desain).
Mengetahui bahwa subtipe apa pun dapat digunakan sebagai pengganti kelas induk Element
, kita akan melanjutkan dan memodifikasi pabrik ini, sehingga ia akan dapat membuat objek dari kelas anak mana pun.
/** * 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 } }
Kita mulai dengan mengawali tipe elemen dengan nama saat ini:
$element_type = __NAMESPACE__ . '\\Elements\\' . $element_type;
Konstanta ajaib __NAMESPACE__
berisi nama namespace saat ini.
Kemudian, kami memastikan bahwa ada kelas untuk tipe elemen yang ditentukan:
if ( ! class_exists( $element_type ) ) { throw new Exception( 'No class exists for the specified type' ); }
Selanjutnya, kita membuat objek baru:
$element = new $element_type( $this->section_id, $options );
Dan terakhir, kami memastikan bahwa objek yang baru dibuat memang merupakan turunan dari Elemen:
if ( ! ( $element instanceof Element ) ) { return; }
Memperluas
Perlu ditunjukkan bahwa kami telah membangun plugin kami agar dapat diperluas. Menambahkan berbagai jenis halaman, bagian, elemen semudah membuat kelas baru yang memperluas Admin_Page
, Section
, Element
dll. Kelas dasar ini tidak menyertakan kode apa pun yang perlu diubah untuk menambahkan halaman, bagian, atau elemen baru.
Prinsip Terbuka/Tertutup (atau OCP), "O" dalam SOLID, menyatakan:
“Entitas perangkat lunak (kelas, modul, fungsi, dll.) harus terbuka untuk ekstensi, tetapi ditutup untuk modifikasi.”
Ini berarti bahwa kita harus dapat memperluas kelas seperti Admin_Page
dan menggunakannya kembali, tetapi kita tidak perlu memodifikasinya untuk melakukannya.
Kesimpulan
Dalam artikel ini, kami mendaftarkan bagian, bidang, dan elemen kami. Saat menerapkan ini, kami melihat lebih dekat apa itu polimorfisme dan mengapa itu berguna. Kami juga telah melihat beberapa prinsip SOLID, “Prinsip Substitusi Liskov” dan “Prinsip Terbuka/Tertutup”.
Tetap bersama kami untuk bagian selanjutnya dari perjalanan ini, di mana kami akan melihat lebih dekat bagaimana kami dapat meningkatkan cara kami mengelola hook WordPress kami.
Klik di sini untuk membaca Bagian 7 dalam Seri Pemrograman Berorientasi Objek kami
Lihat juga
- WordPress dan Pemrograman Berorientasi Objek – Gambaran Umum
- Bagian 2 – WordPress dan Pemrograman Berorientasi Objek: Contoh Dunia Nyata
- Bagian 3 – WordPress dan Pemrograman Berorientasi Objek: Contoh WordPress – Mendefinisikan Ruang Lingkup
- Bagian 4 – WordPress dan Pemrograman Berorientasi Objek: Contoh WordPress – Desain
- Bagian 5 – WordPress dan Pemrograman Berorientasi Objek: Contoh WordPress – Implementasi: Menu Administrasi