"มีเพื่อนดี คนเดียว ถึงจะน้อย ดีกว่าร้อย เพื่อนคิด ริษยา..."
มีอะไรใหม่ใน PHP 8.0? เร็วขึ้นจริงมั้ย โค้ดดูปลอดภัยขึ้นจริงหรือเปล่า มาดูกัน
29 Nov 2020 06:59   [51886 views]

PHP ยังคงเป็นภาษาที่เติบโตต่อไป ล่าสุดเวอร์ชั่นล่าสุดปรับปรุงใหม่อย่าง PHP 8.0 ก็เพิ่งถูกปล่อยออกมาในวันที่ 26 พ.ย. ที่ผ่านมา วันนี้เลยจะมาเล่าให้ฟังว่ามีอะไรใหม่ใน PHP 8.0 บ้าง คนอยู่สาย PHP จะได้เตรียมตัวกันได้ครับ (โดยขอคัดแต่เด็ด ๆ มานะ หลายอย่างไม่เอามาเพราะไม่ได้สำคัญมาก)

มาพร้อม JIT Compiler

หนึ่งในสิ่งที่เพิ่มขึ้นมาแบบ Major ใน PHP 8.0 คงเป็น JIT Compiler นี่แหละ

สำหรับคนที่อยู่สาย Software Engineer มานานก็คงรู้อยู่แล้วหละว่ามันคืออะไร แต่เพื่อความเข้าใจอย่างถ่องแท้ เลยขออธิบายเพิ่มโดยละเอียดหน่อยเพราะใน PHP ก็มีอะไรที่คล้าย ๆ กันอยู่แล้วอย่าง Opcache แล้ว JIT มีประโยชน์ยังไง ? มาดูกัน

เริ่มจาก Opcache ละกัน โดย Opcache มีหน้าที่แคชโค้ด PHP ที่ถูกคอมไพล์เป็น Opcode ไว้ ทำให้คราวหน้าถ้ามีการรันโค้ดตรงเดิมก็จะได้ไม่ต้องคอมไพล์ใหม่ ประหยัดเวลาไปได้มหาศาลเลย แต่ก็ต้องโน้ตไว้นะว่า Opcode มันยังรันไม่ได้นะ ต้องอาศัยตัว Runtime ในการอ่าน Opcode เหล่านี้แล้วรันอีกต่อนึง

ส่วน JIT จะเป็นส่วนต่อขยายของ Opcache อีกทีนึง เวลาจะเปิดใช้ต้องใส่แบบนี้ใน Config

opcache.enable=1
opcache.jit_buffer_size=100M
opcache.jit=1255

หน้าที่ของ JIT คือ แปลง PHP Opcode ที่แคชไว้โดย Opcache ให้กลายเป็น Machine Code เพื่อให้โค้ดที่แคชไว้สามารถรันได้เลยโดยไม่ต้องอาศัย Runtime อีกต่อไป

โดย PHP 8.0 มาพร้อม JIT สองแบบ Tracing JIT และ Function JIT

Function JIT - แคชเป็นรายฟังก์ชั่นไป

Tracing JIT - แคชโค้ดทั้ง Stack

ค่า Default หากไม่กำหนดว่าจะใช้แบบไหนคือ Tracing JIT เพราะเค้าบอกว่ามันจะเร็วกว่าในกรณีทั่วไป แต่สุดท้ายก็ต้องจูนเอาเองตามแต่ละเว็บของตัวเองกันไปแหละนะ

JIT เร็วแค่ไหน ?

อ่าน Benchmark มาหลายเว็บ พบว่ามันไม่ได้ทำให้เร็วขึ้นขนาดนั้นนะ สำหรับเว็บ Bench ทั้งหลายอาจจะดูเร็ว แต่พวกเว็บที่ใช้งานจริงเช่น Wordpress กลับเร็วขึ้นเพียง 5-10% เท่านั้น

โดยรวมก็ดีกว่าไม่มี แต่ผลลัพธ์ของ JIT ก็ไม่ได้มีนัยสำคัญขนาดนั้นครับ

พ่วงมาด้วย Syntax ของ HACK เพียบ

น่าจะรู้กันอยู่แล้วว่า Facebook ใช้ PHP แต่เอาจริง ๆ นาทีนี้ถ้าให้ถูกควรจะบอกว่าใช้ HACK มากกว่า

โดย HACK คือภาษาที่ต่อยอดมาจาก PHP อีกทีนึงและใช้ Runtime อย่าง HHVM ในการรัน ทำให้ได้ประสิทธิภาพการทำงานที่ดีขึ้นมากมายมหาศาล

นอกจากนั้น HACK ยังมาพร้อมวิธีเขียนสมัยใหม่ครบครัน เช่น การบังคับ Type ของตัวแปร หรือ Optional ซึ่งทำให้การเขียนโค้ดมีความสุขขึ้นมากมาย (และปลอดภัยขึ้นด้วย)

และหลังจากรีวิวการเปลี่ยนแปลงของ PHP 8.0 ก็พบว่ามีการเอา Syntax ของ HACK เข้ามาเพิ่มใน PHP 8.0 เยอะมากเลย (จริง ๆ PHP 7 ก็มีการดึงไปใช้จำนวนหนึ่งอยู่แล้ว) ซึ่งดีมาก ๆ สำหรับใครที่เขียน HACK เป็นอยู่แล้วก็คงจะชิว ๆ แทบไม่ต้องเรียนรู้อะไรใหม่เลย แต่สำหรับคนที่มาสาย Traditional PHP ก็อาจจะต้องปรับตัวนิดนึงนะ

Constructor พร้อมตัวแปร

เป็น Syntax ที่ทำให้การประกาศ Constructor สั้นลงมาก ๆ อธิบายยากว่ามันเป็นยังไง มาดูโค้ดกันเลยละกัน อันนี้เป็นโค้ดเก่าใน PHP 7

class Point {
  public float $x;
  public float $y;
  public float $z;

  public function __construct(
    float $x = 0.0,
    float $y = 0.0,
    float $z = 0.0,
  ) {
    $this->x = $x;
    $this->y = $y;
    $this->z = $z;
  }
}

ส่วนนี่เป็น PHP 8.0 ครับ

class Point {
  public function __construct(
    public float $x = 0.0,
    public float $y = 0.0,
    public float $z = 0.0,
  ) {}
}

และตัวแปร $x, $y, $z ก็จะกลายเป็น Class Property และเข้าถึงได้ผ่าน $this->x ทันที

เอาจริง ๆ แค่อันนี้อย่างเดียวก็มีความสุขละ ! โค้ดคลีนขึ้นเป็นกอง

Nullsafe Operation

อีกหนึ่งสิ่งที่ทำให้โค้ดสั้นลงเป็นกอง โดย Syntax นี้จะเปิดให้เราสามารถเรียกตัวแปรที่มีโอกาสเป็น null โดยไม่พังได้ ก่อนหน้านี้ใน PHP 7 เราต้องเช็ค null ซ้ำแล้วซ้ำอีกอย่างวุ่นวาย

$country =  null;

if ($session !== null) {
  $user = $session->user;

  if ($user !== null) {
    $address = $user->getAddress();
 
    if ($address !== null) {
      $country = $address->country;
    }
  }
}

แต่สำหรับ PHP 8 เราทำแบบนี้ได้เลย

$country = $session?->user?->getAddress()?->country;

ซึ่งถ้ามีตัวแปรอันไหนใน Chain ที่เป็น null บรรทัดนี้ก็จะคืนค่าเป็น null กลับมา

โค้ดสั้นลงเป็นกอง !

Union Types

Type Safe เป็นหนึ่งในแนวทางที่ PHP 8 เน้นมาก ๆ เพราะยิ่ง Type มัน Strict มากเท่าไหร่ โอกาสที่โค้ดจะพังก็ต่ำลงเท่านั้น สำหรับ Union Types ก็เป็นหนึ่งในนั้น บ่อยครั้งที่เรามีโอกาสจะโยนตัวแปรเข้าไปในฟังก์ชั่นได้หลายรูปแบบ แต่พอ Type มันมีมากกว่าหนึ่ง ใน PHP 7 เราก็จะไม่สามารถบังคับประเภทของตัวแปรได้อีกต่อไป จะต้องเป็นอะไรก็ได้เท่านั้นซึ่งโอกาสพังสูงแบบนี้

class Number {
  /** @var int|float */
  private $number;

  /**
   * @param float|int $number
   */
  public function __construct($number) {
    $this->number = $number;
  }
}

new Number('NaN'); // Ok

ในขณะที่ PHP 8 นั้นเปิดให้เราสามารถกำหนด Type หลาย ๆ แบบได้แล้วตามนี้

class Number {
  public function __construct(
    private int|float $number
  ) {}
}

new Number('NaN'); // TypeError

ซึ่งหากตัวแปรที่โยนเข้าไปไม่ตรงกับอันใดอันหนึ่งก็จะคอมไพล์ไม่ผ่านทันที

ประเภทตัวแปรใหม่ mixed

ในขณะที่มีความพยายามทำให้โค้ดมีความ Type Safe มากขึ้น แต่ PHP 8 ก็ดันมาพร้อมกับประเภทตัวแปรที่ทำลายความตั้งใจนี้ลงด้วยอย่าง mixed 555

function bar(): mixed {}

mixed หลัก ๆ คือเป็นอะไรก็ได้นั่นแหละ แต่ไม่ค่อยแนะนำให้ใช้เท่าไหร่เพราะอาจจะสร้างปัญหาในอนาคตได้

ใช้ Mathematic Operation มั่ว ๆ ไม่ได้ละนะ

PHP ขึ้นชื่อเรื่องความมั่วซั่วของ Mathematic Operation คือจะเอาอะไรไป + อะไรก็ได้ แต่ตอนนี้ทำไม่ได้ละนะ อย่างเช่น

[] % [42];
$object + 4;

ถ้าเป็น PHP ตัวเก่าก็จะรันได้และได้ผลแปลก ๆ แต่สำหรับ PHP 8 ตัว Runtime จะขึ้น TypeError ทันที

0 จะไม่เท่ากับ 'foo' อีกต่อไป

อีกหนึ่งความมั่วซั่วของการเล่นแร่แปรธาตุใน PHP คือ String ที่ไม่มีตัวเลข ถ้าถูกแปลงเป็นตัวเลขจะกลายเป็น 0 ทำให้เวลาเราสั่งคำสั่งด้านล่างนี้ใน PHP มันจะเท่ากันซะอย่างงั้น

0 == "foo"

แต่สำหรับ PHP 8 สองอันนี้จะไม่เท่ากันอีกต่อไปแล้ว ก็เช็คโค้ดตัวเองกันดี ๆ ด้วยครับว่าทำอะไรแบบนี้อยู่มั้ย

ทั้งนี้ แนะนำให้ใช้ === เสมอ น่าจะเป็นวิธีที่ดีกว่าครับ

Named Arguments

ยิ่งเขียนโค้ดยาวขึ้นซับซ้อนขึ้น ความอ่านง่าย (Readability) ของโค้ดก็จะยิ่งสำคัญมากขึ้น และหน่ึงในสิ่งที่ช่วยได้มาก ๆ คือ การรู้ว่า Argument ที่โยนเข้าไปในฟังก์ชั่นแต่ละตัวเนี่ยมันคือค่าของอะไร ยกตัวอย่างเช่นหากเรากำหนดคลาสตามนี้

class CustomerData
{
    public function __construct(
        public string $name,
        public string $email,
        public int $age,
    ) {}
}

โค้ดตอน PHP 7 เราจะเรียกได้แค่แบบนี้

$customer = new CustomerData(
  'nuuneoi', // name
  '[email protected]', // email
  25, // age
);

ซึ่งคอมเม้นต์ก็จะดูรก ๆ หน่อยแต่ก็จะช่วยให้เข้าใจง่ายขึ้นหน่อยนึง

แต่สำหรับ PHP 8 เราสามารถใส่ชื่อได้ตรง ๆ เลยแบบนี้

$customer = new CustomerData(
  name: 'nuuneoi',
  email: '[email protected]`,
  age: 25,
);

และเนื่องจากมันมีชื่อกำกับแล้ว เราเลยสามารถสลับตำแหน่งได้ด้วย

$customer = new CustomerData(
  email: '[email protected]`,
  age: 25,
  name: 'nuuneoi',
);

ความเจ๋งคือเราสามารถประยุกต์ใช้กับ Array ได้ด้วยตามนี้

$args = [
  name: 'nuuneoi',
  email: '[email protected]`,
  age: 25
];
$customer = new CustomerData(...$args);

แล้วมันจะ Match ให้เองครับ

แนะนำนะ ๆ มันทำให้ Code Maintainability ยั่งยืน

ย่อ switch ลงด้วย match

จะมีหลาย ๆ เคสที่เราใช้ switch เพื่อกำหนดค่าตัวแปรตามเคส แต่ด้วยความใหญ่โตของ Syntax ทำให้โค้ดยาวโดยไม่จำเป็น เช่น

switch ($val) {
  case 1:
    $result = "One";
    break;
  case 2:
  case 3:
    $result = "Two or Three";
    break;
}
echo $result;

PHP 8 ก็เลยมาพร้อม Syntax ใหม่อย่าง match เพื่อให้โค้ดสั้นลง

echo match ($val) {
  1 => "One",
  2, 3 => "Two or Three",
};

ก็สั้นลงและเข้าใจง่ายขึ้นด้วย เจ๋ง ๆ

มีฟังก์ชั่น str_contains ใช้สักที !

ก่อนหน้านี้ถ้าเราจะหาว่ามี String อยู่ในอีก String หรือไม่ เราจะต้องทำแบบนี้

if (strpos('nuuneoi', 'neo') !== false) { }

แต่ตอนนี้เราสามารถทำแบบนี้ได้แล้ว

if (str_contains('nuuneoi', 'neo')) { }

กว่าจะมา !

ลดความสำคัญของ Concat (.) ลง

หากเราเขียนโค้ดแบบนี้

echo "sum: " . $a + $b;

ใน PHP ก่อนหน้านี้จะแปลเป็น

echo ("sum: " . $a) + $b;

แต่ PHP 8 จะลดความสำคัญของ . ลงและแปลเป็น

echo "sum: " . ($a + $b);

สำหรับใครที่เขียนโค้ดไม่ดีก่อนหน้านี้ก็อาจจะต้องเช็คความถูกต้องของผลลัพธ์กันด้วยถ้าจะอัปเป็น PHP 8 ครับ

ใช้ ::class กับ Object ได้แล้ว

ก่อนหน้านี้ถ้าเราอยากได้คลาสของ Object เราจะต้องเรียก get_class() แต่สำหรับ PHP 8 เราสามารถทำได้ง่ายขึ้นด้วย

$foo = new Foo();

var_dump($foo::class);

แต่ get_class ก็ยังใช้ได้อยู่นะ เพียงแค่ ::class นั้นจะดูสวยกว่าเยอะ

มี WeakMap มากับตัวเลย

WeakMap คือตัวแปรที่ใช้อ้างอิง Object แบบหลวม ๆ และอนุญาตให้ GC ไล่เก็บตัวแปรนี้ไปทิ้งได้ มีประโยชน์หลายอย่าง เช่นการทำ Cache

ก่อนหน้านี้ถ้าจะใช้ WeakMap เราจะต้องลง weakref ก่อน แต่กับ PHP 8 นั้นจะมีให้ใช้เลยโดยไม่ต้องลงอะไรเพิ่ม นี่คือตัวอย่างโค้ด

class Foo 
{
    private WeakMap $cache;
 
    public function getSomethingWithCaching(object $obj): object
    {
        return $this->cache[$obj]
           ??= $this->computeSomethingExpensive($obj);
    }
}

คราวนี้ตัวแปร $cache ก็จะโดนเก็บเมื่อไหร่ก็ได้ สามารถประยุกต์ไปทำ Caching ได้อย่างง่ายดายเลย

Array สามารถมี Index เริ่มต้นต่ำกว่า 0 ได้แล้ว

สมมติเราสั่งคำสั่งนี้

$a = array_fill(-5, 4, true);
var_dump($a);

ใน PHP รุ่นก่อนหน้าจะได้ออกมาว่า

array(4) {
	[-5]=>
	bool(true)
	[0]=>
	bool(true)
	[1]=>
	bool(true)
	[2]=>
	bool(true)
}

แต่สำหรับ PHP 8 จะได้เป็นแบบนี้

array(4) {
	[-5]=>
	bool(true)
	[-4]=>
	bool(true)
	[-3]=>
	bool(true)
	[-2]=>
	bool(true)
}

ก็ถือเป็นอีกหนึ่ง Breaking Change ที่อาจทำให้โค้ดที่มีอยู่แล้วพังได้เหมือนกัน ก็เช็คโค้ดตัวเองดี ๆ ครับ

Attributes v2

ก่อนหน้านี้ถ้าเราจะใส่ Annotation ให้กับฟังก์ชั่นหรือคลาส เราจะต้องทำผ่าน Comment ซึ่งกินพื้นที่เยอะเกินความจำเป็นและเข้าใจยากว่าตกลงมันเป็นคอมเม้นต์หรือ Annotation กันแน่

class PostsController
{
    /**
     * @Route("/api/posts/{id}", methods={"GET"})
     */
    public function get($id) { /* ... */ }
}

ล่าสุด PHP 8 เราสามารถทำด้วย #[] ได้แล้ว

class PostsController
{
    #[Route("/api/posts/{id}", methods: ["GET"])]
    public function get($id) { /* ... */ }
}

อันนี้เป็น Syntax ที่มีประโยชน์มากสำหรับ Modern Programming เลยนะ อนาคตน่าจะได้ใช้เยอะขึ้นเรื่อย ๆ

ผล Benchmark PHP 8 vs PHP 7.4

ไปดูผล Benchmark มาหลายเว็บ ได้ข้อสรุปว่า PHP 8 เร็วกว่า PHP 7.4 เพียงเล็กน้อยเท่านั้น (แค่ 5-10%) โดยหลัก ๆ จะเร็วขึ้นก็ต่อเมื่อเปิด JIT เท่านั้น หากไม่เปิดก็จะเท่ากับ PHP 7.4 ทุกประการเลย

สรุปแล้วอย่าคาดหวังประสิทธิภาพที่ดีขึ้นจากการอัปเดตเวอร์ชั่นนี้ครับ ประโยชน์ส่วนใหญ่ของ PHP 8 น่าจะเป็น Modern Syntax มากกว่า ถ้าพร้อมจะเขียนโค้ดแนวใหม่ก็เตรียมตัวย้ายได้เลย

PHP ห่วยจริงหรอ ?

ถ้าเขียนเป็นมันก็ไม่ห่วยเลยนะ ด้วยความ Dynamic สุด ๆ ของภาษาอาจจะทำให้มันเขียนพลาดได้ง่าย แต่ก็แก้ได้ด้วยการใช้พวก Type Safe ซึ่งมีให้ใช้ตั้งแต่ PHP 7 แล้ว เพียงแต่

ทางด้านประสิทธิภาพ อันนี้ต้องยอมรับว่าแย่กว่า Node JS และ Golang อะไรพวกนั้นแน่นอน แต่ก็มี Trade Off ตรงที่เขียนง่ายกว่ามาก

สุดท้ายโจทย์อยู่ที่ว่าเรา Delilver โปรดักส์ได้มั้ย ? ผู้ใช้งานเค้ารู้หรอว่าเราใช้ภาษาอะไรเขียน ก็ไม่ เค้ารู้แค่ว่าเว็บใช้งานได้มั้ย เร็วรึเปล่า มีบั๊กมั้ย ฯลฯ ถ้าทำออกมาได้ด้วย PHP ในงบประมาณที่เหมาะสมทั้งเรื่องของค่าพัฒนาและค่า Maintainence ก็ใช้ PHP ได้หนิ ทำไมถึงต้องไปยึดติดว่าจะต้องไปใช้เทคโนโลยีที่เร็วกว่าแต่ทีมอาจจะ Deliver ไม่ได้

ขนาด Python ที่ประสิทธิภาพห่วยสุด ๆ แย่กว่า PHP ด้วยซำ้ ก็ยังมีคนใช้อยู่หนิ ทำไมหละ ? จริงมะ

References:

PHP 8.0.0 Release Announcement

Stitcher: What's new in PHP 8

Kinsta: What’s New in PHP 8 (Features, Improvements, and the JIT Compiler)

ibexa: PHP 8.0 and 7.4 benchmarks on Symfony: JIT boost up to +9% on top of OPCache Preload

บทความที่เกี่ยวข้อง

Feb 23, 2022, 13:51
23139 views
7 Layers of the Metaverse องค์ประกอบ 7 เลเยอร์ที่ก่อมาเป็นเมตาเวิร์ส
Oct 11, 2020, 13:09
44444 views
Blameless Post-mortem วัฒนธรรมการหาต้นเหตุความผิดพลาดโดยไม่กล่าวโทษใคร
0 Comment(s)
Loading