เขียนคลาส PHP 8 ให้เป็นสำนวนที่ถูกต้อง: การเลื่อนขั้นคุณสมบัติ, readonly, named constructor และ value object ที่เปลี่ยนแปลงไม่ได้
เปิดบทเรียนนี้ใน Kodokonคุณเขียนคลาสมานานแล้ว แต่ PHP 8 ได้ทำให้นิสัยเดิมหลายอย่างล้าสมัยไป สิ่งที่เห็นได้ชัดที่สุดคือ การเลื่อนขั้นคุณสมบัติ (property promotion) ในคอนสตรักเตอร์ การเติมคำสงวนบอกการมองเห็น (private, protected, public) ไว้หน้าพารามิเตอร์ของคอนสตรักเตอร์ จะสร้างคุณสมบัติและกำหนดค่าให้มันในขั้นตอนเดียว บล็อกซ้ำซ้อนสามส่วนของสไตล์เดิม คือการประกาศ พารามิเตอร์ และการกำหนดค่า จะยุบรวมเหลือเพียงส่วนเดียว
<?php
declare(strict_types=1);
class LegacyProduct
{
private string $name;
private int $priceCents;
public function __construct(
string $name,
int $priceCents,
) {
$this->name = $name;
$this->priceCents = $priceCents;
}
}
final class Product
{
public function __construct(
private string $name,
private int $priceCents,
) {
}
public function label(): string
{
return sprintf(
'%s (%.2f EUR)',
$this->name,
$this->priceCents / 100,
);
}
}
$product = new Product('Keyboard', 4990);
echo $product->label();การมองเห็นยังคงเป็นเครื่องมือแรกของคุณสำหรับการห่อหุ้ม (encapsulation): ใช้ private เป็นค่าเริ่มต้น, protected เฉพาะเมื่อคลาสลูกต้องการเข้าถึง, และ public เป็นทางเลือกสุดท้าย ตั้งแต่ PHP 8.1 เป็นต้นมา readonly เพิ่มการรับประกันที่แข็งแกร่งกว่า นั่นคือคุณสมบัติจะถูกเขียนได้เพียง ครั้งเดียว และจากภายในขอบเขตของคลาสที่ประกาศมันเท่านั้น เมื่อผสานกับการเลื่อนขั้น มันจะสร้าง value object ที่เปลี่ยนแปลงไม่ได้ (immutable) ในเพียงไม่กี่บรรทัด การ "เปลี่ยนแปลง" ใด ๆ จะคืนอินสแตนซ์ใหม่ ซึ่งช่วยขจัดผลข้างเคียงจากการใช้ร่วมกัน
<?php
declare(strict_types=1);
final class Money
{
public function __construct(
public readonly int $amount,
public readonly string $currency,
) {
}
public function add(Money $other): self
{
if ($other->currency !== $this->currency) {
throw new InvalidArgumentException(
'Currency mismatch'
);
}
return new self(
$this->amount + $other->amount,
$this->currency,
);
}
}
$total = (new Money(500, 'EUR'))
->add(new Money(250, 'EUR'));
echo $total->amount;ในด้านเมท็อด มีสองแพตเทิร์นที่นักพัฒนาผู้มีประสบการณ์ใช้กันเป็นหลัก Named constructor: คอนสตรักเตอร์แบบ private ที่จับคู่กับ static factory ทำให้เจตนาแต่ละอย่างชัดเจน และห้ามการสร้างอินสแตนซ์ที่ไม่สอดคล้องกัน Fluent interface: การคืน $this (เปลี่ยนแปลงได้) หรือ new self(...) (เปลี่ยนแปลงไม่ได้) ช่วยให้คุณเชื่อมการเรียกต่อกันได้ ข้อแลกเปลี่ยนเป็นที่รู้กันดี เวอร์ชันที่เปลี่ยนแปลงไม่ได้นั้นปลอดภัยกว่า แต่จะสร้างอ็อบเจกต์หนึ่งตัวต่อการเรียกหนึ่งครั้ง ซึ่งแทบไม่มีนัยสำคัญในเกือบทุกที่ แต่จะวัดผลได้ในลูปที่ทำงานถี่ (hot loop)
<?php
declare(strict_types=1);
final class User
{
private function __construct(
public readonly string $email,
public readonly string $role,
) {
}
public static function admin(string $email): self
{
return new self($email, 'admin');
}
public static function member(string $email): self
{
return new self($email, 'member');
}
}
$user = User::admin('lea@example.com');
echo $user->role;readonly เขียนได้เมื่อใด?