การเตรียมตัวสำหรับการสัมภาษณ์ PHP: คีย์เวิร์ด “static. คุณสมบัติและวิธีการแบบคงที่ในฟังก์ชัน PHP แบบคงที่

Reg.ru: โดเมนและโฮสติ้ง

นายทะเบียนและผู้ให้บริการโฮสติ้งที่ใหญ่ที่สุดในรัสเซีย

มีชื่อโดเมนมากกว่า 2 ล้านชื่อที่ให้บริการ

โปรโมชั่น เมลโดเมน โซลูชั่นทางธุรกิจ

ลูกค้ามากกว่า 700,000 รายทั่วโลกได้ตัดสินใจเลือกแล้ว

*เลื่อนเมาส์ไปเหนือเพื่อหยุดการเลื่อนชั่วคราว

กลับไปข้างหน้า

วิธีการและคุณสมบัติคงที่ใน PHP

ในเอกสารก่อนหน้านี้ เราได้เข้าใจความสามารถพื้นฐานของการเขียนโปรแกรมเชิงวัตถุใน PHP แล้ว และตอนนี้กำลังศึกษาแง่มุมที่ซับซ้อนและน่าสนใจมากขึ้น

ก่อนหน้านี้เรามักจะทำงานกับวัตถุเสมอ เราได้อธิบายคลาสว่าเป็นเทมเพลตที่สร้างอ็อบเจ็กต์ และอ็อบเจ็กต์เป็นส่วนประกอบที่ใช้งานซึ่งเราเรียกใช้เมธอดและมีคุณสมบัติที่เราเข้าถึง

สิ่งนี้นำไปสู่ข้อสรุปว่าในการเขียนโปรแกรมเชิงวัตถุ งานจริงเสร็จสิ้นโดยใช้คลาสอินสแตนซ์ และท้ายที่สุดแล้วคลาสก็เป็นเพียงเทมเพลตสำหรับสร้างวัตถุ

แต่ในความเป็นจริงมันไม่ง่ายขนาดนั้น เราสามารถเข้าถึงทั้งวิธีการและคุณสมบัติในบริบทของคลาสมากกว่าวัตถุ วิธีการและคุณสมบัติดังกล่าวเรียกว่า "คงที่" และต้องประกาศโดยใช้คำหลัก คงที่.

Class StaticExample ( static public $aNum = 0; static public function sayHello() ( พิมพ์ "Hello!"; ) )

วิธีการคงที่เป็นฟังก์ชันที่ใช้ในบริบทของคลาส พวกเขาเองไม่สามารถเข้าถึงคุณสมบัติคลาสปกติใดๆ ได้ เนื่องจากคุณสมบัติดังกล่าวเป็นของอ็อบเจ็กต์

อย่างไรก็ตาม ในบรรดาวิธีการแบบคงที่ ดังที่คุณคงเดาได้อยู่แล้ว คุณสามารถเข้าถึงคุณสมบัติแบบคงที่ได้ และหากคุณเปลี่ยนคุณสมบัติคงที่ อินสแตนซ์ทั้งหมดของคลาสนั้นจะสามารถเข้าถึงค่าใหม่ได้

เนื่องจากองค์ประกอบคงที่เข้าถึงได้ผ่านคลาสและไม่ผ่านอินสแตนซ์ของวัตถุ เราจึงไม่จำเป็นต้องมีตัวแปรที่อ้างอิงถึงวัตถุ แต่จะใช้ชื่อคลาสตามด้วยเครื่องหมายโคลอนสองตัว :::" แทน

พิมพ์ StaticExample::$aNum; ตัวอย่างคงที่::sayHello();

คุณควรจะคุ้นเคยกับไวยากรณ์นี้จากพื้นฐานของ OOP ใน PHP แล้ว เราใช้โครงสร้าง "::" ร่วมกับคำหลัก พ่อแม่เพื่อเข้าถึงเมธอดที่ถูกแทนที่ของคลาสพาเรนต์

ในตอนนี้ เราอ้างถึงคลาส ไม่ใช่ข้อมูลที่มีอยู่ในออบเจ็กต์ คุณสามารถใช้คีย์เวิร์ดในรหัสชั้นเรียนได้ พ่อแม่เพื่อเข้าถึงซูเปอร์คลาสโดยไม่ต้องใช้ชื่อคลาส

ในการเข้าถึงวิธีการหรือคุณสมบัติคงที่จากคลาสเดียวกัน (และไม่ใช่จากคลาสย่อย) เราจะใช้คำหลัก ตัวเอง.

คำสำคัญ ตัวเองใช้เพื่อเข้าถึงคลาสปัจจุบันและตัวแปรหลอก $นี่- ไปยังวัตถุปัจจุบัน ดังนั้นจากนอกชั้นเรียน ตัวอย่างแบบคงที่เราเข้าถึงทรัพย์สิน $aหมายเลขโดยใช้ชื่อคลาสของมัน

ตัวอย่างคงที่::$aNum;

และภายในชั้นเรียน ตัวอย่างแบบคงที่คุณสามารถใช้คำหลักได้ ตัวเอง.

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

โปรดทราบ:ยกเว้นเมื่อเข้าถึงเมธอดที่ถูกแทนที่ของคลาสพาเรนต์ โครงสร้าง "::" ควรใช้เสมอเพื่อเข้าถึงเมธอดหรือคุณสมบัติแบบคงที่เท่านั้น

ตามคำนิยาม วิธีการคงที่จะไม่ถูกเรียกในบริบทของวัตถุ ด้วยเหตุนี้ วิธีการและคุณสมบัติแบบคงที่จึงมักเรียกว่าตัวแปรและคุณสมบัติคลาส ด้วยเหตุนี้ คุณจึงไม่สามารถใช้ตัวแปรหลอกได้ $นี่ภายในวิธีการแบบคงที่

เหตุใดจึงต้องใช้วิธีการหรือคุณสมบัติคงที่เลย?

ตอนนี้เรามาถึงคำถามที่สำคัญที่สุดแล้ว ความจริงก็คือองค์ประกอบคงที่มีลักษณะที่เป็นประโยชน์หลายประการ

ประการแรกสามารถเข้าถึงได้จากทุกที่ในสคริปต์ (หากคุณมีสิทธิ์เข้าถึงชั้นเรียน) ซึ่งหมายความว่าคุณสามารถเข้าถึงฟังก์ชันต่างๆ ได้โดยไม่ต้องส่งอินสแตนซ์ของคลาสจากวัตถุหนึ่งไปยังอีกวัตถุหนึ่ง หรือที่แย่กว่านั้นคือเก็บอินสแตนซ์ของวัตถุไว้ในตัวแปรส่วนกลาง

ประการที่สองคุณสมบัติคงที่สามารถใช้ได้กับทุกอินสแตนซ์ของออบเจ็กต์ของคลาสนั้น ดังนั้นคุณจึงสามารถกำหนดค่าที่ควรมีให้กับออบเจ็กต์ทั้งหมดตามประเภทที่กำหนดได้

และสุดท้าย ประการที่สามความจริงที่ว่าคุณไม่จำเป็นต้องมีอินสแตนซ์ของคลาสเพื่อเข้าถึงคุณสมบัติหรือเมธอดคงที่จะหลีกเลี่ยงการสร้างอินสแตนซ์ของอ็อบเจ็กต์เพื่อการเรียกใช้ฟังก์ชันแบบง่ายเท่านั้น

เพื่อสาธิตสิ่งนี้ เรามาสร้างวิธีการคงที่สำหรับชั้นเรียนกัน ร้านค้าสินค้าซึ่งจะสร้างอินสแตนซ์ของวัตถุโดยอัตโนมัติ ร้านค้าสินค้า- มากำหนดตารางโดยใช้ SQLite กันดีกว่า สินค้าดังต่อไปนี้:

สร้างผลิตภัณฑ์ตาราง (id INTEGER PRIMARY KEY AUTOINCREMENT, พิมพ์ TEXT, ชื่อจริง TEXT, ชื่อหลัก TEXT, ชื่อ TEXT, ราคาลอยตัว, numpages int, playlength int, int ส่วนลด)

ตอนนี้เรามาสร้างวิธีการกัน รับอินสแตนซ์()ซึ่งส่งผ่านตัวระบุสตริงและอ็อบเจ็กต์ประเภท PDO พวกเขาจะใช้เพื่อดึงแถวจากตารางฐานข้อมูลโดยขึ้นอยู่กับประเภทของวัตถุ ร้านค้าสินค้ากลับเข้าสู่โปรแกรมการโทร

เราสามารถเพิ่มวิธีการเหล่านี้ในชั้นเรียนได้ ร้านค้าสินค้าซึ่งถูกสร้างขึ้นสำหรับเราในเนื้อหาก่อนหน้านี้ ดังที่คุณคงทราบแล้วว่า PDO ย่อมาจาก PHP Data Object คลาส PDO จัดเตรียมอินเทอร์เฟซทั่วไปสำหรับแอปพลิเคชันฐานข้อมูลต่างๆ

// Class ShopProduct ส่วนตัว $id = 0; ฟังก์ชั่นสาธารณะ setID($id) ( $this->id = $id; ) // ... ฟังก์ชั่นสาธารณะคงที่ getInstance($id, PDO $pdo) ( $stmt = $pdo->prepare("select * from products โดยที่ id=?"); $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[" mainname"], $row["price"]); $product->setId($row["id"]); $product->setDiscount($row["discount"]); product; ) // .. .

อย่างที่คุณเห็นวิธีการ รับอินสแตนซ์()ส่งคืนวัตถุประเภท ร้านค้าสินค้าและมันก็ "ฉลาด" พอที่จะทำโดยพิจารณาจากค่าของฟิลด์ พิมพ์สร้างวัตถุที่มีคุณสมบัติที่ต้องการ

ฉันละเว้นรหัสการจัดการข้อผิดพลาดโดยเฉพาะเพื่อให้ตัวอย่างกระชับที่สุด ตัวอย่างเช่น ในเวอร์ชันที่ใช้งานจริงของโค้ดนี้ เราไม่สามารถเชื่อใจได้มากนักและถือว่าออบเจ็กต์ PDO ที่ส่งผ่านนั้นได้รับการเตรียมใช้งานอย่างถูกต้องและเชื่อมต่อกับฐานข้อมูลที่ต้องการ

ที่จริงแล้ว เราควรรวมอ็อบเจ็กต์ PDO ไว้ในคลาสที่รับประกันพฤติกรรมนี้ เราจะกลับมาที่ปัญหานี้อีกครั้งในเอกสารในอนาคตของเรา

วิธี รับอินสแตนซ์()มีประโยชน์ในบริบทของคลาสมากกว่าในบริบทของอ็อบเจ็กต์ ช่วยให้เราสามารถแปลงข้อมูลที่อยู่ในฐานข้อมูลเป็นวัตถุได้อย่างง่ายดาย และด้วยเหตุนี้ เราจึงไม่จำเป็นต้องมีอินสแตนซ์ประเภทวัตถุแยกต่างหาก ร้านค้าสินค้า.

เมธอดนี้ไม่ได้ใช้เมธอดหรือคุณสมบัติใดๆ ที่จำเป็นต้องมีอินสแตนซ์แยกต่างหากของออบเจ็กต์ ดังนั้นจึงไม่มีเหตุผลที่จะไม่ประกาศให้เป็นแบบคงที่ จากนั้นเมื่อมีวัตถุ PDO ที่ถูกต้อง เราก็สามารถเรียกวิธีนี้ได้จากทุกที่ในแอปพลิเคชัน

$dsn = "sqlite://home/bob/projects/products.db"; $pdo = 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; // ...

คุณสมบัติถาวรสามารถมีค่าเฉพาะเจาะจงกับประเภทดั้งเดิมเท่านั้น ไม่สามารถกำหนดวัตถุคงที่ได้

เช่นเดียวกับคุณสมบัติคงที่ คุณสมบัติถาวรจะถูกเข้าถึงผ่านคลาสมากกว่าผ่านอินสแตนซ์อ็อบเจ็กต์ เช่นเดียวกับค่าคงที่ที่กำหนดโดยไม่มีเครื่องหมายดอลลาร์ ก็ไม่จำเป็นต้องมีสัญลักษณ์นำหน้าเมื่อเข้าถึง

ร้านพิมพ์สินค้า::AVAILABLE;

การพยายามกำหนดค่าให้กับค่าคงที่หลังจากที่มีการประกาศจะส่งผลให้เกิดข้อผิดพลาดในระหว่างขั้นตอนการแยกวิเคราะห์

ค่าคงที่ควรใช้เมื่อคุณสมบัติต้องสามารถเข้าถึงได้โดยทุกอินสแตนซ์ของคลาส และเมื่อมูลค่าของคุณสมบัติต้องได้รับการแก้ไขและไม่เปลี่ยนรูป

นี่เป็นการสรุปบทความนี้ และเราจะพูดถึงในบทความหน้า

คุณชอบเนื้อหาและต้องการขอบคุณฉันหรือไม่?
เพียงแบ่งปันกับเพื่อนและเพื่อนร่วมงานของคุณ!


ไม่ใช่เรื่องลับที่ผู้คนชอบถามคำถามที่ยุ่งยากในระหว่างการสัมภาษณ์ ไม่เพียงพอเสมอไป ไม่เกี่ยวข้องกับความเป็นจริงเสมอไป แต่ความจริงยังคงเป็นข้อเท็จจริง - พวกเขาถาม แน่นอนว่าคำถามนั้นแตกต่างออกไป และบางครั้งคำถามที่เมื่อมองแวบแรกดูเหมือนโง่สำหรับคุณ จริงๆ แล้วมีจุดมุ่งหมายเพื่อทดสอบว่าคุณรู้จักภาษาที่คุณกำลังเขียนดีแค่ไหน

ลองแยกคำถามเหล่านี้ออกทีละข้อ - คำว่า "คงที่" หมายถึงอะไรใน PHP และเหตุใดจึงใช้?

คำหลักแบบคงที่มีความหมายที่แตกต่างกันสามประการใน PHP ลองดูตามลำดับเวลาตามที่ปรากฏในภาษา

ค่าแรกคือตัวแปรท้องถิ่นแบบคงที่

ฟังก์ชั่น foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 0 foo(); // 0

ใน PHP ตัวแปรอยู่ในเครื่อง ซึ่งหมายความว่าตัวแปรที่กำหนดและกำหนดค่าภายในฟังก์ชัน (วิธีการ) จะมีอยู่เฉพาะในระหว่างการทำงานของฟังก์ชัน (วิธีการ) นั้นเท่านั้น เมื่อเมธอดออก ตัวแปรในเครื่องจะถูกทำลาย และเมื่อกลับเข้ามาใหม่ ตัวแปรจะถูกสร้างขึ้นใหม่ ในโค้ดด้านบน ตัวแปรโลคอลดังกล่าวคือตัวแปร $a ซึ่งมีอยู่ภายในฟังก์ชัน foo() เท่านั้น และจะถูกสร้างขึ้นใหม่ทุกครั้งที่เรียกใช้ฟังก์ชันนี้ การเพิ่มตัวแปรในโค้ดนี้ไม่มีความหมาย เนื่องจากในบรรทัดถัดไปของโค้ด ฟังก์ชันจะทำงานให้เสร็จ และค่าของตัวแปรจะหายไป ไม่ว่าเราจะเรียกใช้ฟังก์ชัน foo() กี่ครั้ง ฟังก์ชันก็จะส่งออกเป็น 0...

อย่างไรก็ตาม ทุกอย่างจะเปลี่ยนไปหากเราใส่ static keyword ก่อนการมอบหมายงาน:

ฟังก์ชั่น foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 1 foo(); // 2

คีย์เวิร์ดแบบคงที่ซึ่งเขียนก่อนกำหนดค่าให้กับตัวแปรโลคัลจะมีผลกระทบดังต่อไปนี้:

  1. การมอบหมายจะดำเนินการเพียงครั้งเดียวในการเรียกฟังก์ชันครั้งแรก
  2. ค่าของตัวแปรที่ทำเครื่องหมายด้วยวิธีนี้จะถูกบันทึกหลังจากฟังก์ชันสิ้นสุด
  3. ในการเรียกใช้ฟังก์ชันครั้งต่อๆ ไป แทนที่จะกำหนด ตัวแปรจะได้รับค่าที่เก็บไว้ก่อนหน้านี้
การใช้คำว่าคงที่นี้เรียกว่า ตัวแปรท้องถิ่นแบบคงที่.
ข้อผิดพลาดของตัวแปรคงที่
แน่นอนว่า PHP ก็มีข้อผิดพลาดอยู่บ้างเช่นเคย

แนวทางแรกคือเฉพาะค่าคงที่หรือนิพจน์คงที่เท่านั้นที่สามารถกำหนดให้กับตัวแปรคงที่ได้นี่คือรหัส:
คงที่ $a = bar();
จะนำไปสู่ข้อผิดพลาดของพาร์เซอร์อย่างหลีกเลี่ยงไม่ได้ โชคดีที่เริ่มต้นด้วยเวอร์ชัน 5.6 คุณสามารถกำหนดไม่เพียงแต่ค่าคงที่เท่านั้น แต่ยังรวมถึงนิพจน์คงที่ด้วย (เช่น "1+2" ​​หรือ "") นั่นคือนิพจน์ที่ไม่ขึ้นอยู่กับโค้ดอื่นและสามารถคำนวณได้ ในขั้นตอนการรวบรวม

หินก้อนที่สองคือวิธีการที่มีอยู่ในสำเนาเดียว
ที่นี่ทุกอย่างซับซ้อนขึ้นเล็กน้อย เพื่อทำความเข้าใจสาระสำคัญ นี่คือโค้ด:
คลาส A ( ฟังก์ชั่นสาธารณะ foo() ( static $x = 0; echo ++$x; ) ) $a1 = A ใหม่; $a2 = A ใหม่; $a1->ฟู(); // 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->ฟู(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

สรุป: วิธีการแบบไดนามิกใน PHP มีอยู่ในบริบทของคลาส ไม่ใช่วัตถุ และเฉพาะในรันไทม์เท่านั้นที่การแทนที่ “$this = current_object” จะเกิดขึ้น

ความหมายที่สองคือคุณสมบัติคงที่และวิธีการเรียน

ในโมเดลออบเจ็กต์ PHP เป็นไปได้ที่จะตั้งค่าคุณสมบัติและวิธีการไม่เพียงแต่สำหรับอ็อบเจ็กต์ - อินสแตนซ์ของคลาส แต่ยังสำหรับคลาสโดยรวมด้วย คำหลักแบบคงที่ยังใช้สำหรับสิ่งนี้:

คลาส A ( public static $x = "foo"; public static function test() ( return 42; ) ) echo A::$x; // "foo" echo A::test(); // 42
ในการเข้าถึงคุณสมบัติและวิธีการดังกล่าว จะใช้โครงสร้าง double-colon (“Paamayim Nekudotayim”) เช่น CLASS_NAME::$Variablename และ CLASS_NAME::Methodname()

ไม่ต้องบอกว่าคุณสมบัติคงที่และวิธีการคงที่มีลักษณะเฉพาะและข้อผิดพลาดของตัวเองที่คุณจำเป็นต้องรู้

คุณลักษณะแรกซ้ำซาก - ไม่มี $thisจริงๆ แล้ว สิ่งนี้มีต้นกำเนิดมาจากคำจำกัดความของวิธีการแบบสแตติก เนื่องจากมันเชื่อมโยงกับคลาส ไม่ใช่อ็อบเจ็กต์ ตัวแปรหลอก $this ซึ่งชี้ไปยังอ็อบเจ็กต์ปัจจุบันในวิธีไดนามิกจึงไม่สามารถใช้ได้ ซึ่งเป็นตรรกะที่สมบูรณ์

อย่างไรก็ตาม คุณต้องรู้ว่า PHP ไม่เหมือนกับภาษาอื่นๆ ตรงที่ตรวจไม่พบสถานการณ์ “$this is wrote in a static method” ในขั้นตอนการแยกวิเคราะห์หรือคอมไพล์ ข้อผิดพลาดเช่นนี้สามารถเกิดขึ้นได้ที่รันไทม์เท่านั้นหากคุณพยายามรันโค้ดด้วย $this ภายในวิธีการแบบคงที่

รหัสเช่นนี้:
คลาส A ( public $id = 42; ฟังก์ชันสาธารณะแบบคงที่ foo() ( echo $this->id; ) )
จะไม่ทำให้เกิดข้อผิดพลาดใดๆ ตราบใดที่คุณไม่ได้พยายามใช้เมธอด foo() อย่างไม่เหมาะสม:
$a = ก ใหม่; $a->foo();

(และได้รับ “ข้อผิดพลาดร้ายแรง: การใช้ $this เมื่อไม่อยู่ในบริบทของวัตถุ”) ทันที
คลาส A ( ฟังก์ชั่นสาธารณะแบบคงที่ foo() ( echo 42; ) ) $a = new A; $a->foo();
แค่นั้นแหละใช่ วิธีการแบบสแตติก หากไม่มี $this ในโค้ด ก็สามารถเรียกได้ในบริบทแบบไดนามิก เช่น วิธีการแบบอ็อบเจ็กต์ นี่ไม่ใช่จุดบกพร่องใน PHP

สิ่งที่ตรงกันข้ามไม่เป็นความจริงทั้งหมด:
คลาส A ( ฟังก์ชั่นสาธารณะ foo() ( echo 42; ) ) A::foo();
วิธีการแบบไดนามิกที่ไม่ได้ใช้ $this สามารถดำเนินการได้ในบริบทคงที่ อย่างไรก็ตาม คุณจะได้รับคำเตือน "ไม่ควรเรียกเมธอด A::foo() แบบคงที่" ที่ระดับ E_STRICT ขึ้นอยู่กับคุณที่จะตัดสินใจว่าจะปฏิบัติตามมาตรฐานโค้ดอย่างเคร่งครัดหรือระงับคำเตือน แน่นอนว่าอันแรกจะดีกว่า

อย่างไรก็ตามทุกสิ่งที่เขียนข้างต้นใช้ได้กับวิธีการเท่านั้น การใช้คุณสมบัติคงที่ผ่าน "->" เป็นไปไม่ได้และทำให้เกิดข้อผิดพลาดร้ายแรง

ความหมายที่สามซึ่งดูเหมือนจะยากที่สุด - การผูกแบบคงที่ล่าช้า

นักพัฒนาภาษา PHP ไม่ได้หยุดอยู่เพียงสองความหมายของคำว่า "คงที่" และในเวอร์ชัน 5.3 ได้เพิ่ม "คุณสมบัติ" ของภาษาอีกอันหนึ่งซึ่งใช้งานด้วยคำเดียวกัน! เรียกว่า "Late Static Binding" หรือ LSB (Late Static Binding)

วิธีที่ง่ายที่สุดในการทำความเข้าใจสาระสำคัญของ LSB คือตัวอย่างง่ายๆ:

โมเดลคลาส ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) echo Model::getTable(); // "โต๊ะ"
คีย์เวิร์ด self ใน PHP มักจะหมายถึง "ชื่อของคลาสที่เขียนคำนี้" ในกรณีนี้ self จะถูกแทนที่ด้วยคลาส Model และ self::$table ด้วย Model::$table
คุณลักษณะภาษานี้เรียกว่า "การเชื่อมโยงแบบคงที่ล่วงหน้า" ทำไมเร็ว? เนื่องจากการผูกตนเองและชื่อคลาสเฉพาะไม่ได้เกิดขึ้นในรันไทม์ แต่ในขั้นตอนก่อนหน้า - การแยกวิเคราะห์และการคอมไพล์โค้ด “คงที่” - เพราะเรากำลังพูดถึงคุณสมบัติและวิธีการคงที่

มาเปลี่ยนรหัสของเรากันหน่อย:

โมเดลคลาส ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) class User ขยายโมเดล ( public static $table = "users"; ) echo User::getTable() ; // "โต๊ะ"

ตอนนี้คุณเข้าใจแล้วว่าทำไม PHP ถึงทำงานโดยไม่ตั้งใจในสถานการณ์นี้ self เชื่อมโยงกับคลาส Model เมื่อไม่มีความรู้เกี่ยวกับคลาส User จึงชี้ไปที่ Model

ฉันควรทำอย่างไร?

เพื่อแก้ปัญหาภาวะที่กลืนไม่เข้าคายไม่ออกนี้ กลไกการเชื่อมโยง "ล่าช้า" จึงถูกประดิษฐ์ขึ้นในขั้นตอนรันไทม์ มันใช้งานได้ง่ายมาก - เพียงแค่เขียน "static" แทนคำว่า "self" และการเชื่อมต่อกับคลาสที่เรียกโค้ดนี้จะเกิดขึ้น ไม่ใช่กับคลาสที่เขียนไว้:
class Model ( public static $table = "table"; public static function getTable() ( return static::$table; ) ) class User ขยายโมเดล ( public static $table = "users"; ) echo User::getTable() ; // "ผู้ใช้"

นี่คือ "การผูกมัดแบบคงที่ล่าช้า" อันลึกลับ

ควรสังเกตว่าเพื่อความสะดวกยิ่งขึ้นใน PHP นอกเหนือจากคำว่า "คงที่" แล้วยังมีฟังก์ชันพิเศษ get_call_class() ซึ่งจะบอกคุณในบริบทว่าโค้ดของคุณกำลังทำงานอยู่ในคลาสใด

สัมภาษณ์สุขสันต์!



ในโลกนี้มีนักพัฒนา PHP สองประเภท บางคนชอบวิธีการแบบคงที่เพราะมันง่ายต่อการใช้งาน ในขณะที่บางคนกลับมองว่าวิธีการแบบคงที่นั้นชั่วร้ายและไม่ได้ใช้มันในการปฏิบัติของพวกเขา
ในบทความนี้ ฉันจะลองใช้ประสบการณ์ของฉันกับเฟรมเวิร์กต่างๆ เพื่ออธิบายว่าทำไมนักพัฒนาบางรายจึงเพิกเฉยต่อแนวทางปฏิบัติที่ดีที่สุดและใช้วิธีการคงที่ทั้งหมด

ใครชอบวิธีการแบบคงที่บ้าง?

โดยเฉพาะอย่างยิ่งมักใช้โดยนักพัฒนาที่เคยใช้เฟรมเวิร์ก CodeIgniter ในการทำงานของพวกเขา

นอกจากนี้นักพัฒนา Kohana และ Laravel ส่วนใหญ่ยังติดตามวิธีการทางสถิติอีกด้วย
ที่นี่เราอดไม่ได้ที่จะพูดถึงความจริงที่ว่าโปรแกรมเมอร์ที่ตัดสินใจเริ่มเขียนสิ่งต่าง ๆ ของตนเองมักจะปฏิเสธที่จะใช้ CodeIgniter

ทำไมคุณถาม?

CodeIgniter รองรับ PHP 4 ก่อนที่จะเพิ่มวิธีการคงที่ใน PHP 5 นอกจากนี้ CodeIgniter ยังใช้ "super object" ที่ให้การเข้าถึงคลาสทั้งหมดที่กำหนดให้กับคอนโทรลเลอร์อย่างเท่าเทียมกัน ดังนั้นจึงสามารถใช้งานได้ทั่วทั้งระบบ

ซึ่งหมายความว่าสามารถเข้าถึงคลาสต่างๆ ได้จากทุกโมเดลโดยใช้เมธอด __get() ซึ่งจะค้นหาคุณสมบัติที่ร้องขอโดยใช้ get_instance()->($var)- ก่อนหน้านี้ เมื่อ PHP 4 ไม่รองรับฟังก์ชัน __get() สิ่งนี้ทำได้โดยใช้โครงสร้าง foreach ผ่านพารามิเตอร์ CI_Controller จากนั้นจึงกำหนดให้กับตัวแปร $this ในโมเดล

ในไลบรารีคุณต้องเรียก get_instance ไลบรารีไม่ได้บังคับการสืบทอดคลาส ดังนั้นจึงไม่มีทางเลี่ยงฟังก์ชัน __get() ได้

ปริมาณ…

ส่งผลให้มีโครงสร้างที่ค่อนข้างยุ่งยากในการเข้าถึงโค้ด ฟังก์ชันการทำงานเดียวกันนี้สามารถทำได้ด้วยวิธีคงที่โดยไม่ต้องใช้ความพยายามใดๆ เพิ่มเติม

และไม่มีประเด็นเฉพาะในการหารือเกี่ยวกับการออกแบบดังกล่าว โอเค คุณสามารถเข้าถึงข้อมูลเซสชันในโมเดลของคุณได้ แต่ทำไมคุณถึงทำเช่นนี้?

"สารละลาย"

นักพัฒนา Kohana เป็นคนแรกที่ทำงานอย่างจริงจังกับวิธีการแบบคงที่ พวกเขาได้ทำการเปลี่ยนแปลงดังต่อไปนี้:
// เป็น $this->input->get("foo");
// กลายเป็น Input::get("foo");

สำหรับนักพัฒนา CodeIgniter จำนวนมากที่ใช้ PHP 4 แบบเดิมที่ย้ายไปยังเฟรมเวิร์ก Kohana เพื่อใช้ประโยชน์จากข้อดีทั้งหมดของ PHP 5 นี่ไม่ใช่เรื่องแปลก แต่ยิ่งตัวละครน้อยลงก็ยิ่งดีใช่ไหม?

แล้วปัญหาคืออะไร?

นักพัฒนา PHP หลายคน (โดยเฉพาะผู้ที่เชี่ยวชาญเรื่อง Symfony และ Zend) จะพูดว่า: “ชัดเจนแล้ว - ใช้ Dependency Injection!” แต่มีนักพัฒนาไม่กี่รายในชุมชน 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() ; )) โดยการโทรในเบราว์เซอร์ตัวควบคุมb/baz คุณจะเห็นผลลัพธ์ "val1" แต่ถ้าคุณพิมพ์ controllerb/baz?param=แทนที่

จากนั้นรับทั้งสองสายเพื่อให้เมธอดส่งคืนค่าเดียวกัน

ความเกี่ยวข้อง
รหัสสากลจะไม่ให้ความสัมพันธ์ใด ๆ แก่คุณ ตัวอย่างดีกว่าคำใด ๆ :
$this->request->input->get("param");

ออบเจ็กต์ที่ร้องขอจะมีอินสแตนซ์ใหม่ทั้งหมดสำหรับแต่ละคำขอ จากนั้นออบเจ็กต์อินพุตจะถูกสร้างขึ้นสำหรับแต่ละคำขอที่มีเฉพาะข้อมูลอินพุตสำหรับคำขอเฉพาะเท่านั้น นี่เป็นเรื่องจริงสำหรับแผน FuelPHP 2.0 ที่จะทำงานและแก้ไขปัญหา Dependency Injection รวมถึงปัญหา HMVC

แล้วไวยากรณ์คร่าวๆล่ะ?

นักพัฒนา Symfony หรือ Zend ไม่ต้องทนทุกข์ทรมานจากสิ่งนี้ แต่ผู้ที่ใช้ CodeIgniter จะฝันร้ายเกี่ยวกับการ "กลับสู่ PHP 4" เป็นเวลานาน

$this->request->input->get() อาจดูเหมือนเป็นรูปแบบยาวของไวยากรณ์ CodeIgniter แต่จริงๆ แล้วเราอยู่ในคอนโทรลเลอร์เท่านั้น เมื่อตัวควบคุมสร้างอินสแตนซ์แบบสอบถามใหม่ซ้อนอยู่ภายใน ตัวสร้างแบบสอบถามยังได้รับอินสแตนซ์เป็นอินพุตด้วย

หากคุณอยู่ในโมเดลหรือคลาสอื่น การเข้าถึงแบบ $this->request->input->foo() จะไม่ทำงานเนื่องจาก $this ไม่ใช่คอนโทรลเลอร์

โครงสร้าง Input::get("foo") จะสร้างส่วนหน้าสำหรับอินสแตนซ์ลอจิกในเบื้องหลัง แต่นี่ไม่สามารถแก้ปัญหาที่เกี่ยวข้องกับการทำงานของโค้ดสากลได้ คนที่ขี้เกียจที่สุดเมื่อทดสอบแอปพลิเคชันสามารถสลับระหว่างสองโหมดได้โดยไม่ต้องใช้เฟรมเวิร์กใหม่ทั้งหมด

มีวิดีโอที่ยอดเยี่ยมจาก Taylor Otwell (ผู้สร้างหรือ laravel 4) ซึ่งเขาอธิบายว่าคุณสามารถแทนที่โค้ดแบบคงที่ด้วยอินสแตนซ์เดียวที่ทดสอบผ่านคอนเทนเนอร์ DiC ของเขาได้อย่างไร

Laravel 4 - การฉีดคอนโทรลเลอร์ IoC และการทดสอบหน่วยจาก UserScape บน Vimeo

นี่เป็นการนำเสนอที่ยอดเยี่ยมเกี่ยวกับวิธีการหลีกเลี่ยงโดยไม่ใช้วิธีคงที่ใน Laravel แม้ว่าเฟรมเวิร์กสมัยใหม่บางอันเมื่อมองแวบแรกจะคล้ายกับ Kohana มาก แต่ก็สามารถแก้ปัญหาได้แม้กระทั่งปัญหามาตรฐานส่วนใหญ่ด้วยวิธีที่แตกต่างกันโดยสิ้นเชิง

ในบันทึกอันน่าเศร้านี้...

ขณะนี้ฉันกำลังอยู่ในขั้นตอนการแปลง PyroCMS จาก CodeIgniter เป็น Laravel การพยายามเปลี่ยนจากโค้ด PHP 4 ทั่วโลกไปสู่การฉีดการพึ่งพาที่สมบูรณ์แบบถือเป็นการฆ่าตัวตายโดยสิ้นเชิง ขั้นตอนกลางก่อนที่จะใช้ตัวโหลด CI คือการใช้โค้ดตัวโหลดอัตโนมัติ PHP 5, PSR-2 พร้อมวิธีคงที่มากมาย ตอนนี้เรายังอยู่ใน CodeIgniter

การเปลี่ยนจากโค้ดแบบคงที่ไปเป็น DiC สามารถแสดงให้เห็นได้อย่างง่ายดายเมื่อเราทำการเปลี่ยนไปใช้ Laravel ในที่สุด

การย้ายจากโค้ด CodeIgniter ที่เชื่อมโยงอย่างแน่นหนาไปเป็น PSR-2 ที่ทดสอบได้ถือเป็นความท้าทายที่สำคัญ ทีมไพโรกำลังเดินทางมา - และมันจะเป็นมหากาพย์อย่างแน่นอน

เริ่มตั้งแต่ PHP 5.3.0 มีคุณลักษณะที่เรียกว่า late staticbinding ซึ่งสามารถใช้เพื่อรับการอ้างอิงถึงคลาสที่สามารถเรียกได้ในบริบทของการสืบทอดแบบคงที่

แม่นยำยิ่งขึ้น การเชื่อมโยงแบบคงที่ล่าช้าจะรักษาชื่อของคลาสที่ระบุใน "การโทรที่ไม่ส่งต่อ" ครั้งล่าสุด ในกรณีของการโทรแบบคงที่ นี่คือคลาสที่ระบุอย่างชัดเจน (โดยปกติจะอยู่ทางด้านซ้ายของตัวดำเนินการ :: - ในกรณีของการเรียกแบบไม่คงที่ นี่คือคลาสของอ็อบเจ็กต์ "การโทรเปลี่ยนเส้นทาง" คือการโทรคงที่ที่เริ่มต้นด้วย ตัวเอง::, พ่อแม่::, คงที่::หรือถ้าเราเลื่อนลำดับชั้นของคลาสขึ้น ส่งต่อ_static_call()- การทำงาน get_call_class()สามารถใช้เพื่อรับสตริงที่มีชื่อของคลาสที่เรียกว่าและ คงที่::แสดงถึงขอบเขตของมัน

ชื่อ “การเชื่อมโยงแบบคงที่ล่าช้า” นั้นสะท้อนถึงการใช้งานคุณลักษณะนี้ภายใน "การผูกมัดล่าช้า" สะท้อนถึงความจริงที่ว่าการโทรผ่าน คงที่::จะไม่ถูกคำนวณโดยสัมพันธ์กับคลาสที่กำหนดวิธีการที่เรียกว่า แต่จะถูกคำนวณตามข้อมูลที่รันไทม์

คุณลักษณะนี้เรียกอีกอย่างว่า "การผูกแบบคงที่" เนื่องจากสามารถใช้ได้ (แต่ไม่จำเป็นต้องทำ) ในวิธีการแบบคงที่ ตัวเอง::

ข้อจำกัด ตัวเอง::

ตัวอย่าง #1 การใช้งาน
คลาสเอ ( ;
}
ก้อง __คลาส__
ฟังก์ชั่นคงที่สาธารณะ
ทดสอบ()(
}
}

ตัวเอง::ใคร();
คลาส B ขยาย A (
ฟังก์ชันคงที่สาธารณะ who() (
}
}

เสียงสะท้อน __คลาส__ ;
?>

B::ทดสอบ();

การใช้การผูกแบบคงที่ตอนปลาย การเชื่อมโยงแบบคงที่ในภายหลังพยายามเอาชนะข้อจำกัดนี้โดยจัดเตรียมคีย์เวิร์ดที่อ้างอิงถึงคลาสที่เรียกโดยตรงที่รันไทม์ พูดง่ายๆ ก็คือ คำหลักที่จะช่วยให้คุณสามารถเชื่อมโยงไปถึงได้บี จากทดสอบ() คงที่ในตัวอย่างก่อนหน้านี้ มีการตัดสินใจว่าจะไม่แนะนำคำหลักใหม่ แต่ให้ใช้

ที่ได้จองไว้แล้ว คงที่::

ตัวอย่าง #1 การใช้งาน
คลาส B ขยาย A (
คลาสเอ ( ;
}
ก้อง __คลาส__
ฟังก์ชั่นคงที่สาธารณะ
ตัวอย่างที่ 2 ใช้งานง่าย คงที่::ใคร();
}
}

ตัวเอง::ใคร();
คลาส B ขยาย A (
ฟังก์ชันคงที่สาธารณะ who() (
}
}

เสียงสะท้อน __คลาส__ ;
?>

// การผูกแบบคงที่ล่าช้ามีผลที่นี่

ผลลัพธ์ของการรันตัวอย่างนี้::

ความคิดเห็น ในบริบทที่ไม่คงที่ คลาสที่ถูกเรียกจะเป็นคลาสที่อินสแตนซ์อ็อบเจ็กต์อยู่ เนื่องจาก$นี่-> คงที่::จะพยายามเรียกวิธีการส่วนตัวจากขอบเขตเดียวกันโดยใช้ คงที่::อาจให้ผลลัพธ์ที่แตกต่างกัน

ข้อแตกต่างก็คือว่า คงที่::สามารถอ้างถึงฟิลด์คงที่ของคลาสเท่านั้น

ตัวอย่าง #1 การใช้งาน
ตัวอย่าง #3 การใช้งาน
ในบริบทที่ไม่คงที่
}
ฟังก์ชั่นส่วนตัว foo() (
สะท้อน "ความสำเร็จ!\n" ;
การทดสอบฟังก์ชั่นสาธารณะ () (
}
}

ตัวเอง::ใคร();
$นี่ -> ฟู();
คงที่::foo();
}

/* foo() จะถูกคัดลอกไปที่ B ดังนั้นขอบเขตของมันจึงยังคงเป็น A
ตัวอย่าง #3 การใช้งาน
และการโทรจะสำเร็จ*/
}
}

คลาส C ขยาย A (
/* วิธีการเดิมถูกแทนที่; ขอบเขตของวิธี C ใหม่ */
$b = B ใหม่();
$b -> ทดสอบ();
?>

// การผูกแบบคงที่ล่าช้ามีผลที่นี่

$c = C ใหม่();

ผลลัพธ์ของการรันตัวอย่างนี้::

$c -> ทดสอบ(); //ไม่จริง พ่อแม่::ความสำเร็จ! ความสำเร็จ! ความสำเร็จ! ข้อผิดพลาดร้ายแรง: การเรียกไปยังวิธีส่วนตัว C::foo() จากบริบท "A" ใน /tmp/test.php ออนไลน์ 9 ตัวเอง::ขอบเขตการแก้ไขของการผูกแบบคงที่ล่าช้าจะได้รับการแก้ไขโดยการเรียกแบบคงที่ที่คำนวณ ในทางกลับกัน การโทรแบบคงที่โดยใช้คำสั่งเช่น

หรือ

ไม่ใช่เรื่องลับที่ผู้คนชอบถามคำถามที่ยุ่งยากในระหว่างการสัมภาษณ์ ไม่เพียงพอเสมอไป ไม่เกี่ยวข้องกับความเป็นจริงเสมอไป แต่ความจริงยังคงเป็นข้อเท็จจริง - พวกเขาถาม แน่นอนว่าคำถามนั้นแตกต่างออกไป และบางครั้งคำถามที่เมื่อมองแวบแรกดูเหมือนโง่สำหรับคุณ จริงๆ แล้วมีจุดมุ่งหมายเพื่อทดสอบว่าคุณรู้จักภาษาที่คุณกำลังเขียนดีแค่ไหน

ลองแยกคำถามเหล่านี้ออกทีละข้อ - คำว่า "คงที่" หมายถึงอะไรใน PHP และเหตุใดจึงใช้?

คำหลักแบบคงที่มีความหมายที่แตกต่างกันสามประการใน PHP ลองดูตามลำดับเวลาตามที่ปรากฏในภาษา

ค่าแรกคือตัวแปรท้องถิ่นแบบคงที่

ฟังก์ชั่น foo() ( $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 0 foo(); // 0

ใน PHP ตัวแปรอยู่ในเครื่อง ซึ่งหมายความว่าตัวแปรที่กำหนดและกำหนดค่าภายในฟังก์ชัน (วิธีการ) จะมีอยู่เฉพาะในระหว่างการทำงานของฟังก์ชัน (วิธีการ) นั้นเท่านั้น เมื่อเมธอดออก ตัวแปรในเครื่องจะถูกทำลาย และเมื่อกลับเข้ามาใหม่ ตัวแปรจะถูกสร้างขึ้นใหม่ ในโค้ดด้านบน ตัวแปรโลคอลดังกล่าวคือตัวแปร $a ซึ่งมีอยู่ภายในฟังก์ชัน foo() เท่านั้น และจะถูกสร้างขึ้นใหม่ทุกครั้งที่เรียกใช้ฟังก์ชันนี้ การเพิ่มตัวแปรในโค้ดนี้ไม่มีความหมาย เนื่องจากในบรรทัดถัดไปของโค้ด ฟังก์ชันจะทำงานให้เสร็จ และค่าของตัวแปรจะหายไป ไม่ว่าเราจะเรียกใช้ฟังก์ชัน foo() กี่ครั้ง ฟังก์ชันก็จะส่งออกเป็น 0...

อย่างไรก็ตาม ทุกอย่างจะเปลี่ยนไปหากเราใส่ static keyword ก่อนการมอบหมายงาน:

ฟังก์ชั่น foo() ( static $a = 0; echo $a; $a = $a + 1; ) foo(); // 0 foo(); // 1 foo(); // 2

คีย์เวิร์ดแบบคงที่ซึ่งเขียนก่อนกำหนดค่าให้กับตัวแปรโลคัลจะมีผลกระทบดังต่อไปนี้:

  1. การมอบหมายจะดำเนินการเพียงครั้งเดียวในการเรียกฟังก์ชันครั้งแรก
  2. ค่าของตัวแปรที่ทำเครื่องหมายด้วยวิธีนี้จะถูกบันทึกหลังจากฟังก์ชันสิ้นสุด
  3. ในการเรียกใช้ฟังก์ชันครั้งต่อๆ ไป แทนที่จะกำหนด ตัวแปรจะได้รับค่าที่เก็บไว้ก่อนหน้านี้
การใช้คำว่าคงที่นี้เรียกว่า ตัวแปรท้องถิ่นแบบคงที่.
ข้อผิดพลาดของตัวแปรคงที่
แน่นอนว่า PHP ก็มีข้อผิดพลาดอยู่บ้างเช่นเคย

แนวทางแรกคือเฉพาะค่าคงที่หรือนิพจน์คงที่เท่านั้นที่สามารถกำหนดให้กับตัวแปรคงที่ได้นี่คือรหัส:
คงที่ $a = bar();
จะนำไปสู่ข้อผิดพลาดของพาร์เซอร์อย่างหลีกเลี่ยงไม่ได้ โชคดีที่เริ่มต้นด้วยเวอร์ชัน 5.6 คุณสามารถกำหนดไม่เพียงแต่ค่าคงที่เท่านั้น แต่ยังรวมถึงนิพจน์คงที่ด้วย (เช่น "1+2" ​​หรือ "") นั่นคือนิพจน์ที่ไม่ขึ้นอยู่กับโค้ดอื่นและสามารถคำนวณได้ ในขั้นตอนการรวบรวม

หินก้อนที่สองคือวิธีการที่มีอยู่ในสำเนาเดียว
ที่นี่ทุกอย่างซับซ้อนขึ้นเล็กน้อย เพื่อทำความเข้าใจสาระสำคัญ นี่คือโค้ด:
คลาส A ( ฟังก์ชั่นสาธารณะ foo() ( static $x = 0; echo ++$x; ) ) $a1 = A ใหม่; $a2 = A ใหม่; $a1->ฟู(); // 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->ฟู(); // 1 $b1->foo(); // 1 $a1->foo(); // 2 $b1->foo(); // 2

สรุป: วิธีการแบบไดนามิกใน PHP มีอยู่ในบริบทของคลาส ไม่ใช่วัตถุ และเฉพาะในรันไทม์เท่านั้นที่การแทนที่ “$this = current_object” จะเกิดขึ้น

ความหมายที่สองคือคุณสมบัติคงที่และวิธีการเรียน

ในโมเดลออบเจ็กต์ PHP เป็นไปได้ที่จะตั้งค่าคุณสมบัติและวิธีการไม่เพียงแต่สำหรับอ็อบเจ็กต์ - อินสแตนซ์ของคลาส แต่ยังสำหรับคลาสโดยรวมด้วย คำหลักแบบคงที่ยังใช้สำหรับสิ่งนี้:

คลาส A ( public static $x = "foo"; public static function test() ( return 42; ) ) echo A::$x; // "foo" echo A::test(); // 42
ในการเข้าถึงคุณสมบัติและวิธีการดังกล่าว จะใช้โครงสร้าง double-colon (“Paamayim Nekudotayim”) เช่น CLASS_NAME::$Variablename และ CLASS_NAME::Methodname()

ไม่ต้องบอกว่าคุณสมบัติคงที่และวิธีการคงที่มีลักษณะเฉพาะและข้อผิดพลาดของตัวเองที่คุณจำเป็นต้องรู้

คุณลักษณะแรกซ้ำซาก - ไม่มี $thisจริงๆ แล้ว สิ่งนี้มีต้นกำเนิดมาจากคำจำกัดความของวิธีการแบบสแตติก เนื่องจากมันเชื่อมโยงกับคลาส ไม่ใช่อ็อบเจ็กต์ ตัวแปรหลอก $this ซึ่งชี้ไปยังอ็อบเจ็กต์ปัจจุบันในวิธีไดนามิกจึงไม่สามารถใช้ได้ ซึ่งเป็นตรรกะที่สมบูรณ์

อย่างไรก็ตาม คุณต้องรู้ว่า PHP ไม่เหมือนกับภาษาอื่นๆ ตรงที่ตรวจไม่พบสถานการณ์ “$this is wrote in a static method” ในขั้นตอนการแยกวิเคราะห์หรือคอมไพล์ ข้อผิดพลาดเช่นนี้สามารถเกิดขึ้นได้ที่รันไทม์เท่านั้นหากคุณพยายามรันโค้ดด้วย $this ภายในวิธีการแบบคงที่

รหัสเช่นนี้:
คลาส A ( public $id = 42; ฟังก์ชันสาธารณะแบบคงที่ foo() ( echo $this->id; ) )
จะไม่ทำให้เกิดข้อผิดพลาดใดๆ ตราบใดที่คุณไม่ได้พยายามใช้เมธอด foo() อย่างไม่เหมาะสม:
$a = ก ใหม่; $a->foo();

(และได้รับ “ข้อผิดพลาดร้ายแรง: การใช้ $this เมื่อไม่อยู่ในบริบทของวัตถุ”) ทันที
คลาส A ( ฟังก์ชั่นสาธารณะแบบคงที่ foo() ( echo 42; ) ) $a = new A; $a->foo();
แค่นั้นแหละใช่ วิธีการแบบสแตติก หากไม่มี $this ในโค้ด ก็สามารถเรียกได้ในบริบทแบบไดนามิก เช่น วิธีการแบบอ็อบเจ็กต์ นี่ไม่ใช่จุดบกพร่องใน PHP

สิ่งที่ตรงกันข้ามไม่เป็นความจริงทั้งหมด:
คลาส A ( ฟังก์ชั่นสาธารณะ foo() ( echo 42; ) ) A::foo();
วิธีการแบบไดนามิกที่ไม่ได้ใช้ $this สามารถดำเนินการได้ในบริบทคงที่ อย่างไรก็ตาม คุณจะได้รับคำเตือน "ไม่ควรเรียกเมธอด A::foo() แบบคงที่" ที่ระดับ E_STRICT ขึ้นอยู่กับคุณที่จะตัดสินใจว่าจะปฏิบัติตามมาตรฐานโค้ดอย่างเคร่งครัดหรือระงับคำเตือน แน่นอนว่าอันแรกจะดีกว่า

อย่างไรก็ตามทุกสิ่งที่เขียนข้างต้นใช้ได้กับวิธีการเท่านั้น การใช้คุณสมบัติคงที่ผ่าน "->" เป็นไปไม่ได้และทำให้เกิดข้อผิดพลาดร้ายแรง

ความหมายที่สามซึ่งดูเหมือนจะยากที่สุด - การผูกแบบคงที่ล่าช้า

นักพัฒนาภาษา PHP ไม่ได้หยุดอยู่เพียงสองความหมายของคำว่า "คงที่" และในเวอร์ชัน 5.3 ได้เพิ่ม "คุณสมบัติ" ของภาษาอีกอันหนึ่งซึ่งใช้งานด้วยคำเดียวกัน! เรียกว่า "Late Static Binding" หรือ LSB (Late Static Binding)

วิธีที่ง่ายที่สุดในการทำความเข้าใจสาระสำคัญของ LSB คือตัวอย่างง่ายๆ:

โมเดลคลาส ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) echo Model::getTable(); // "โต๊ะ"
คีย์เวิร์ด self ใน PHP มักจะหมายถึง "ชื่อของคลาสที่เขียนคำนี้" ในกรณีนี้ self จะถูกแทนที่ด้วยคลาส Model และ self::$table ด้วย Model::$table
คุณลักษณะภาษานี้เรียกว่า "การเชื่อมโยงแบบคงที่ล่วงหน้า" ทำไมเร็ว? เนื่องจากการผูกตนเองและชื่อคลาสเฉพาะไม่ได้เกิดขึ้นในรันไทม์ แต่ในขั้นตอนก่อนหน้า - การแยกวิเคราะห์และการคอมไพล์โค้ด “คงที่” - เพราะเรากำลังพูดถึงคุณสมบัติและวิธีการคงที่

มาเปลี่ยนรหัสของเรากันหน่อย:

โมเดลคลาส ( public static $table = "table"; public static function getTable() ( return self::$table; ) ) class User ขยายโมเดล ( public static $table = "users"; ) echo User::getTable() ; // "โต๊ะ"

ตอนนี้คุณเข้าใจแล้วว่าทำไม PHP ถึงทำงานโดยไม่ตั้งใจในสถานการณ์นี้ self เชื่อมโยงกับคลาส Model เมื่อไม่มีความรู้เกี่ยวกับคลาส User จึงชี้ไปที่ Model

ฉันควรทำอย่างไร?

เพื่อแก้ปัญหาภาวะที่กลืนไม่เข้าคายไม่ออกนี้ กลไกการเชื่อมโยง "ล่าช้า" จึงถูกประดิษฐ์ขึ้นในขั้นตอนรันไทม์ มันใช้งานได้ง่ายมาก - เพียงแค่เขียน "static" แทนคำว่า "self" และการเชื่อมต่อกับคลาสที่เรียกโค้ดนี้จะเกิดขึ้น ไม่ใช่กับคลาสที่เขียนไว้:
class Model ( public static $table = "table"; public static function getTable() ( return static::$table; ) ) class User ขยายโมเดล ( public static $table = "users"; ) echo User::getTable() ; // "ผู้ใช้"

นี่คือ "การผูกมัดแบบคงที่ล่าช้า" อันลึกลับ

ควรสังเกตว่าเพื่อความสะดวกยิ่งขึ้นใน PHP นอกเหนือจากคำว่า "คงที่" แล้วยังมีฟังก์ชันพิเศษ get_call_class() ซึ่งจะบอกคุณในบริบทว่าโค้ดของคุณกำลังทำงานอยู่ในคลาสใด

สัมภาษณ์สุขสันต์!