الجزء 6 - WordPress والبرمجة الموجهة للكائنات: مثال على WordPress - التنفيذ: تسجيل الأقسام

نشرت: 2022-02-04

مرحبًا بكم مرة أخرى في سلسلتنا حول البرمجة الشيئية.

كما أوضحنا في جزء التصميم من السلسلة ، تتكون صفحة المسؤول من أقسام . يحتوي كل قسم على حقل واحد أو أكثر ، ويحتوي كل حقل من هذه الحقول على عنصر واحد أو أكثر.

كيف سيبدو ذلك في الكود؟

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

حسنًا ، يبدو هذا سهل الاستخدام ويمكننا بالفعل أن نقول أننا ربما نحتاج إلى إنشاء ثلاث فئات جديدة: Section Field Element .

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

دعنا نتوقف لحظة ونسأل أنفسنا ما نعرفه حتى الآن عن هذه الفصول.

  • $my_section->add_field() → يجب أن تكون فئة Section قادرة على إضافة (وتخزين) كائن Field جديد
  • $my_field->add_element() → يجب أن تكون فئة Field قادرة على إضافة (وتخزين) كائن Element جديد.

نبدأ بتخزين كائنات المجال الخاصة بنا في مصفوفة ، كما نفعل عادةً:

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

متغير $fields هذا هو عضو في فئة وهو ما نسميه خاصية . الخصائص هي متغيرات PHP ، تعيش في فئة ، ويمكن أن تكون من أي نوع بيانات ( string ، integer ، object ، إلخ).

سنكتب أيضًا طريقة add_field() لإنشاء حقل جديد وإضافته.

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

تقوم هذه الطريقة بإنشاء كائن Field جديد وإضافته إلى خاصية الحقول وإرجاع هذا الكائن الذي تم إنشاؤه حديثًا. واضحة ومباشرة جدا.

دعنا نكرر نفس العملية لفئة 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; } }

هذه بداية! ماذا بعد؟

فئة القسم

نحتاج إلى استدعاء add_settings_section () ، عند إنشاء قسم جديد. مرة أخرى ، طريقة الباني هي طريقة رائعة لإجراء التهيئة الخاصة بنا. دعنا نضيفه في الفصل:

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

يبدو أن القسم يحتاج إلى اسم سبيكة لتعريفه (مستخدم في سمة معرف العلامات). يمكن أن يكون لها أيضًا عنوان ووصف وينتمي إلى صفحة معينة.

 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;

يمكننا تحديد عنوان القسم من خلال القيام بشيء مثل هذا:

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

حسنًا ، هذا ليس صحيحًا تمامًا. على الرغم من أن الكود أعلاه صالح تمامًا ، إلا أنه لا يفعل ما نتوقعه في الواقع.

يتم تنفيذ طريقة المُنشئ عند إنشاء كائن قسم جديد. لذلك سيتم استدعاء add_settings_section() قبل أن نحصل على فرصة لتعيين العنوان. نتيجة لذلك ، لن يكون للقسم عنوان.

استضافة موقع الويب الخاص بك مع Pressidium

ضمان استرداد الأموال لمدة 60 يومًا

اطلع على خططنا

يجب أن يكون العنوان متاحًا أثناء تهيئة الكائن ، لذلك نحتاج إلى القيام بذلك في المنشئ.

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

احذر من أن $this->title تشير إلى خاصية فئة العنوان ، حيث يشير $title إلى وسيطة المنشئ.

هنا ، نستفيد أيضًا من الرؤية . نظرًا لأن خاصية $title الخاصة بنا لن يتم الوصول إليها إلا من خلال الفئة التي حددتها ، فيمكننا إعلانها private . لذلك ، فإننا نمنع الوصول إليها خارج الفصل.

أوه ، وعلينا أيضًا إضافة طريقة print_description() والتي ستطبع وصف القسم جيدًا.

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

بتجميعها معًا ، يبدو فصل القسم لدينا هكذا.

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

فئة المجال

بطريقة مشابهة Section ، يمكننا الآن المضي قدمًا وبناء فئة Field ، والتي ستستخدم وظيفة 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 ); } }

هنا ، نود أيضًا تقديم القيم الافتراضية للمعرف والتسمية ووصف الحقل. يمكننا القيام بذلك عن طريق تمرير مصفوفة خيارات إلى المُنشئ واستخدام وظيفة wp_parse_args () WordPress لتحليل تلك الخيارات.

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

ستسمح لنا الوظيفة wp_parse_args () بدمج القيم التي يحددها المستخدم (مصفوفة $options ) مع القيم الافتراضية.

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

يتعين علينا أيضًا تعيين تسميات فريدة لكل حقل. يمكننا التعامل مع هذا عن طريق تعيين التسمية إلى بادئة ( 'field_' ) متبوعة برقم ، والتي ستتم زيادتها في كل مرة يتم فيها إنشاء كائن Field جديد. سنخزن هذا الرقم في الخاصية الثابتة $number_of_fields .

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

يمكن الوصول إلى خاصية ثابتة مباشرة دون الحاجة إلى إنشاء مثيل للفئة أولاً.

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

تُستخدم الكلمة الأساسية self للإشارة إلى الفئة الحالية ، وبمساعدة عامل تحليل النطاق :: (يُطلق عليه عادةً "النقطتان المزدوجة") ، يمكننا الوصول إلى خاصيتنا الثابتة.

بهذه الطريقة ، في المنشئ ، نصل دائمًا إلى نفس خاصية $number_of_fields ، مما يزيد من قيمتها في كل مرة يتم فيها إنشاء كائن ، مما ينتج عنه تسمية فريدة مرتبطة بكل حقل.

من الآن فصاعدًا ، تقوم طريقة العرض render() ، بعد طباعة الوصف (إن وجد) ، بالتكرار خلال جميع العناصر وعرض كل عنصر منها.

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

ضع كل شيء معا…

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

فئة العنصر

من الآن فصاعدًا ، سنبني فئة Element بطريقة مماثلة!

سنبدأ في كتابة الفصل مثل هذا:

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

تأكد من أنك تهرب من مخرجاتك - كما نفعل هنا ، باستخدام esc_attr () و esc_html () WordPress - لمنع أي هجمات نصية عبر المواقع. على الرغم من أننا نعرض عناصرنا في صفحات المسؤول فقط ، إلا أنه لا يزال من الجيد الهروب دائمًا من أي بيانات ناتجة.

ملاحظة: البرمجة النصية عبر المواقع (أو XSS) هي نوع من الثغرات الأمنية التي توجد عادةً في تطبيقات الويب. يتيح XSS للمهاجمين إدخال تعليمات برمجية من جانب العميل في صفحات الويب التي يعرضها المستخدمون الآخرون. قد يستخدم المهاجمون ثغرة في البرمجة النصية عبر المواقع لتجاوز عناصر التحكم في الوصول مثل سياسة المصدر نفسه.

عندما كنا نجمع متطلبات المكون الإضافي ، لاحظنا أن هناك أنواعًا متعددة من العناصر - مربعات الاختيار وأزرار الاختيار وحقول الأرقام وما إلى ذلك. وعندما توصلنا إلى تصميمنا ، اتخذنا قرارًا بإنشاء فئة Element من المفترض أن يتم تمديدها. لذلك ، نعلم أننا سننتهي مع فئة فرعية لكل نوع عنصر.

يجب أن تختلف المخرجات اعتمادًا على نوع العنصر ، لذلك سنقوم بتحويل render() إلى طريقة مجردة. هذا يعني ، بالطبع ، أن الفصل نفسه يجب أن يكون أيضًا مجردًا.

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

على سبيل المثال ، قد تبدو فئة Number_Element بالشكل التالي:

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

وبالمثل ، يمكننا إنشاء Checkbox_Element و Radio_Element وحتى فئة Custom_Element لبقية العناصر.

لاحظ أننا نبني فصولنا الدراسية بحيث يمكن استخدامها جميعًا بنفس الطريقة . سيؤدي استدعاء طريقة العرض render() على أي عنصر تابع للعنصر إلى إخراج بعض HTML.

هذا مثال على تعدد الأشكال ، أحد المفاهيم الأساسية للبرمجة الشيئية.

تعدد الأشكال

"تعدد الأشكال" تعني حرفياً "العديد من الأشكال" (من الكلمات اليونانية "بولي" التي تعني "كثير" ، و "مورفي" تعني "شكل"). يمكن أن يكون للفصل الفرعي Element العديد من الأشكال ، حيث يمكن أن يتخذ أي شكل من أشكال الفصل في التسلسل الهرمي الأصل.

يمكننا استخدام Number_Element ، أو Checkbox_Element ، أو أي نوع فرعي آخر في أي مكان يُتوقع فيه وجود Element ، نظرًا لأنه يمكن استخدام جميع الكائنات الفرعية بنفس الطريقة تمامًا (أي استدعاء طريقة العرض render() ) ، مع استمرار القدرة على التصرف بشكل مختلف (سيختلف الإخراج لكل نوع عنصر).

كما يمكنك أن تقول على الأرجح ، فإن تعدد الأشكال والوراثة مفاهيم مرتبطة ارتباطًا وثيقًا.

الاستبدالية

ينص مبدأ استبدال Liskov (أو LSP) ، "L" في SOLID ، على ما يلي:

"في برنامج كمبيوتر ، إذا كان S نوعًا فرعيًا من T ، فيمكن استبدال كائنات من النوع T بأشياء من النوع S (على سبيل المثال ، يمكن استبدال كائن من النوع T بأي كائن من النوع الفرعي S) دون تغيير أي من الخصائص المرغوبة للبرنامج ".

وفقًا لمصطلحات الشخص العادي ، يجب أن تكون قادرًا على استخدام أي فئة فرعية بدلاً من فئة الأصل دون أي سلوك غير متوقع.

المصانع

دعنا نعود إلى فئة Field الخاصة بنا ، حيث لدينا حاليًا طريقة create_element() لإنشاء 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; }

غالبًا ما يُطلق على الطريقة التي تُرجع عنصرًا جديدًا اسم مصنع بسيط (يجب عدم الخلط بينه وبين "طريقة المصنع" ، وهو نمط تصميم).

مع العلم أن أي نوع فرعي قابل للاستخدام بدلاً من فئة Element الرئيسي ، سنمضي قدمًا ونقوم بتعديل هذا المصنع ، بحيث يكون قادرًا على إنشاء كائنات من أي فئة فرعية.

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

نبدأ ببدء نوع العنصر بالاسم الحالي:

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

يحتوي الثابت السحري __NAMESPACE__ على اسم مساحة الاسم الحالي.

بعد ذلك ، نتأكد من وجود فئة لنوع العنصر المحدد:

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

تمتد

تجدر الإشارة إلى أننا أنشأنا المكون الإضافي الخاص بنا ليكون قابلاً للتوسعة. تعد إضافة أنواع مختلفة من الصفحات ، والأقسام ، والعناصر أمرًا سهلاً مثل إنشاء فئة جديدة توسع Admin_Page ، و Section ، و Element وما إلى ذلك. لا تتضمن هذه الفئات الأساسية أي كود يلزم تغييره لإضافة صفحة أو قسم أو عنصر جديد.

ينص المبدأ المفتوح / المغلق (أو OCP) ، "O" في SOLID ، على ما يلي:

"يجب أن تكون كيانات البرامج (الفئات والوحدات والوظائف وما إلى ذلك) مفتوحة للتمديد ، لكنها مغلقة للتعديل."

هذا يعني أننا يجب أن نكون قادرين على توسيع فئة مثل Admin_Page وإعادة استخدامها ، ولكن لا ينبغي علينا تعديلها للقيام بذلك.

استنتاج

في هذه المقالة ، قمنا بتسجيل الأقسام والحقول والعناصر الخاصة بنا. أثناء تنفيذ ذلك ، ألقينا نظرة فاحصة على ماهية تعدد الأشكال وسبب فائدتها. لقد ألقينا نظرة سريعة على اثنين من مبادئ SOLID ، "مبدأ استبدال Liskov" و "المبدأ المفتوح / المغلق".

ابق معنا في الجزء التالي من هذه الرحلة ، حيث سنلقي نظرة فاحصة على كيفية تحسين الطريقة التي ندير بها خطافات WordPress الخاصة بنا.

انقر هنا لقراءة الجزء 7 في سلسلة البرمجة الشيئية

أنظر أيضا

  • WordPress and Object-Oriented Programming - نظرة عامة
  • الجزء 2 - WordPress والبرمجة الموجهة للكائنات: مثال من العالم الحقيقي
  • الجزء 3 - WordPress والبرمجة الموجهة للكائنات: مثال WordPress - تحديد النطاق
  • الجزء 4 - WordPress والبرمجة الموجهة للكائنات: مثال على WordPress - التصميم
  • الجزء الخامس - البرمجة الموجهة للكائنات و WordPress: مثال على WordPress - التنفيذ: قائمة الإدارة