目前分類:PHP (15)

瀏覽方式: 標題列表 簡短摘要
Yii Framework 本身對資料庫的操作除了提供 Active Record 以外,也有提供類似 ZendFramework Zend_Db_Table 以物件透過函式組合 SQL 語法的方法:CDbCommand

基本的使用方式是:
<?php
// 取得 tbl_user 資料表中,id = 1 的資料列
$row = Yii::app()->db->createCommand()
->select('username, password')
->from('tbl_user')
->where('id = :id', array(':id' => 1))
->queryRow();
?>
其中 where 的用法是需要特別說明的地方,因為 Yii 在底層也是使用 PDO 實作,為了防止 SQL Injection 的發生,提供非常類似的使用方法;where() 函式可以引用二個參數:$conditions、$params。$conditions 是 array 型態時,包含連結方式、欄位、值;也可以使用單純 string 型態的字串。$params 非必填,是 array 型態,對應 $conditions 設定的值做 quote 的處理。範例如下:
<?php
$id = (int) $id;
$typeId = (int) $typeId;
$conditions = array(
'and',
'id = :id',
'type_id = :typeId'
);
$params = array(
':id' => $id,
':typeId' => $typeId
);
// 取得 tbl_table 資料表中,id = $id AND type_id = $typeId 的資料列
$row = Yii::app()->db->createCommand()
->from('tbl_table')
->where($conditions, $params)
->queryRow();
?>
其他關於 where 更詳細的用法,請參考 casahama 熱心的翻譯文章(簡體)。

其實相比較之下,個人覺得 Zend_Db_Table 比較好用一點,因為他的 where() 函式可以一直串接下去,但 CDbCommand 的 where() 必須先自己依造規則組好,而且只能下一次...(後面下的參數會蓋過前面下的參數)不過在這些 Framework 提供的 Query Builder 的幫助下,能確實減少我們在撰寫 SQL 語法時可能發生的錯誤,同時過濾有可能造成 SQL Injection 的參數。感謝 Framework 製作者的貼心,提供這麼方便的工具^^
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

簡單紀錄一下幾個在 PHP 中讓效能更好的方法,其實基本的原則就是「少用函式,多用 isset()empty() 取代」

1. 用 (int) $var 取代 intval($var)
PHP 是一種弱型別的語言,所以在面對外部傳進來的變數時,我們通常會在作一次型別的轉換處理;PHP 內建提供型別的強制轉換,如:(int)、(string)、(array),強制型別轉換比函式如:intval()strval() 來的有效率。

2. 用 isset($var[0]) 取代 0 < strlen($var)
當變數是字串型態時,我經常會用 strlen() 這個判斷字串長度的函式檢查他是否為空字串;不過其實 isset() 再檢查字串變數時的效率更高。其他像是在判斷字串長度時,也可以用它來代替。

3. 用 strpos($var, 'str') !== FALSE 取代 preg_match('/str/', $var, $matches)
在字串查找時,第一個想到的函式通常是 preg_match(),但是如果尋找的字串很單純時,使用 strpos() 的效能會更好。

其他還有許多撰寫 PHP 程式時增進效能的小技巧,請參考 Tsung's Blog 有更詳盡的說明。
文章標籤

danielhuang030 發表在 痞客邦 留言(1) 人氣()

喔~大概有八百年沒有寫文章了...(汗)最近遇到一個需求,需要用到 cookie 紀錄;不過在實作的過程中,發現明明以 AJAX 在 PHP 中 setcookie(),重新整理後卻無法取得 $_COOKIE 資訊的問題。後來以關鍵字餵了 google 大神以後,才找到解決的方法:

1. 設定 setcookie() $path 參數:
以 AJAX 設定 cookie 時,如果沒有指定 $path 參數程式會自動指定一個虛擬目錄給它,但依照 cookie 的特性:「僅有它的目錄以及以下的子目錄能夠存取」;所以未指定 $path 參數的 cookie 可能會造成在其他目錄下的程式無法存取。一勞永逸的方式就是直接指定 $path 參數為根目錄「/」:
<?php
// 第三個參數是 cookie 的存活時間,0 表示存活至瀏覽器關閉為止
setcookie('variable', $value, 0, '/');
?>
2. PHP 伺服端回傳變數,由 JavaScript 寫入 cookie:
做法有很多種,不過大概就是由 JavaScript 寫入 cookie 的概念。

最後我是使用第一種方式,有些問題還真是沒有碰到就不會知道呢~google 大神真是大家的好朋友!(拇指)
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

PHP 從 PHP5 開始,在資料庫操作方面新增了 PDO(PHP Data Objects)的 extension,利用物件導向的方式與資料庫進行溝通。捨棄以往透過單一函式的方式,而是透過物件導向程式設計的抽象化概念,操作時使用統一的方法,如果需要更換資料庫時,只要在建立物件時給予資料庫的形式,而不用更動到原來的程式碼。這也是物件導向程式設計擁有優良靈活性的最佳表現!

連線的方式:
<?php
// 給定資料庫變數
$dbtype_sql = 'mysql';
$host_sql = 'localhost';
$dbname_sql = 'dbname';
$username_sql = 'username';
$password_sql = 'password';


// 資料庫連線
try {
$dbh = new PDO($dbtype_sql . ':host=' . $host_sql . ';dbname=' . $dbname_sql, $username_sql, $password_sql);
// 資料庫使用 UTF8 編碼
$dbh->query('SET NAMES UTF8');
} catch (PDOException $e) {
echo 'Error!: ' . $e->getMessage() . '<br />';
}
?>

一般的 SELECT 查詢方式:
<?php
// 使用 quote 避免 SQL Injection;相當於 mysql_real_escape_string()
$id = $dbh->quote(2);
$name = $dbh->quote('John');

// 組合 SQL 語法,取得符合 id = 2、name = 'John' 的資料
$query = sprintf("SELECT * FROM table WHERE id = %s AND name = %s", $id, $name);
foreach ($dbh->query($query) as $row) {
print_r($row);
}
?>

利用預載的方式:(推薦使用)
<?php
// 組合 SQL 語法,取得符合 id = 2、name = 'John' 的資料
$sth = $dbh->prepare('SELECT * FROM table WHERE id = :id AND name = :name');
$where = array(':id' => 2, ':name' => 'John');
// 使用 execute(),會自動 quote $where 的參數
$sth->execute($where);

foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
print_r($row);
}
?>


統一採用物件的方式來操作資料庫,不僅增加系統的彈性,透過相同的 quote() 方法,即可針對不同的資料庫形式進行 SQL Injection 的預防;真的非常方便!雖然於其他專業的 ORM(如:Doctrine)比較,仍有不足;但是在比較簡單的功能的實作上,PHP5 內建的 PDO 的確提供良好的資料庫操作模式~推薦使用!^^
文章標籤

danielhuang030 發表在 痞客邦 留言(2) 人氣()

一般在使用 ZendFrameworkZend_Db_Table_Rowset fetchAll() 時,都是需要 Rowset 裡面全部的資料;不過偶爾有會有只需要單筆資料列的情況,這個時候就可以藉助 current() 或是 getRow() 取得單筆資料列。

current() 很單純,就是目前 Rowset 中指向的資料列,通常在沒有指定指標的情況下,就是該 Rowset 中的第一筆資料:
$table = new Table();
$select = $table->select();
$rowset = $table->fetchAll($select);
$row = $rowset->current();
// 這裡的 $row 就會是 $rowset 的第一筆資料列。
getRow() 就比較有彈性,它可以指定取得第幾個資料列,並決定是不是要將指標移至該資料列(預設為不指定)。
getRow($position, $seek = false) 
// $position 設定取得第幾列資料(從 0 開始)
// $seek 設定是否要將指標移至該資料列(預設為不指定)
用法如下:
$table = new Table();
$select = $table->select();
$rowset = $table->fetchAll($select);
$row = $rowset->getRow(1);
// 這裡的 $row 就會是 $rowset 的第二筆資料列。
要比較注意的是 $seek 的使用時機,是否有需要移動指標,就看程式的需求囉;使用後呈現的資料,如以下的例子:
$table = new Table();
$select = $table->select();
$rowset = $table->fetchAll($select);
$row = $rowset->current();
// 這裡的 $row 就會是 $rowset 的第一筆資料列。

$row = $rowset->getRow(1);
// 這裡的 $row 就會是 $rowset 的第二筆資料列($seek 預設為 false,故指標不移動)。

$row = $rowset->current();
// 這裡的 $row 還是 $rowset 的第一筆資料列。

$row = $rowset->getRow(1, true);
// 這裡的 $row 就會是 $rowset 的第二筆資料列($seek 設為 true,故指標移動)。

$row = $rowset->current();
// 這裡的 $row 就會變成是 $rowset 的第二筆資料列。
大概就是這樣;在這邊做個紀錄,感謝 Jace 解惑^^
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

最近在練習中有用到搜尋的功能。一般對MySQL資料庫作搜尋,常用的做法是針對資料表中的特定欄位,用「%」LIKE的方式去尋找。然而這樣的做法常伴隨著許多限制,使用者必須先選定所要輸入的資料欄位,再對其進行搜尋;習慣了Google搜尋所帶來的便利,最理想的方式是只有一個輸入格,且可以在此輸入格中任意輸入,即可對整個資料庫進行搜尋。在MySQL中稱為Full-Text(全文檢索);然而拜完Google大神以後,網路上前輩們幾乎是一面倒的否定全文檢索。最主要的原因是因為它不支持中文!

全文檢索的做法,即是對資料庫裡的資料進行「分詞」的索引處理,有了索引,搜尋起來自然有效率的多;然而中文字不同於英文,一個句子中單獨一個中文字就可能有它的意思,另一個最大的分別在於中文句子可不像英文句子由單字與「空格」組成;建立索引時的「分詞」的動作,就是以空格進行判斷!

全文檢索的問題在網路上一直存在著,但是前輩們似乎都沒有非常完美的解答;甚至有人直接勸退提問者:「全文檢索的功能,是可以讓你寫好幾篇博士論文的研究!」如此可見,Google雲端運算的強大。既然資料庫端無解,我就從PHP的方向著手吧~Google的確是大神,讓我找到了Zend Framework就有分詞的函式Zend Search Lucene;而且原本這個功能其實也不支持中文,萬能的Google大神還幫我找到了支持中文的解決方法!

PHP製作中文全文搜尋不求人中,作者巨細靡遺的說明了資料夾配置、原始程式碼,還非常貼心的提供範例程式的下載。在如何讓Zend_Search_Lucene支持中文分詞中,作者改良了分詞用的類別,讓中文分詞的動作更加準確!既然有如此完整的範例,我當然是馬上適用在練習中啦!

目前練習所利用的開發框架,是Jace一手打造的Wacow Framework。結合Zend FrameworkSmarty,以及許多在專案製作時常會用到的工具,是公司目前專案開發的主力,也是我目前需要熟練的工具。我把建立索引的功能放在首頁,這樣只要有人進入首頁就會觸發建立索引(當然這樣做的代價是每次進首頁的速度都會被拖慢)。因為自動載入類別的關係,在建立索引時不需再額外include或require,程式碼如下:

IndexController.php(部分節錄)

// 建立分詞索引
// 關閉 Notice 錯誤提醒
error_reporting(E_ALL ^ E_NOTICE);
// 資料是 utf8 為編碼的這句為重點。如果你是 utf8 的話必需加入,否則資料會錯誤;另Phpbean是需要另外建立的中文分詞的類別
Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Phpbean());
if (function_exists("set_time_limit") && ! get_cfg_var('safe_mode')) {
set_time_limit(0);
}
$index = new Zend_Search_Lucene('index', true);
$itemTable = new Items();
$itemRowset = $itemTable->fetchAll();
foreach ($itemRowset as $itemRow) {
$url = '/gime/item/detail/id/' . $itemRow->id; // 建立連結
$itemName = $itemRow->name; // 抓出物品名稱
$description = $itemRow->description; // 抓出物品敘述
//儲存網頁的位置以在搜尋結果中連結.
$doc = new Zend_Search_Lucene_Document(); // 建立新的索引文件
$doc->addField(Zend_Search_Lucene_Field::UnIndexed('url', strtolower($url)));
$doc->addField(Zend_Search_Lucene_Field::Text('name', strtolower($itemName), 'utf-8'));
$doc->addField(Zend_Search_Lucene_Field::Text('contents', strtolower($description), 'utf-8'));
$index->addDocument($doc); //把索引文件加到索引中
}
$index->commit(); //提交,及保存索引

Phpbean.php

文章標籤

danielhuang030 發表在 痞客邦 留言(3) 人氣()

在PHP中最常見的應用就是互動表單,因此接收用戶端資訊是非常常見的事!然而俗話說:「世風日下,人心不古」,這年頭誰知道用戶端的人在想些什麼?用戶端送出的資訊很有可能會包含一些惡意的語法,對於24小時暴露在網路環境的網站來說,時時刻刻都得小心防範駭客的攻擊,提升自我的資訊安全觀念非常重要。所以在接收用戶端資訊後,存入資料庫之前,都會先對其進行基本的過濾;第一個要判斷的就是HTML語法的攻擊!

許多惡意語法都是建立在HTML上,PHP本身有提供轉換HTML碼的函數:htmlspecialchars()htmlentities();其中htmlspecialchars()只會轉換HTML相關碼:
  • '&' => '&amp;'
  • '"' => '&quot;'
  • ''' => '&#039;' 
  • '<' => '&lt;'
  • '>' => '&gt;'
htmlentities()則是把字串中所有字元做轉換,另外還可以設定轉換字串的編碼方式。還有一個函數:strip_tags()是直接把HTML的標籤整個過濾掉。

之前在題目試作/哇寶基本能力測試一文中,也有提到相關的觀念。另外附上那個時候google到的一個國外防止XSS的網頁:PHP XSS (cross site scripting) filter function,他有對HTML作是否可能被當作惡意的語法作判斷,不過測試後發現對中文的處理好像有點問題...就當作是英文的過濾函式吧~
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

ZendFramework是PHP的一個開發用框架,主要以PHP5作為撰寫時的基礎。要使用之前當然得先建立環境,以下是安裝的順序:參考「在Zend Framework上開發一個HelloWorld

1、首先先從ZendFramework的官方網站下在最新版本的ZendFramework 1.5.1;解壓縮後放在任意資料夾。我是放在「C:\ZendFramework」這個目錄下。

2、修改Apache httpd.conf設定:
(1)啟動 Apache的 .htaccess功能,搜尋「AllowOverride」 並將其設定為 All
(2)
開啟 LoadModule rewrite_module modules/mod_rewrite.so。

3、
修改PHP php.ini設定:
(1)設定include_path = ".;C:\ZendFramework"
(2)開啟extension=php_pdo.dll、extension=php_pdo_mysql.dll

重新啟動
Apache,如此一來基本的環境算是建置完成了。
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

一般正常在處理Big5<->UTF-8編碼上的轉換,第一個想到的就是iconv()函數;雖然需要額外安裝iconv函式庫,不過因為大部分人都有這種需求,虛擬主機商一般都會安裝,如果是自己的主機就更沒有這種問題了~

在Big5轉換成UTF-8方面,完全沒有問題;不過在UTF-8轉換成Big5方面,可就出現問題啦!因為UTF-8編碼對應的字遠比Big5編碼多,所以在一些特殊中文字的轉換上,如:堃、犇等字,iconv()函數就會出現錯誤啦!本來一直都沒有發現到這方面的問題,不過最近在公司專案管理系統上的Excel表轉出時,遇到有對方公司有「犇」這個字,讓iconv()函數發生錯誤而導致轉出的Excel檔案整個損壞的情況發生...Excel在正常輸入時是對應UTF-8編碼的,不過在轉出檔案時必須改為Big5編碼,不然UTF-8編碼的中文字會變成亂碼~無法自動判斷真是傷腦筋耶!

後來上網找了很久,都沒有令人滿意的答案;大家在轉出Excel檔時,幾乎都還是利用iconv()函數先轉成Big5編碼再轉出。後來我發現到另外一個函數:mb_convert_encoding(),似乎也是在處理編碼轉換的函數。試用之後雖然不存在的字還是不會自己生出來(如:堃、犇這些Big5編碼本來就沒有的字),但是轉不出來的字它會以「?」顯示,不會發生錯誤訊息,自然也就能正確的開啟轉出的Excel檔囉!雖然這並不是治本的方法,不過算是有稍微解決一些問題啦!

編碼真的是我目前最深的痛,轉來轉去真的很麻煩;目前我寫的PHP已經一律採用UTF-8編碼了~在網頁上是沒什麼問題,不過需要配合其他程式(如:Excel)時就必須轉來轉去...還蠻麻煩的哩!希望以後開發的軟體都能支援Unicode,畢竟萬國碼才是未來的趨勢吧!
文章標籤

danielhuang030 發表在 痞客邦 留言(6) 人氣()

偶然在網路上看到介紹的文章:免費好用的 PDF Library 大蒐集。原本是想用來寫一個套印的小程式的,不過沒想到這玩意兒這麼難搞;因為中文編碼的關係。何況已經有人寫出類似的程式囉:藍色小舖PHP -> FPDF。還是稍微記錄一下使用的過程吧!

說到pdf檔產生,網路上似乎一面倒的推薦FPDF,其他pdf產生的class也幾乎是在它的基礎下衍生出來的~算是元老級的pdf class!另外對於中文的支援有提供
chinese.zip、以及支援中文Unicode的chinese-unicode.zip;目前我試用中文Unicode的FPDF可以完整呈現!(未使用Unicode的FPDF不知道為什麼我的FoxitReader一片空白...)以下是範例的程式碼:

<?php
require('fpdf/chinese-unicode.php');  //include必要程式

$pdf=new PDF_Unicode();  //
調用PDF_Unicode class

$pdf->Open();
$pdf->AddPage();
$pdf->AddUniCNShwFont('uni');
$pdf->SetFont('uni','',20);
 
$pdf->Write(10, "1234abcd學生名字\n伃綉堃亘");
$pdf->Ln();
$pdf->MultiCell (120, 10, "服\n務\n單\n位");
$pdf->Cell (240, 10, "本文用UTF8做為中文字編碼, 在這裡還是呼叫同樣的FPDF函數");
$pdf->Ln();

$pdf->Output();
?>

我另外還試過TCPDF,號稱支援Unicode,日文、德文、阿拉伯文洋洋灑灑的列了十幾種...只可惜獨漏中文(繁/簡體)~而且它所謂支援
Unicode的方法,是直接將字型檔壓縮在pdf檔中,所以會造成pdf檔的異常肥大;實在不是一種很優的方式。
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

轉入之後當然接下來的就是轉出囉!我原本採用的轉出方法是非常陽春的斷欄「\t」與斷行「\n」的應用,所以在使用M$ Excel 2007時總是會出現錯誤訊息提示。與轉入的方式比較起來,Excel檔案轉出的方式似乎比較多:PHP導入導出Excel方法小結。裡面共介紹6種方式~不巧在下小弟我因為儲存格格式的問題,幾乎把所有方法都試過一遍了...下面來分別介紹使用方式與心得吧!

1、PHPExcel:PHPExcel,一個最好的控制excel的類

非常可惜的這是我能找到介紹最完整的網路說明~這套class是屬於非常新的一個class,所以關於用法網路上並沒有介紹的很詳細;官方網站裡的說明文件也非常陽春...雖然功能強大,而且通吃M$ Excel 2007及其以下的各版本,文章裡大力推薦,但是因為說明文件資料不足、網上範例太少、寫入時採用英文字母,很難用迴圈跑出來(汗)、儲存格格式無法設定...等原因,最後放棄這一個方法。

/*PHPExcel使用*/
error_reporting(E_ALL);  //開啟錯誤顯示(?)
set_include_path(get_include_path() . PATH_SEPARATOR . '../Excel/');  //設定class路徑
include 'PHPExcel.php';  //include必要程式
include 'PHPExcel/Writer/Excel5.php';  //使用Excel 2003以下的版本

$objPHPExcel = new PHPExcel();  //調用PHPExcel class
$objWriter = new PHPExcel_Writer_Excel5($objPHPExcel);  //調用Excel 2003以下的版本
$objPHPExcel->setActiveSheetIndex(0);  //設定動作Sheet      
       
$objPHPExcel->getActiveSheet()->setCellValue('A1', '日期');  //指定A1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('B1', '姓名');  //指定B1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('C1', '上班時數');  //指定C1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('D1', '加班時數');  //指定D1儲存格內容
$objPHPExcel->getActiveSheet()->setCellValue('E1', '工程案號');  //指定E1儲存格內容
       
$objPHPExcel->getActiveSheet()->setTitle('Simple');  //指定Sheet名稱
$objWriter->save('error_log.xls');  //另存Excel檔案

最後儲存的檔案會再放置網頁的資料夾中,如果要讓使用者另存下載,就必須加上header。

2、pear的Spreadsheet_Excel_Writer:關於PEAR類庫中用於操作EXCEL的類庫Spreadsheet_Excel_Writer

這算是一種歷史悠久的方法,運用PHP非常強大的函式庫pear;關於pear我其實一直不是很了解,這次剛好藉由這次機會接觸。首先是pear的安裝,實際跑過一遍以後才發現原來安裝這麼容易:直接執行PHP目錄下的「go-pear.bat」,安裝過程一直按「Enter」就OK了!安裝完成以後,PHP目錄下PEAR的目錄裡面就會有資料了,此時pear就算安裝完成啦!接下來是Spreadsheet_Excel_Writer的安裝;在命令提示字元下執行:

pear install OLE-0.5
pear install Spreadsheet_Excel_Writer-0.9.1

pear就會自動上網下載與安裝!感覺好linux唷~安裝也變得非常容易。全部安裝完成以後,只剩下要注意pear在PHP裡的path路徑是否正確,就可以直接include進來囉!使用Spreadsheet_Excel_Writer唯一比較麻煩的是必須先安裝pear及其函式庫,使用上非常便利。原本是我心目中的第一首選!不過不知道為什麼,我們公司的伺服器pear裝不起來,試了好久都不行;雖然網路上說可以直接COPY pear整個資料夾再設定path,不過因為怕不正確安裝會導致不可預期的後果,且儲存格格式也是無法設定;所以放棄此一方法。

/*PEAR的Spreadsheet_Excel_Writer*/
require_once 'Spreadsheet/Excel/Writer.php';  //require必要程式
       
$workbook = new Spreadsheet_Excel_Writer();  //調用Spreadsheet_Excel_Writer class
$workbook->setVersion(8);  //設定Excel版本為XP以上
$worksheet =& $workbook->addWorksheet('Sheet1');  //增加一個Sheet
$worksheet->setInputEncoding('utf-8');  //設定編碼為UTF   
$format_locked =& $workbook->addFormat();
$format_locked -> setLocked();  //設定鎖定格式,防止別人修改
//$worksheet->setColumn(0,255,8.38,$format_locked);  //此為指定整個工作表鎖定狀態 
     
$worksheet->writeString(0, 0, '日期');  //設定(0,0)儲存格內容
$worksheet->writeString(0, 1, '姓名');  //設定(0,1)儲存格內容
$worksheet->writeString(0, 2, '上班時數');  //設定(0,2)儲存格內容
$worksheet->writeString(0, 3, '加班時數');  //設定(0,3)儲存格內容
$worksheet->writeString(0, 4, '工程案號');  //設定(0,4)儲存格內容

//錯誤資料寫入Excel      
for ($i = 1;$i <= $error_row;$i++){
    for ($j = 1;$j <= 5;$j++){
        $worksheet->write($i, $j-1, $error_msg[$i][$j]);
    }
}

$workbook->send('error_log.xls');  //另存下載Excel檔案          
$workbook->close();  //關閉(?)

3、XML轉出:使用 PHP 輸出帶格式的 Excel 文件

話說M$ Excel從2003開始,導入了對XML檔案的支援,終於冥頑不靈的M$也向開放性格事低頭了呢!只要由PHP輸出正確的XML格式,再另存成xls檔就可以餵給Excel讀了吧...本來是這樣想的...沒想到我用的M$ Excel 2007在另存成XML檔時好像還得經過些設定;我整個不知道要怎麼做啊!在不確定因素及設定太過複雜的情況下,我放棄了這個方法~(其實也是懶得弄一些有的沒的啦...)

4、利用pack()函數將資料包裝使其接近Excel格式:這段操作excel的代碼應該怎麼操作設置每列的寬度

如同標題說,就是利用pack()函數將資料包裝使其接近Excel格式。Excel的格式是M$自定的一種資料格式,這個方法是利用PHP的pack()函數,把我們所需的資料包裝成接近Excel原始格式的一種方法;其實說穿了不過就是把「\t」「\n」替換掉的另一種陽春的方法。不過這種方是因為接近Excel原始格式,所以在開啟的時候並不會有錯誤訊息提示,算是一種簡單易懂又最貼近Excel格式的方法。文章裡也推薦這個方法,所以最後我採用了這個方式~雖然它仍然無法設定儲存格格式...(汗)

/*利用pack()函數將資料包裝使其接近Excel格式*/
$filename = 'error_log.xls';  //設定另存下載檔案名稱
       
header ('Content-type: application/x-msexcel');  //送出header
header ("Content-Disposition: attachment; filename={$filename}" );
       
xlsBOF();  //起始包裝函數
//標題列
xlsWriteLabel(0, 0, u2b('日期'));  //設定(0,0)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 1, u2b('姓名'));  //設定(0,1)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 2, u2b('上班時數'));  //設定(0,2)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 3, u2b('加班時數'));  //設定(0,3)儲存格內容,儲存格內容為文字
xlsWriteLabel(0, 4, u2b('工程案號'));  //設定(0,4)儲存格內容,儲存格內容為文字
//錯誤列 
for ($i = 0;$i < $error_row;$i++){
    xlsWriteLabel($i + 1, 0, $error_msg[$i][0]);  //設定($i + 1,0)儲存格內容,儲存格內容為文字
    xlsWriteLabel($i + 1, 1, $error_msg[$i][1]);  //設定($i + 1,1)儲存格內容,儲存格內容為文字
    xlsWriteNumber($i + 1, 2, $error_msg[$i][2]);  //設定($i + 1,2)儲存格內容,儲存格內容為數字
    xlsWriteNumber($i + 1, 3, $error_msg[$i][3]);  //設定($i + 1,3)儲存格內容,儲存格內容為數字
    xlsWriteLabel($i + 1, 4, $error_msg[$i][4]);  //設定($i + 1,4)儲存格內容,儲存格內容為文字
}
xlsEOF();  //結束包裝函數

//Excel生成用函數,起始包裝
function xlsBOF() {
    echo pack("ssssss", 0x809, 0x8, 0x0, 0x10, 0x0, 0x0); 
    return;
}
//Excel生成用函數,結束包裝
function xlsEOF() {
    echo pack("ss", 0x0A, 0x00);
    return;
}
//Excel生成用函數,數字包裝用
function xlsWriteNumber($Row, $Col, $Value) {
    echo pack("sssss", 0x203, 14, $Row, $Col, 0x0);
    echo pack("d", $Value);
    return;
}
//Excel生成用函數,文字包裝用
function xlsWriteLabel($Row, $Col, $Value ) {
    $L = strlen($Value);
    echo pack("ssssss", 0x204, 8 + $L, $Row, $Col, 0x0, $L);
    echo $Value;
    return;
}

5、使用「\t」、「\n」的方法:

這就是我原來使用的方法,雖然簡單易懂易用,但是因為用M$ Excel 2007開啟時總是會出現錯誤訊息提示~所以才換掉。順便一提,它也沒辦法設定儲存格格式。

/*最原始的\t\n用法*/
$filename = 'error_log.xls';  //設定另存下載檔案名稱
       
header("Content-Type: application/vnd.ms-excel");  //送出header,這我是直接從網路上抄來的
header("Content-Disposition:filename=sub.xls");
header("Content-Disposition: attachment; filename={$filename}");
header("Pragma: no-cache");
header("Expires: 0");
       
$error = '';  //起始錯誤字串的值
//      
for ($i = 1;$i <= $error_row;$i++){
    for ($j = 1;$j <= 5;$j++){
        $error .= $error_msg[$i][$j]."\t";  //跨欄
    }
    $error .= "\n";  //斷行
}
echo $error;  //印出完整錯誤字串

6、使用com()函數:

這個方式應該是設定功能最完整個方法!因為它是直接調用Server端的M$ Excel來產生檔案的,關於這個方法我沒有實際研究。因為這個方法有限制:它只適用在Server為M$ Windows作業系統平台,且必須安裝M$ Office才能用;也就是說,如果作業平台是Linux,或是沒有安裝M$ Office就不能用。非常剛好的,我們公司的Server雖然是M$ Windows作業系統平台,卻沒有安裝M$ Office...想當然爾我就放棄這個方法啦!不過這或許是唯一一個可以設定儲存格格式的方法...

很難想像為了儲存格格式,我把網路上傳授的6種方法幾乎都試過了...雖然最後仍然沒有解決我的問題,不過因此而認識到PHP的無限可能~也學到不少東西哩!最後我對日期的處理方式,是消極的用錯誤訊息防止使用者匯入錯誤格式;這也不失為解決問題的其中一個方法啦!^^
文章標籤

danielhuang030 發表在 痞客邦 留言(16) 人氣()

依然是專案管理系統的功能。因為之前Excel檔案的匯入是採用另存新檔後的csv匯入;因為以PHP來說,xls的Excel檔內含太多不可確定的格式,不如單純的逗號分隔檔案csv的資料來的純粹。不過站在使用者的角度,「另存新檔」本身就是一個多餘的動作,何況再轉存成csv檔後並不是直接按確定這麼簡單;因為Excel檔案格式的改變,所以會有M$貼心的提醒...總之最後導致整個匯入動作感覺很不友善。

儘管PTT PHP版一面倒的建議採用csv檔作資料的讀入處理,但是使用者才不管你這麼多勒!對使用者友善,就是對程式設計者的殘忍啊...然而,之所以會需要程式設計者,也是為了要對使用者更友善啊~所以,我找了一下網路上直接上傳Excel檔轉入資料的方法,目前找到二種試驗後都成功的方法:

第一種是利用PHP的COM()函數,呼叫PHP所在Server的M$ Office程式做處理。這個函數運用的感覺比較像是在寫bat檔,利用一連串指令在使用者不知情的狀況下完成想要達到的目的;當然,缺點就是這種方式只適用在M$ Windows的作業系統,而且Server上還必須要有M$ Office...

雖然說是直接上傳Excel檔轉入資料,但說穿了不過是利用COM()函數在使用者不知情的情況下另存成csv檔後,再作處理。以下是利用COM()函數另存新檔的程式碼:

$excel = new COM("excel.application") or die("Unable to instanciate excel");  //調用COM class,順便判斷是否能正常連結Excel應用程式
$excel->DisplayAlerts = 0;  //關閉開啟Excel時的警告
$strTemp = 'C:\\xxx\\xxx\\xxx\\csv\\'.$file_name;  //上傳成功檔案的絕對路徑
$excel->Workbooks->Open($strTemp);  //打開該檔案
$csvfile_name = substr($file_name, 0,strlen($file_name)-4).'.csv';  //利用原檔名將副檔名改寫成csv
$strTemp = 'C:\\xxx\\xxx\\xxx\\csv\\'.$csvfile_name;  //另存成csv檔後的絕對路徑
$excel->ActiveWorkbook->SaveAs($strTemp ,6);  //另存新檔,6代表csv逗號分隔檔案                     
$excel->Quit();  //退出Excel
//$excel->Release();  //釋放Excel所佔用的記憶體...吧?不過我的電腦必須註解掉這行才能動作,不知道為什麼
$excel = null;  //釋放Excel所佔用的記憶體...吧?其實我不太知道這跟上面有什麼不同

下來就是利用另存的csv檔對資料做處理囉!基本上作法沒有變,只是使用者可以省略自己另存的動作,而由Server代替轉存,使用者自然會覺得方便許多。可惜本公司的Server雖然是M$ Windows系統,但是並沒有安裝M$ Office;所以只能用第二種方法囉。

第二種方法是偉大的前輩事先做好的class:PHP-ExcelReader。PHP真不愧為網路程式設計的龍頭,許多功能其實前輩們都已經想好了!其實主要用的的檔案也只有二個:oleread.inc跟reader.php。而且因為已經封裝成class,使用方面也非常的簡單,以下式簡單的程式碼:

require_once '../Excel/reader.php';
$data = new Spreadsheet_Excel_Reader();  //調用Spreadsheet_Excel_Reader class
$data->setOutputEncoding('utf-8');  //設定輸出的編碼,可直接選擇輸出為UTF8編碼
$data->read("../xxx/xxx/test.xls");  //檔案的路徑(可使用相對路徑)
error_reporting(E_ALL ^ E_NOTICE);  //錯誤顯示

for ($i = 1; $i <= $data->sheets[0]['numRows']; $i++) {
    for ($j = 1; $j <= $data->sheets[0]['numCols']; $j++) {
        echo "\"".$data->sheets[0]['cells'][$i][$j]."\",";
    }
    echo "\n";
}

利用陣列的方式,擷取Excel檔裡面的資料;['numRows']列、['numCols']行、['cells']元素,搭配二維陣列取得。擷取出來的資料就可以直接對其作處理,非常容易使用!

關於Excel檔寫入也有另一個class:Spreadsheet_Excel_Writer。這個class需要pear的支援,所以使用前必須先安裝pear;這個部份我還沒有深入研究測試,就先行打住。另外還有一個class比較新,是支援M$ Office Excel 2007的格式:PHPExcel。這個class同時支援讀入跟寫出,不過目前只針對Excel 2007的版本作處理。目前我們公司仍以2003為主,所以這個版本也沒有深究。當然2007一定是未來的趨勢,所以以後應該還是有機會會接觸到吧。

文章標籤

danielhuang030 發表在 痞客邦 留言(8) 人氣()

因為公司會計部門的人還是比較喜歡(習慣)過去使用Excel編輯成本的環境,新系統上線的不習慣在無形間浪費了許多工時;所以現在專案管理系統在輸入方面將新增轉入功能。簡單說就是利用原先熟悉的Excel環境提供轉檔程式轉進資料庫中。不過這也代表著我先前做的輸入介面與自動完成完全無用武之地啊~(噗)當然已經作的介面也不需要特別去刪掉,就當作有二種輸入介面吧!

因為Excel格式之複雜,導致PHP在轉檔時會發生許多不可預期的問題;所以不論是網路上或是PTT幾乎都建議改以csv檔作轉入的動作;Excel本身也有提供另存成csv檔的功能~也算是一種折衷的方案囉。

之前有試寫人工成本Excel的資料轉入,昨天嘗試著修改了一下介面與原系統整合,本來以為已經沒有問題了~沒想到今天早上卻發現原始csv檔內的特殊字元ex:\t、\n、\r在作怪,導致原先的Javascript警告視窗失效。上網搜尋一下馬上就找到解決的辦法啦~網路真是大家好朋友:PHP如何取消字串裡的特殊字元呢??

主要是利用str_replace()函數,運用取代函數將特殊字數取代掉;是很聰明的應用方式哩!其中以陣列方式一次取代是最方便:


$str = str_replace(array("\n","\t","\r"), array("","",""), $str);

剛好我從csv檔取得的資料也是陣列的格式,真是幫了我一個大忙哩^^
文章標籤

danielhuang030 發表在 痞客邦 留言(0) 人氣()

很久以前就有這樣的計畫,利用現有的圖片由使用者自行加註文字,然後合成為另一張新的圖片~爬了一下PTT的PHP版,很快就看到類似的東西。剛好十天的假期在家閒閒沒事,就來研究一下這玩意兒要怎麼弄吧。

說到跟圖片有關的東西,就不能忘了PHP的GD函式庫啦!它強大的功能也曾經幫我在名片查詢系統時,利用PHP上傳名片圖檔的時候同步縮小圖片呢~這次運用到的函數是ImageTTFText,它可以利用自備的TrueType字體檔,寫入文字到圖形中。最近我對微軟正黑體還蠻有好感的,再加上它算是一套免費(由微軟官方釋出,不過好像是不能任意提供給人家下載啦)的字體;拿來當圖片用的字型,在適合不過啦!

下面是php程式碼:


header("content-type:image/jpeg"); // 送出JPG的header
$nimage=imagecreatetruecolor(1024,729); // 建立一個寬 1024 高 729 像素的圖片
$black=imagecolorallocate($nimage,0,0,0); // 設定文字顯示RGB顏色
$simage =ImageCreateFromJPEG('card2008.jpg'); // 利用ImageCreateFromJPEG函數讀取原始圖片
imagecopy($nimage,$simage,0,0,0,0,1024,729); // 利用imagecopy函數複製原始圖片到建立的新圖片上
$font = '/xxx/xxx/xxx.TTF'; // 字形路徑設定
ImageTTFText($nimage,26,0,60,570,$black,$font,$wish_word); // 利用ImageTTFText函數將文字合併於圖片
ImageJPEG($nimage); // 輸出JPEG圖片
imagedestroy($nimage);
imagedestroy($simage); //釋放之前暫存圖片的記憶體


當然,重點是在ImageTTFText函數的用法:

ImageTTFText($nimage,26,0,60,570,$black,$font,$wish_word); 

$nimage:新建立的圖片的變數名
26:字型的大小
0:字型的角度
60:X座標軸位置
570:Y座標軸位置
$black:字型顏色
$font:字體檔位置;這邊因為我架設主機的平台是linux,不知道為什麼相對位置整個沒反應。弄了好久最後用絕對位置搞定,所以要特別注意。
$wish_word:輸出於圖片上的話

另外因為輸出字數的問題也花了我一點時間...
因為ImageTTFText不會自動幫你斷行,所以必須自己加『\n』去斷行
UTF-8的編碼方式中文字是3個字元,解決方法就是自己算字數然後加『\n』:

if(strlen($wish_words) > (26*3) and (strlen($wish_words) < (26*3*3+1))){
   for($i=0;$i<floor(strlen($wish_words)/78);$i++){
    $wish_word .= substr($wish_words,($i*78),78)."\n";
   }
}

我設定在字數多於26個字時斷行
其中floor()函數是取小數點後無條件捨去的正整數,另一個ceil()函數則是無條件進位後的正整數
這裡我使用floor()函數
substr()函數擷取我要的字段後在自己加『\n』斷行

其實我來以為這只是一個很簡單的GD函數運用,可是因為字型檔路徑跟編碼問題還是讓我花了不少時間
成功了以後又覺得怎麼那麼簡單?
所以雖然函數就在那,但還是要用過才知道用法,才知道會碰到什麼問題,要怎麼解決!

因為從前年開始,我每年都會為PTT的資科系版畫新年賀圖
今年就想利用這個賀圖結合一些祝福的話,在除夕夜的簡訊中連同網址一起發出
所以才會心血來潮的想做這玩意兒;本來以為簡單的東西,其實還是花了不少時間。
這裡是成果,祝大家新年快樂^^
文章標籤

danielhuang030 發表在 痞客邦 留言(1) 人氣()

原來PHP有內建數字格式建立的函數啊:number_formatmoney_format;可以直接對數字重新格式化。當然提到格式化就不能忘了sprintf這個函數囉!考慮加入在專案管理程式的可能性...
文章標籤

danielhuang030 發表在 痞客邦 留言(1) 人氣()

Close

您尚未登入,將以訪客身份留言。亦可以上方服務帳號登入留言

請輸入暱稱 ( 最多顯示 6 個中文字元 )

請輸入標題 ( 最多顯示 9 個中文字元 )

請輸入內容 ( 最多 140 個中文字元 )

reload

請輸入左方認證碼:

看不懂,換張圖

請輸入驗證碼