Syntax ใหม่ใน PHP 8 มาดูกัน
PHP ได้ออกเวอร์ชัน 8.0 มาตั้งแต่วันที่ 26 พ.ย. 63 โดยได้เพิ่มความสามารถใหม่ ๆ และ syntax ของภาษาให้ทันสมัยมากยิ่งขึ้น ในบทความนี้เราจะมา highlight ฟีเจอร์ใหม่ของ PHP 8 ที่น่าสนใจที่เราน่าจะได้ใช้ในการเขียน code ของเราทั่วไปกัน
Named Arguments การใส่ชื่อให้ Parameters
// PHP 7
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
// PHP 8
// ระบุเฉพาะ parameter ที่จำเป็น ข้าม optional parameter ได้
// ไม่จำเป็นต้องส่ง parameter ตามลำดับ
htmlspecialchars($string, double_encode: false);
Union types rfc
โดยปกติแล้ว PHP เป็นภาษาที่ dynamic type คือ function สามารถรับ parameter เป็น data type อะไรก็ได้ (Union Type) หลาย ๆ ครั้งการรับ parameter แบบ Union Type ก็มีประโยชน์ โดยเราสามารถระบุประเภท Class ทั้งหมดที่ parameter สามารถรองรับได้แล้ว
// PHP 7
class Number {
/** @var int|float */
private $number;
/**
* @param float|int $number
*/
public function __construct($number) {
$this->number = $number;
}
}
new Number('NaN'); // ใช้ได้
// PHP 8
class Number {
public function __construct(
private int|float $number
) {}
}
new Number(1); // ใช้ได้
new Number(1.234); // ใช้ได้
new Number('NaN'); // TypeError
ข้อสังเกตอย่างหนึ่งคือ เราไม่สามารถใช้ void เป็นส่วนหนึ่งใน Union Type ได้ เพราะ void เป็นการบอกว่า "ไม่ต้อง return อะไรมาเลย" แต่ถ้าหากเราอยากระบุว่าให้ส่งค่า null เข้ามาได้ เราสามารถใช้ |null หรือ ? หน้าชื่อ Type ได้ เช่น
public function foo(Foo|null $foo): void;
public function bar(?Bar $bar): void;
Nullsafe operator rfc
เราอาจจะคุ้นเคยกับ Null coalescing operator ซึ่งมีมาตั้งแต่ PHP 7 แล้วที่มีข้อจำกัดอย่างหนึ่งคือไม่สามารถใช้กับการเรียกผ่านเมธอดได้ ซึ่งเราอาจใช้ Ternary Operator ของ PHP ในการดักก่อน หรือถ้าเราเขียนบน Laravel อาจใช้เมธอด optional() คลุมไว้ เช่น
$startDate = $booking->getStartDate();
// PHP
$dateAsString = $startDate ? $startDate->asDateTimeString() : null;
// Laravel
$dateAsString = optional($startDate)->asDateTimeString();
ด้วยฟีเจอร์ nullsafe operator ใน PHP 8 เราสามารถใช้ syntax แบบ null coalesce กับเมธอดได้แล้ว แบบนี้
$dateAsString = $booking->getStartDate()?->asDateTimeString();
อ่านเพิ่มเติมเกี่ยวกับ Nullsafe Operator ได้ ที่นี่
Attributes rfc
Attributes, หรือที่เรารู้จักกันว่า annotations ในภาษาอื่น ๆ ทำให้เราสามารถเพิ่มข้อมูล meta เกี่ยวกับ class ได้ โดยไม่ต้องใช้ comment หรือ docblocks เลย
ตัวอย่างการใช้ attributes จาก RFC เป็นแบบนี้
use App\Attributes\ExampleAttribute;
#[ExampleAttribute]
class Foo
{
#[ExampleAttribute]
public const FOO = 'foo';
#[ExampleAttribute]
public $x;
#[ExampleAttribute]
public function foo(#[ExampleAttribute] $bar) { }
}
#[Attribute]
class ExampleAttribute
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
Match expression rfc
อาจจะเรียกได้ว่า match เป็น expression ที่มีความสามารถเหนือกว่า switch อีกชั้นหนึ่งเลยก็ว่าได้ match สามารถ return ค่าได้โดยไม่จำเป็นต้องใช้ statement break สามารถมีเงื่อนไข ใช้การเปรียบเทียบแบบ strict type (ค่าและ data type ต้องเป็นประเภทเดียวกัน) และไม่มีการตัดค่าลง
วิธีการเขียน match expression เป็นแบบนี้
$result = match($input) {
0 => "hello",
'1', '2', '3' => "world",
};
อ่านเพิ่มเติมเกี่ยวกับ match expression จาก link นี้.
Constructor Property Promotion การประกาศ Constructor พร้อม Property rfc
หลาย ๆ ครั้งที่เรามีการส่งค่าตัวแปรไปใน Constructor แล้วเราต้องประกาศ Property ขึ้นมารองรับตัวแปรจาก Constructor
โดยปกติแล้วเราจะเขียนแบบนี้
// PHP 7
class Money
{
public Currency $currency;
public int $amount;
public function __construct(
Currency $currency,
int $amount,
) {
$this->currency = $currency;
$this->amount = $amount;
}
}
ใน PHP 8 เราสามารถรวมให้เหลือการเขียนแบบนี้ได้
// PHP 8
class Money
{
public function __construct(
public Currency $currency,
public int $amount,
) {}
}
ทำให้ Code ดู clean ขึ้นเยอะเลยทีเดียว
ดูเพิ่มเติมเกี่ยวกับ Constructor Property Promotion
static return type แบบใหม่ rfc
จริงอยู่ว่าเราสามารถ ระบุ return type เป็น self ใน PHP ได้อยู่แล้ว แต่เราเพิ่งจะระบุ static เป็น return type ได้ใน PHP 8
class Foo
{
public function test(): static
{
return new static();
}
}
mixed type แบบใหม่ rfc
บางคนอาจจะบอกว่า mixed type ทำให้ code ดูงงสับสนกว่าเดิม เพราะเราไม่รู้ว่าเมธอดหรือ function จะ return datatype อะไรออกมา ทำให้เราต้องมี code คอยเช็คเงื่อนไขกับข้อมูลแต่ละประเภท แต่ถ้าเรามี mixed type ใน PHP ก็จะบอกอะไรกับเราใน code ได้หลาย ๆ อย่าง เช่น
- function ไม่ return ค่าอะไรกลับมาหรือ return
null - เราสามารถส่งค่าได้หลาย type
- เรากำลังเขียน function ที่ไม่สามารถ type-hinted ใน ได้
โดย mixed type ที่เพิ่มขึ้นมาสามารถเป็นได้หลาย ๆ อย่าง ดังนี้
arrayboolcallableintfloatnullobjectresourcestring
สังเกตว่า mixed สามารถใช้เป็นได้ทั้ง parameter, Property หรือ Return type
ข้อสังเกตอีกอย่างว่า mixed ได้รวม null เป็น type หนึ่งใน mixed type อยู่แล้ว เราจึงไม่สามารถระบุเป็น nullable (มี ? นำหน้า) เพิ่มได้
// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}
Throw expression rfc
ใน PHP 8 ได้เปลี่ยน throw จาก statement เป็นเพียง expression ทำให้เราสามารถเรียก throw ในบางส่วนของ code ที่เราไม่เคยทำได้มาก่อน เช่น
$triggerError = fn () => throw new MyError();
$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');
สามารใช้ ::class กับ Object ได้แล้ว rfc
อาจะเป็นเรื่องเล็ก ๆ น้อย ๆ แต่ก็เป็นฟีเจอร์ที่มีประโยชน์พอสมควร คือใน PHP 7 ลงมาถ้าเราต้องการเช็คว่า Object มีคลาสอะไรเราต้งใช้เมธอด get_class() แต่ใน php เราสามารถใช้ syntax ::class กับ ตัวแปรได้โดยตรง แบบนี้
$foo = new Foo();
// PHP 7
var_dump(get_class($foo));
// PHP 8
var_dump($foo::class);
catches แบบไม่ต้องรับตัวแปร rfc
เมื่อไหร่ก็ตามที่เราต้องการ catch exception ก่อน PHP 8 เราต้องมีตัวแปรมารับใน Exception ด้วย แต่บางทีเราไม่ได้ต้องการใช้ตัวแปรนั้นใน cache ใน PHP 8 เราสามารถละการรับตัวแปรใน cache ได้แบบนี้
แทนที่จะเขียนแบบนี้
// PHP 7
try {
// Something goes wrong
} catch (MySpecialException $exception) {
Log::error("Something went wrong");
}
สามารถเขียนแบบนี้ได้
// PHP 8
try {
// Something goes wrong
} catch (MySpecialException) {
Log::error("Something went wrong");
}
ข้อสังเกตุอย่างหนึ่งคือ เรายังคงต้องระบุ type ของ exception ที่เราต้องการดักไว้อยู่ดี ถ้าแต่ถ้าเราต้องการ catch exception ทั้งหมดโดยไม่สนใจประเภท เราสามารถ ใช้ Throwable ใน catch type ได้
สามารถมี comma (,) ต่อท้ายใน parameter ได้ rfc
จริง ๆ เราสามารถมี comma , ต่อท้ายตอนเรียกใช้ function ได้อยู่แล้ว แต่ใน PHP 8 ได้เพิ่มการทิ้ง comma ต่อท้ายตอนประกาศ function ได้ แบบนี้
public function doSomething(string $a, int $b, Foo $c,)
{
// …
}
เพิ่มเติมว่า เรายังสามารถทิ้ง comma ต่อท้ายใน function closure ได้เช่นเดียวกัน
$longArgs_longVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument, // Trailing commas were allowed in parameter lists in PHP 8.0
) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
เพิ่ม Stringable interface rfc
Stringable interface สามารถใช้เพื่อ type hint คลาสอะไรก็ตามที่มีการ implements เมธอด __toString(). เมื่อไรก็ตามที่เราเขียนเมธอด __toString() ไว้ในคลาส PHP จะทำการ implements interface Stringable ให้เราโดยอัตโนมัติ เราไม่จำเป็นต้องเขียน implement เอง ตัวอย่าง
class Foo
{
public function __toString(): string
{
return 'foo';
}
}
function bar(string|Stringable $stringable) { /* … */ }
bar(new Foo());
bar('abc');
เพิ่ม function str_contains() rfc
ใน PHP 8 ได้เพิ่ม function str_contains ขึ้นมา ทำให้เราไม่จำเป็นต้องเรียกใช้ function strpos() อีกต่อไปในการเช็คว่ามี string ที่เราต้องการค้นหมหรือไมj ตัวอย่าง
if (strpos('string with lots of words', 'words') !== false) { /* … */ }
เราสามารถเขียนแบบนี้ได้เลย
if (str_contains('string with lots of words', 'words')) { /* … */ }
แต่อย่าเพิ่งสับสนว่าถ้าเราใช้ Laravel เราอาจเคยเห็น function str_contains กันอยู่แล้ว นั่นเป็นเพราะเบื้องหลัง Laravel มีการใช้ package Polyfills ของ Symfony framework ซึ่งมี function str_contains ให้เราเรียกใช้ได้อยู่แล้วนั่นเอง
เพิ่ม str_starts_with() และ str_ends_with() rfc
คล้าย ๆ กับ function str_contains ก่อนหน้านี้ มีอีกสอง string function ให้เราเรียกใช้เพิ่มได้ซึ่งก็ถูกรวมอยู่ใน symfony polyfills แล้วด้วยเช่นกัน
str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true
สรุป
ที่นำมาเสนอในบทความนี้เป็นเพียงฟีเจอร์ส่วนหนึ่งที่ผมคิดว่าน่าสนใจและมีผลต่อวิธีการเขียน code PHP ของเราพอสมควร จริง ๆ แล้วฟีเจอร์ใหม่ที่เพิ่มขึ้นมาใน PHP 8 ยังมีอีกมากทั้งเรื่องของ JIT การปรับปรุงประสิทธิภาพการประมวลผล PHP มี Breaking Change บางอย่างจากฟังก์ชันหรือคลาสที่ถูก deprecated มาตั้งแต่ PHP 7.* ถ้าเราสนใจอยากดูรายละเอียดมากกว่านี้ลองดูเพิ่มเติมได้ที่ PHP 8.0 Release Note