2009-11-26

在 Prologic 設計的系統架構

2009-11-25

[PHP] 取得 Google Analytics 的統計資料

這裡我使用GAPI(google-analytics-php-interface)這個工具來取得 Google Analytics 的統計資料

如果不想使用這個工具取得資料,Google Analytics 也有 Protocol 的連接教學 Data Export API - Protocol

在取得資料的過程中,我在一個觀念上問題花了不少時間,主要是下面這兩個參數上的設定:
  • dimensions : 特性類似 SQL 的 GROUP BY
  • metrics : 類似 SQL SELECT 的資料欄位

其他的資料欄位:Dimensions & Metrics Reference


工具的連結範例:
<?php
require_once('gapi.class.php');

/*建立與帳戶的連結*/
$ga = new gapi('email@yourdomain.com','password');


/*取得統計報告*/
$ga->requestReportData(
    145141242,
    array('browser','browserVersion'),
    array('pageviews','visits')
);

foreach($ga->getResults() as $result){
    echo $result;
    echo 'Pageviews:',$result->getPageviews();
    echo 'Visits:',$result->getVisits();
}

echo 'Total pageviews:',$ga->getPageviews(); 
echo 'total visits:',$ga->getVisits();



函數的參數說明:
<?php
requestReportData(
    $report_id,
    $dimensions,
    $metrics,
    $sort_metric=null,
    $filter=null,
    $start_date=null,
    $end_date=null,
    $start_index=1,
    $max_results=30
)

屬性型態描述範例
$report_idString統計報告的ID1892302
$dimensionsArray尺寸欄位,類似 SQL 的群組array('browser')
$metricsArray結果欄位,類似 SQL 的顯示欄位array('pageviews')
$sort_metricArray(選擇性)資料排序依據,"visits" 為 ASC,"-visits" 為 DESCarray('-visits')
$filterString(選擇性)過濾的邏輯條件 
$start_dateString(選擇性)報告的起始時間'YYYY-MM-DD''2009-04-30'
$end_dateString(選擇性)報告的起始時間'YYYY-MM-DD''2009-06-30'
$start_indexInt(選擇性)資料的起始指標1
$max_resultsInt(選擇性)資料的起始指標.最大 1000 筆30
2009-11-24

今天終於見識到了

今天去一家公司面試
見到一個令我驚訝的事

那就是用 JavaScript 寫 application
整個架構都是 Object-Oriented
光這點還不足已驚訝
架構上幾乎是跟 Java 的 Swing 差不多
所有的 HTML 都是 Object 再控制
整體的顯示效果跟 flash 差不多
真是給他妙的說

不過聽 engineer 說有一個嚴重問題
就是 memory 會用掉 500 Mbyte
engineer 是想利用回收 Object 的方式來減少 memory 的用量
但至於要如何減少 memory 的用量
還真是個大問題

雖然方法很多
但要不影響當前的架構去處理
能用的方法就很有限了
2009-11-04

[PHP] 利用檔案作資料快取

當沒辦法架設好用的快取機制時
又想利用快取的方式降低 DB 負荷時
不妨使用檔案的方式處理

這裡使用 serialize() 這個函數將變數轉成字串
然後存到文件裡做快取存放
這個函數跟 $_SEEION 用的是一樣的函數

<?php
class Cache {
private static $_savePath = '.';
private static $_cached = true;

/**初始化
* @param string $savePath 暫存檔的存放路徑
* @param bool $cached 是否啟用暫存,這個設定可以快速關閉所有暫存的使用
*/
public static function start($savePath='.', $cached=true ) {
self::$_savePath = $savePath.'/';
self::$_cached = (bool)$cached;
}


/**取得暫存資料
* @param string $index 暫存檔的名稱
* @return var 所暫存的資料,當暫存不存在時回傳 null
*/
public static function load($index) {
if(!self::$_cached){ return null; }

$filePath = self::$_savePath.$index.'.cache';
if(file_exists($filePath)){
$value = file_get_contents($filePath);
return unserialize($value);

}else{
return null;
}
}


/**儲存暫存資料
* @param string $index 暫存檔的名稱
* @param var $value 需要暫存的資料
*/
public static function save($index, $value) {
$filePath = self::$_savePath.$index.'.cache';
$value = serialize($value);

try {
file_put_contents($filePath ,$value);
} catch (Exception $e) {
/*檔案讀取失敗*/
throw $e;
}
}

/**清除暫存資料
* @param string $index 暫存檔的名稱,當設定為 null 時清除所有 Cache
*/
public static function remove($index=null) {
/*清除所有 Cache*/
if($index===null){
$dir = opendir(self::$_savePath);
while (false !== ($fileName = readdir($dir))) {
if(strpos($fileName,'.cache')===false) {
continue;
}elseif(file_exists($filePath = self::$_savePath.$fileName)){
unlink($filePath);
}
}
closedir($dir);


/*清除指定 Cache*/
}elseif(file_exists($filePath = self::$_savePath.$index.'.cache')){
unlink($filePath);
}
}
}


使用範例:

<?php
/**(初始化)變數快取類別,設定存放的目錄*/
Cache::start('/www/cache');


if(!$sysSetting = Cache::load('sysSetting')){
/*取得資料*/
$sysSetting = $dbAdapter->query("
SELECT `Group`, `Name`, `Value`
FROM `system_setting`
")->fetchAll();


/*快取資料*/
Cache::save('sysSetting', $sysSetting);
}
2009-11-01

[PHP] 繼承改寫 PDO

根據過去對 PDO 的使用習慣
我從新改寫跟簡化一些函數
基本上跟原本的沒有差太多

物件宣告:

<?php

/*改寫資料連結物件*/
class DB extends PDO {

/* 預設的資料取得格式 */
protected static $_fetch_mode = self :: FETCH_ASSOC;

/**
* @param array $conf
* [host] = "localhost"
* [username] = "root"
* [password] = "1111"
* [dbname] = "test"
* [init] = "SET NAMES 'utf8'; SET group_concat_max_len=65536;"
*
* @return class
*/
public function __construct(array $conf){
parent :: __construct(
'mysql:host='.$conf['host'].';dbname='.$conf['dbname'],
$conf['username'],
$conf['password']
);

/* MySQL 前置設定 */
foreach($conf['init'] as $query){
parent :: exec($query);
}

/*以例外方式處理錯誤*/
$this->setAttribute(
self :: ATTR_ERRMODE,
self :: ERRMODE_EXCEPTION
);

/*改寫資料流物件*/
$this->setAttribute(
self :: ATTR_STATEMENT_CLASS,
array ('DBStatement',array (
$this
)
));
}


/**===============================================================**/
/* 將字串加入引號及反斜線 */
public function quote($var = null){
if(is_array($var)){
foreach ($var as $key => $value) {
$var[$key] = parent :: quote($value);
}
return join(',', $var);

}elseif($var === null){
return 'NULL';
}else{
return parent :: quote($var);
}
}

/* 將 $query 字串與陣列做變數合併 */
public function bind($query, array $bind = array ()){
foreach($bind as $key => $value){
if ($key[0] == '/' && $key[1] == '*') {
continue;
}

$bind[$key] = $this->quote($value);
}

return strtr($query, $bind);
}


/**===============================================================**/
/*重新封裝 exec*/
public function exec($query, array $bind = array ()){
$query = $this->bind($query, $bind);

$stmt = parent :: query($query);
$rowCount = $stmt->rowCount();
$stmt->closeCursor();
$stmt = null;
return $rowCount;
}

/*重新封裝 query*/
public function query($query, array $bind = array ()){
$query = $this->bind($query, $bind);

$stmt = parent :: query($query);
$stmt->setFetchMode(self :: $_fetch_mode);
return $stmt;
}

/*針對僅取得筆數時的便利函數*/
public function queryCount($query, array $bind = array ()){
$query = $this->bind($query, $bind);
$query = sprintf('SELECT COUNT(*) FROM (%s)AS QUERYCOUNT', $query);

$stmt = parent :: query($query);
$rowCount = $stmt->fetchColumn();
$stmt->closeCursor();
$stmt = null;
return $rowCount;

}

}


/*改寫資料流物件*/
class DBStatement extends PDOStatement{
public $dbh;

protected function __construct($dbh){
$this->dbh = $dbh;
}
}


對於資料的操作跟原本的事一樣的
但我習慣將 fetch_mode 設定為 FETCH_ASSOC

使用用範例:

<?php
/* 資料庫連結參數 */
$conf = array(
'host' => "localhost",
'username' => "root",
'password' => "1111",
'dbname' => "test_db",
'init' => "SET NAMES 'utf8'; SET group_concat_max_len=65536;"
);

/* 初始化資料庫連結介面 */
$dbAdapter = new DB($conf);




/*exec 操作*/
$bind = array();
$bind['/*prefix*/'] = 'mw_';/*資料表前綴*/
$bind[':title'] = '文章標題';
$bind[':content'] = '文章內容';

try {
$dbAdapter->exec("
INSERT INTO `/*prefix*/article`
SET `Title` = :title,
`Content` = :content,
`ModifyTime` = NOW()
", $bind);

echo "成功新增";

/*錯誤處理*/
} catch (Exception $e) {
switch ($e->getCode()) {
case 23000:
echo "主鍵重複"; break;

default:
echo "新增失敗"; break;
}

}


/*query 操作*/
$bind = array();
$bind['/*prefix*/'] = 'mw_';/*資料表前綴*/
$bind[':id'] = array('1','2','4');

$data = $dbAdapter->query("
SELECT `Id`, `Title`, `Content`
FROM `/*prefix*/article`
WHERE `Id` IN(:id)
", $bind)->fetchAll();

var_dump($data);


/*queryCount 單只要取得查詢的筆數時*/
$bind = array();
$bind['/*prefix*/'] = 'mw_';/*資料表前綴*/
$bind[':id'] = array('1','2','4');

$count = $dbAdapter->queryCount("
SELECT `Id`, `Title`, `Content`
FROM `/*prefix*/article`
WHERE `Id` IN(:id)
", $bind);

echo $count;


/*bind 取得查詢的 Query 字串*/
$bind = array();
$bind['/*prefix*/'] = 'mw_';/*資料表前綴*/
$bind[':id'] = array('1','2','4');

$query_string = $dbAdapter->bind("
SELECT `Id`, `Title`, `Content`
FROM `/*prefix*/article`
WHERE `Id` IN(:id)
", $bind);

echo $query_string;

[PHP] 好用的 HTML 解析工具

PHP Simple HTML DOM Parser Manual

之前為了解析 HTML 所找到的工具
這是 S.C. Chen 所製作的工具
作者文章:PHP Simple HTML DOM Parser 強力解析html工具
檔案下載:PHP Simple HTML DOM Parser

具有以下特點:
  1. 只支援PHP5以上
  2. 可以分析不嚴謹(invalid)的HTML.
  3. 支援簡單的 CSS Selector.
  4. 簡單的DOM操作
  5. 會維持HTML中的原始格式.


對於可以支援 CSS Selector 就夠方便了
幾本上函數操作跟 JavaScript 差不多
使用起來的確蠻方便的

不過在實際使用後發現對於不嚴謹 HTML 的解析會有意想不到的問題出現,由於元素的結束點判斷不正確而出現遞迴效應,造成大量使用記憶體空間,最好還是避免解析不嚴謹 HTML。


使用範例:
<?php
// 示範如何讀取HTML元素
require_once("simple_html_dom.php");

// 產生DOM物件
$dom = file_get_dom('http://www.google.com/');

// 找出所有網頁連結
$result = $dom->find('a');
foreach($result as $v) {
    echo $v->href;
}

// 找出所有網頁圖片
$result = $dom->find('img');
foreach($result as $v) {
    echo $v->src;
}

// 找出所有網頁中所有id=gbar的div標籤
$result = $dom->find('div#gbar');
foreach($result as $v) {
    echo $v->innertext;
}

// 找出所有網頁中所有calss=gb1的span 標籤
$result = $dom->find('span.gb1');
foreach($result as $v) {
    echo $v->outertext;
}

// 找出所有網頁中所有align=center的'td標籤
$result = $dom->find('td[align=center]');
foreach($result as $v) {
    echo $v->outertext;
}