เครื่องมือคู่บุญอย่างหนึ่งของชาว PHP นั่นคือฟังก์ชัน cURL ที่ว่ากันตามตรงก็ถือว่ามีฟีเจอร์ที่ครบถ้วนสมบูรณ์มากๆ อยู่แล้ว แต่ข้อเสียที่น่ารำคาญอย่างหนึ่งของ cURL นั่นก็คือโค้ดค่อนข้างยาว และอ่านยาก! (เชื่อว่าหลายๆ คนน่าจะเคยงงกับ curl_setopt()
ว่าโค้ดหน้าตาแปลกๆ นี้ทำอะไรของมัน)
วันนี้จะมาแนะนำทางเลือกหนึ่งที่โค้ดอ่านง่ายกว่า นั่นก็คือ Guzzle
Guzzle เป็น HTTP Client ตัวหนึ่งของ PHP หน้าที่ของมันก็เหมือน cURL นั่นแหละ คือเอาไปเชื่อมต่อไปยัง URL ต่างๆ (เช่นพวก REST API) แต่ข้อดีของมันนอกจากโค้ดที่อ่านง่ายกว่ามากๆ แล้ว คือมันรองรับ HTTP Transport หลายรูปแบบมาก ตั้งแต่ cURL, Socket, PHP Streams, หรือถ้าใช้ transporter ตัวอื่น จะเขียน handler เสียบเข้ามาเองก็ได้เช่นกัน
นั่นหมายความว่าถ้าเซิร์ฟเวอร์ที่เราใช้นั้นไม่มี cURL ให้ใช้ ก็ยังมีโอกาสที่ Guzzle จะยังทำงานได้ผ่าน HTTP Transport ตัวอื่น โดยเราแทบไม่จำเป็นจะต้องแก้ไขโค้ดเพิ่มเติมเลย
และในกรณีที่เราต้องมีการ process ข้อมูลที่ดึงมา เราจะเอา response ดิบๆ มาใช้ประมวลผลเอง หรือจะเขียน middleware เสียบเข้าไปเพื่อให้ประมวลผลข้อมูล และ response พร้อมใช้งานทันที ก็ทำได้เช่นกัน
ติดตั้ง Guzzle
Guzzle ทำงานได้กับ PHP 5.5.0 ขึ้นไป ในการเชื่อมต่อกับ API ตามปกตินั้นจะต้องมี cURL
หรือ allow_url_fopen
เปิดเอาไว้บนเซิร์ฟเวอร์ด้วย
เราสามารถติดตั้ง Guzzle ได้ง่ายๆ ผ่าน composer (เราเคยพูดถึง composer คร่าวๆ ไว้ในนี้ เดี๋ยวโอกาสหน้าจะเขียนแบบละเอียดให้อีกที)
composer require guzzlehttp/guzzle
และอย่าลืม include ไฟล์ autoload เข้ามาด้วยล่ะ
การใช้งาน Guzzle
ขั้นแรกให้เราประกาศ use
ตัว Guzzle ขึ้นมาก่อน โดยจะต้องประกาศไว้ที่บรรทัดบรรสุดของไฟล์ หรือต่อท้ายประกาศ namespace
จากนั้นก็ new Client()
ขึ้นมา ก็พร้อมใช้งาน
<?php namespace App; use GuzzleHttp\Client; $client = new Client();
<?php use GuzzleHttp\Client; $client = new Client();
การประกาศ
use
ใน PHP จะเป็นการประกาศว่าเราจะใช้คลาสจากเนมสเปซนั้นๆ ถ้าเราไม่ประกาศuse
เราจะต้องเรียกใช้คลาสด้วยชื่อเนมสเปซเต็มๆ เช่นnew GuzzleHttp\Client()
การส่ง request ใน Guzzle ทำได้ง่ายๆ ผ่านเมท็อด request ดังนี้
$client = new Client(); $response = $client->request( $method, $url, $options );
พารามิเตอร์ทั้งสามตัวคือ
$method
คือ HTTP Method ต่างๆGET
,POST
,PUT
,DELETE
(หลักๆ เราใช้กันแค่GET
และPOST
)$url
คือ URL ของ API Endpoint (จริงๆ ก็ URL ของอะไรก็ได้)$options
เป็นอาเรย์เก็บออพชันต่างๆ เช่นค่าของ HTTP Header, Authentication, หรือ Cookies
ดังนั้นถ้าสมมุติว่าเราจะ GET
ไปยัง endpoint https://reqres.in/api/users
เราจะสามารถเขียนโค้ดได้ดังนี้
$client = new Client(); $response = $client->request( 'GET', 'https://reqres.in/api/users' ); echo $response->getBody();
ก็จะได้ผลลัพธ์ออกมาดังนี้

ดังนั้นเวลาใช้จริงก็คือเราสามารถเอา $response->getBody()
ไปโยนเก็บไว้ในตัวแปร แล้ว json_decode()
มันออกมา ก็พร้อมนำไปใช้ทันที
การใช้ Request Options
Request Options นั้นคือพารามิเตอร์ตัวที่สามในเมท็อด request()
ที่เปิดให้เรากำหนดได้ว่าเราจะส่งข้อมูลต่างๆ เช่น HTTP headers หรือ form data อะไรไปบ้าง โดยเราจะต้องเขียนออปชันทั้งหมดในอาเรย์แล้วส่งเข้าไป
ในขั้นตอนการ authenticate API request ต่างๆ นั้น เรามักจะต้องแนบ token ไปกับ HTTP Headers ด้วย โดยใน Guzzle เราสามารถส่ง HTTP Headers เหล่านี้ไปกับออปชัน headers
$response = $client->request( 'GET', 'https://localhost:5000/api.php', [ 'headers' => [ 'X-Token' => 'f4a0a0f9ff05f80f201818151dd92c3d6bf00daa' ] ] );
ถ้าเราต้องการส่ง POST ค่าจากฟอร์มไปด้วย (ค่าที่เรารับด้วย $_POST
นั่นแหละ) เราสามารถส่งไปกับออปชัน form_params ได้
$response = $client->request( 'POST', 'https://localhost:5000/api.php', [ 'headers' => [ 'X-Token' => 'f4a0a0f9ff05f80f201818151dd92c3d6bf00daa', ], 'form_params' => [ 'foo' => 'bar', ], ] );

ถ้าเราต้องการส่ง query parameter (ค่าที่เรารับผ่าน $_GET
) เราสามารถแนบไปกับออปชัน query
ได้
$response = $client->request( 'GET', 'https://localhost:5000/api.php', [ 'headers' => [ 'X-Token' => 'f4a0a0f9ff05f80f201818151dd92c3d6bf00daa', ], 'query' => [ 'foo' => 'bar', ], ] );

ข้อควรระวังคือถ้า request url ของเรามีค่า query string อยู่ และเรากำหนดออปชัน query
ด้วยชื่อพารามิเตอร์เดียวกัน ค่าใน query string จะถูกทับด้วยค่าใน query
แทน เช่น
$response = $client->request( 'GET', 'https://localhost:5000/api.php?search=foo§ion=baz', [ 'query' => [ 'search' => 'bar', ], ] );
จากโค้ดด้านบนนี้ เมื่อเรา $_GET['search']
ออกมา จะได้ค่าเป็น bar
ในขณะที่ $_GET['section']
จะยังมีค่าเป็น baz
เหมือนเดิม
ในความเป็นจริงเราไม่แนะนำให้ใช้
$_GET
และ$_POST
เท่าไหร่นัก เพราะมันเป็นข้อมูลดิบที่เราต้องทำไป sanitize อีกรอบ (และมักจะเจอปัญหาลืม sanitize กันเป็นประจำ) แต่เราแนะนำให้รับค่าอินพุตพวกนี้ด้วยฟังก์ชันfilter_input()
แทน ซึ่งเราจะกล่าวถึงในบทความต่อไป
สำหรับ Request Options อื่นๆ สามารถเข้าไปอ่านได้ในลิงก์นี้
ลดความยาว url ด้วย base_url
ในการใช้งาน API จริงๆ นั้น เรามักจะต้องส่ง request ไปยังโดเมนเดิมซ้ำๆ กันแต่เป็นคนละ endpoint เช่น
- https://localhost/api/v1/users
- https://localhost/api/v1/contents
- https://localhost/api/v1/contents/wordpress
จะเห็นได้ว่าในแต่ละ request จะมีส่วนที่ซ้ำกันอยู่คือ https://localhost/api/v1
โดยเราเรียกส่วนที่ซ้ำๆ กันเหล่านี้ว่า base url
ท่าปกติที่เราทำด้วย CURL คือเก็บ base url เอาไว้ในตัวแปรตัวหนึ่ง จากนั้นเวลาใช้งานก็เอามาแปะต่อกัน เช่น
$base_url = "https://localhost/api/v1"; curl_setopt($ch, CURLOPT_URL, "{$base_url}/contents");
แม้โค้ดจะสั้นลงได้อีกหน่อย แต่ก็ยังต้องแปะ $base_url
ไปตลอดอยู่ดี
Guzzle นั้นจะฉลาดเรื่องนี้ขึ้นมาอีกนิดหน่อย โดยตอนที่เราสั่ง new Client()
นั้น เราสามารถส่งค่า base url ไปให้ Guzzle ได้ด้วย และพารามิเตอร์ $url
ของเมท็อด request()
เราก็ระบุแค่ endpoint ได้เลย
$client = new Client([ 'base_url' => 'https://localhost/api/v1/' ]); $response = $client->request( 'GET', 'users' );
นอกจากจะทำให้โค้ดสั้นลงแล้ว เรายังเอามาประยุกต์ใช้โดยสร้าง client หลายตัวสำหรับ API หลายๆ เจ้าก็ได้ เช่น
$twitter = new Client([ 'base_url' => 'https://api.twitter.com/' ]); $pinterest = new Client([ 'base_url' => 'https://api.pinterest.com/' ]);
ถ้าอยากจะใช้ cURL เหมือนเดิมล่ะ?
เอาจริงๆ ก็ไม่มีใครว่าอ่ะนะ เพราะโดยปกติแล้ว Guzzle ก็ใช้ cURL อยู่ข้างหลังเหมือนกัน แต่ในแง่ของความสะอาดของโค้ดนั้น Guzzle จะช่วยให้โค้ดอ่านง่ายกว่า คนอื่นรับไปทำต่อได้สะดวกกว่า
โดยส่วนตัวผมว่าเรื่องโค้ดอ่านง่ายนี่สำคัญนะ PHP โดนด่าเยอะมากเพราะความรกของโค้ด (โดยเฉพาะที่เราเขียนโค้ด PHP รวมกับไฟล์ HTML เนี่ย) ดังนั้นถ้าออกจาก comfort zone มาสักหน่อย ปรับโค้ดที่เขียนให้อ่านง่ายและเป็นมาตรฐานมากขึ้น มันก็จะดีกับคนอื่นด้วยในภาพรวมครับ
Leave a Reply