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

Phattarachai Chaimongkol

เกี่ยวกับ phattarachai.dev

มองหาคนช่วยทำ Web App ใช้ภายในธุรกิจอยู่มั้ยครับ
มีความชำนาญในการพัฒนา Web Application ด้วย Laravel รับพัฒนาโปรเจคให้ผู้ประกอบการ ธุรกิจ SME ทั้งขนาดเล็กและขนาดใหญ่ พัฒนาระบบใช้ในองค์กรทั้งภาครัฐและเอกชน เป็นพาร์ทเนอร์กับบริษัททางด้าน Digital Agency เพื่อพัฒนาโปรเจคให้แก่ลูกค้า ทักเข้ามาพูดคุยกันก่อนได้เลยครับ

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

Backup ฐานข้อมูลด้วย Laravel อย่างง่าย ๆ ด้วย Spatie DB Snapshots
6 กุมภาพันธ์ 2564
Backup ฐานข้อมูลด้วย Laravel อย่างง่าย ๆ ด้วย Spatie DB Snapshots
วิธีการให้ git จดจำ password โดยไม่ต้องระบุใหม่ทุกครั้ง
6 กุมภาพันธ์ 2564
วิธีการให้ git จดจำ password โดยไม่ต้องระบุใหม่ทุกครั้ง
วิธีการเช็ค Detect Browser ผู้ใช้จาก Laravel
6 กุมภาพันธ์ 2564
วิธีการเช็ค Detect Browser ผู้ใช้จาก Laravel