บทความนี้จะเป็นการเขียนตัวอย่างโค้ดสำหรับอัปโหลดไฟล์ด้วย cURL ไปยัง server ปลายทาง.
ก่อนที่จะเริ่มส่วนของ cURL นั้น ให้ทำหน้าฟอร์มปกติสำหรับผู้ใช้ และหน้ารับคำสั่งอัปโหลดแบบปกติเสียก่อน เพื่อตรวจสอบการทำงานได้จริง.
กระบวนการอัปโหลดตามปกติ
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form upload</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="hidden-input" value="hidden value">
<p>
<label for="inputfile">Select image:</label><br>
<input id="inputfile" type="file" name="image" accept="image/jpeg,image/gif,image/png">
</p>
<p>
<button type="submit">Submit</button>
</p>
</form>
</body>
</html>
form.html
โค้ดด้านบนนี้จะเป็นหน้าฟอร์มธรรมดา โดยจะส่งค่าไปยังหน้า upload.php ซึ่งจากตัวอย่างจะมี input hidden อยู่ 1 ช่อง และ input file ที่จะรับเฉพาะรูปภาพเท่านั้น.
<?php
header('Content-type: application/json');
$output = [];
if (empty($_FILES['image']['name']) || !isset($_FILES['image']['error'])) {
$_FILES['image']['error'] = 4;
}
if ($_POST && $_FILES) {
// @link https://www.php.net/manual/en/features.file-upload.errors.php#115746
$phpFileUploadErrors = array(
0 => 'There is no error, the file uploaded with success',
1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
3 => 'The uploaded file was only partially uploaded',
4 => 'No file was uploaded',
6 => 'Missing a temporary folder',
7 => 'Failed to write file to disk.',
8 => 'A PHP extension stopped the file upload.',
);
$uploadFolder = __DIR__ . DIRECTORY_SEPARATOR . 'uploaded';
$allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (isset($_FILES['image']['error']) && $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
$output['error'] = true;
$output['message'] = $phpFileUploadErrors[$_FILES['image']['error']];
} else {
if (!is_dir($uploadFolder)) {
$oldumask = umask(0);
mkdir($uploadFolder, 0777);
umask($oldumask);
unset($oldumask);
}
$moveToFileName = $uploadFolder . DIRECTORY_SEPARATOR . $_FILES['image']['name'];
if (move_uploaded_file($_FILES['image']['tmp_name'], $moveToFileName)) {
$Finfo = new finfo();
$output['uploadedMimeType'] = $Finfo->file($moveToFileName, FILEINFO_MIME_TYPE);
unset($Finfo);
if (in_array(strtolower($output['uploadedMimeType']), $allowedMimeTypes)) {
http_response_code(201);
$output['uploadSuccess'] = true;
$output['message'] = 'Uploaded completed.';
} else {
http_response_code(400);
$output['uploadSuccess'] = false;
$output['error'] = true;
$output['message'] = 'You have uploaded disallowed file types.';
@unlink($moveToFileName);
}
} else {
$output['error'] = true;
$output['message'] = 'Unable to move uploaded file.';
}
unset($moveToFileName);
}
unset($uploadFolder);
}
$output['debug']['_POST'] = $_POST;
$output['debug']['_FILES'] = $_FILES;
echo json_encode($output);
upload.php
โค้ดด้านบนนี้จะเป็นการรับค่ามาจากหน้า form.html แล้วตรวจสอบข้อผิดพลาดต่างๆ รวมทั้งตรวจสอบ mime type ที่แท้จริงจากตัวไฟล์ว่าจะต้องเป็นไฟล์ภาพเท่านั้น. เมื่อทุกอย่างผ่านหมดแล้วจะแสดงข้อความอัปโหลดสำเร็จ หรือถ้าไม่ผ่านจะแสดงข้อความผิดพลาดให้ได้รู้. หน้านี้จะแสดงข้อมูลออกมาในรูปแบบ JSON.
เมื่อทำการทดลองตามปกติ ถ้าอัปโหลดสำเร็จก็ถือว่าโค้ดอัพโหลดนี้พร้อมใช้งานแล้ว.
อัปโหลดด้วย cURL
ตัวอย่างต่อไปนี้ จะเป็นการใช้ไฟล์รูปที่มีพร้อมใช้งานอยู่แล้ว โดยขอให้เตรียมไฟล์รูปขนาดเล็กไม่เกิน 2 MB จำนวน 1 รูป เป็นประเภทรูป jpeg โดยตั้งชื่อไฟล์ image.jpg ไว้ในที่เดียวกันกับโค้ดทั้งหมดด้านบน.
ในส่วนของการอัปโหลดนั้น จะใช้คลาส CURLFile()
ซึ่งมีรองรับตั้งแต่ PHP 5.5 เรื่อยมา. โดยจะสั่งให้คลาสนี้อ่านข้อมูลไฟล์แล้วแทรกใน POST fields เพื่อส่งข้อมูลเป็น method POST ไปยังเป้าหมายคือ upload.php.
<?php
$imageFile = __DIR__ . DIRECTORY_SEPARATOR . 'image.jpg';
$targetURL = (strtolower($_SERVER['REQUEST_SCHEME']) === 'https' ? 'https://' : 'http://')
. ($_SERVER['HTTP_HOST'] ?? 'localhost')
. (isset($_SERVER['REQUEST_URI']) ? dirname($_SERVER['REQUEST_URI']) : '')
. '/upload.php';
if (!is_file($imageFile)) {
http_response_code(404);
echo '<p>Image file was not found. Please prepare image file at this location: <strong>' . $imageFile . '</strong></p>';
exit();
}
$headers = [];
$allHeaders = [];
$postFields = [];
// create post fields
$postFields['hidden-input'] = 'hidden value (from cURL).';
// create upload file to post fields.
$Finfo = new finfo();
$fileMimeType = $Finfo->file($imageFile, FILEINFO_MIME_TYPE);
unset($Finfo);
$CurlFile = new CURLFile($imageFile, $fileMimeType, 'curl-upload-' . basename($imageFile));
$postFields['image'] = $CurlFile;
unset($CurlFile, $fileMimeType);
// start cURL.
$ch = curl_init($targetURL);
// don't echo out immediately.
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// method POST.
curl_setopt($ch, CURLOPT_POST, true);
// disable SSL verify. this is for test with self signed (local host) only. remove this on production site.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// response headers work.
curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'headerFunction');
// POST data with file upload.
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
// execute cURL.
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
http_response_code(500);
echo '<p>cURL error: ' . curl_error($ch) . '</p>';
curl_close($ch);
exit();
}
// close cURL.
curl_close($ch);
unset($ch, $postFields);
http_response_code($httpCode);
if (isset($headers['content-type'])) {
header('Content-type: ' . $headers['content-type']);
}
unset($headers);
// add headers to `$response` to see what we have here.
$responseJson = json_decode($response);
unset($response);
$responseJson->debug->allHeaders = $allHeaders;
$responseJson->debug->httpCode = $httpCode;
// echo out cURL response.
echo json_encode($responseJson);
unset($responseJson);
/**
* cURL header manipulatement function.
*
* @param resource $ch cURL resource
* @param string $header A header line.
* @return int Return length of header line.
*/
function headerFunction($ch, $header)
{
global $allHeaders, $headers;
if (!empty(trim($header))) {
$headerExp = explode(':', $header, 2);
if (count($headerExp) === 2) {
$headers[strtolower(trim($headerExp[0]))] = trim($headerExp[1]);
}
$allHeaders[] = $header;
}
return (int) mb_strlen($header);
}// headerFunction
curl-upload.php
สำหรับออปชั่น CURLOPT_SSL_VERIFYPEER
, CURLOPT_SSL_VERIFYHOST
นั้นแนะนำให้เอาออกถ้าหากทำงานอยู่บน server จริง โดยออปชั่นเหล่านี้เหมาะสำหรับทดสอบบนเครื่องที่ใช้ self signed SSL เท่านั้น.
จากโค้ดด้านบน ค่าในฟอร์มทั้งหมดรวมถึงไฟล์ที่อัปโหลดจะถูกกำหนดลงในออปชั่น CURLOPT_POSTFIELDS
เพียงรายการเดียว. เมื่อทำงานเสร็จแล้วจะมีการดึงค่า header มาใช้งาน ซึ่งคุณผู้อ่านอาจจะไม่ต้องใช้ก็ได้ แต่เขียนไว้เพื่อเป็นตัวอย่างเผื่อนำไปใช้ได้ต่อไป.
เมื่อทดลองเรียกไฟล์ curl-upload.php ดู หากทำการอัปโหลดสำเร็จ ไฟล์รูปที่เรากำหนดไว้จะไปอยู่ที่ปลายทางในชื่อ curl-upload-image.jpg.
สรุป
โดยสรุปแล้วการอัปโหลดด้วย cURL นั้นไม่ได้ยุ่งยากมากอย่างที่คิด. หลายๆตัวอย่างจะมีการกำหนดอะไรมากมายนั้น อาจจะเป็นเพราะโค้ดเหล่านั้นใช้กับ PHP รุ่นที่เก่ามากๆ ก็เลยยังไม่ค่อยมีอะไรรองรับมากนัก เลยต้องเขียนมาก. ส่วนที่จำเป็นจริงๆก็คือ กำหนดค่าในการเรียก URL เป็น method POST, อ่านไฟล์ที่มีอยู่บน server เข้าไปในคลาส CURLFile()
แล้วนำค่านั้นใส่ลงใน post fields, นำค่าใน post fields ทั้งหมดใส่ลงในออปชั่น CURLOPT_POSTFIELDS
แล้วสั่งให้ cURL ทำงานเป็นอันเสร็จสิ้น.