Tag Archive clone

PHPWord: กรอกข้อมูลใส่ template

เพราะว่า word นั้นมีความสามารถปรับแต่งรูปแบบได้ซับซ้อนมากกว่า excel อยู่มาก อย่างน้อยแค่ตำแหน่งของส่วนต่างๆ ในแบบฟอร์มก็มีมากกว่า excel ที่เลือกตำแหน่งได้แค่ว่าจะใส่ column / row ไหน

การใช้ PHP สร้างไฟล์ word ขึ้นมาการสร้างไฟล์แม่แบบขึ้นก่อน โดยการสร้างตัว word template ขึ้นมาก่อนโดยใช้โปรแกรม Microsoft Word สร้างเอกสารไปตามปกติ จึงง่ายกว่า จากนั้นก็แก้เพียงแต่ในส่วนที่่ต้องการจะแทนค่าให้ใส่ ${key name} ลงไป (ระวังเรื่องช่องว่างด้วยนะครับ) หรือส่วนที่ต้องการ repeat ก็เพิ่ม ${key name} … ${/key name} เข้าไปเท่านั้นจะง่ายกว่า ไฟล์ในตัวอย่างนี้สามารถโหลดได้จาก template.docx

จากนั้นก็เขียน code

<?php
ini_set('max_execution_time', 0);
ini_set('memory_limit', '-1');

include '../vendor/autoload.php';

use PhpOffice\PhpWord;

$title = 'Template Render ' . date('Y-m-d H:i:s');

/* load template */
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('template.docx');

/* fill data */
$templateProcessor->setValue('header', $title);

/* random data */
$scores = [];
for ($a = 0; $a < 10; $a++) {
    $scores[$a + 1] = rand(0, 100);
}

/* clone table row and fill */
$templateProcessor->cloneRow('student_id', count($scores));
foreach ($scores as $key => $value) {
    $templateProcessor->setValue('score#' . $key, htmlspecialchars($value, ENT_COMPAT, 'UTF-8'));
    $templateProcessor->setValue('student_id#' . $key, htmlspecialchars($key, ENT_COMPAT, 'UTF-8'));
}

/* clone all block */
$templateProcessor->cloneBlock('repeater', 5);

header("Content-Description: File Transfer");
header('Content-Disposition: attachment; filename="' . $title . '.docx"');
header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Expires: 0');
$templateProcessor->saveAs('php://output');

อธิบาย code

  • การทำงานโดยจะโหลด ตัว PHPWord มาใช้ก่อน จะสังเกตุว่าจะมีการใช้ namespaces แทนที่จะใช้ include เข้ามาเหมือน PHPExcel
  • กำหนดจุดที่จะกรอกข้อมูลโดยใส่ ${key name} ใน word เช่น ${header} จากนั้นใน PHP ก็ส่งค่าตัวแปรไปแทนที่โดยใช้
    $templateProcessor->setValue('header', $title);
  • สามารถ repeat ตารางได้โดยการใส่ ${key name} ใน column ที่ต้องการคัดลอกแถวทั้งแถว เช่น ${student_id} และสั่งให้คัดลอกแถวโดยใช้
    $templateProcessor->cloneRow('student_id', จำนวนครั้ง);

    แถวในตารางที่ใส่ ${student_id} จะเพิ่มขึ้นมาทั้งแถวรวมทั้งคอลัมน์อื่นด้วย

  • สามารถเติิมช้อมูลให้คอลัมน์ที่เพิ่มมาใหม่นี้โดยใช้
    foreach ($scores as $key => $value) {
        $templateProcessor->setValue('score#' . $key, htmlspecialchars($value, ENT_COMPAT, 'UTF-8'));
        $templateProcessor->setValue('student_id#' . $key, htmlspecialchars($key, ENT_COMPAT, 'UTF-8'));
    }

    สังเกตุว่าจะมี

    #' . $key

    เพิ่มขึ้นมาเพิื่อจะแทนที่ลำดับที่เพิ่มขึ้นมานั่นเอง

  • ในรูปแบบตารางที่ซับซ้อนเช่น ตาราง “ใบขออนุญาตสอบซ่อม” ที่ต้องทำซ้ำหลายแถวและหลายคอลัมน์พร้อมๆกัน ทั้งตารางจะไม่สามารถใช้
    $templateProcessor->cloneRow('student_id', จำนวนครั้ง);

    ได้ ต้องเปลี่ยนไปใช้

    $templateProcessor->cloneBlock('repeater', จำนวนครั้ง);

    แทน แต่จะไม่สามารถใส่ตัวแปรลงไปแทนที่ได้ใน version นี้ (PHPWord version 0.13.0)

คัดลอกหมดทั้งโฟลเดอร์

โปรเจ็คที่ทำอยู่ตอนนี้ ทางลูกค้าต้องการให้สามารถ สร้างระบบย่อยได้เอง จะทำงานคล้ายๆกัน แต่ละส่วนสามารถปรับแต่งได้อิสระจากตัวอื่นๆ เช่น ใช้ logo ของบริษัทพันธมิตร โดยจะมีส่วนย่อยๆ นี้ตามจำนวนพันธมิตรที่เค้าหามาได้ และใน backed จะต้องสามารถสร้างขึ้นมาได้เอง โดยที่ไม่ต้องการให้โปรแกรมเมอร์ มาเซ็ตระบบเริ่มต้นให้

หนึ่งในกระบวนการที่คิดไว้ก็คือ แยก code ของแต่ละ partner ออกไปในอีกโพลเดอร์และใช้ php clone ออกไปทั้ง sub directory ไปไว้อีกที่ ให้แต่ละบริษัทใช้

<?php

function cloning($folderSource, $folderClone) {
	if (!@mkdir($folderClone, 0755)) {
		return error_get_last();
	}
	foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderSource, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
		if ($item->isDir()) {
			mkdir($folderClone . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
		} else {
			copy($item, $folderClone . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
		}
	}
	return true;
}

$message = cloning('partner', 'partner_dolly');
if ($message === true) {
	echo 'Success';
} else {
	echo print_r($message, true);
}