Bölüm 8 – WordPress ve Nesne Yönelimli Programlama: Bir WordPress Örneği – Uygulama: Seçenekler

Yayınlanan: 2022-02-04

Şimdiye kadar yalnızca kullanıcı tanımlı seçenekleri depolamamız gerekiyordu, bu nedenle Ayarlar API'sini kullandık. Bununla birlikte, bir IP adresinin şu anda kilitliyse, bir IP adresinin kaç kez başarısız bir şekilde oturum açmaya çalıştığını "hatırlamak" için eklentimizin seçenekleri okuyabilmesi/yazabilmesi gerekir.

Seçenekleri depolamak ve almak için nesne yönelimli bir yola ihtiyacımız var. “Tasarım” aşamasında, bunu kısaca tartıştık, ancak yalnızca gerçekleştirmek istediğimiz eylemlere (bir seçeneği alma , ayarlama ve kaldırma ) odaklanarak bazı uygulama ayrıntılarını soyutladık.

Ayrıca, organize olmalarını sağlamak için bölümlerine göre "grup" seçeneklerini bir araya getireceğiz. Bu tamamen kişisel tercihe dayalı.

Bunu bir arayüze çevirelim:

 interface Options { /** * Return the option value based on the given option name. * * @param string $name Option name. * @return mixed */ public function get( $name ); /** * Store the given value to an option with the given name. * * @param string $name Option name. * @param mixed $value Option value. * @param string $section_id Section ID. * @return bool Whether the option was added. */ public function set( $name, $value, $section_id ); /** * Remove the option with the given name. * * @param string $name Option name. * @param string $section_id Section ID. */ public function remove( $name, $section_id ); }

İdeal olarak, şunun gibi bir şey yaparak WordPress Seçenekler API'si ile etkileşim kurabiliriz:

 $options = new WP_Options(); $options->get( 'retries' );

Bu noktada, kendi arayüzümüzü ve sınıfımızı yaratma zahmetine girmek yerine neden sadece get_option() WordPress fonksiyonunu kullanmadığımızı merak ediyor olabilirsiniz. WordPress işlevlerini doğrudan kullanmak, eklentimizi geliştirmenin tamamen kabul edilebilir bir yolu olsa da, bir adım daha ileri giderek ve bağımlı olacak bir arayüz oluşturarak esnek kalıyoruz.

WP_Options sınıfımız, Options arayüzümüzü uygulayacak. Bu şekilde, gelecekte ihtiyaçlarımız değişirse hazır olacağız. Örneğin, seçeneklerimizi özel bir tabloda, harici bir veritabanında, bellekte (örn. Redis) saklamamız gerekebilir, adını siz koyun. Bir soyutlamaya (yani arayüze) bağlı olarak, uygulamada bir şeyi değiştirmek, aynı arayüzü uygulayan yeni bir sınıf oluşturmak kadar basittir.

WP_Options

Yapıcısındaki get_option() WordPress işlevini kullanarak tüm seçenekleri alarak WP_Options sınıfımızı yazmaya başlayalım.

 class WP_Options { /** * @var array Stored options. */ private $options; /** * WP_Options constructor. */ public function __construct() { $this->options = get_option( Plugin::PREFIX ); } }

$options özelliği dahili olarak kullanılacağından, onu private olarak ilan edeceğiz, böylece sadece onu tanımlayan sınıf, WP_Options sınıfı tarafından erişilebilir.

Şimdi, implements operatörünü kullanarak Options arayüzümüzü uygulayalım.

 class WP_Options implements Options { // ...

IDE'miz ya sınıfımızın özetini bildirmemiz ya da arayüzde tanımlanan get() , set() ve remove() yöntemlerini uygulamamız için bize bağırıyor.

Öyleyse, bu yöntemleri uygulamaya başlayalım!

Bir seçenek elde etmek

$options özelliğimizde belirtilen seçenek adını arayacak ve değerini döndürecek veya yoksa false döndürecek olan get() yöntemiyle başlayacağız.

 class WP_Options implements Options { private $options; public function __construct() { $this->options = get_option( Plugin::PREFIX ); } /** * Return the option value based on the given option name. * * @return mixed */ public function get( $option_name ) { if ( ! isset( $this->options[ $option_name ] ) ) { return false; } return $this->options[ $option_name ]; } }

Şimdi varsayılan seçenekler hakkında düşünmenin tam zamanı.

Varsayılan seçenekler

Daha önce de belirtildiği gibi, seçenekleri bölümlerine göre birlikte gruplandırmak istiyoruz. Bu yüzden, muhtemelen seçenekleri birkaç bölüme ayıracağız. “Genel Seçenekler” bölümü ve takip etmemiz gereken veriler için bir tane daha. Kilitlemeler, yeniden denemeler, kilitleme günlükleri ve toplam kilitleme sayısı—bu durumu keyfi olarak arayacağız.

Varsayılan seçeneklerimizi saklamak için bir sabit kullanacağız. Kodumuz yürütülürken bir sabitin değeri değiştirilemez, bu da onu varsayılan seçeneklerimiz gibi bir şey için ideal kılar. Sınıf sabitleri, her sınıf örneği için değil, sınıf başına bir kez tahsis edilir.

NOT: Bir sabitin adı, kural gereği tamamı büyük harfle yazılır.

 const DEFAULT_OPTIONS = array( 'general_options' => array( 'allowed_retries' => 4, 'normal_lockout_time' => 1200, // 20 minutes 'max_lockouts' => 4, 'long_lockout_time' => 86400, // 24 hours 'hours_until_retries_reset' => 43200, // 12 hours 'site_connection' => 'direct', 'handle_cookie_login' => 'yes', 'notify_on_lockout_log_ip' => true, 'notify_on_lockout_email_to_admin' => false, 'notify_after_lockouts' => 4 ), 'state' => array( 'lockouts' => array(), 'retries' => array(), 'lockout_logs' => array(), 'total_lockouts' => 0 ) );

DEFAULT_OPTIONS iç içe dizide, tüm seçeneklerimiz için varsayılan bir değer belirledik.

Daha sonra yapmak istediğimiz şey, eklenti başlatıldığında, add_option() WordPress işlevini kullanarak varsayılan seçenek değerlerini veritabanında saklamaktır.

 class WP_Options { public function __construct() { $all_options = array(); foreach ( self::DEFAULT_OPTIONS as $section_id => $section_default_options ) { $db_option_name = Plugin::PREFIX . '_' . $section_id; $section_options = get_option( $db_option_name ); if ( $section_options === false ) { add_option( $db_option_name, $section_default_options ); $section_options = $section_default_options; } $all_options = array_merge( $all_options, $section_options ); } $this->options = $all_options; } }

Gelin bu parçaya daha yakından bakalım. İlk olarak, varsayılan seçenekler dizisini yineliyoruz ve get_option() WordPress işlevini kullanarak seçenekleri alıyoruz.

 foreach ( self::default_options as $section_id => $section_default_options ) { $db_option_name = Plugin::PREFIX . '_' . $section_id; $section_options = get_option( $db_option_name ); // ...

Ardından, veritabanında her seçeneğin zaten var olup olmadığını kontrol ederiz ve yoksa varsayılan seçeneğini saklarız.

 if ( $section_options === false ) { add_option( $db_option_name, $section_default_options ); $section_options = $section_default_options; }

Son olarak tüm bölümlerin seçeneklerini topluyoruz.

 $all_options = array_merge( $all_options, $section_options );

Ve onları daha sonra erişebilmemiz için $options özelliğinde saklayın.

 $this->options = $all_options;

Veritabanındaki WordPress seçenekler tablosu, option_name , bölüm adıyla birleştirilmiş eklenti önekinden oluştuğu birkaç satıra sahip olacaktır.

Şimdi uygulamamız gereken diğer yöntemlere geçelim.

Bir seçeneğin saklanması

Benzer şekilde, veritabanında yeni bir seçeneği kolayca depolamak ve aşağıdaki gibi önceki değerlerin üzerine yazmak istiyoruz:

 $options = new Options(); $options->set( 'retries', 4 );

Şimdi, update_option() WordPress işlevini kullanacak olan set() yöntemini uygulayalım.

 /** * Store the given value to an option with the given name. * * @param string $name Option name. * @param mixed $value Option value. * @param string $section_id Section id. Defaults to 'state'. * @return bool Whether the option was added. */ public function set( $name, $value, $section_ ) { $db_option_name = Plugin::PREFIX . '_' . $section_id; $stored_option = get_option( $db_option_name ); $stored_option[ $name ] = $value; return update_option( $db_option_name, $stored_option ); }

Bir seçeneğin kaldırılması

Son olarak, seçeneği başlangıç ​​değerine ayarlayacak olan remove() yöntemini uygulayacağız:

 /** * Remove the option with the given name. * * @param string $name Option name. * @param string $section_id Section id. Defaults to 'state'. * @return bool Whether the option was removed. */ public function remove( $name, $section_ ) { $initial_value = array(); if ( isset( self::DEFAULT_OPTIONS[ $section_id ][ $name ] ) ) { $initial_value = self::DEFAULT_OPTIONS[ $section_id ][ $name ]; } return $this->set( $name, $initial_value, $section_id ); }

Her şeyi tek bir sınıfta topladık. Seçeneklerle ilgili tüm veriler (yani özelliklerimiz) ve uygulama ayrıntıları (yani yeni uyguladığımız yöntemler) WP_Options sınıfında kapsüllenir .

Kapsülleme/Soyutlama

Her şeyi tek bir sınıfa sarmak, içindekileri (bir kapsül içindeymiş gibi) içine almak, esasen onları dış dünyadan "gizlemek", kapsülleme dediğimiz şeydir. Kapsülleme, nesne yönelimli programlamanın bir diğer temel kavramıdır.

Pressidium ile web sitenizi barındırın

60 GÜN PARA GERİ GARANTİSİ

PLANLARIMIZI GÖRÜN

Options arayüzünü kullanarak, seçeneklerimizi nasıl yaptığımıza değil, seçeneklerle ne yaptığımıza odaklandık, seçenekler fikrini soyutladık, işleri kavramsal olarak basitleştirdik. Bu, nesne yönelimli programlamanın bir başka temel kavramı olan soyutlama dediğimiz şeydir.

Kapsülleme ve soyutlama tamamen farklı kavramlardır, ancak açıkça görebileceğiniz gibi, oldukça ilişkilidir. Temel farkları, kapsüllemenin uygulama düzeyinde, soyutlamanın ise tasarım düzeyinde var olmasıdır.

bağımlılıklar

Aşağıdaki senaryoyu ele alalım:

Bir IP adresinin kilitlenmesi gerekip gerekmediğini, bu kilitlenme süresinin ne olması gerektiğini, etkin bir kilitlemenin hala geçerli olup olmadığını veya süresinin dolduğunu vb. belirlemekten sorumlu bir Kilitleme sınıfı vardır. Bu sınıf, belirlemeden sorumlu olan bir Lockouts should_get_locked_out() yöntemini içerir. bir IP adresinin kilitlenip kilitlenmeyeceği. Bu yöntemin, bir IP adresi kilitlenmeden önce izin verilen maksimum yeniden deneme sayısını okuması gerekir; bu yapılandırılabilir bir değerdir, yani bir seçenek olarak saklanır.

Yani, az önce tanımladığımız kod şuna benzer:

 class Lockouts { // ... /** * @var WP_Options An instance of `WP_Options`. */ private $options; /** * Lockouts constructor */ public function __construct() { $this->options = new WP_Options(); } /** * Return the number of retries. * * @return int */ private function get_number_of_retries() { // ... } /** * Check whether this IP address should get locked out. * * @return bool */ public function should_get_locked_out() { $retries = $this->get_number_of_retries(); $allowed_retries = $this->options->get( 'allowed_retries' ); return $retries % $allowed_retries === 0; } // ... }

Temel olarak, yapıcıda yeni bir WP_Options örneği oluşturuyoruz ve ardından bu örneği allowed_retries seçeneğinin değerini almak için kullanıyoruz.

Bu kesinlikle iyi, ancak Lockouts sınıfımızın artık WP_Options bağlı olduğunu aklımızda tutmalıyız. WP_Options'a bağımlılık diyoruz.

Gelecekte ihtiyaçlarımız değişirse, örneğin harici bir veritabanında seçenekleri okumamız/yazmamız gerekirse, WP_Options bir DB_Options sınıfıyla değiştirmemiz gerekir. Yalnızca bir sınıftaki seçenekleri almamız gerekirse, bu o kadar da kötü görünmüyor. Ancak, birden çok bağımlılığa sahip birçok sınıf olduğunda bu biraz zor olabilir. Tek bir bağımlılıktaki herhangi bir değişiklik, büyük olasılıkla kod tabanında dalgalanacak ve bağımlılıklarından biri değişirse bizi bir sınıfı değiştirmeye zorlayacaktır.

Dependency Inversion Principle'ı takip etmek için kodumuzu yeniden yazarak bu sorunu ortadan kaldırabiliriz.

Ayrışma

SOLID'deki "D" olan Bağımlılık Tersine Çevirme İlkesi (DIP) şunları belirtir:

  • Yüksek seviyeli modüller, düşük seviyeli modüllerden hiçbir şey almamalıdır. Her ikisi de soyutlamalara dayanmalıdır.
  • Soyutlamalar ayrıntılara bağlı olmamalıdır. Ayrıntılar (somut uygulamalar) soyutlamalara bağlı olmalıdır.

Bizim durumumuzda, Lockouts sınıfı "yüksek seviyeli modül"dür ve "düşük seviyeli modül" olan WP_Options sınıfına bağlıdır.

Bunu, göründüğünden daha kolay olan Dependency Injection kullanarak değiştireceğiz. Lockouts sınıfımız, bağımlı olduğu nesneleri oluşturmak yerine alacak.

 class Lockouts { // ... /** * Lockouts constructor. * * @param WP_Options $options */ public function __construct( WP_Options $options ) { $this->options = $options; } // ... }

Yani, bir bağımlılık enjekte ediyoruz:

 $options = new WP_Options(); $lockouts = new Lockouts( $options );

Artık WP_Options bağımlılığıyla gevşek bir şekilde birleştiğinden , Lockouts sınıfımızın bakımını daha kolay hale getirdik. Ek olarak, kodumuzu test etmeyi kolaylaştırarak bağımlılıklarla alay edebileceğiz. WP_Options davranışını taklit eden bir nesneyle değiştirmek, kodumuzu bir veritabanında herhangi bir sorgu yürütmeden test etmemize olanak tanır.

 /** * Lockouts constructor. * * @param WP_Options $options */ public function __construct( WP_Options $options ) { $this->options = $options; }

Lockouts bağımlılıklarının kontrolünü başka bir sınıfa vermiş olsak da (bağımlılıkların kendisini kontrol eden Lockouts aksine), Lockouts yine de bir WP_Options nesnesi bekler. Yani, bir soyutlama yerine hala somut WP_Options sınıfına bağlıdır. Daha önce belirtildiği gibi, her iki modül de soyutlamalara bağlı olmalıdır .

Hadi düzeltelim!

 /** * Lockouts constructor. * * @param Options $options */ public function __construct( Options $options ) { $this->options = $options; }

Ve $options argümanının türünü WP_Options sınıfından Options arabirimine değiştirerek, Lockouts sınıfımız bir soyutlamaya bağlıdır ve bir DB_Options nesnesini veya aynı arabirimi uygulayan herhangi bir sınıfın örneğini iletmekte özgürüz, onun yapıcısına.

Tek Sorumluluk

IP adresinin kilitlenip kilitlenmediğini kontrol etmek için should_get_locked_out() adlı bir yöntem kullandığımızı belirtmekte fayda var.

 /** * Check whether this IP address should get locked out. * * @return bool */ public function should_get_locked_out() { $retries = $this->get_number_of_retries(); $allowed_retries = $this->options->get( 'allowed_retries' ); return $retries % $allowed_retries === 0; }

Kolayca şöyle bir tek satırlık yazabiliriz:

 if ( $this->get_number_of_retries() % $this->options->get( 'allowed_retries' ) === 0 ) {

Ancak, bu mantığı kendi küçük yöntemine taşımanın birçok faydası vardır.

  • Bir IP adresinin kilitlenmesi gerekip gerekmediğini belirleme koşulu değişirse, yalnızca bu yöntemi değiştirmemiz gerekir (if ifademizin tüm oluşumlarını aramak yerine)
  • Her "birim" daha küçük olduğunda birim testleri yazmak daha kolay hale gelir
  • Kodumuzun okunabilirliğini büyük ölçüde iyileştirir

Bunu okuyor:

 if ( $this->should_get_locked_out() ) { // ...

bize bunu okumaktan çok daha kolay görünüyor:

 if ( $this->get_number_of_retries() % $this->options->get( 'allowed_retries' ) === 0 ) { // ...

Bunu eklentimizin hemen hemen her yöntemi için yaptık. Çıkarılacak başka bir şey kalmayana kadar yöntemleri daha uzun olanlardan çıkarmak. Aynı şey sınıflar için de geçerlidir, her sınıf ve yöntemin tek bir sorumluluğu olmalıdır.

SOLID'deki “S” olan Tek Sorumluluk İlkesi (SRP) , şunları belirtir:

"Bir bilgisayar programındaki her modül, sınıf veya işlev, o programın işlevselliğinin tek bir bölümünden sorumlu olmalı ve bu bölümü kapsamalıdır."

Veya Robert C. Martin'in (“Bob Amca”) dediği gibi:

"Bir sınıfın değişmek için bir ve sadece bir nedeni olmalıdır."

Ana eklenti dosyasını yeniden ziyaret etme

Şu anda ana eklenti dosyamız sadece şunu içeriyor:

 /** * Plugin Name: PRSDM Limit Login Attempts * Plugin URI: https://pressidium.com * Description: Limit rate of login attempts, including by way of cookies, for each IP. * Author: Pressidium * Author URI: https://pressidium.com * Text Domain: prsdm-limit-login-attempts * License: GPL-2.0+ * Version: 1.0.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; }

Bir kez daha, her şeyi bir Plugin sınıfına saracağız, bu sefer sadece adlandırma çakışmalarını önlemek için.

 namespace Pressidium\Limit_Login_Attempts; if ( ! defined( 'ABSPATH' ) ) { exit; } class Plugin { /** * Plugin constructor. */ public function __construct() { // ... } }

Bu Plugin sınıfını, yapıcısında kodu çalıştıracak olan dosyanın sonunda başlatacağız.

 new Plugin();

Yapıcıda, etkinleştirilmiş eklentiler yüklendikten sonra tetiklenen plugins_loaded eylemine bağlanacağız.

 public function __construct() { add_action( 'plugins_loaded', array( $this, 'init' ) ); } public function init() { // Initialization }

Ayrıca tüm PHP dosyalarımızı yüklemek için bir require_files() yöntemini çağıracağız.

 public function __construct() { $this->require_files(); add_action( 'plugins_loaded', array( $this, 'init' ) ); } private function require_files() { require_once __DIR__ . '/includes/Sections/Section.php'; require_once __DIR__ . '/includes/Pages/Admin_Page.php'; require_once __DIR__ . '/includes/Pages/Settings_Page.php'; // ... }

Son olarak, init() yöntemimizde bazı nesneler oluşturarak eklentimizi başlatacağız.

NOT: Aşağıdaki pasaj, ana eklenti dosyasının yalnızca küçük bir bölümünü içerir. Asıl dosyayı eklentinin GitHub deposunda okuyabilirsiniz.

 public function init() { $options = new Options(); $hooks_manager = new Hooks_Manager(); $settings_page = new Settings_Page( $options ); $hooks_manager->register( $settings_page ); // ... }

Dosyaları düzenlemek

Dosyalarınızı düzenli tutmak, özellikle çok sayıda kod içeren büyük eklentiler üzerinde çalışırken çok önemlidir. Klasör yapınız benzer dosyaları birlikte gruplandırmalı ve sizin ve ekip arkadaşlarınızın düzenli kalmasına yardımcı olmalıdır.

Pages , Sections , Fields , Elements , vb. için birkaç alt ad alanı içeren bir ad alanı ( Pressidium\Limit_Login_Attempts ) zaten tanımladık. Dizinlerimizi ve dosyalarımızı düzenlemek için bu hiyerarşiyi takiben, buna benzer bir yapı elde ettik:

 . ├── includes │ ├── Hooks │ │ ├── Actions.php │ │ ├── Filters.php │ │ └── Hooks_Manager.php │ ├── Pages │ │ ├── Admin_Page.php │ │ └── Settings_Page.php │ ├── Sections │ │ ├── Fields │ │ │ ├── Elements │ │ │ │ ├── Checkbox_Element.php │ │ │ │ ├── Custom_Element.php │ │ │ │ ├── Element.php │ │ │ │ ├── Number_Element.php │ │ │ │ └── Radio_Element.php │ │ │ └── Field.php │ │ └── Section.php │ └── WP_Options.php ├── prsdm-limit-login-attempts.php └── uninstall.php

Her dosya tek bir sınıf içerir. Dosyalar, içerdikleri sınıflara göre adlandırılır ve dizinler ve alt dizinler, (alt-)ad alanlarından sonra adlandırılır.

Kullanabileceğiniz birden çok mimari desen ve adlandırma şeması vardır. Size mantıklı gelen ve projenizin ihtiyaçlarına uygun olanı seçmek size kalmış. Projenizi yapılandırmaya gelince, önemli olan tutarlı olmaktır.

Çözüm

Tebrikler! WordPress ve nesne yönelimli programlama hakkındaki yazı dizimizi tamamladınız.

Umarım birkaç şey öğrenmişsinizdir ve öğrendiklerinizi kendi projelerinizde uygulamaya başlamaktan heyecan duymuşsunuzdur!

İşte bu seride ele aldıklarımızın kısa bir özeti:

  • Gereksinimlerin toplanması: Eklentinin ne yapması gerektiğine karar verdik.
  • Tasarım: Eklentinin nasıl yapılandırılacağını, potansiyel sınıflarımız arasındaki ilişkileri ve soyutlamalarımıza üst düzey bir genel bakışı düşündük.
  • Uygulama: Eklentinin bazı önemli bölümlerinin gerçek kodunu yazdık. Bunu yaparken de sizi çeşitli kavram ve ilkelerle tanıştırdık.

Ancak, OOP'nin sunduğu ve sunduğu şeylerin yüzeyini zar zor çizdik. Yeni bir beceride iyi olmak pratik gerektirir, bu yüzden devam edin ve kendi nesne yönelimli WordPress eklentilerinizi oluşturmaya başlayın. Mutlu kodlama!

Ayrıca bakınız

  • WordPress ve Nesne Yönelimli Programlama – Genel Bakış
  • Bölüm 2 – WordPress ve Nesne Yönelimli Programlama: Gerçek Bir Dünya Örneği
  • Bölüm 3 – WordPress ve Nesne Yönelimli Programlama: Α WordPress Örneği – Kapsamın Tanımlanması
  • Bölüm 4 – WordPress ve Nesne Yönelimli Programlama: Bir WordPress Örneği – Tasarım
  • Bölüm 5 – WordPress ve Nesne Yönelimli Programlama: Bir WordPress Örneği – Uygulama: Yönetim Menüsü
  • Bölüm 6 – WordPress ve Nesne Yönelimli Programlama: Bir WordPress Örneği – Uygulama: Bölümlerin Kaydedilmesi
  • Bölüm 7 – WordPress ve Nesne Yönelimli Programlama: Bir WordPress Örneği – Uygulama: WordPress Kancalarını Yönetme