الخصائص والأساليب الثابتة في PHP. التحضير لمقابلة PHP: الكلمة الرئيسية "static Php static

27.06.2020


هناك نوعان من مطوري PHP في العالم. يفضل البعض الأساليب الثابتة لأنه من السهل التعامل معها، والبعض الآخر، على العكس من ذلك، يعتبرون الأساليب الثابتة شريرة ولا يستخدمونها في ممارساتهم.
سأحاول في هذه المقالة، باستخدام خبرتي في العمل مع العديد من أطر العمل، شرح سبب تجاهل بعض المطورين لأفضل الممارسات واستخدام مجموعة كاملة من الأساليب الثابتة.

من يحب الأساليب الثابتة؟

يتم استخدامها بشكل خاص من قبل المطورين الذين سبق لهم استخدام إطار عمل CodeIgniter في عملهم.

كما أن غالبية مطوري Kohana وLaravel يتبعون الأساليب الإحصائية.
وهنا لا يسعنا إلا أن نذكر حقيقة أن المبرمجين الذين يقررون البدء في كتابة أشياء خاصة بهم عادة ما يرفضون استخدام CodeIgniter.

لماذا تسأل؟

كان CodeIgniter يدعم PHP 4 قبل إضافة الأساليب الثابتة في PHP 5. بالإضافة إلى ذلك، يستخدم CodeIgniter "كائنًا فائقًا" يمنح وصولاً متساويًا إلى جميع الفئات المخصصة لوحدة التحكم. وبالتالي، تصبح متاحة للاستخدام في جميع أنحاء النظام.

هذا يعني أنه يمكن الوصول إلى الفئات من أي نموذج باستخدام طريقة __get()، والتي ستبحث عن الخاصية المطلوبة باستخدامها get_instance()->($var). في السابق، عندما لم تكن الدالة __get() مدعومة في PHP 4، كان يتم ذلك باستخدام بنية foreach من خلال معلمات CI_Controller ثم تعيينها للمتغير $this في النموذج.

في المكتبة يجب عليك الاتصال بـ get_instance. لا تفرض المكتبة وراثة الفئة، لذا لا توجد طريقة لتجاوز الدالة __get().

مقدار…

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

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

"حل"

كان مطورو Kohana أول من عمل بجدية على الأساليب الثابتة. قاموا بالتغييرات التالية:
// كان $this->input->get("foo"); // أصبح الإدخال::get("foo");
بالنسبة للعديد من مطوري CodeIgniter الذين يستخدمون PHP 4 القديم والذين انتقلوا إلى إطار عمل Kohana للاستفادة من جميع مزايا PHP 5، فإن هذا ليس بالأمر غير المعتاد. لكن كلما قل عدد الشخصيات، كلما كان ذلك أفضل، أليس كذلك؟

إذا ما هي المشكلة؟

سيقول العديد من مطوري PHP (خاصة أولئك الذين هم على دراية جيدة بـ Symfony وZend): "إنه أمر واضح - استخدم Dependency حقن!" لكن ليس لدى الكثير من المطورين في مجتمع CodeIgniter خبرة حقيقية في هذه العملية، لأنها معقدة للغاية.

حقيقة أخرى حول إطار عمل Fuel PHP هي أن الأساليب الثابتة في الغالب تعمل كواجهة. على سبيل المثال، لا يزال المنطق يواجه مشاكل مع الإحصائيات، خاصة عندما يتعلق الأمر بمفهوم HMVC.

هذا هو الكود الكاذب الذي لم أستخدمه في FuelPHP منذ الإصدار 1.1:
تعمل فئة ControllerA على توسيع وحدة التحكم (الوظيفة العامة action_foo() ( echo Input::get("param"); ​​​​)) )
كود قياسي جدا. هذه الطريقة سوف تخرج القيمة ?شريط=في الطريقة.

ماذا يحدث عندما نقوم بتقديم طلب HMVC لهذه الطريقة؟
توسع فئة ControllerB وحدة التحكم (الوظيفة العامة action_baz() ( echo Input::get("param"); ​​​echo " & "; echo request::forge("controllera/foo?param=val1")->execute() ; ) )
عن طريق الاتصال في المتصفح تحكم ب/باز، سترى الإخراج "val1" ولكن إذا كتبت Controllerb/baz?param=override، ثم احصل على كلا الاستدعاءين للحصول على الطريقة التي تُرجع نفس القيمة.

ملاءمة

لن يمنحك الرمز العالمي أي علاقة. المثال خير من كل كلمة:
$this->request->input->get("param");
سيحتوي الكائن المطلوب على مثيل جديد تمامًا لكل طلب، ثم سيتم إنشاء كائن إدخال لكل طلب يحتوي فقط على بيانات الإدخال للطلب المحدد. ينطبق هذا على خطط FuelPHP 2.0 للعمل وحل مشكلة حقن التبعية بالإضافة إلى مشكلات HMVC.

ماذا عن بناء الجملة الخام؟

لا يعاني مطورو Symfony أو Zend من هذا، لكن أولئك الذين يستخدمون CodeIgniter سيعانون من كوابيس حول "العودة إلى PHP 4" لفترة طويلة.

يشير $this دائمًا إلى الكائن "الحالي"، وبالتأكيد لا ينبغي عليك استخدامه للوصول إلى التعليمات البرمجية العالمية.

قد يبدو $this->request->input->get() كنموذج طويل من بناء جملة CodeIgniter، لكننا في الواقع مجرد وحدة تحكم. عندما تقوم وحدة التحكم بإنشاء استعلام جديد متداخل داخلها، يتلقى منشئ الاستعلام أيضًا مثيلًا كمدخل.

إذا كنت في نموذج أو فئة أخرى، فإن الوصول مثل $this->request->input->foo() لن يعمل لأن $this ليس وحدة تحكم.

يُنشئ إنشاء Input::get("foo") واجهة للمثيلات المنطقية في الخلفية. لكن هذا لا يحل المشكلات المتعلقة بتشغيل الكود العالمي. أولئك الذين هم الأكثر كسلاً عند اختبار التطبيقات يمكنهم التبديل بين الوضعين دون الحاجة إلى استخدام إطار عمل جديد بالكامل.

يوجد مقطع فيديو رائع من Taylor Otwell (المنشئ أو laravel 4) يصف فيه كيف يمكنك استبدال الكود الثابت بمثيل واحد تم اختباره من خلال حاوية DiC الخاصة به.

Laravel 4 - حقن وحدة تحكم IoC واختبار الوحدة من UserScape على Vimeo.

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

على هذه الملاحظة الحزينة..

أنا حاليًا بصدد تحويل PyroCMS من CodeIgniter إلى Laravel. إن محاولة الانتقال مباشرة من كود PHP 4 العالمي إلى حقن التبعية المثالي هو انتحار مطلق. الخطوة المتوسطة قبل استخدام أداة تحميل CI هي استخدام كود التحميل التلقائي PHP 5 وPSR-2 مع مجموعة من الطرق الثابتة. حسنًا، في الوقت الحالي ما زلنا في CodeIgniter.

يمكن توضيح الانتقال من الكود الثابت إلى كود DiC بسهولة عندما ننتقل أخيرًا إلى Laravel.

يمثل الانتقال من كود CodeIgniter المقترن بإحكام إلى PSR-2 القابل للاختبار تحديًا كبيرًا. فريق Pyro في طريقه - وسيكون ملحميًا.

Reg.ru: المجالات والاستضافة

أكبر مزود مسجل واستضافة في روسيا.

أكثر من 2 مليون اسم نطاق في الخدمة.

الترويج، بريد المجال، حلول الأعمال.

لقد قام أكثر من 700 ألف عميل حول العالم باختيارهم بالفعل.

* مرر الماوس فوق لإيقاف التمرير مؤقتًا.

العودة إلى الأمام

الأساليب والخصائص الثابتة في PHP

في المواد السابقة، أتقننا القدرات الأساسية للبرمجة الموجهة للكائنات في PHP وننتقل الآن إلى دراسة الجوانب الأكثر تعقيدًا وإثارة للاهتمام.

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

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

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

Class StaticExample ( static public $aNum = 0; static public function sayHello() ( print "Hello!"; ) )

الأساليب الثابتةهي وظائف تستخدم في سياق فئة. هم أنفسهم لا يستطيعون الوصول إلى أي خصائص فئة عادية، لأن هذه الخصائص تنتمي إلى الكائنات.

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

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

طباعة مثال ثابت::$aNum; StaticExample::sayHello();

يجب أن تكون على دراية ببناء الجملة هذا من أساسيات OOP في PHP. استخدمنا البناء "::" مع الكلمة الرئيسية الأبوينمن أجل الوصول إلى الطريقة التي تم تجاوزها للفئة الأصل.

الآن، كما كان الحال في السابق، نشير إلى الفئة، وليس البيانات الموجودة في الكائن. يمكنك استخدام الكلمة الأساسية في رمز الفصل الأبوينمن أجل الوصول إلى الفئة الفائقة دون استخدام اسم الفئة.

للوصول إلى طريقة أو خاصية ثابتة من نفس الفئة (وليس من فئة فرعية)، سوف نستخدم الكلمة الأساسية الذات.

الكلمة الرئيسية الذاتيتم استخدامه للوصول إلى الفئة الحالية والمتغير الزائف $هذا- إلى الكائن الحالي. لذلك من خارج الصف StaticExampleنصل إلى العقار $aNumباستخدام اسم الفئة الخاصة به.

مثال ثابت::$aNum;

وداخل الفصل StaticExampleيمكنك استخدام الكلمات الرئيسية الذات.

Class StaticExample ( static public $aNum = 0; static public function sayHello() ( self::$aNum++; print "Hello! (" . self::$aNum . ")\n"; ) )

ملحوظة:باستثناء عند الوصول إلى أسلوب تم تجاوزه لفئة أصل، يجب دائمًا استخدام البنية "::" فقط للوصول إلى الأساليب أو الخصائص الثابتة.

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

لماذا تستخدم طريقة أو خاصية ثابتة على الإطلاق؟

والآن وصلنا إلى السؤال الأهم. الحقيقة هي أن العناصر الثابتة لها عدد من الخصائص المفيدة.

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

ثانيًا، خاصية ثابتة متاحة لكل مثيل لكائن من تلك الفئة. لذلك، يمكنك تحديد القيم التي يجب أن تكون متاحة لجميع الكائنات من نوع معين.

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

لتوضيح ذلك، دعونا ننشئ طريقة ثابتة للفئة ShopProductوالتي سوف تقوم بإنشاء مثيل للكائنات تلقائيًا ShopProduct. دعونا نحدد جدولاً باستخدام SQLite منتجاتبالطريقة الآتية:

إنشاء منتجات الجدول (معرف INTEGER PRIMARY KEY AUTOINCREMENT، اكتب TEXT، TEXT، الاسم الرئيسي TEXT، عنوان TEXT، تعويم السعر، عدد الصفحات int، طول التشغيل int، خصم int)

الآن دعونا ننشئ طريقة الحصول على مثيل ()، والذي يتم تمرير معرف سلسلة وكائن من النوع PDO. سيتم استخدامها لاستخراج صف من جدول قاعدة البيانات، بناءً على نوع الكائن ShopProduct، عاد إلى برنامج الاتصال.

يمكننا إضافة هذه الأساليب إلى الفصل ShopProductالذي تم إنشاؤه لنا في المواد السابقة. كما تعلم على الأرجح، PDO تعني PHP Data Object. توفر فئة PDO واجهة عامة لتطبيقات قواعد البيانات المختلفة.

// Class ShopProduct public $id = 0; public function setID($id) ( $this->id = $id; ) // ... الوظيفة الثابتة العامة getInstance($id, PDO $pdo) ( $stmt = $pdo->prepare("select * from Products أين المعرف =؟")؛ $result = $stmt->execute(array($id)); $row = $stmt->fetch(); if (empty($row)) ( return null; ) if ($ صف["type"] == "كتاب") ( $product = new BookProduct($row["title"], $row["firstname"], $row["mainname"], $row["price"] , $row["numpages"] ) else if ($row["type"] == "cd") ( $product = new CdProduct($row["title"], $row["firstname"], $row ["mainname"], $row["price"], $row["playlength"] ) else ( $product = new ShopProduct($row["title"], $row["firstname"], $row[" الاسم الرئيسي"], $row["price"]); $product->setId($row["id"]); $product->setDiscount($row["discount"]); المنتج; ) // .. .

كما ترون، الطريقة الحصول على مثيل ()إرجاع كائن من النوع ShopProduct، وهو "ذكي" بدرجة كافية، بناءً على قيمة الحقل يكتبإنشاء كائن بالخصائص المطلوبة.

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

في الواقع، ربما ينبغي علينا تغليف كائن PDO في فئة تضمن هذا السلوك. سنعود إلى هذه المشكلة في إحدى موادنا المستقبلية.

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

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

$dsn = "sqlite://home/bob/projects/products.db"; $pdo = new PDO($dsn, null, null); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $obj = ShopProduct::getInstance(1, $pdo);

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

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


خصائص دائمة

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

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

على عكس العقارات العادية، لا يسبق اسم العقار الدائم علامة الدولار. حسب التقليد، غالبًا ما يتم إعطاؤهم أسماء تتكون من أحرف كبيرة، كما في المثال التالي:

فئة ShopProduct ( const AVAILABLE = 0; const OUT_OF_STOCK = 1; // ...

يمكن أن تحتوي الخصائص الدائمة فقط على قيم خاصة بالنوع البدائي. لا يمكن تعيين كائن ثابت.

مثل الخصائص الثابتة، يتم الوصول إلى الخصائص المستمرة من خلال الفئة وليس من خلال مثيل الكائن. كما يتم تعريف الثابت بدون علامة الدولار، فهو أيضًا لا يتطلب أي رمز بادئ عند الوصول إليه.

متجر الطباعة المنتج::متاح؛

ستؤدي محاولة تعيين قيمة لثابت بعد الإعلان عنه إلى حدوث خطأ أثناء مرحلة التحليل.

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

بهذا نختتم هذا المقال وسنتحدث عنه في المقال التالي.

هل أعجبتك المادة وتريد أن تشكرني؟
مجرد مشاركتها مع أصدقائك وزملائك!


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

دعونا نحاول تفكيك أحد هذه الأسئلة "قطعة قطعة" - ماذا تعني كلمة "ثابت" في PHP ولماذا يتم استخدامها؟

الكلمة الأساسية الثابتة لها ثلاثة معاني مختلفة في PHP. دعونا ننظر إليها بالترتيب الزمني، كما ظهرت في اللغة.

القيمة الأولى هي متغير محلي ثابت

الدالة foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 فو(); // 0 فو(); // 0

في PHP، المتغيرات محلية. هذا يعني أن المتغير المحدد والمعطى قيمة داخل دالة (طريقة) موجود فقط أثناء تنفيذ تلك الوظيفة (الطريقة). عند الخروج من الطريقة، يتم تدمير المتغير المحلي، وعند إعادة الدخول يتم إنشاؤه من جديد. في الكود أعلاه، مثل هذا المتغير المحلي هو المتغير $a - وهو موجود فقط داخل الدالة foo() ويتم إنشاؤه من جديد في كل مرة يتم فيها استدعاء هذه الدالة. إن زيادة متغير في هذا الكود لا معنى له، لأنه في السطر التالي من الكود، ستنتهي الوظيفة من عملها وستفقد قيمة المتغير. بغض النظر عن عدد المرات التي نستدعي فيها الدالة foo()، فستخرج دائمًا 0...

ومع ذلك، كل شيء يتغير إذا وضعنا الكلمة الأساسية الثابتة قبل المهمة:

الوظيفة foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 فو(); // 1 فو(); // 2

الكلمة الأساسية الثابتة، المكتوبة قبل تعيين قيمة لمتغير محلي، لها التأثيرات التالية:

  1. يتم تنفيذ المهمة مرة واحدة فقط، عند أول استدعاء للوظيفة
  2. يتم حفظ قيمة المتغير المحدد بهذه الطريقة بعد انتهاء الوظيفة.
  3. في الاستدعاءات اللاحقة للوظيفة، بدلاً من التعيين، يتلقى المتغير القيمة المخزنة مسبقًا
هذا الاستخدام للكلمة ثابت يسمى المتغير المحلي الثابت.
مطبات المتغيرات الثابتة
وبطبيعة الحال، كما هو الحال دائما في PHP، هناك بعض المزالق.

الحجر الأول هو أنه يمكن تخصيص الثوابت أو التعبيرات الثابتة فقط لمتغير ثابت.إليك الكود:
ثابت $a = شريط();
سوف يؤدي حتما إلى خطأ محلل. لحسن الحظ، بدءًا من الإصدار 5.6، أصبح من الممكن تعيين ليس فقط الثوابت، ولكن أيضًا التعبيرات الثابتة (على سبيل المثال، "1+2" ​​أو "")، أي التعبيرات التي لا تعتمد على تعليمات برمجية أخرى ويمكن حسابها في مرحلة التجميع

الحجر الثاني هو أن الأساليب موجودة في نسخة واحدة.
هنا كل شيء أكثر تعقيدًا بعض الشيء. لفهم الجوهر، إليك الكود:
class A ( public function foo() ( static $x = 0; echo ++$x; ) ) $a1 = new A; $a2 = جديد أ؛ $a1->foo(); // 1 $a2->foo(); // 2 $a1->foo(); // 3 $a2->foo(); // 4
على عكس التوقع البديهي "كائنات مختلفة - طرق مختلفة"، نرى بوضوح في هذا المثال أن الأساليب الديناميكية في PHP "لا تتضاعف". حتى لو كان لدينا مائة كائن من هذه الفئة، فستكون الطريقة موجودة في حالة واحدة فقط؛ سيتم طرح $this مختلفًا فيها مع كل استدعاء.

يمكن أن يكون هذا السلوك غير متوقع بالنسبة للمطور غير المستعد له ويمكن أن يكون مصدرًا للأخطاء. تجدر الإشارة إلى أن وراثة الفئة (والطريقة) تؤدي إلى إنشاء طريقة جديدة:

الفئة A ( الوظيفة العامة foo() ( static $x = 0; echo ++$x; ) ) الفئة B تمتد A ( ) $a1 = new A; $b1 = جديد B؛ $a1->foo(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

الخلاصة: الأساليب الديناميكية في PHP موجودة في سياق الفئات، وليس الكائنات. وفقط في وقت التشغيل يحدث الاستبدال "$this = current_object".

المعنى الثاني هو الخصائص الثابتة وطرق الفئات

في نموذج كائن PHP، من الممكن تعيين الخصائص والأساليب ليس فقط للكائنات - مثيلات الفئة، ولكن أيضًا للفئة ككل. يتم استخدام الكلمة الأساسية الثابتة أيضًا لهذا:

الفئة أ ( public static $x = "foo"; اختبار الوظيفة الثابتة العامة() ( return 42; ) ) echo A::$x; // "foo" صدى A::test(); // 42
للوصول إلى هذه الخصائص والأساليب، يتم استخدام بنيات النقطتين المزدوجتين ("Paamayim Nekudotayim")، مثل CLASS_NAME::$Variablename وCLASS_NAME::Methodname().

وغني عن القول أن الخصائص الثابتة والأساليب الثابتة لها خصائصها ومزالقها الخاصة التي تحتاج إلى معرفتها.

الميزة الأولى عادية - لا يوجد $this.في الواقع، ينبع هذا من تعريف الطريقة الثابتة - نظرًا لأنها مرتبطة بفئة، وليس بكائن، فإن المتغير الزائف $this، الذي يشير إلى الكائن الحالي في الأساليب الديناميكية، غير متوفر. وهو أمر منطقي تماما.

ومع ذلك، عليك أن تعرف أنه، على عكس اللغات الأخرى، لا تكتشف PHP الحالة "$this is مكتوب بطريقة ثابتة" في مرحلة التحليل أو التجميع. لا يمكن أن يحدث خطأ كهذا إلا في وقت التشغيل إذا حاولت تنفيذ تعليمات برمجية باستخدام $this داخل طريقة ثابتة.

كود مثل هذا:
الفئة أ ( public $id = 42; وظيفة عامة ثابتة foo() ( echo $this->id; ) )
لن يسبب أي أخطاء، طالما أنك لا تحاول استخدام التابع foo() بشكل غير مناسب:
$a = جديد أ؛ $a->foo(); (واحصل على الفور على "خطأ فادح: استخدام $this عندما لا يكون في سياق الكائن")

الميزة الثانية هي أن الساكنة ليست بديهية!
class A ( static public function foo() ( echo 42; ) ) $a = new A; $a->foo();
هذا كل شيء، نعم. يمكن استدعاء الطريقة الثابتة، إذا لم تحتوي على $this في الكود، في سياق ديناميكي، مثل طريقة الكائن. هذا ليس خطأ في PHP.

والعكس ليس صحيحاً تماماً:
class A ( public function foo() ( echo 42; ) ) A::foo();
يمكن تنفيذ الطريقة الديناميكية التي لا تستخدم $this في سياق ثابت. ومع ذلك، ستتلقى تحذيرًا "لا ينبغي استدعاء الطريقة غير الثابتة A::foo() بشكل ثابت" على المستوى E_STRICT. الأمر متروك لك لتقرر ما إذا كنت تريد اتباع معايير التعليمات البرمجية بدقة أو منع التحذيرات. الأول هو الأفضل بطبيعة الحال.

وبالمناسبة، كل ما هو مكتوب أعلاه ينطبق فقط على الأساليب. يعد استخدام خاصية ثابتة عبر "->" أمرًا مستحيلًا ويؤدي إلى خطأ فادح.

المعنى الثالث، الذي يبدو أنه الأصعب، هو الربط الثابت المتأخر

مطورو لغة PHP لم يتوقفوا عند معنيين للكلمة “static” ففي الإصدار 5.3 أضافوا “ميزة” أخرى للغة يتم تنفيذها بنفس الكلمة! يطلق عليه "الربط الثابت المتأخر" أو LSB (الربط الثابت المتأخر).

أسهل طريقة لفهم جوهر LSB هي من خلال أمثلة بسيطة:

نموذج الفئة ( public static $table = "table"; الوظيفة الثابتة العامة getTable() ( return self::$table; ) ) echo Model::getTable(); // "طاولة"
الكلمة الأساسية self في PHP تعني دائمًا "اسم الفئة التي كتبت فيها هذه الكلمة". في هذه الحالة، يتم استبدال self بفئة Model، ويتم استبدال self::$table بـ Model::$table.
تسمى ميزة اللغة هذه "الربط الثابت المبكر". لماذا في وقت مبكر؟ لأن الربط الذاتي واسم فئة محدد لا يحدث في وقت التشغيل، ولكن في المراحل السابقة - تحليل التعليمات البرمجية وتجميعها. حسنًا، "ثابت" - لأننا نتحدث عن خصائص وأساليب ثابتة.

دعونا نغير الكود الخاص بنا قليلاً:

نموذج الفئة ( public static $table = "table"; الوظيفة الثابتة العامة getTable() ( return self::$table; ) ) فئة المستخدم يمتد النموذج ( public static $table = "users"; ) echo User::getTable() ; // "طاولة"

أنت الآن تفهم سبب تصرف PHP بشكل غير بديهي في هذه الحالة. self كانت مرتبطة بفئة Model عندما لم يكن هناك شيء معروف عن فئة User، وبالتالي تشير إلى Model.

ماذا علي أن أفعل؟

لحل هذه المعضلة، تم اختراع آلية ربط "متأخرة" في مرحلة التشغيل. إنه يعمل بكل بساطة - فقط اكتب "ثابت" بدلاً من كلمة "نفسي" وسيتم إنشاء الاتصال مع الفئة التي تستدعي هذا الرمز، وليس مع الفئة التي تم كتابتها فيها:
نموذج الفئة ( public static $table = "table"; الوظيفة الثابتة العامة getTable() ( return static::$table; ) ) فئة المستخدم يمتد النموذج ( public static $table = "users"; ) echo User::getTable() ; // "المستخدمون"

هذا هو "الربط الثابت المتأخر" الغامض.

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

مقابلات سعيدة!

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

دعونا نحاول تفكيك أحد هذه الأسئلة "قطعة قطعة" - ماذا تعني كلمة "ثابت" في PHP ولماذا يتم استخدامها؟

الكلمة الأساسية الثابتة لها ثلاثة معاني مختلفة في PHP. دعونا ننظر إليها بالترتيب الزمني، كما ظهرت في اللغة.

القيمة الأولى هي متغير محلي ثابت

الدالة foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 فو(); // 0 فو(); // 0

في PHP، المتغيرات محلية. هذا يعني أن المتغير المحدد والمعطى قيمة داخل دالة (طريقة) موجود فقط أثناء تنفيذ تلك الوظيفة (الطريقة). عند الخروج من الطريقة، يتم تدمير المتغير المحلي، وعند إعادة الدخول يتم إنشاؤه من جديد. في الكود أعلاه، مثل هذا المتغير المحلي هو المتغير $a - وهو موجود فقط داخل الدالة foo() ويتم إنشاؤه من جديد في كل مرة يتم فيها استدعاء هذه الدالة. إن زيادة متغير في هذا الكود لا معنى له، لأنه في السطر التالي من الكود، ستنتهي الوظيفة من عملها وستفقد قيمة المتغير. بغض النظر عن عدد المرات التي نستدعي فيها الدالة foo()، فستخرج دائمًا 0...

ومع ذلك، كل شيء يتغير إذا وضعنا الكلمة الأساسية الثابتة قبل المهمة:

الوظيفة foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 فو(); // 1 فو(); // 2

الكلمة الأساسية الثابتة، المكتوبة قبل تعيين قيمة لمتغير محلي، لها التأثيرات التالية:

  1. يتم تنفيذ المهمة مرة واحدة فقط، عند أول استدعاء للوظيفة
  2. يتم حفظ قيمة المتغير المحدد بهذه الطريقة بعد انتهاء الوظيفة.
  3. في الاستدعاءات اللاحقة للوظيفة، بدلاً من التعيين، يتلقى المتغير القيمة المخزنة مسبقًا
هذا الاستخدام للكلمة ثابت يسمى المتغير المحلي الثابت.
مطبات المتغيرات الثابتة
وبطبيعة الحال، كما هو الحال دائما في PHP، هناك بعض المزالق.

الحجر الأول هو أنه يمكن تخصيص الثوابت أو التعبيرات الثابتة فقط لمتغير ثابت.إليك الكود:
ثابت $a = شريط();
سوف يؤدي حتما إلى خطأ محلل. لحسن الحظ، بدءًا من الإصدار 5.6، أصبح من الممكن تعيين ليس فقط الثوابت، ولكن أيضًا التعبيرات الثابتة (على سبيل المثال، "1+2" ​​أو "")، أي التعبيرات التي لا تعتمد على تعليمات برمجية أخرى ويمكن حسابها في مرحلة التجميع

الحجر الثاني هو أن الأساليب موجودة في نسخة واحدة.
هنا كل شيء أكثر تعقيدًا بعض الشيء. لفهم الجوهر، إليك الكود:
class A ( public function foo() ( static $x = 0; echo ++$x; ) ) $a1 = new A; $a2 = جديد أ؛ $a1->foo(); // 1 $a2->foo(); // 2 $a1->foo(); // 3 $a2->foo(); // 4
على عكس التوقع البديهي "كائنات مختلفة - طرق مختلفة"، نرى بوضوح في هذا المثال أن الأساليب الديناميكية في PHP "لا تتضاعف". حتى لو كان لدينا مائة كائن من هذه الفئة، فستكون الطريقة موجودة في حالة واحدة فقط؛ سيتم طرح $this مختلفًا فيها مع كل استدعاء.

يمكن أن يكون هذا السلوك غير متوقع بالنسبة للمطور غير المستعد له ويمكن أن يكون مصدرًا للأخطاء. تجدر الإشارة إلى أن وراثة الفئة (والطريقة) تؤدي إلى إنشاء طريقة جديدة:

الفئة A ( الوظيفة العامة foo() ( static $x = 0; echo ++$x; ) ) الفئة B تمتد A ( ) $a1 = new A; $b1 = جديد B؛ $a1->foo(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

الخلاصة: الأساليب الديناميكية في PHP موجودة في سياق الفئات، وليس الكائنات. وفقط في وقت التشغيل يحدث الاستبدال "$this = current_object".

المعنى الثاني هو الخصائص الثابتة وطرق الفئات

في نموذج كائن PHP، من الممكن تعيين الخصائص والأساليب ليس فقط للكائنات - مثيلات الفئة، ولكن أيضًا للفئة ككل. يتم استخدام الكلمة الأساسية الثابتة أيضًا لهذا:

الفئة أ ( public static $x = "foo"; اختبار الوظيفة الثابتة العامة() ( return 42; ) ) echo A::$x; // "foo" صدى A::test(); // 42
للوصول إلى هذه الخصائص والأساليب، يتم استخدام بنيات النقطتين المزدوجتين ("Paamayim Nekudotayim")، مثل CLASS_NAME::$Variablename وCLASS_NAME::Methodname().

وغني عن القول أن الخصائص الثابتة والأساليب الثابتة لها خصائصها ومزالقها الخاصة التي تحتاج إلى معرفتها.

الميزة الأولى عادية - لا يوجد $this.في الواقع، ينبع هذا من تعريف الطريقة الثابتة - نظرًا لأنها مرتبطة بفئة، وليس بكائن، فإن المتغير الزائف $this، الذي يشير إلى الكائن الحالي في الأساليب الديناميكية، غير متوفر. وهو أمر منطقي تماما.

ومع ذلك، عليك أن تعرف أنه، على عكس اللغات الأخرى، لا تكتشف PHP الحالة "$this is مكتوب بطريقة ثابتة" في مرحلة التحليل أو التجميع. لا يمكن أن يحدث خطأ كهذا إلا في وقت التشغيل إذا حاولت تنفيذ تعليمات برمجية باستخدام $this داخل طريقة ثابتة.

كود مثل هذا:
الفئة أ ( public $id = 42; وظيفة عامة ثابتة foo() ( echo $this->id; ) )
لن يسبب أي أخطاء، طالما أنك لا تحاول استخدام التابع foo() بشكل غير مناسب:
$a = جديد أ؛ $a->foo(); (واحصل على الفور على "خطأ فادح: استخدام $this عندما لا يكون في سياق الكائن")

الميزة الثانية هي أن الساكنة ليست بديهية!
class A ( static public function foo() ( echo 42; ) ) $a = new A; $a->foo();
هذا كل شيء، نعم. يمكن استدعاء الطريقة الثابتة، إذا لم تحتوي على $this في الكود، في سياق ديناميكي، مثل طريقة الكائن. هذا ليس خطأ في PHP.

والعكس ليس صحيحاً تماماً:
class A ( public function foo() ( echo 42; ) ) A::foo();
يمكن تنفيذ الطريقة الديناميكية التي لا تستخدم $this في سياق ثابت. ومع ذلك، ستتلقى تحذيرًا "لا ينبغي استدعاء الطريقة غير الثابتة A::foo() بشكل ثابت" على المستوى E_STRICT. الأمر متروك لك لتقرر ما إذا كنت تريد اتباع معايير التعليمات البرمجية بدقة أو منع التحذيرات. الأول هو الأفضل بطبيعة الحال.

وبالمناسبة، كل ما هو مكتوب أعلاه ينطبق فقط على الأساليب. يعد استخدام خاصية ثابتة عبر "->" أمرًا مستحيلًا ويؤدي إلى خطأ فادح.

المعنى الثالث، الذي يبدو أنه الأصعب، هو الربط الثابت المتأخر

مطورو لغة PHP لم يتوقفوا عند معنيين للكلمة “static” ففي الإصدار 5.3 أضافوا “ميزة” أخرى للغة يتم تنفيذها بنفس الكلمة! يطلق عليه "الربط الثابت المتأخر" أو LSB (الربط الثابت المتأخر).

أسهل طريقة لفهم جوهر LSB هي من خلال أمثلة بسيطة:

نموذج الفئة ( public static $table = "table"; الوظيفة الثابتة العامة getTable() ( return self::$table; ) ) echo Model::getTable(); // "طاولة"
الكلمة الأساسية self في PHP تعني دائمًا "اسم الفئة التي كتبت فيها هذه الكلمة". في هذه الحالة، يتم استبدال self بفئة Model، ويتم استبدال self::$table بـ Model::$table.
تسمى ميزة اللغة هذه "الربط الثابت المبكر". لماذا في وقت مبكر؟ لأن الربط الذاتي واسم فئة محدد لا يحدث في وقت التشغيل، ولكن في المراحل السابقة - تحليل التعليمات البرمجية وتجميعها. حسنًا، "ثابت" - لأننا نتحدث عن خصائص وأساليب ثابتة.

دعونا نغير الكود الخاص بنا قليلاً:

نموذج الفئة ( public static $table = "table"; الوظيفة الثابتة العامة getTable() ( return self::$table; ) ) فئة المستخدم يمتد النموذج ( public static $table = "users"; ) echo User::getTable() ; // "طاولة"

أنت الآن تفهم سبب تصرف PHP بشكل غير بديهي في هذه الحالة. self كانت مرتبطة بفئة Model عندما لم يكن هناك شيء معروف عن فئة User، وبالتالي تشير إلى Model.

ماذا علي أن أفعل؟

لحل هذه المعضلة، تم اختراع آلية ربط "متأخرة" في مرحلة التشغيل. إنه يعمل بكل بساطة - فقط اكتب "ثابت" بدلاً من كلمة "نفسي" وسيتم إنشاء الاتصال مع الفئة التي تستدعي هذا الرمز، وليس مع الفئة التي تم كتابتها فيها:
نموذج الفئة ( public static $table = "table"; الوظيفة الثابتة العامة getTable() ( return static::$table; ) ) فئة المستخدم يمتد النموذج ( public static $table = "users"; ) echo User::getTable() ; // "المستخدمون"

هذا هو "الربط الثابت المتأخر" الغامض.

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

مقابلات سعيدة!

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

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

  • ولا يقدم أي مزايا مقارنة بالبرمجة الإجرائية
  • لا تتخلى عن الأشياء
  • وجود أعضاء فئة ثابتة = الموت للاختبارات
على الرغم من أن هذه المقالة تتحدث عن لغة PHP، إلا أن المفاهيم تنطبق على اللغات الأخرى أيضًا.

التبعيات

عادةً ما يعتمد الرمز على رمز آخر. على سبيل المثال:

$foo = substr($bar, 42);
يعتمد هذا الرمز على المتغير $bar والدالة substr. $bar هو مجرد متغير محلي محدد بشكل أعلى قليلاً في نفس الملف وفي نفس النطاق. substr هي وظيفة PHP الأساسية. كل شيء بسيط هنا.

الآن، إليك مثال:

فئة BloomFilter ( ... الوظيفة العامة __construct($m, $k) ( ... ) الوظيفة الثابتة العامة getK($m, $n) ( return ceil(($m / $n) * log(2)); ) ... )
توفر هذه الوظيفة المساعدة الصغيرة ببساطة غلافًا لخوارزمية محددة تساعد في حساب رقم جيد للوسيطة $k المستخدمة في المُنشئ. لأن يجب أن يتم استدعاؤه قبل إنشاء مثيل الفئة، ويجب أن يكون ثابتًا. لا تحتوي هذه الخوارزمية على تبعيات خارجية ومن غير المرجح أن يتم استبدالها. يتم استخدامه مثل هذا:

$م = 10000؛ $ ن = 2000؛ $b = new BloomFilter($m, BloomFilter::getK($m, $n));
هذا لا يخلق أي تبعيات إضافية. الطبقة تعتمد على نفسها.

  • منشئ بديل. وخير مثال على ذلك هو فئة DateTime المضمنة في PHP. يمكن إنشاء مثيله بطريقتين مختلفتين:

    $date = new DateTime("2012-11-04"); $date = DateTime::createFromFormat("d-m-Y", "04-11-2012");
    في كلتا الحالتين، ستكون النتيجة نسخة DateTime، وفي كلتا الحالتين يرتبط الكود بفئة DateTime بطريقة أو بأخرى. الطريقة الثابتة DateTime::createFromFormat هي منشئ كائن بديل يُرجع نفس الشيء مثل DateTime الجديد، ولكن مع وظائف إضافية. حيث يمكنك كتابة فئة جديدة، يمكنك أيضًا كتابة Class::method() . هذا لا يخلق أي تبعيات جديدة.

  • تؤثر الاستخدامات الأخرى للطرق الثابتة على الربط وقد تنشئ تبعيات ضمنية.

    كلمة عن التجريد

    لماذا كل هذه الضجة مع الإدمان؟ القدرة على التجريد! مع نمو منتجك، يزداد تعقيده. والتجريد هو مفتاح إدارة التعقيد.

    على سبيل المثال، لديك فئة تطبيق تمثل التطبيق الخاص بك. يتحدث إلى فئة المستخدم، وهو تمثيل للمستخدم. الذي يستقبل البيانات من قاعدة البيانات. تحتاج فئة قاعدة البيانات إلى DatabaseDriver. يحتاج DatabaseDriver إلى معلمات الاتصال. وما إلى ذلك وهلم جرا. إذا قمت فقط باستدعاء Application::start() بشكل ثابت، والذي سوف يستدعي User::getData() بشكل ثابت، والذي سوف يستدعي قاعدة البيانات بشكل ثابت، وما إلى ذلك، وتأمل أن تقوم كل طبقة بفرز تبعياتها، فقد ينتهي بك الأمر إلى فوضى رهيبة إذا حدث خطأ ما ليس بهذه الطريقة. من المستحيل تخمين ما إذا كان استدعاء Application::start() سيعمل لأنه ليس من الواضح على الإطلاق كيف ستتصرف التبعيات الداخلية. والأسوأ من ذلك أن الطريقة الوحيدة للتأثير على سلوك Application::start() هي تغيير الكود المصدري لتلك الفئة ورمز الفئات التي تستدعيها ورمز الفئات التي تستدعي تلك الفئات... في المنزل الذي بناه جاك.

    الطريقة الأكثر فعالية عند إنشاء تطبيقات معقدة هي إنشاء أجزاء منفصلة يمكنك البناء عليها لاحقًا. الأجزاء التي يمكنك التوقف عن التفكير فيها، والتي يمكنك الوثوق بها. على سبيل المثال، عند استدعاء قاعدة بيانات ثابتة::fetchAll(...) ، ليس هناك ضمان بأن الاتصال بقاعدة البيانات قد تم إنشاؤه بالفعل أو أنه سيتم إنشاؤه.

    الوظيفة (قاعدة البيانات $قاعدة البيانات) (...)
    إذا تم تنفيذ التعليمات البرمجية الموجودة داخل هذه الوظيفة، فهذا يعني أنه تم تمرير مثيل قاعدة البيانات بنجاح، مما يعني أنه تم إنشاء مثيل كائن قاعدة البيانات بنجاح. إذا تم تصميم فئة قاعدة البيانات بشكل صحيح، فيمكنك التأكد من أن وجود مثيل لهذه الفئة يعني القدرة على تنفيذ استعلامات قاعدة البيانات. إذا لم يكن هناك مثيل للفئة، فلن يتم تنفيذ نص الوظيفة. هذا يعني أن الوظيفة لا ينبغي أن تهتم بحالة قاعدة البيانات؛ فئة قاعدة البيانات ستقوم بذلك بنفسها. يتيح لك هذا الأسلوب نسيان التبعيات والتركيز على حل المشكلات.

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

    البرمجة الموجهة نحو الفصل غبية. تعلم كيفية استخدام OOP.