ใช้ Guzzle ในการเชื่อมต่อ API แทน cURL

เครื่องมือคู่บุญอย่างหนึ่งของชาว 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&section=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 มาสักหน่อย ปรับโค้ดที่เขียนให้อ่านง่ายและเป็นมาตรฐานมากขึ้น มันก็จะดีกับคนอื่นด้วยในภาพรวมครับ


Posted

in

by

Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.