Syntax ใหม่ใน PHP 8 มาดูกัน

6 กุมภาพันธ์ 2564
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 ที่เพิ่มขึ้นมาสามารถเป็นได้หลาย ๆ อย่าง ดังนี้

  • 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

LINE Store 500 Internal Server Error Sticker
สนับสนุน phattarachai.dev
หากบทความใน phattarachai.dev มีประโยชน์กับคุณ โปรดสนับสนุน Sticker และ Theme LINE ที่ผมทำขึ้นได้ทาง เพื่อเป็นกำลังใจให้ผมนำเนื้อหาสาระดี ๆ และ Open Source Library ให้แก่ Laravel Developer ชาวไทยมากขึ้นนะครับ

เรื่องล่าสุด

เคล็ดลับ HTML ที่คุณอาจไม่เคยรู้
6 กุมภาพันธ์ 2564
เคล็ดลับ HTML ที่คุณอาจไม่เคยรู้
เลือกรหัสสีบนหน้าจอได้ง่าย ๆ ด้วย Color Picker บน Windows
6 กุมภาพันธ์ 2564
เลือกรหัสสีบนหน้าจอได้ง่าย ๆ ด้วย Color Picker บน Windows
Validation Rule สำหรับตรวจสอบรหัสบัตรประชาชน
6 กุมภาพันธ์ 2564
Validation Rule สำหรับตรวจสอบรหัสบัตรประชาชน