Syntax ใหม่ใน PHP 8 มาดูกัน
6 กุมภาพันธ์ 2564
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 ที่เพิ่มขึ้นมาสามารถเป็นได้หลาย ๆ อย่าง ดังนี้
array
bool
callable
int
float
null
object
resource
string
สังเกตว่า 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 ที่เราต้องการค้นหมหรือไม่
ตัวอย่าง
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