เนื้อหาตอนต่อไปนี้ เรามาดูเกี่ยวกับการใช้งานฟอร์ม
ในส่วนของการอัพโหลดไฟล์ โดยใช้ใช้รูปแบบการจัดการ
จากเนื้อหาของ Form ในตอนที่แล้วมาประกอบ
ทบทวนเกี่ยวกับการจัดการฟอร์มได้ที่
การใช้งาน Form การรับส่งข้อมูล Form ใน CodeIgniter 4 http://niik.in/1007
https://www.ninenik.com/content.php?arti_id=1007 via @ninenik
เราจะจำลองการสร้างฟอร์ม และกำหนดส่วนของ input file ในรูปแบบต่างๆ ประกอบ
การอธิบายเกี่ยวกับการใช้งานการอัพโหลดไฟล์
เตรียมส่วนของไฟล์ทดสอบ
ให้เราเตรียมส่วนของไฟล์ทดสอบตามรูปแบบดังนี้
app/Controllers/Helloworld.php
<?php namespace App\Controllers; use CodeIgniter\Controller; // เรียกใช้งาน Controller class class Helloworld extends Controller { public function index() { helper('form'); // ใช้งาน form helper ฟังก์ชั่น // ตรวจสอบการ submit ข้อมูลจากฟอร์ม และแสดงข้อมูลที่ถูกส่งมาเบื้องต้น if ($this->request->getMethod() === 'post'){ echo "<pre>"; $files = $this->request->getFiles(); print_r($files); echo "</pre>"; } $data = [ 'title' => 'test upload file' ]; echo view('pages/myform', $data); } }
app/Views/pages/myform.php
<!doctype html> <html lang="th"> <head> <meta charset="utf-8"> <title><?= esc($title) ?></title> </head> <body> <!--กำหนด css style สำหรับฟอร์ม สำหรับทดสอบ --> <style type="text/css"> form{ display: flex;flex-direction: column; } </style> <!-- ส่วนของข้อมูลฟอร์ม ที่เราจะเพิ่ม--> <?= form_open_multipart('helloworld') ?> <input type="file" name="avatar" /> <button type="submit">Send</button> <?= form_close() ?> <!-- สร้างลิ้งค์สำหรับรีโหลดหน้าทดสอบ--> <?= anchor(current_url(),'Reload') ?> </body> </html>
สังเกตว่าส่วนของฟอร์มสำหรับการอัพโหลดไฟล์ เราต้องเปลี่ยนมาใช้เป็น form_open_multipart() ไม่เช่นนั้น จะไม่
สามารถทำการอัพโหลดไฟล์ได้
ใน CI ปกติแล้วเราจะอัพโหลดไฟล์ไว้ในในโฟลเดอร์ 'writable/uploads' แต่ก็สามารถปรับตำแหน่งที่ต้องการอัพโหลด
ได้ตามต้องการ ในที่นี้จะใช้รูปแบบที่ CI กำหนด
path ของ 'writable' สามารถใช้งานผ่านตัวแปรค่าคงที่ชื่อว่า WRITEPATH ดังนั้นเวลาย้ายไฟล์ไปไว้ในโฟลเดอร์
uploads ก็สามารถกำหนดในลักษณะ นี้ได้
WRITEPATH.'uploads'
การจัดการ Files ที่ละหลายไฟล์พร้อมกัน
ในการใช้งาน Controllers ข้างต้น เรากำหนดการจัดการไฟล์ผ่าน IncomingRequest โดยใช้คำสั่ง getFiles() โดย
ผลลัพธ์ที่ได้ จะคืนค่าเป็น instance ของ UploadedFile class ( CodeIgniter\HTTP\Files\UploadedFile ) ในรูปแบบ
array ข้อมูล ดูผลลัพธ์เมื่อเราทดสอบกดปุ่ม submit โดยไม่ต้องเลือกไฟล์ใดๆ จะได้เป็นดังนี้
Array ( [avatar] => CodeIgniter\HTTP\Files\UploadedFile Object ( [path:protected] => [originalName:protected] => [name:protected] => [originalMimeType:protected] => [error:protected] => 4 [hasMoved:protected] => [size:protected] => 0 [pathName:SplFileInfo:private] => [fileName:SplFileInfo:private] => ) )
เราสามารถใช้งาน instance ผ่านตัวแปร $files['avatar']
CI4 กำหนดรูปแบบการจัดการไฟล์ที่แตกต่างกับ CI3 และเพิ่มความสามารถ ให้ทำงานได้ดีกว่าการใช้งานในรูปแบบปกติ
ที่ผ่านตัวแปร $_FILES ถ้าหากเราลองให้แสดงค่าข้อมูลไฟล์ กรณียังไม่เลือกไฟล์ใดๆ แล้วแสดงผลผ่าน print_r($_FILES)
ก็จะได้ผลลัพธ์ประมาณนี้
Array ( [avatar] => Array ( [name] => [type] => [tmp_name] => [error] => 4 [size] => 0 ) )
รูปแบบคำสั่ง
$files = $this->request->getFiles();
จะคืนค่าเป็น array ของ instance แตกต่างกันไปตามการกำหนด input file ที่ใช้สำหรับอัพโหลด อย่างในข้างต้น
เรากำหนดแค่
<input type="file" name="avatar" />
ค่า array instance ก็จะอยู่ในรูปแบบ
[ 'avatar' => // UploadedFile instance ]
มาดูกรณีกำหนดแบบ array
<input type="file" name="avatar[]" /> <input type="file" name="avatar[]" />
ค่า array instance ก็จะอยู่ในรูปแบบ
[ 'avatar' => [ 0 => /* UploadedFile instance */, 1 => /* UploadedFile instance */ ] ]
กรณีกำหนดแบบ array ใช้ key เป็นชื่้อ
<input type="file" name="my-form[details][avatars][]" /> <input type="file" name="my-form[details][avatars][]" />
ค่า array instance ก็จะอยู่ในรูปแบบ
[ 'my-form' => [ 'details' => [ 'avatar' => [ 0 => /* UploadedFile instance */, 1 => /* UploadedFile instance */ ] ] ]
การใช้งานตัวแปร instance ก็จะขึ้นกับรูปแบบของค่าที่คืนกลับมา ประมาณนี้เป็นต้น
$files['avatar'] $files['avatar'][0] $files['avatar'][1] $files['my-form']['details']['avatar'][0] $files['my-form']['details']['avatar'][1]
การจัดการ Files ที่ละไฟล์
ในกรณีที่เราต้องการใช้งานไฟล์อัพโหลดแค่ไฟล์เดียว เราสามารถใช้คำสั่ง getFile() แทนได้ (*คำสั่งคล้ายกัน เพียงแต่
ไม่มีตัว s ) คำสั่งนี้ จะไม่คืนค่าเป็น array เพราะมีแค่ค่าเดียว จึงคืนค่าเป็น instance โดยตรง ดังนั้น เราสามารถใช้งาน
instance ผ่านตัวแปร ที่รับค่าได้เลย ทดสอบ สมมติฟอร์มเรามีแค่ไฟล์
<input type="file" name="avatar" /> <button type="submit">Send</button>
แล้วส่วนของ Controller ก็เปลี่ยนเป็นคำสั่ง getFile() ดังนี้แทน
$file = $this->request->getFile('avatar'); var_dump($file);
กรณีไม่ได้เลือกไฟล์ใดๆ ค่าตัวแปร $file จะมีค่าเป็น NULL
แต่ถ้าสมมติเลือกไฟล์ จะค่าตัวแปร $file ซึ่งเป็น instance มีผลลัพธ์ประมาณนี้
*ผลลัพธ์จากคำสั่ง var_dump()
object(CodeIgniter\HTTP\Files\UploadedFile)#65 (9) { ["path":protected]=> string(24) "D:\xampp\tmp\phpB397.tmp" ["originalName":protected]=> string(7) "001.png" ["name":protected]=> string(7) "001.png" ["originalMimeType":protected]=> string(9) "image/png" ["error":protected]=> int(0) ["hasMoved":protected]=> bool(false) ["size":protected]=> int(5255) ["pathName":"SplFileInfo":private]=> string(24) "D:\xampp\tmp\phpB397.tmp" ["fileName":"SplFileInfo":private]=> string(11) "phpB397.tmp" }
กรณีกำหนดชื่อเป็นแบบ array
<input type="file" name="avatar[]" /> <input type="file" name="avatar2[me][profile]" />
ก็สามารถใช้งานคำสั่งโดยกำหนดชื่อ กับค่า key ต่อกันด้วย . (จุด)
$file = $this->request->getFile('avatar.0'); $file = $this->request->getFile('avatar2.me.profile');
การใช้งาน input file แบบสามารถเลือกได้หลายๆ ไฟล์พร้อมกัน ปกติจะกำหนดชื่อในรูปแบบ [] base zero key
แล้วเพิ่มส่วนของ multiple attribute เข้าไปตามรูปแบบดังนี้
<input type="file" name="images[]" multiple />
ดังนั้น ถ้าเป็นรูปแบบข้างต้น จะเป็นการใช้งานทีละหลายไฟล์พร้อมกัน เราต้องใช้คำสั่ง getFiles() แทน
โดยเมื่้อได้ค่า instance ที่เป็น array มา ก็ทำการวนลูป จัดการแต่ละไฟล์ต่อ
รูปแบบก็จะเป็นประมาณนี้
if($imagefile = $this->request->getFiles()) // เช็คว่ามีการส่งไฟล์มาหรือไม่ ถ้ามี จะได้ array ของ instance { foreach($imagefile['images'] as $img) // วนลูปจัดการแต่ละ instance { if ($img->isValid() && ! $img->hasMoved()) // ถ้าอัพโหลดผ่าน และไฟล์ยังไม่ถูกลบ { $newName = $img->getRandomName(); // กำหนดชื่อใหม่แบบ random $img->move(WRITEPATH.'uploads', $newName); // ย้ายไปไว้ในโฟลเดอร์ที่ต้องการ } } }
รูปแบบโค้ดด้านบน สมมติว่ามีการอัพโหลด โดยเลือกไฟล์มาหลายๆ ไฟล์พร้อมกัน เราจัดการแต่ละไฟล์ผ่านการ
วนลูป instance มาใช้งานในตัวแปร $img หากไม่มีอะไรผิดพลาด ไฟล์ทั้่งหมดจะถูกอัพโหลดไปที่โฟลเดอร์ temp
ชั่วคราวของ server เราสามารถตรวจสอบผ่านคำสั่ง isValid() ถ้าไฟล์อัพโหลดไปใน temp ได้ และ เราตรวจสอบว่า
ไฟล์ใน temp ยังไม่ถูกลบด้วยคำสั่ง ! $img->hasMoved() เพราะปกติไฟล์ใน temp จะอยู่เพียงชั่วคราวเท่านั้น
หากเข้าเงื่อนไขทั้งสอง เราก็ทำการย้ายไฟล์จาก temp พร้อมกับ random ชื่อไฟล์ใหม่ โดยใช้คำสั่ง getRandomName()
ย้ายไฟล์ที่อัพโหลดไปไว้ในโฟลเดอร์ 'writable/uploads' เพื่อใช้งานต่อไป
กรณีข้างต้น เราสามารถใช้คำสั่ง getFileMultiple() แทนได้ เพราะมีการใช้ชื่อ เดียวกัน ดังนั้นก็สามารถปรับเป็นดังนี้
if($imagefile = $this->request->getFileMultiple('images')) { foreach($imagefile['images'] as $img) // วนลูปจัดการแต่ละ instance { /// do something } }
หรือจะจัดการผ่านคำสั่ง getFile() แต่ละไฟล์ตามที่อธิบายไปด้านบนก็ได้ แต่ขึ้นกับการนำไปใช้งาน เพราะบางกรณี
อาจจะไม่สะดวกมากนัก ถ้าเราไม่สามารถระบุแน่ชัดว่าเป็นกี่ไฟล์ที่ต้องการอัพโหลด ตัวอย่างการจัดการแต่ละไฟล์
จะเป็นประมาณนี้
$file1 = $this->request->getFile('images.0'); $file2 = $this->request->getFile('images.1');
ดังนั้นกรณีใช้ชื่อเดียวกัน การอัพโหลพร้อมกันหลายๆ ไฟล์ ใช้คำสั่ง getFiles() น่าจะเหมาะกว่า
การตรวจสอบ UploadedFile instance
ตอนนี้เรารู้การเรียกใช้งาน instance แต่ละรูปแบบที่อาจจะเกิดขึ้นได้ ไปแล้วตามที่อธิบายด้านบน แต่ก่อนที่ใช้งาน เราต้องมั่นใจ
ในระดับหนึ่งก่อนว่ามีการอัพโหลดไฟล์จริงหรือไม่ โดยเงื่อนไขการตรวจสอบ ถ้าใช้งานในรูปแบบ PHP ทั่วไปก็ใช้การเช็คชื่อของ
input file ว่ามีชื่อไฟล์ถูกส่งมาหรือไม่ ในรูปแบบประมาณนี้
if($_FILES['avatar']['tmp_name'] !="" ){ // do something }
ใน CI เราก็สามารถทำได้ในรูปแบบดังนี้
if ($file->isValid()){ // do something }
คำสั่งข้างต้นเป็นการตรวจสอบว่าไฟล์ได้ทำการอัพโหลดผ่าน HTTP โดยไม่มี error ใดๆ เกิดขึ้นหรือไม่ โดยเรียกใช้คำสั่ง
isValid() เราสามารถกำหนดเงื่อนไขการทำงาน กรณีการอัพโหลดไฟล์นั้นๆ มีข้อผิดพลาด โดยเพิ่มส่วนของ else เข้าไป หรือจะ
ใช้รูปแบบตรวจสอบค่าตรงข้ามเป็น if(!$file->isValid()){} สำหรับเงื่อนไข error ก็ได้
$file = $this->request->getFile('avatar'); if ($file->isValid()){ var_dump($file); }else{ echo $file->getError(); echo "<br>"; echo $file->getErrorString(); }
ตัวอย่างข้างต้น สมมติเรากำหนดให้มีการจัดการกับ error ที่เกิดขึ้นจากการอัพโหลดไฟล์ จะมีค่าสองค่าคือ รหัสเลข error และ
ข้อความแจ้ง error สมมติข้างต้นเราไม่เลือกไฟล์ แต่กดส่งข้อมูล ก็จะเกิดรหัส error เท่ากับ 4 กับข้อความ 'No file was uploaded.'
หรือกรณีเราต้องการกำหนดการจัดการ Error โดยใช้ Exceptions ก็อาจจะกำหนดในลักษณะนี้ได้
throw new \Exception($file->getErrorString().'('.$file->getError().')');
ตัวอย่างการใช้งาน
try{ if($file->isValid()){ var_dump($file); }else{ throw new \Exception($file->getErrorString().'('.$file->getError().')'); } }catch (\Exception $e){ die($e->getMessage()); }
กรณีค่า Error ที่เป็นไปได้ อาจจะเป็นตามรูปแบบด้านล่าง ดังนี้
- ขนาดของไฟล์เกินค่าที่กำหนดใน ค่า upload_max_filesize ในไฟล์ php.ini ที่สัมพันธ์กับโฟลเดอร์นั้นๆ
- ขนาดของไฟล์เกินค่าที่กำหนด ในการจำกัดขนาดภายในฟอร์ม
- ไฟล์อัพโหลดไม่สมบูรณ์
- ไม่ได้ทำการเลือกไฟล์สำหรับอัพโหลด
- ไม่สามารถเขียนไฟล์ที่อัพโหลดในโฟลเดอร์นั้นๆ ได้
- ไม่พบโฟลเดอร์ temp สำหรับเก็บไฟล์ชั่วคราวบน server
- ไฟล์หยุดอัพโหลดจากการทำงานของบาง PHP extension
นอกจากการตรวจสอบว่ามีการอัพโหลดไฟล์แล้ว เรายังสามารถตรวจสอบว่าไฟล์ที่อัพโหลดไปไว้ในโฟลเดอร์ temp ชั่วคราว
นั้น ยังอยู่หรือไม่หรือถูกลบไปแล้ว เพื่อให้มั่นใจ ก่อนเรียกใช้คำสั่งย้ายไฟล์ไปยังโฟลเดอร์ที่ต้องการ โดยใช้คำสั่ง hasMoved()
ความหมายตามตัวก็เหมือนกับว่าไฟล์ถูกย้ายไปแล้วหรือไม่ หากเป็นเงื่อนไขว่า ไฟล์จะต้องยังไม่ถูกย้าย ก็ใช้คำสั่งตรงข้ามเป็น
ดูตัวอย่างการเพิ่มเงื่อนไขการตรวจสอบไฟล์
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ // ถ้าอัพโหลดผ่าน และไฟล์ยังไม่ถูกลบ var_dump($file); }
การจัดการกับชื่อไฟล์ File Name
ชื่อของไฟล์เป็นส่วนหนึ่ง ที่มักถูกเรียกใช้งานบ่อย ไม่ว่ากรณีต้องการเก็บชื่อไฟล์เดิมไว้ในฐานข้อมูล หรือต้องการเปลี่ยนชื่อไฟล์
เป็นชื่อใหม่แบบกำหนดเอง หรือจะให้ทำการสร้างชื่อไฟล์ใหม่อัตโนมัติ หัวข้อนี้เรามาดูเกี่ยวกับการจัดการชื่อไฟล์ ที่ได้ทำการอัพโหลด
สามารถเรียกใช้ผ่าน instance ด้วยคำสั่งต่างๆ ดังนี้
getName()
คำสั่งสำหรับแสดงชื่อไฟล์ที่ทำการอัพโหลด คำสั่งนี้จะมีข้อสังเกตตรงที่ ชื่อไฟล์ ก่อนและหลังการย้ายไฟล์ ด้วยคำสั่ง move()
หากมีการเปลี่ยนชื่อไฟล์ ค่าที่ได้ จะเป็นชื่อไฟล์ใหม่ที่เปลี่ยน แต่ถ้าไม่มีการเปลี่ยนชื่อไฟล์ ก็จะได้ค่าเหมือนกัน ดูตัวอย่างการอัพโหลด
และแสดงค่าชื่อไฟล์ ก่อนและหลังอัพโหลด กรณีที่มีการเปลี่ยนชื่อไฟล์
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getName(); // Bridge.jpg $newName = $file->getRandomName(); $file->move(WRITEPATH.'uploads',$newName); echo $file->getName(); // 1598675044_0b4f3290dc5e9973c110.jpg }
จะเห็นชื่อก่อนย้ายไฟล์ กับหลังย้ายไฟล์ และเปลี่ยนชื่อ จะได้ค่าที่ต่างกัน ให้เป็นข้อสังเกตไว้ แต่ถ้าไม่มีการเปลี่ยนชื่อไฟล์
ค่าของก่อนและหลังอัพโหลด ก็จะยังมีค่าเท่ากัน
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getName(); // Bridge.jpg $file->move(WRITEPATH.'uploads');// อัพโหลดในชื่อเดิม echo $file->getName(); // Bridge.jpg }
แต่ถ้าเราต้องการใช้งานชื่อต้นฉบับหรือชื่อก่อนย้ายไฟล์ ไม่ว่าจะเปลี่ยนชื่อใหม่หรือไม่ก็ตาม สามารถใช้คำสั่งถัดไปได้
getClientName()
คำสั่งสำหรับแสดงชื่อไฟล์ต้นฉบับ ใช้แสดงชื่อไฟล์ที่ถูกอัพโหลด กรณีเราต้องการใช้งานชื่อไฟล์นี้ อาจจะต้องการเก็บเป็นข้อมูล
หรือต้องการนำไปจัดรูปแบบใหม่ ค่านี้จะเป็นค่าเดียวตลอด ทั้งก่อนและหลังการย้ายไฟล์แล้วมีการเปลี่ยนชื่อ ดูตัวอย่างด้านล่าง
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getName(); // Bridge.jpg echo $file->getClientName(); // Bridge.jpg $newName = $file->getRandomName(); $file->move(WRITEPATH.'uploads',$newName); echo $file->getName(); // 1598675044_0b4f3290dc5e9973c110.jpg echo $file->getClientName(); // Bridge.jpg }
ลองเปรียบเทียบค่าผลลัพธ์ด้านบนดู จะเห็นว่ากรณีใช้คำสั่ง getClientName() ค่าจะเป็นชื่อไฟล์เดิมแน่นอน กว่าคำสั่ง getName()
ทีนี้ก็ขึ้นกับเราว่า จะใช้ข้อมูลใด ก็เลือกใช้คำสั่งที่เหมาะสมตามต้องการ หรืออาจจะเก็บทั้งชื่อใหม่กับชื่อเก่าทั้งสองฟิลด์ก็ได้ เช่น
กรณีบางครั้งชอบการกำหนดชื่อไฟล์เป็นภาษาไทย และชื่อไฟล์ก็เหมือนอธิบายรายละเอียดของไฟล์ในตัว แล้วเราต้องการไว้อ้างอิง
ก็สามารถเก็บทั้งชื่อเก่าที่ใช้อ้างอิงข้อมูลไฟล์ กับชื่อใหม่ ที่ไว้อ้างอิงตำแหน่งการเรียกใช้ไฟล์ เป็นต้น
getTempName()
คำสั่งสำหรับแสดงตำแหน่งของไฟล์ชั่วคราวแบบ absolute path ปกติก่อนทำการอัพโหลด จะเป็นค่าที่ระบุไฟล์ temp ที่อัพโหลด
ไว้ใน tmp โฟลเดอร์ชั่วคราวของ server คำสั่งนี้หากเรียกใช้งานหลังจากย้ายไฟล์แล้ว จะเป็นค่า absolute path ของที่จัดเก็บไฟล์
หรือย้ายไฟล์ไป ดูตัวอย่างคำสั่งและผลลัพธ์ข้างต้น
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getName(); // Bridge.jpg echo $file->getClientName(); // Bridge.jpg echo $file->getTempName(); // C:\xampp\tmp\php8C54.tmp $newName = $file->getRandomName(); $file->move(WRITEPATH.'uploads',$newName); echo $file->getName(); // 1598675044_0b4f3290dc5e9973c110.jpg echo $file->getClientName(); // Bridge.jpg echo $file->getTempName(); // C:\xampp\htdocs\mysslweb\writable\uploads/ }
นอกจากข้อมูลชื่อไฟล์แล้ว เรายังสามารถจัดการส่วนอื่นๆ เชน ดูข้อมูลชนิดของไฟล์ ขนาดของไฟล์ เพิ่มเติมด้วยคำสังดังนี้ได้
getClientExtension()
getExtension()
คำสั่งทั้งสองใช้สำหรับคืนค่าเป็นนามสกุลของไฟล์ที่อัพโหลด คำสั่งข้างต้นต่างกันตรงที่ถ้าเป็น getClientExtension() ค่าชื่อ
ไฟล์มีนามสกุลเป็นแบบไหน ค่าก็จะแสดงตามนั้น เช่น ถ้าเป็นตัวใหญ่ก็แสดงเป็นตัวใหญ่ ตามต้นฉบับ ในขณะที่คำสั่ง getExtension()
จะแปลงชื่อนามสกุลไฟล์เป็นตัวเล็ก เช่น จาก JPG ก็เป็น jpg ดังนั้นเวลาใช้งาน ถ้าต้องตรวจสอบนามสกุลไฟล์ ควรใช้แบบที่
ปรับเป็นตัวพิมพ์เล็ก แล้วตรวจสอบจะเหมาะสมกว่า คำสั่งทั้งสองสามารถใช้งานได้ทั้งก่อน และหลังการย้ายไฟล์ ให้ผลลัพธ์เหมือนกัน
ทั้งก่อน และหลัง การย้ายไฟล์ ดูตัวอย่าง
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getClientExtension(); // Bridge.JPG เป็น JPG echo $file->getExtension(); // เป็น jpg $newName = $file->getRandomName(); $file->move(WRITEPATH.'uploads',$newName); echo $file->getClientExtension(); // Bridge.JPG เป็น JPG echo $file->getExtension(); // เป็น jpg }
คำสั่ง getExtension() เราสามารถใช้เป็นเงื่อนไขการกำหนดกานย้ายไฟล์ ถ้าเป็นไฟล์ที่อนุญาต ในรูปแบดังนี้ได้
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved() && in_array($file->getExtension(),['png','gif'])){ // move file }
หรือ
$file = $this->request->getFile('avatar'); $allowed = ['png','gif']; $ext = $file->getExtension(); if ($file->isValid() && !$file->hasMoved() && in_array($ext, $allowed)){ // move file }
getClientMimeType()
getMimeType()
คำสั่งทั้งสองจะทำการคืนค่าเป็นรูปแบบมาตรฐานของประเภทของไฟล์จากค่า MIME types จะต่างจากค่าที่แยกด้วยนามสกุลไฟล์
และจะมีความถูกต้องในการตรวจสอบมากกว่า ยกตัวอย่างเช่น สมมติเรามีไฟล์ชื่อ script.php แล้วเราเปลี่ยนชื่อไฟล์เป็น photo.jpg
ก่อนอัพโหลด คำสั่ง getClientMimeType() จะได้ค่าเป็น image/jpeg แต่คำสั่ง getMimeType() จะได้ค่าเป็น text/x-php
ดังนั้น ถ้าจะตรวจสอบความถูกต้องที่แม่นยำของไฟล์ที่อนุญาตให้อัพโหลด ควรใช้คำสั่ง getMimeType() ซึ่งเป็นค่าจริงของไฟล์นั้นๆ
สำหรับคำสั่ง getMimeType() จะไม่สามารถเรียกใช้งานผ่าน instance ที่ย้ายไฟล์แล้วได้ จะใช้ได้กับ instance ก่อนย้ายไฟล์
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getClientExtension(); // photo.jpg เป็น jpg echo $file->getExtension(); // เป็น jpg echo $file->getClientMimeType(); // image/jpeg echo $file->getMimeType(); // text/x-php $newName = $file->getRandomName(); $file->move(WRITEPATH.'uploads',$newName); echo $file->getClientExtension(); // photo.jpg เป็น jpg echo $file->getExtension(); // เป็น jpg echo $file->getClientMimeType(); // image/jpeg // echo $file->getMimeType(); // error }
สามารถใช้คำสั่ง getMimeType() ในการตรวจสอบดังนี้ได้
$file = $this->request->getFile('avatar'); $allowed = ['image/jpg','image/png','image/gif']; $ext = $file->getMimeType(); if ($file->isValid() && !$file->hasMoved() && in_array($ext, $allowed)){ // move file }
getSize()
getSizeByUnit()
คำสั่งทั้งสองใช้สำหรับแสดงขนาดของไฟล์ที่อัพโหลด จะเป็นค่าในหน่วย byte ดูตัวอย่างการใช้งานตามคำสั่งด้านล่าง
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ echo $file->getSize(); // 1476435 echo $file->getSizeByUnit(); // 1476435 echo $file->getSizeByUnit('kb'); // 1,441.831 echo $file->getSizeByUnit('mb'); // 1.408 }
การบันทึกและย้ายไฟล์ Moving / Store Files
ในการย้ายไฟล์ที่อัพโหลดจากโฟลเดอร์ชั่วคราว tmp บน server ไปไว้ในโฟลเดอร์ที่เรากำหนดในคำสั่ง move() เราพอเห็น
ตัวอย่างไปบ้างแล้ว มาทบทวนกันอีกครั้งในหัวข้อนี้ รวมถึงมาดูอีกคำสั่งที่ใช้ในการบันทึกไฟล์ ด้วยคำสั่ง store() ดังนี้
move()
เป็นคำสั่งสำหรับย้ายไฟล์ในโฟลเดอร์ tmp ไปไว้ในโฟลเดอร์ที่กำหนด โดยเราสามารถกำหนด path ไปยังตำแหน่งต่างๆ ได้
ซึ่ง path ที่กำหนดจะเป็นรูปแบบ absolute patth โดยทั่วไป เรามักจะอัพโหลดไปไว้ในส่วนที่สามารถเขียนไฟล์ได้ในโฟลเดอร์
'writable/uploads' เพื่อป้องกันในเรื่องของความปลอดภัย โดยสามารถกำหนด path ผ่านตัวแปรค่าคงที่ดังนี้
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ $file->move(WRITEPATH.'uploads'); }
คำสั่งข้างต้น จะย้ายไฟล์ไปไว้ใน path ประมาณนี้ โดยใช้ชื่อไฟล์เดิม
C:\xampp\htdocs\mysslweb\writable\uploads
กรณีเราต้องการสร้างโฟลเดอร์ย่อยเพิ่มเข้าไปอีก ก็สามารถกำหนดได้ดังนี้
$file->move(WRITEPATH.'uploads/photo');
เราสามารถกำหนดชื่อใหม่ให้กับไฟล์ที่อัพโหลด โดยอาจจะใช้ชื่อ random ด้วยคำสั่ง getRandomName() และกำหนดชื่อใหม่
ใน paramter ค่าที่สอง
$newName = $file->getRandomName(); $file->move(WRITEPATH.'uploads/photo', $newName);
หรือจะตั้งเป็นรุปแบบชื่อใหม่ตามต้องการก็ได้เช่น
$newName = date("DMY").$file->getExtension(); // 31082020.jpg $file->move(WRITEPATH.'uploads/photo', $newName);
การอัพโหลดไปยัง WRITEPATH ข้างต้น อย่างที่เรารู้ดีว่า จะช่วยในเรื่องของความปลอดภัยในระดับหนึ่ง และเราก็ต้องระบบจัดการ
กับข้อมูลในโฟลเดอร์นี้ด้วย ยกตัวอย่างเช่น เราให้อัพโหลดเป็นเอกสารข้อมูล pdf ไว้ในโฟลเดอร์ writable/uplpads/pdf/simple.pdf
ในหน้าเพจ เราจะไม่สามารถเรียกใช้งานผ่าน url ด้านล่างนี้ได้ หากต้องเปิดใช้งาน หรือดาวน์โหลดไฟล์ pdf
https://www.mysslweb.com/writable/uploads/pdf/simple.pdf https://www.mysslweb.com/uploads/pdf/simple.pdf
ทั้งนี้เพราะ site root ของเราอยู่ที่โฟลเดอร์ public การอ้างอิงไฟล์ข้างต้นตาม url จึงไม่สามารถเรียกใช้งานได้โดยตรง
แต่ก็สามารถสร้างคำสั่งสำหรับเรียกไปยังไฟล์ path ของ pdf และให้สามารถดาวน์โหลด หรือเปิดมาแสดงได้
อย่างไรก็ตาม ในบางการใช้งาน เช่นรูปภาพ ที่เมื่อผู้ใช้อัพโหลดแล้ว เราต้องการให้สามารถเรียกใช้งานได้โดยตรง ไม่ต้องสร้าง
คำสั่งแสดงรูปซึ่งทำให้เปลืองหน่วยความจำ ดังนั้นวิธีที่เหมาะสมสำหรับกรณีนี้คือ ให้เราทำการย้ายไฟล์ในกรณีนี้ ไปยัง public โฟลเดอร์
โดยอาจจะสร้างโฟลเดอร์ย่อยเป็น uploads และกำหนด permission ให้สามารถเขียนไฟล์ได้ จากนั้นก็กำหนด path ในขั้นตอน
การย้ายไฟล์เป็นดังนี้
$file->move(ROOTPATH.'public/uploads');
เท่านี้เราก็สามารถเรียกใช้งานไฟล์ต่างๆ ที่อัพโหลดผ่าน site url โดยตรงได้เลย โดยเฉพาะรูปภาพ
เช่น สมมติไฟล์ อัพโหลดไปไว้ใน public/uploads/avatar/mypic.jpg ก็สามารถเรียกใช้งานผ่าน path ดังนี้ได้เลย
https://www.mysslweb.com/uploads/avatar/mypic.jpg
สำหรับไฟล์ข้อมูล ที่เราไม่ต้องการให้ผู้ใช้ที่อัพโหลด เข้าถึงได้โดยตรง เราก็อาจจะยังอัพโหลดไว้ใน WRITEPATH เหมือนเดิมก็ได้
แล้วค่อยใช้คำสั่งจัดการตอนเรียกใช้งานอีกที
คำสั่ง move() จะคืนค่าเป็น boolean ถ้าย้ายไฟล์สำเร็จก็จะเป็น true เราสามารถใช้เงื่อนไข if เพื่อเช็คว่าย้ายไฟล์สำหรับ
หรือไม่ได้ ประมาณนี้
if($file->move(WRITEPATH.'uploads/photo')){ // ย้ายไฟล์สำเร็จ }
store()
คำสั่งนี้ โดยหลักการทำงานแล้วก็จะคล้ายกับคำสั่ง move() แต่ path ของไฟล์หลักจะอยู่ในโฟลเดอร์ 'writable/uploads' นั่นคือ
ถ้าเราไม่กำหนด path ตัวไฟล์ก็ถูกอัพโหลดไปยังโฟลเดอร์ 'writable/uploads' โดยมีการสร้างโฟลเดอร์ย่อยเป็นรูปแบบวันที่ขณะนั้น
ในรูปแบบ YYYYMMDD เช่น สมมติวันนี้เป็นวันที่ 29 /08 / 2020 ชื่อโฟลเดอร์ของวันนั้นก็จะเป้น 20200829 และชื่อไฟล์ที่ถูกบันทึก
ก็จะเป็นชื่อ random ดูตัวอย่างการใช้งาน
$file = $this->request->getFile('avatar'); if ($file->isValid() && !$file->hasMoved()){ $path = $file->store(); echo $path; // 20200829/1598695231_f5472b7b8c237b8e6272.jpg }
คำสั่ง store() จะทำการย้ายไฟล์ไปไว้ในโฟลเดอร์ writable/uploads/20200829
แต่ถ้าหากเราต้องการกำหนดโฟลเดอร์ย่อยเอง ไม่ใช้รูปแบบ YYYYMMDD ก็สามารถกำหนดชื่อโฟลเดอร์ที่ต้องการได้ เช่น
$path = $file->store('avatar/'); // writable/uploads/avatar/1598695231_f5472b7b8c237b8e6272.jpg echo $path; // avatar/1598696002_799e2b95c82c8be81255.jpg
หรือต้องการกำหนดชื่อเองก็สามารถเพิ่มเป็นดังนี้
$newName = date("DMY").$file->getExtension(); // 31082020.jpg $path = $file->store('avatar/', $newName);
ความแตกต่างของคำสั่ง store() คือจะสร้างโฟลเดอร์ไว้เฉพาะใน 'writable/uploads' เท่านั้น นั้นคือถ้ากำหนดโฟลเดอร์ย่อย ก็
จะถูกสร้างในโฟลเดอร์ดังกล่าว ไม่สามารถกำหนด path ภายนอกเหมือนคำสั่ง move() ได้
และอีกอย่างคือ คำสั่ง store() จะคืนค่าเป็น path ของไฟล์ที่ย้ายเป็น string ตามตัวอย่างด้านบน
การใช้งาน File Class
การใช้งานการอัพโหลดไฟล์ข้างเป็น เป็นรูปแบบการใช้งานผ่าน IncomingRequest ใน Controller โดยจัดการผ่าน instance
ของ UploadedFile class นอกจากวิธีนี้แล้ว เรายังสามารถจัดการไฟล์ด้วย File class (CodeIgniter\Files\File()) ในขั้นตอนอื่นๆ
ที่ไม่ใช่การอัพโหลดไฟล์ได้ เช่น อาจจะอยากดูชื่อไฟล์ นามสกุลไฟล์ ขนาดไฟล์ หรือแม้จะอัพโหลดไฟล์จากที่อื่นๆ ที่ไม่ได้ใช้งาน
ใน Controller class ก็สามารถทำได้เหมือนกัน โดยตัว File class ของ CI จะมีการใช้งาน SplFileInfo class ของ PHP ในการจัดการ
เราสามารถเรียกใช้งานผ่าน controller หรือส่วนอื่นๆ ดังนี้
<?php namespace App\Controllers; use CodeIgniter\Controller; // เรียกใช้งาน Controller class use CodeIgniter\Files\File; // ใช้งาน File class class Helloworld extends Controller { public function index() { $pathFile = WRITEPATH.'uploads/20200829/1598695231_f5472b7b8c237b8e6272.jpg'; // $file = new \CodeIgniter\Files\File($pathFile); $file = new File($pathFile); // ถ้าใช้ use ก็กำหนดสั้นๆ แบบนี้ได้ } }
สำหรับ path ไฟล์ที่จะกำหนด สามารถกำหนดได้ทั้งแบบ absolute path เหมือนตัวอย่างด้านบน หรือเป็นแบบ ralative path
เช่น 'uploads/avatar5.png' โดยกรณีถ้ากำหนดเป็นแบบ relative path จะอิงตำแหน่งของ site root เป็นหลักหรือก็คือจากโฟลเดอร์
public ตัวอย่าง
$pathFile = 'uploads/avatar5.png'; $file = new File($pathFile);
คำสั่งนี้จะคืนค่าเป็น instance ของ File class ที่รองรับการใช้งาน method ของ Spl ได้ ตัวอย่างเช่น
$pathFile = 'uploads/avatar5.png'; $file = new File($pathFile); echo $file->getBasename(); // avatar5.png echo $file->getMTime(); // 1589920871 echo $file->getRealPath(); // C:\xampp\htdocs\mysslweb\writable\uploads\avatar5.png echo $file->getPerms(); // 33206
นอกจากนั้นยังรองรับคำสั่ง เช่นเดียวกับ UploadedFile class ที่เราอธิบายไปก่อนหน้าได้ ไม่ว่าจะเป็นคำสั่ง
getRandomName(); getSize() getSizeByUnit() getMimeType() guessExtension() move()
โดยเฉพาะคำสั่ง guessExtension() และ move() ที่เราสามารถประยุกต๋ใช้งานร่วมกับขั้นตอนการอัพโหลดไฟล์ได้
อยางคำสัง guessExtension() ก็จะคืนค่าเป็นนามสกุลของไฟล์โดยเดาค่าจาก MIME type ของไฟล์นั้น แทนการใช้นามสกุล
ไฟล์โดยตรง เพราะนามสกุลไฟล์ อาจจะเปลี่ยนเป็นค่าอะไรก็ได้ แต่ MIME type ถึงจะเปลี่ยน แต่ค่าก็จะยังอ้างอิงรูปแบบของไฟล์
นั้นๆ แทน ค่า MIME ที่ใชสำหรับเดาหรือกำหนดนามสกุลของไฟล์ สามารถปรับเพิ่มเติมได้ในไฟล์ app/Config/Mimes.php
ยกตัวอย่างเช่น เรามีไฟล์ script.php แล้วเราเปลี่ยนชื่อไฟล์ เป็น photo.jpg ถ้าเราใช้คำสั่งสำหรับดูนามสกุลของไฟล์ด้วย
คำสั่ง getExtension() ก็จะได้ค่าเป็น jpg เพราะว่าดูที่นามสกุลของชื่อไฟล์เป็นหลัง แต่ถ้าเราใช้คำสั่ง guessExtension() จะได้ค่า
เป็น php เพราะใช้การเดาค่า MIME เนื้องจากเดิมก่อนเปลี่ยนชื่อเป็นไฟล์ php ดังนั้นค่า MIME จึงอาจจะเป็นค่าดังต่อไปนี้ได้
app/Config/Mimes.php บางส่วน
'php' => [ 'application/x-php', 'application/x-httpd-php', 'application/php', 'text/php', 'text/x-php', 'application/x-httpd-php-source', ],
หากค่า MIME type ตรงกับค่าใดค่าหนึ่ง ตัวคำสั่ง guessExtension() ก็จะคืนค่าเป็น php แบบนี้เป็นต้น
คำสั่ง move() ใน UuploadFile instance เราใช้สำหรับย้ายไฟล์จาก tmp โฟลเดอร์ไปยังโฟลเดอร์ที่เราต้องการ ในขณะคำสั่ง
move() ใน File instance เราใช้สำหรับย้ายไฟล์ที่เรากำหนด จากที่หนึ่งไปยังอีกที่หนึ่งตามต้องการได้
ก่อนจบเนื้อหาเกี่ยวกับการจัดการไฟล์ และการอัพโหลดไฟล์ ขอรวมตัวอย่างฟอร์ม และการใช้คำสั่งสำหรับอัพโหลด ไว้เป็น
แนวทางสำประยุกต์ใช้งานต่อไป ดังนี้
// <input type="file" name="avatar" /> $file = $this->request->getFile('avatar'); $destPath = ROOTPATH.'public/uploads/avatar'; if ($file->isValid() && !$file->hasMoved() && $file->move($destPath)){ echo $file->getClientName(); echo $file->getName(); echo $file->getExtension(); echo $file->getSize(); }
// <input type="file" name="avatar[]" multiple /> // หรือ /*<input type="file" name="avatar[]" /> <input type="file" name="avatar[]" /> <input type="file" name="avatar[]" />*/ $files = $this->request->getFileMultiple('avatar'); $destPath = ROOTPATH.'public/uploads/avatar'; if($files){ foreach($files as $file){ if ($file->isValid() && !$file->hasMoved() && $file->move($destPath)){ echo $file->getClientName(); echo $file->getName(); echo $file->getExtension(); echo $file->getSize(); } } }
หรือชื่อเดียวกัน แต่อยากแยกเก็บคนละโฟลเดอร์ ก็ให้ใช้ค่า key เป็นตัวกำหนด path ได้เช่น
/*<input type="file" name="avatar[]" /> <input type="file" name="avatar[]" /> <input type="file" name="avatar[]" />*/ $files = $this->request->getFileMultiple('avatar'); $pathArr = ['avatar1','avatar2','avatar3']; if($files){ foreach($files as $key => $file){ $destPath = ROOTPATH.'public/uploads/'.$pathArr[$key]; if ($file->isValid() && !$file->hasMoved() && $file->move($destPath)){ echo $file->getClientName(); echo $file->getName(); echo $file->getExtension(); echo $file->getSize(); } } }
กรณีคนละชื่ออัพพร้อมกัน คนละโฟลเดอร์
/*<input type="file" name="avatar" /> <input type="file" name="cover" />*/ $files = $this->request->getFiles(); $pathArr = [ 'avatar' => 'avatar', 'cover' => 'cover' ]; if($files){ foreach($files as $key => $file){ $destPath = ROOTPATH.'public/uploads/'.$pathArr[$key]; if ($file->isValid() && !$file->hasMoved() && $file->move($destPath)){ echo $file->getClientName(); echo $file->getName(); echo $file->getExtension(); echo $file->getSize(); } } }
กรณีที่เราไม่กำหนดชื่อ หรือกำหนดชื่อเป็นชื่อเดิม หรือมีไฟล์ชื่อนี้อยู่แล้ว ชื่อจะถูกเปลี่ยนเป็น ชื่อเดิม ต่อด้วย _x
เมื่อ x คือตัวเลขตั้ง 1 นับไปเรื่อยๆ เช่น _1 _2 ....
หวังว่าเนื้อหานี้ จะเป็นแนวทางนำไปปรับประยุกต๋ใช้งานต่อไป
เนื้อหาเกี่ยวกับฟอร์ม ยังไม่จบเท่านี้ เรายังมีส่วนของการตรวจสอบข้อมูลหรือที่เรียกว่าการ validated data จะได้นำ
เสนอในลำดับต่อไป รอติดตาม