<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>roga&#039;s blog &#187; PHP</title>
	<atom:link href="http://blog.roga.tw/category/php/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.roga.tw</link>
	<description>walk away in slow motion.</description>
	<lastBuildDate>Tue, 31 Jan 2012 15:56:01 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.4-alpha-19861</generator>
		<item>
		<title>Plurk 訊息取得時間分布</title>
		<link>http://blog.roga.tw/2011/11/plurk-%e8%a8%8a%e6%81%af%e5%8f%96%e5%be%97%e6%99%82%e9%96%93%e5%88%86%e5%b8%83/</link>
		<comments>http://blog.roga.tw/2011/11/plurk-%e8%a8%8a%e6%81%af%e5%8f%96%e5%be%97%e6%99%82%e9%96%93%e5%88%86%e5%b8%83/#comments</comments>
		<pubDate>Thu, 10 Nov 2011 16:23:47 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2572</guid>
		<description><![CDATA[這是一隻我寫的 Plurk 機器人(註一) 在不同時間差的情況下個別抓的「待回應訊息」 (註二) 分布狀況。 目前共有七隻 fetcher ，第一隻 fetcher 使用 Plurk Real Time Channel API ，其餘六隻 fetcher 使用 Plurk Polling API 並且以延遲 10s, 30s, 60s, 90s, 180s, 240s 的時間差 (offset) 來抓取訊息，圖表的縱軸代表「待回應的訊息」數目，橫軸則是時間差。整個圖表趨勢如果越往左靠，則表示 Plurk 系統對訊息同步的即時性越好，如果越往右靠，表示 Plurk 系統在訊息同步上花的時間越多。 之前在抓取訊息時，我都只抓取一分鐘之內訊息，過期的都不抓，但這樣也造成不少人說機器人沒有回應 (沒回應表示一分鐘內機器人沒抓到使用者發的訊息)，以上圖來看，就是以前至少有 30% 的「待回應訊息」我漏抓了。 這種情況下，唯一的解決方法大概就是把時間差 (offset) 再加大。但其實這麼做的幫助很有限，因為使用者通常會因為等太久而自行砍掉沒有回應的訊息，然後機器人這邊則是會因此發生 (plurk not found) 的錯誤。 目前透過 Real Time Channel API 拿到的只佔 34% 左右，有 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://gallery.roga.tw/var/resizes/storage/plurk_reply.png?m=1320941241" alt="" /></p>
<p>這是一隻我寫的 Plurk 機器人(註一) 在不同時間差的情況下個別抓的「待回應訊息」 (註二) 分布狀況。</p>
<p>目前共有七隻 fetcher ，第一隻 fetcher 使用 Plurk Real Time Channel API ，其餘六隻 fetcher 使用 Plurk Polling API 並且以延遲 10s, 30s, 60s, 90s, 180s, 240s 的時間差 (offset) 來抓取訊息，圖表的縱軸代表「待回應的訊息」數目，橫軸則是時間差。整個圖表趨勢如果越往左靠，則表示 Plurk 系統對訊息同步的即時性越好，如果越往右靠，表示 Plurk 系統在訊息同步上花的時間越多。</p>
<p><strong>之前在抓取訊息時，我都只抓取一分鐘之內訊息</strong>，過期的都不抓，但這樣也造成不少人說機器人沒有回應 (沒回應表示一分鐘內機器人沒抓到使用者發的訊息)，以上圖來看，就是以前至少有 30% 的「待回應訊息」我漏抓了。</p>
<p>這種情況下，<strong>唯一的解決方法大概就是把時間差 (offset) 再加大</strong>。但其實這麼做的幫助很有限，因為使用者通常會因為等太久而自行砍掉沒有回應的訊息，然後機器人這邊則是會因此發生 (plurk not found) 的錯誤。</p>
<p>目前透過 Real Time Channel API 拿到的只佔 34% 左右，有 30% 是 offset 在 10s &#8211; 60s 之間拿到，而剩下 35% 則是超過 90s 才拿到。</p>
<p>備註：</p>
<ul>
<li>這隻機器人的架構設計分為 fetcher 和 replier ，可各別決定要跑幾個 process 起來，目前機器人有 26.8 萬個好友，現在實際狀況是跑七隻 fetcher 和一隻 replier 。</li>
<li>「待回應的訊息」就是拿到的訊息中含有觸發機器人的關鍵字，其餘則為「不回應的訊息」 ，我會記錄每則回應的訊息，並以 fetch_offset 欄位代表是在哪個時間區間的 fetcher 抓到的，統計回應時間差的 SQL 如下</li>
</ul>
<pre class="brush: sql; title: ; notranslate">
SELECT COUNT( id ) AS  &quot;次數&quot;, fetch_offset AS  &quot;時間差&quot;
FROM  `reply_message`
GROUP BY fetch_offset
HAVING fetch_offset IS NOT NULL
ORDER BY  &quot;時間差&quot; ASC
</pre>
<p>最近機器人的回應狀況不甚理想，我會再觀察一至兩週之後再 update 一下。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2011/11/plurk-%e8%a8%8a%e6%81%af%e5%8f%96%e5%be%97%e6%99%82%e9%96%93%e5%88%86%e5%b8%83/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP 使用 APC 增進執行速度。</title>
		<link>http://blog.roga.tw/2011/07/php-%e4%bd%bf%e7%94%a8-apc-%e5%a2%9e%e9%80%b2%e5%9f%b7%e8%a1%8c%e9%80%9f%e5%ba%a6%e3%80%82/</link>
		<comments>http://blog.roga.tw/2011/07/php-%e4%bd%bf%e7%94%a8-apc-%e5%a2%9e%e9%80%b2%e5%9f%b7%e8%a1%8c%e9%80%9f%e5%ba%a6%e3%80%82/#comments</comments>
		<pubDate>Tue, 05 Jul 2011 05:03:25 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2535</guid>
		<description><![CDATA[其實，如果單純講速度的話，其實 PHP 還真的不夠快，但為什麼其它語言會快呢？其實原因很多，但其實都不脫以下幾項： 1. 程式事先編譯好，而不是要用的時候才用 Interpreter 直譯出結果。 2. 東西能放記憶體的就放記憶體 3. 主機的硬體夠力(這個因素佔很大) PHP 也有非常多方式可以幫自己加速，這邊我要推薦的是 APC 這個 PHP Extension。 安裝方法大家都會，甚至有些架站包都預設幫你裝好了，所以這邊就不贅述。這邊要提的是 APC 有許多參數可以設置，彈性很大，尤其有個對於加速至關緊要的參數：apc.stat=0 如果你覺得自己的 PHP 網站不快，可以在自己的 php.ini 裡面，load 進來 apc.so 後面加上 apc.stat=0 ，對樣對網站的效能會有顯著地提昇。原理是因為 APC 在每次讀取 PHP 程式碼的時候，都會先 stat() 程式碼所在的檔案，以確定要不要重新編譯成 OPCODE ，而 apc.stat=0 則是直接省略 stat() 的步驟，拿原本就有的 opcode 來用。 而當使用了 APC 並且設置 apc.stat=0 之後，其實網站在第一次編譯成 OPCODE 之後，除非重起或是清除快取，要不然就是會一直拿 OPCODE 來跑，而這樣速度也因此有了顯著的提升。 假設有一段程式碼如下： 則在背後 [...]]]></description>
			<content:encoded><![CDATA[<p>其實，如果單純講速度的話，其實 PHP 還真的不夠快，但為什麼其它語言會快呢？其實原因很多，但其實都不脫以下幾項：</p>
<p>1. 程式事先編譯好，而不是要用的時候才用 Interpreter 直譯出結果。<br />
2. 東西能放記憶體的就放記憶體<br />
3. 主機的硬體夠力(這個因素佔很大)</p>
<p>PHP 也有非常多方式可以幫自己加速，這邊我要推薦的是 <a href="http://pecl.php.net/package/APC">APC</a> 這個 PHP Extension。</p>
<p>安裝方法大家都會，甚至有些架站包都預設幫你裝好了，所以這邊就不贅述。這邊要提的是 APC 有許多參數可以設置，彈性很大，尤其有個對於加速至關緊要的參數：<strong>apc.stat=0</strong><br />
<span id="more-2535"></span><br />
如果你覺得自己的 PHP 網站不快，可以在自己的 php.ini 裡面，load 進來 apc.so 後面加上 apc.stat=0 ，對樣對網站的效能會有顯著地提昇。原理是因為 APC 在每次讀取 PHP 程式碼的時候，都會先 stat() 程式碼所在的檔案，以確定要不要重新編譯成 OPCODE ，而 apc.stat=0 則是直接省略 stat() 的步驟，拿原本就有的 opcode 來用。</p>
<p>而當使用了 APC 並且設置 apc.stat=0 之後，其實網站在第一次編譯成 OPCODE 之後，除非重起或是清除快取，要不然就是會一直拿 OPCODE 來跑，而這樣速度也因此有了顯著的提升。</p>
<p>假設有一段程式碼如下：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
    include_once('/www/includes/1.inc');
    include_once('/www/includes/2.inc');
    include_once('/www/includes/3.inc');
    include_once('/www/includes/4.inc');
    include_once('/www/includes/5.inc');
    include_once('/www/includes/6.inc');
    include_once('/www/includes/7.inc');
    include_once('/www/includes/8.inc');
    include_once('/www/includes/9.inc');
    include_once('/www/includes/10.inc');

    echo 'ok';
?&gt;
</pre>
<p>則在背後 APC 的動作會像是上面這張圖一樣，可以看得出來 apc.stat=0 時，省了非常多的 stat() 。</p>
<p><img src="http://gallery.roga.tw/var/albums/storage/apc.png?m=1309840399" alt="" /></p>
<p>這樣的優點是速度會變快，但小缺點是當程式有更動的時候，要 FLUSH OPCODE CACHE 才能看到程式修改後的結果(FLUSH OPCODE CACHE 的方式可以用 APC 內建的 Function 或是重新啟動 Web Server)。我在自己筆電上面灌好 MAMP ，把加速器換成 APC 然後 /Applications/MAMP/conf/php5.3/php.ini 裡面只加入一行設定：apc.stat=0 。印 Hello World 跑出來結果如下：</p>
<p><code>ab -c 10 -t 1 -k http://127.0.0.1/index.php</code></p>
<pre>
roga@Hephaestus:/Applications/MAMP/conf/php5.3$ ab -c 10 -t 1 -k http://127.0.0.1/index.php
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 5000 requests
Completed 10000 requests
Completed 15000 requests
Finished 15598 requests

Server Software:        Apache/2.0.64
Server Hostname:        127.0.0.1
Server Port:            80

Document Path:          /index.php
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   1.000 seconds
Complete requests:      15598
Failed requests:        0
Write errors:           0
Keep-Alive requests:    15448
Total transferred:      3721178 bytes
HTML transferred:       171578 bytes
<strong>Requests per second:    15597.49 [#/sec] (mean)</strong>
Time per request:       0.641 [ms] (mean)
Time per request:       0.064 [ms] (mean, across all concurrent requests)
Transfer rate:          3633.84 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     0    1   0.2      1       4
Waiting:        0    1   0.2      1       4
Total:          0    1   0.2      1       5

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      1
 100%      5 (longest request)</pre>
<p>每秒大概處理 15597.49 個 request 。所以如果只印 Hello World 的話，其實 PHP 也不差 <img src='http://blog.roga.tw/wp-includes/images/smilies/icon_razz.gif' alt=':P' class='wp-smiley' /> </p>
<p>不過我也得說明一下，我的 MBP 是 15&#8243; 的 Intel Core i7 2.2GHz, 8GB RAM, 120GB SSD (哈，這樣算作弊嗎？) 另外，在我的 MBP 上，如果連線不 keep alive 的話，速度會降到 5793.44 req/sec ，但這是 tcp connection 和 apache 產生的 overhead ，就不要怪到 PHP 的速度上了。最後，有篇 Facebook 的 Slide 在講 APC 的細部調整，對 performance tuning 有興趣的，不妨一讀：<a href="http://www.slideshare.net/shire/php-tek-2008-apc-facebook">PHP Tek 2008 : APC @ Facebook</a> 。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2011/07/php-%e4%bd%bf%e7%94%a8-apc-%e5%a2%9e%e9%80%b2%e5%9f%b7%e8%a1%8c%e9%80%9f%e5%ba%a6%e3%80%82/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>MySQL HandlerSocket Plugin for PHP</title>
		<link>http://blog.roga.tw/2011/03/mysql-handlersocket-plugin-for-php/</link>
		<comments>http://blog.roga.tw/2011/03/mysql-handlersocket-plugin-for-php/#comments</comments>
		<pubDate>Sun, 13 Mar 2011 15:45:45 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2499</guid>
		<description><![CDATA[如果要用 PHP 透過 HandlerSocket 存取 MySQL ，必須先安裝好 php-handlersocket 這個 PHP Extension ，在 Debian Squeeze 安裝 MySQL HandlerSocket Plugin 有提到安裝方法，安裝完之後有以下 function 可以用。 有鑑於 php-handlersocket 提供的 function 使用起來不是很直覺，所以我寫了一個 wrapper 方便使用，以下是範例，首先必須先建立一個資料表： 接下來就可以開使用程式把上面的 MySQL Storage Engine (InnoDB) 當成 NoSQL DB 使用了。你可以直接用 php-handlersocket 這個 extension 本身提供的 PHP function 來寫，或是用我寫的 php-handlersocket-wrapper，來簡化操作。 以下程式，是 php-handlersocket-wrapper 的範例，可以從這邊下載。 非常簡單好用，不過也看到了缺點&#8230;目前看來沒有認證機制。]]></description>
			<content:encoded><![CDATA[<p>如果要用 PHP 透過 HandlerSocket 存取 MySQL ，必須先安裝好 <a href="http://code.google.com/p/php-handlersocket/">php-handlersocket</a> 這個 PHP Extension ，在 <a href="http://blog.roga.tw/2011/03/debian-squeeze-%E5%AE%89%E8%A3%9D-mysql-handlersocket-plugin/">Debian Squeeze 安裝 MySQL HandlerSocket Plugin</a> 有提到安裝方法，安裝完之後有以下 function 可以用。</p>
<pre class="brush: php; title: ; notranslate">
HandlerSocket {
    /* Constants */
    const HandlerSocket::PRIMARY;

    /* Methods */
    __construct  ( string $host, int $port, [ array $options ])
    public bool openIndex ( int $id, string $db, string $table, string $index, string $fields )
    public mixed executeSingle ( int $id, string $op, array $fields [, int $limit, int $skip, strint $modop, array $values ] )
    public mixed executeMulti ( array $requests )
    public int executeUpdate ( int $id, string $op, array $fields, array $values [, int $limit, int $skip ] )
    public int executeDelete ( int $id, string $op, array $fields [, int $limit, int $skip ] )
    public bool executeInsert ( int $id, array $values )
    public string getError ( void )
}
</pre>
<p>有鑑於 <a href="http://code.google.com/p/php-handlersocket/">php-handlersocket</a> 提供的 function 使用起來不是很直覺，所以我寫了一個 wrapper 方便使用，以下是範例，首先必須先建立一個資料表：</p>
<p><span id="more-2499"></span></p>
<pre class="brush: sql; title: ; notranslate">
--
-- DB SCHEME
--

CREATE TABLE IF NOT EXISTS `user` (
  `user_id` int(10) unsigned NOT NULL,
  `user_name` varchar(50) DEFAULT NULL,
  `user_email` varchar(255) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Insert DATA
--

INSERT INTO `user` (`user_id`, `user_name`, `user_email`, `created`) VALUES
(101, 'foo', 'foo@example.com', '2011-03-13 17:50:40'),
(102, 'cat', 'cat@example.com', '2011-03-30 18:13:11'),
(111, 'dog', 'dog@example.com', '0000-00-00 00:00:00'),
(112, 'bar', 'bar@example.com', NULL);
</pre>
<p>接下來就可以開使用程式把上面的 MySQL Storage Engine (InnoDB) 當成 NoSQL DB 使用了。你可以直接用 <a href="http://code.google.com/p/php-handlersocket/">php-handlersocket</a> 這個 extension 本身提供的 PHP function 來寫，或是用我寫的 <a href="http://code.google.com/p/php-handlersocket-wrapper/">php-handlersocket-wrapper</a>，來簡化操作。</p>
<p>以下程式，是 <a href="http://code.google.com/p/php-handlersocket-wrapper/">php-handlersocket-wrapper</a> 的範例，可以從<a href="http://code.google.com/p/php-handlersocket-wrapper/downloads/detail?name=handlersocket_wrapper-0.1.zip">這邊下載</a>。</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

require('handlersocket_wrapper.php');

$db_host = '127.0.0.1';
$port_read = 9998;
$port_write = 9999;
$dbname = 'test';
$table = 'user';
$indexid = 1;

/* 初始化連線設定 */
$hsr = new HandlerScoketWrapper($db_host, $port_read, $dbname, $table);
/* 只拿 user_id, user_email 兩個欄位的資料 */
$hsr-&gt;init(array('user_id', 'user_email'), $indexid);

$result = $hsr-&gt;get('101');
/* 只要 DB 中有資料，就可以用 primary key 拿出來 */
var_dump($result);

/* 當然也可以一次拿多筆資料 */
$result = $hsr-&gt;get(array('101', '102', '111'));
var_dump($result);

/* 如果要寫入資料，要用 $port_write 連接*/
$hsw = new HandlerScoketWrapper($db_host, $port_write, $dbname, $table);
/* 如果想要拿的欄位和上次建立連線不相同， index id 的值要不同，這次是拿 user_id, user_name, user_email 三個欄位的資料 */
$hsw-&gt;init(array('user_id', 'user_name', 'user_email'), $indexid + 1);

/* 新增一筆資料，第一個參數是 pkey, 後面是值，幾個欄位就幾個陣列元素 */
$result = $hsw-&gt;add(array('104', 'cool', 'cool@example.com'));
var_dump($result);

/* $port_write 一樣可以哪資料 */
$result = $hsw-&gt;get('104');
var_dump($result);

/* update 資料，第一個參數是 pkey, 後面是要 update 的資料 */
$data = array('hot', 'hot@example.com');
$result = $hsw-&gt;update('104', $data);
/* first param is primary key, second param is data (as an array) */
var_dump($result);

/* 用 pkey 刪掉資料 */
$status = $hsw-&gt;del('104');
var_dump($status);
</pre>
<p>非常簡單好用，不過也看到了缺點&#8230;目前看來沒有認證機制。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2011/03/mysql-handlersocket-plugin-for-php/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>加強 WordPress 的 SEO</title>
		<link>http://blog.roga.tw/2011/02/%e5%8a%a0%e5%bc%b7%e9%83%a8%e8%90%bd%e6%a0%bc%e7%9a%84-seo/</link>
		<comments>http://blog.roga.tw/2011/02/%e5%8a%a0%e5%bc%b7%e9%83%a8%e8%90%bd%e6%a0%bc%e7%9a%84-seo/#comments</comments>
		<pubDate>Thu, 17 Feb 2011 15:12:10 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2487</guid>
		<description><![CDATA[以往幾年我的部落格網址樣式都是這樣： http://blog.roga.tw/YYYY/MM/DD/POST_ID 作法是寫 rewrite rule 把 HTTP request 導給 WordPress 的 index.php 並且在後台的 Permalink Settings 採用 Custom Structure ( /%year%/%monthnum%/%day%/%post_id% ) 。 這樣好處就是簡單而且簡短，當時有不少用 WordPress 的朋友都用這種方法改寫網址，但缺點就是對搜尋引擎和訪客都不友善。所以，其實比較好的表示方式是 Permalink Settings 內建的 Month and name 格式。例如：http://blog.roga.tw/2008/05/james-blunts-live-concert-in-taipei/，這種格式可以直接從網址看出發文日期，也可以看出文章標題，而且和 http://blog.roga.tw/2008/05/18/581 是同一篇文章！ 由於這個部落格已經有不少網址「流落在外」了，所以更換網址結構的時候，必須要寫個 Mapping 的機制，以避免文章找不到。 以下是我寫的小程式，存檔放到 plug-in 的目錄就可以解決新舊網址 Mapping 的問題 (當然，你的目錄結構也得和我以前一樣)，程式採用 WordPress Native 的 Function 實做，設定也是拿 WordPress 定義的常數，所以不用擔心 hard code 的問題，如果不想用，在後台管理 plug-in 的地方也可以解除安裝。 [...]]]></description>
			<content:encoded><![CDATA[<p>以往幾年我的部落格網址樣式都是這樣：</p>
<p><code>http://blog.roga.tw/YYYY/MM/DD/POST_ID</code></p>
<blockquote><p>
作法是寫 rewrite rule 把 HTTP request 導給 WordPress  的 index.php 並且在後台的 Permalink Settings 採用 Custom Structure ( /%year%/%monthnum%/%day%/%post_id% ) 。
</p></blockquote>
<p><span id="more-2487"></span><br />
這樣好處就是簡單而且簡短，當時有不少用 WordPress 的朋友都用這種方法改寫網址，但缺點就是對搜尋引擎和訪客都不友善。所以，其實比較好的表示方式是 Permalink Settings 內建的 Month and name 格式。例如：<a href="http://blog.roga.tw/2008/05/james-blunts-live-concert-in-taipei/">http://blog.roga.tw/2008/05/james-blunts-live-concert-in-taipei/</a>，這種格式可以直接從網址看出發文日期，也可以看出文章標題，而且和 <a href="http://blog.roga.tw/2008/05/18/581">http://blog.roga.tw/2008/05/18/581</a>  是同一篇文章！</p>
<p>由於這個部落格已經有不少網址「流落在外」了，所以更換網址結構的時候，必須要寫個 Mapping 的機制，以避免文章找不到。</p>
<p>以下是我寫的小程式，存檔放到 plug-in 的目錄就可以解決新舊網址 Mapping 的問題 (當然，你的目錄結構也得和我以前一樣)，程式採用 WordPress Native 的 Function 實做，設定也是拿 WordPress 定義的常數，所以不用擔心 hard code 的問題，如果不想用，在後台管理 plug-in 的地方也可以解除安裝。</p>
<p>以下是程式碼：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/*
Plugin Name: roga's url hotfix
Plugin URI: http://blog.roga.tw/2011/02/%E5%8A%A0%E5%BC%B7%E9%83%A8%E8%90%BD%E6%A0%BC%E7%9A%84-seo/
Description: redirect http requests.
Version: 0.2
Author: roga
Author URI: http://blog.roga.tw
License: GPL v2
*/

function roga_wrap()
{
	GLOBAL $wpdb;

	$request_uri = trim(getenv('REQUEST_URI'), '/');

	$array = explode('/', $request_uri);

	$status = TRUE;

	foreach($array as $row)
	{
		if( ! is_numeric($row) &amp;&amp; ! empty($row)) $status = FALSE;
	}

	if(count($array) != 4 || $status != TRUE)
		return NULL;

	$post_id = (int) $array[3]; // http://blog.roga.tw/2011/02/16/2484

	$wp_result = $wpdb-&gt;get_row(&quot;SELECT `post_type`, `post_name`, `post_date` FROM `$wpdb-&gt;posts` WHERE `ID` = $post_id &quot;);

	if( ! isset($wp_result))
		return NULL;

	$post_type = $wp_result-&gt;post_type;
	$post_name = $wp_result-&gt;post_name;
	$post_date = $wp_result-&gt;post_date;

	if($post_type == 'revision')
		return NULL;

	$time = strtotime($post_date);
	$year = date('Y', $time);
	$month = date('m', $time);
	//  old:   /%year%/%monthnum%/%day%/%post_id%
	//  new:   /%year%/%monthnum%/%postname%/
	$new_request_uri = &quot;/$year/$month/$post_name&quot;;
	$http_host = getenv('HTTP_HOST');

	$logfile = WP_CONTENT_DIR . &quot;/cache/wp-roga-redirect.log&quot;;
	if(file_exists($logfile))
		file_put_contents($logfile, sprintf(&quot;[%s] %s -&gt; %s / %s &quot; . PHP_EOL, date_i18n(&quot;Y-m-d H:i:s&quot;), $request_uri, urldecode($new_request_uri), getenv('HTTP_USER_AGENT')), FILE_APPEND);

	if (substr(PHP_SAPI, 0, 3) == 'cgi')
		header(&quot;Status: 301 Moved Permanently&quot;);
	else
		header(&quot;HTTP/1.1 301 Moved Permanently&quot;);

	header(&quot;Location: http://$http_host$new_request_uri&quot;);
	exit();
}

add_action('init', 'roga_wrap');
</pre>
<p>update:</p>
<p>謝謝 appleboy 的 <a href="http://blog.wu-boy.com/2011/02/wordpress-plugin-seo/">patch</a> ，修改一下一些錯誤。</p>
<p>1. 主要是加強判斷網址  blog.roga.tw/2011/02/17/2542 和  blog.roga.tw/2011/02/17/2542<b>/</b> 兩者的差異 (trailing slash)<br />
2. 最下面送 Header 的地方新增支援 CGI 和 Apache Handler (php module) 兩種，原本是只有支援 CGI, 因為我用 Fast-CGI<br />
3. 上面的程式已經有 patch 過了~~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2011/02/%e5%8a%a0%e5%bc%b7%e9%83%a8%e8%90%bd%e6%a0%bc%e7%9a%84-seo/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Gallery3 轉換筆記</title>
		<link>http://blog.roga.tw/2010/10/gallery3-%e8%bd%89%e6%8f%9b%e7%ad%86%e8%a8%98/</link>
		<comments>http://blog.roga.tw/2010/10/gallery3-%e8%bd%89%e6%8f%9b%e7%ad%86%e8%a8%98/#comments</comments>
		<pubDate>Sat, 02 Oct 2010 09:58:34 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2448</guid>
		<description><![CDATA[試了一個晚上，終於成功轉換了我的五千多張照片，以下紀錄轉換過程和方法： 建一個新目錄對應新的 Domain Name 給 Gallery3 用(Virtual Host)。 啟用 rewrite rule ，並且檢查各項功能，確定一切正常。 安裝完畢後，在後台啟用 「從 Gallery 2 匯入」工具。 在後台「設定」 -> 「進階設定」的地方把 image_quality 這個參數設成 100 ，預設的 75 畫質太糟糕了。 在後台「設定」 -> 「圖像工具」啟用的工具選擇 GD ，不要選 ImageMagick 或是 GraphicsMagick 。因為畫質的關係。 用官方提供的「從 Gallery 2 匯入」工具匯入所有相片。 不要針對「從 Gallery 2 匯入」工具下面的指示新增 rewrite rule 。 更改 Virtual Host 設定，把舊的 Domain Name 分配給 Gallery3 至於舊的 [...]]]></description>
			<content:encoded><![CDATA[<p>試了一個晚上，終於成功轉換了我的五千多張照片，以下紀錄轉換過程和方法：<br />
<span id="more-2448"></span></p>
<ol>
<li>建一個新目錄對應新的 Domain Name 給 Gallery3  用(Virtual Host)。</li>
<li>啟用 rewrite rule ，並且檢查各項功能，確定一切正常。</li>
<li>安裝完畢後，在後台啟用 「從 Gallery 2 匯入」工具。</li>
<li>在後台「設定」 -> 「進階設定」的地方把 image_quality 這個參數設成 100 ，預設的 75 畫質太糟糕了。</li>
<li>在後台「設定」 -> 「圖像工具」啟用的工具選擇 GD ，不要選 ImageMagick 或是 GraphicsMagick 。因為畫質的關係。</li>
<li>用官方提供的「從 Gallery 2 匯入」工具匯入所有相片。</li>
<li>不要針對「從 Gallery 2 匯入」工具下面的指示新增 rewrite rule 。</li>
<li>更改 Virtual Host 設定，把舊的 Domain Name 分配給 Gallery3 至於舊的 Gallery2 就先不管他。</li>
</ol>
<p>接下來開始開始針對 Gallery3 進行 Dirty Hack 。</p>
<blockquote><p>
一般來說，官方希望的方式是 Gallery2 和 Gallery3 都有自己的 Domain Name，並且在轉換完成之後，把所有 Gallery2 的來源都改寫到 Gallery3 。實做方法是在 Gallery2 下面改寫網址，並且把所有要求都轉交給 Gallery3 目錄下面的 /modules/g2_import/controllers/g2.php 這隻程式去處理，理想的狀態是可以處裡 thumbnail, resize, file (原始檔) 幾種情況，不過由於我遇到了一些奇怪的問題，所以我放棄使用官方的方法，自己寫一個 dirty hack 來轉換網址。</p>
<p>目前我的方法能解像是：<br />
<a href="http://gallery.roga.tw/main.php?g2_view=core.DownloadItem&#038;g2_itemId=9991&#038;g2_serialNumber=1">http://gallery.roga.tw/main.php?g2_view=core.DownloadItem&#038;g2_itemId=9991&#038;g2_serialNumber=1</a><br />
或是改寫過的：<br />
<a href="http://gallery.roga.tw/d/40727-2/_MG_2281.JPG">http://gallery.roga.tw/d/40727-2/_MG_2281.JPG</a><br />
這兩種狀況，如果希望處理其他 url 請自行修改我的程式。
</p></blockquote>
<p>1. 在 Gallery3 根目錄下的 index.php 第 50 行左右找到</p>
<pre class="brush: php; title: ; notranslate">
	define(&quot;SYSPATH&quot;, realpath(&quot;system&quot;) . &quot;/&quot;);
</pre>
<p>這行下面多 require 這個檔案：</p>
<pre class="brush: php; title: ; notranslate">
	require(DOCROOT  . DIRECTORY_SEPARATOR . 'g2g3.php');
</pre>
<p>在 Gallery3 根目錄新增 g2g3.php 這個檔案，內容如下：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
defined(&quot;SYSPATH&quot;) or die(&quot;No direct script access.&quot;);

define(&quot;GALLERY_HTTP_HOST&quot;, 'http://gallery.roga.tw');

$query = explode('/', $_SERVER['REQUEST_URI']);

if($query[1] == 'd')
{
	$query2 = explode('-', $query[2]);
	$g2_id = $query2[0];
	g3_mapping_g2($g2_id);

	exit();
}
else if(substr($query[1], 0, 8 ) == 'main.php')
{
	parse_str($query[1]);

	if(isset($g2_itemId))
		g3_mapping_g2($g2_itemId);
	else
		g3_redirect(GALLERY_HTTP_HOST);

	exit();
}

function g3_redirect($url)
{
	$status = (substr(php_sapi_name(), 0, 3) == 'cgi') ? 'Status:' : 'HTTP/1.1';
	header(&quot;$status 301 Moved Permanently&quot;);
	header(&quot;Location: $url&quot;);

	exit();
}

function g3_conn()
{
	// for db connection
	require(   $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'var' .DIRECTORY_SEPARATOR . 'database.php');
	$host = $config['default']['connection']['host'];
	$user =  $config['default']['connection']['user'];
	$pass = $config['default']['connection']['pass'];
	$database =  $config['default']['connection']['database'];
	$link = mysql_connect( $host, $user, $pass);
	mysql_select_db($database);
}

function g3_mapping_g2($g2_id)
{
	g3_conn();

	$g2_id = (int) $g2_id;

	$result = mysql_query(&quot;SELECT g3_id FROM g2_maps WHERE g2_id = $g2_id LIMIT 1&quot;);

	if(mysql_num_rows($result) &lt; 1) exit('G2 item not Found');

	list($g3_id) = mysql_fetch_row($result);

	$path = g3_get_parent($g3_id);

	$url = GALLERY_HTTP_HOST . &quot;/var/resizes/$path&quot;;
	// such as http://gallery.roga.tw/var/resizes/miscellaneous/20100924/_MG_2277.JPG

	g3_redirect($url);
}

function g3_get_parent($id)
{
	g3_conn();

	$id = (int) $id

	static $count = 0; // avoid from infinity loop.
	static $path = '';

	$result = mysql_query(&quot;SELECT * FROM items WHERE id= $id&quot;); 

	while($row = mysql_fetch_assoc($result))
	{
		$count++;
		if($count &gt; 128) die('it is too deep....');

		if($row['parent_id'] != 1)
		{
			g3_get_parent($row['parent_id']);
			$path .= '/' . $row['name'];
		}
		else
		{
			$path .= $row['name']  ;
		}
	}
	return $path;
}
?&gt;
</pre>
<p>說明一下，這隻主要程式的作法是：</p>
<p>1. 找出 Gallery3 的 item id 和 Gallery3 的 item id 兩者之間的關聯。</p>
<p>2. 遞迴找出 Gallery3 Item 的路徑，並組出 Gallery3 對應的 url 。</p>
<p>3. 然後把 url 送給 user 轉址到正確的照片網址。</p>
<p>以上，大功告成。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/10/gallery3-%e8%bd%89%e6%8f%9b%e7%ad%86%e8%a8%98/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>正式從 Gallery2 轉換到 Gallery3</title>
		<link>http://blog.roga.tw/2010/10/%e6%ad%a3%e5%bc%8f%e5%be%9e-gallery2-%e8%bd%89%e6%8f%9b%e5%88%b0-gallery3/</link>
		<comments>http://blog.roga.tw/2010/10/%e6%ad%a3%e5%bc%8f%e5%be%9e-gallery2-%e8%bd%89%e6%8f%9b%e5%88%b0-gallery3/#comments</comments>
		<pubDate>Fri, 01 Oct 2010 17:51:44 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2444</guid>
		<description><![CDATA[最近 Gallery3 出了 RC 2，我想應該是時候把相片轉換到 Gallery3 了。 先抱怨 Gallery3 有三個大缺點： 1. 它使用 short open tag ，我不喜歡。 2. 必須關閉 Suhosin 的 suhosin.session.encrypt 才能上傳照片，真是 OOXX 。 3. 前台用很多 jQuery ，所以我只能用 Chrome 觀看/管理相簿，要不然速度太慢了。 不過因為其他的好處，所以我還是轉換了。]]></description>
			<content:encoded><![CDATA[<p>最近 Gallery3 出了 <a href="http://gallery.menalto.com/gallery_3.0_rc2_released">RC 2</a>，我想應該是時候把相片轉換到 Gallery3 了。</p>
<p>先抱怨 Gallery3 有三個大缺點：</p>
<p>1. 它使用 short open tag ，我不喜歡。<br />
2. 必須關閉 Suhosin 的 suhosin.session.encrypt 才能上傳照片，真是 OOXX 。<br />
3. 前台用很多 jQuery ，所以我只能用 Chrome 觀看/管理相簿，要不然速度太慢了。</p>
<p>不過因為其他的好處，所以我還是轉換了。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/10/%e6%ad%a3%e5%bc%8f%e5%be%9e-gallery2-%e8%bd%89%e6%8f%9b%e5%88%b0-gallery3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Command Line Mode 的 Pause</title>
		<link>http://blog.roga.tw/2010/09/command-line-mode-%e7%9a%84-pause/</link>
		<comments>http://blog.roga.tw/2010/09/command-line-mode-%e7%9a%84-pause/#comments</comments>
		<pubDate>Wed, 29 Sep 2010 05:55:16 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2440</guid>
		<description><![CDATA[for bash: for php:]]></description>
			<content:encoded><![CDATA[<p><span id="more-2440"></span><br />
for bash:</p>
<pre class="brush: bash; title: ; notranslate">
read -p &quot;Press any key to continue...&quot;
</pre>
<p>for php:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
    echo &quot;Press enter to continue...&quot; . PHP_EOL;
    fgets(STDIN); // length = 1
?&gt;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/09/command-line-mode-%e7%9a%84-pause/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>淺談 PHP-MySQL,  PHP-MySQLi, PDO 的差異</title>
		<link>http://blog.roga.tw/2010/06/%e6%b7%ba%e8%ab%87-php-mysql-php-mysqli-pdo-%e7%9a%84%e5%b7%ae%e7%95%b0/</link>
		<comments>http://blog.roga.tw/2010/06/%e6%b7%ba%e8%ab%87-php-mysql-php-mysqli-pdo-%e7%9a%84%e5%b7%ae%e7%95%b0/#comments</comments>
		<pubDate>Thu, 24 Jun 2010 04:17:06 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2403</guid>
		<description><![CDATA[PHP-MySQL 是 PHP 操作 MySQL 資料庫最原始的 Extension ，PHP-MySQLi 的 i 代表 Improvement ，提更了相對進階的功能，就 Extension 而言，本身也增加了安全性。而 PDO (PHP Data Object) 則是提供了一個 Abstraction Layer 來操作資料庫，用講的其實看不出來有有什麼差別，所以就直接看程式吧&#8230; 首先，先來看一段用 PHP-MySQL 寫成的程式碼，這類的範例常用在世界各地： 乍看之下沒什麼問題，但其實背後有些學問&#8230; 這種方式不能 Bind Column ，以前例的 SQL 敘述來說，$location 的地方容易被 SQL Injection。後來於是發展出了 mysql_escape_string() (備註：5.3.0之後棄用) 以及 mysql_real_escape_string() 來解決這個問題，不過這麼一搞，整個敘述會變得複雜且醜陋，而且如果欄位多了，可以想見會是怎樣的情形&#8230; 在 PHP-MySQLi 中有了不少進步，除了透過 Bind Column 來解決上述問題，而且也多援 Transaction, Multi Query ，並且同時提供了 Object oriented style (下面這段 [...]]]></description>
			<content:encoded><![CDATA[<p>PHP-MySQL 是 PHP 操作 MySQL 資料庫最原始的 Extension ，PHP-MySQLi 的 i 代表 Improvement ，提更了相對進階的功能，就 Extension 而言，本身也增加了安全性。而 PDO (PHP Data Object)  則是提供了一個 Abstraction Layer 來操作資料庫，用講的其實看不出來有有什麼差別，所以就直接看程式吧&#8230;</p>
<p>首先，先來看一段用 PHP-MySQL 寫成的程式碼，這類的範例常用在世界各地：<br />
<span id="more-2403"></span></p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

	mysql_connect($db_host, $db_user, $db_password);
	mysql_select_db($dn_name);

	$result = mysql_query(&quot;SELECT `name` FROM `users` WHERE `location` = '$location'&quot;);

	while ($row = mysql_fetch_array($result,  MYSQL_ASSOC))
	{
		echo $row['name'];
	}

	mysql_free_result($result);

?&gt;
</pre>
<p>乍看之下沒什麼問題，但其實背後有些學問&#8230;</p>
<p>這種方式不能 Bind Column ，以前例的 SQL 敘述來說，$location 的地方容易被 SQL Injection。後來於是發展出了 mysql_escape_string() (備註：5.3.0之後棄用) 以及 mysql_real_escape_string() 來解決這個問題，不過這麼一搞，整個敘述會變得複雜且醜陋，而且如果欄位多了，可以想見會是怎樣的情形&#8230;</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

	$query = sprintf(&quot;SELECT * FROM users WHERE user='%s' AND password='%s'&quot;,
			mysql_real_escape_string($user),
			mysql_real_escape_string($password));

	mysql_query($query);

?&gt;
</pre>
<p>在 PHP-MySQLi 中有了不少進步，除了透過 Bind Column 來解決上述問題，而且也多援 Transaction, Multi Query ，並且同時提供了 Object oriented style (下面這段 PHP-MySQLi 範例的寫法) 和 Procedural style (上面 PHP-MySQL 範例的寫法)兩種寫法&#8230;等等。</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

	$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);

	$sql = &quot;INSERT INTO `users` (id, name, gender, location) VALUES (?, ?, ?, ?)&quot;;
	$stmt = $mysqli-&gt;prepare($sql);

	$stmt-&gt;bind_param('dsss', $source_id, $source_name, $source_gender, $source_location);

	$stmt-&gt;execute();

	$stmt-&gt;bind_result($id, $name, $gender, $location);

	while ($stmt-&gt;fetch())
	{
		echo $id . $name . $gender . $location;
	}

	$stmt-&gt;close();
	$mysqli-&gt;close();

?&gt;
</pre>
<p>但看到這邊又發現了一些缺點，例如得 Bind Result，這個就有點多餘，不過這其實無關緊要，因為最大的問題還是在於這不是一個抽象(Abstraction)的方法，所以當後端更換資料庫的時候，就是痛苦的開始&#8230;</p>
<p>於是 PDO 就出現了(備註：目前 Ubuntu 和 Debian 來說，PDO 並沒有直接的套件可以安裝，而是必須透過  PECL 安裝)。</p>
<pre>
roga@carlisten-lx:~$ pecl search pdo
=======================================
Package      Stable/(Latest) Local
<b>PDO          1.0.3 (stable)        PHP Data Objects Interface.</b>
PDO_4D       0.3 (beta)            PDO driver for 4D-SQL database
PDO_DBLIB    1.0 (stable)          FreeTDS/Sybase/MSSQL driver for PDO
PDO_FIREBIRD 0.2 (beta)            Firebird/InterBase 6 driver for PDO
PDO_IBM      1.3.2 (stable)        PDO driver for IBM databases
PDO_INFORMIX 1.2.6 (stable)        PDO driver for IBM Informix INFORMIX databases
<b>PDO_MYSQL    1.0.2 (stable)        MySQL driver for PDO</b>
<b>PDO_OCI      1.0 (stable)          Oracle Call Interface driver for PDO</b>
PDO_ODBC     1.0.1 (stable)        ODBC v3 Interface driver for PDO
PDO_PGSQL    1.0.2 (stable)        PostgreSQL driver for PDO
PDO_SQLITE   1.0.1 (stable)        SQLite v3 Interface driver for PDO
pdo_user     0.3.0 (beta)          Userspace driver for PDO
</pre>
<p>當透過 PECL 安裝裝好後，就可以透過以下方式來操作資料庫：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

	$dsn = &quot;mysql:host=$db_host;dbname=$db_name&quot;;
	$dbh = new PDO($dsn, $db_user, $db_password);

	$sql = &quot;SELECT `name`, `location` FROM `users` WHERE `location` = ? , `name` = ?&quot;;
	$sth = $dbh-&gt;prepare($sql);

	$sth-&gt;execute(array($location, $name));

	$result = $sth-&gt;fetch(PDO::FETCH_OBJ);
	echo $result-&gt;name . $result-&gt;location;

	$dbh = NULL;

?&gt;
</pre>
<p>乍看之下，PDO 的程式碼好像也沒有比較短，那到底好處是什麼呢？</p>
<p>1. PDO 連接資料庫時透過 Connection String 來決定連接何種資料庫。<br />
2. PDO 可以透過 PDO::setAttribute 來決定連線時的設定，像是 Persistent Connection, 回傳錯誤的方式(Exception, E_WARNING, NULL)。甚至是回傳欄位名稱的大小寫&#8230;等等。<br />
2. PDO 支援 Bind Column 的功能，除了基本的 Prepare, Execute 以外，也可以 Bind 單一欄位，並且指定欄位型態。<br />
4. PDO 是 Abstraction Layer 所以就算更換儲存媒介，需要花的功夫比起來是最少的。</p>
<p>可惜的是，儘管這些東西都已經出現很久了，但還是不夠大眾化。我想或許是肇因於大家習慣看坊間的書籍學習，但那些書本往往只會介紹最簡單最傳統的方式。導致很多人還是在用 MySQL 這種方直接連資料庫。</p>
<p>不過，目前來說我個人還是最喜愛透過 DBI 來連接資料庫，像是 <a href="http://www.phpactiverecord.org/">ActiveRecord</a> 以及 <a href="http://www.propelorm.org/">Propel ORM(Object-Relational Mapping)</a>。</p>
<p>例如說以 <a href="http://www.phpactiverecord.org/">ActiveRecord</a> 為例，如果要實現這樣的 SQL 敘述&#8230;</p>
<p><code>INSERT INTO `users` (id, name, gender, location) VALUES(1, 'roga', 'male', 'tpe')</code></p>
<p>以 PDO 來寫是：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

	$sql = &quot;INSERT INTO `users` (id, name, gender, location) VALUES(?, ?, ?, ?)&quot;;
	$sth = $dbh-&gt;prepare($sql);

	$sth-&gt;execute(array(1, 'roga', 'male', 'tpe'));

?&gt;
</pre>
<p>但以 <a href="http://www.phpactiverecord.org/">ActiveRecord</a> 來說的話，則是：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

	$user = new User();

	$user-&gt;id = 1;
	$user-&gt;name = 'roga';
	$user-&gt;gender = 'male';
	$user-&gt;location = 'tpe';

	$user-&gt;save();

?&gt;
</pre>
<p>後者在語法上是不是簡潔很多呢，而且也大幅降低對 SQL 語言的依賴性！(不同資料庫對 SQL 實作的問題可參考 <a href="http://troels.arvin.dk/db/rdbms/">Comparison of different SQL implementations</a>)</p>
<p>以上是一些簡單的介紹，如有疏漏謬誤也歡迎大家補充。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/06/%e6%b7%ba%e8%ab%87-php-mysql-php-mysqli-pdo-%e7%9a%84%e5%b7%ae%e7%95%b0/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>PHP 的效能測試</title>
		<link>http://blog.roga.tw/2010/04/php-%e4%bd%bf%e7%94%a8-echo-%e7%9a%84%e6%95%88%e8%83%bd/</link>
		<comments>http://blog.roga.tw/2010/04/php-%e4%bd%bf%e7%94%a8-echo-%e7%9a%84%e6%95%88%e8%83%bd/#comments</comments>
		<pubDate>Tue, 13 Apr 2010 07:56:31 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2387</guid>
		<description><![CDATA[有篇有名的文章叫做：40 Tips for optimizing your php code ，不過原文已經消失了，這邊有轉貼版 ，至於翻譯則是在論壇上面隨便找的(看得懂就好，懶得去找原本是誰翻的，我猜是對岸網友)。 所以我做了一點實驗&#8230; 測試環境： CPU: Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz Memory: 8007 MB (用 Free -m 看) Kernel: Linux 2.6.26-2-amd64 (debian) FS: reiserfs OS: Debian x86_64 PHP: PHP 5.2.6-1+lenny8 with Suhosin-Patch 0.9.6.2 (cli) 載入模組 (只有預先關閉 APC) [PHP Modules] bcmath, bz2, calendar, ctype, curl, date, dba, dom, [...]]]></description>
			<content:encoded><![CDATA[<p>有篇有名的文章叫做：40 Tips for optimizing your php code ，不過<a href="http://reinholdweber.com/?p=3">原文</a>已經消失了，這邊有<a href="http://coffeepaste.tagabukid.com/2009/03/40-tips-for-optimizing-your-php-code.html">轉貼版</a> ，至於翻譯則是在論壇上面隨便找的(看得懂就好，懶得去找原本是誰翻的，我猜是對岸網友)。</p>
<p>所以我做了一點實驗&#8230;</p>
<p><span id="more-2387"></span></p>
<p>測試環境：<br />
CPU: Intel(R) Core(TM)2 Quad CPU    Q6600  @ 2.40GHz<br />
Memory: 8007 MB (用 Free -m 看)<br />
Kernel: Linux 2.6.26-2-amd64 (debian)<br />
FS: reiserfs<br />
OS: Debian x86_64<br />
PHP: PHP 5.2.6-1+lenny8 with Suhosin-Patch 0.9.6.2 (cli)</p>
<p>載入模組 (<strong>只有預先關閉 APC</strong>)<br />
[PHP Modules] bcmath, bz2, calendar, ctype, curl, date, dba, dom, exif, filter, ftp, gd, gettext, hash, iconv, imagick, json, libxml, mbstring, mcrypt, mhash, mime_magic, mysql, mysqli, ncurses, openssl, pcntl, pcre, PDO, pdo_mysql, posix, Reflection, session, shmop, SimpleXML, soap, sockets, SPL, standard, suhosin, sysvmsg, sysvsem, sysvshm, tokenizer, wddx, xml, xmlreader, xmlwriter, zip,<br />
zlib<br />
[Zend Modules] Suhosin </p>
<p>測試方法有兩種，在 command line 下面用 time php5 test.php 或是用 php-benchmark ，我用後者，方便寫測試程式，有興趣的人應該也可以用前者驗證，挑幾條來玩玩看：</p>
<p><b>原文</b> If a method can be static, declare it static. Speed improvement is by a factor of 4.<br />
<b>翻譯</b> 如果一個方法可靜態化，就對它做靜態聲明。速率可提升至4倍<br />
<b>結論</b> <strong>比較快是一定的&#8230;但究竟快了幾倍，那也得看方法內是做什麼事情而定。</strong></p>
<p><b>原文</b> echo is faster than print.<br />
<b>翻譯</b> echo 比 print 快<br />
<b>結論</b> <strong>的確如此</strong></p>
<p><b>原文</b> Use echo&#8217;s multiple parameters instead of string concatenation.<br />
<b>翻譯</b> 使用echo的多重參數（譯註：指用逗號而不是句點）代替字元串連接<br />
<b>結論</b> <strong>的確如此</strong></p>
<p><b>原文</b> When echoing strings it&#8217;s faster to separate them by comma instead of dot. Note: This only works with echo, which is a function that can take several strings as arguments.<br />
<b>翻譯</b> 輸出多個字元串時，用逗號代替句點來分隔字元串，速度更快。註意：只有echo能這麼做，它是一種可以把多個字元串當作參數的“函數”（譯註：PHP手冊中說echo是語言結構，不是真正的函數，故把函數加上了雙引號）<br />
<b>結論</b> <strong>的確如此</strong></p>
<p><b>原文</b> It&#8217;s better to use switch statements than multi if, else if, statements.<br />
<b>翻譯</b> 使用選擇分支語句（譯註：即switch case）好於使用多個if，else if語句<br />
<b>結論</b> <strong>我測的結果是如果要轉換型別，Switch 會比較慢，但兩者還是沒有顯著差異。</strong></p>
<p><b>原文</b> Error suppression with @ is very slow.<br />
<b>翻譯</b> 用@屏蔽錯誤消息的做法非常慢<br />
<b>結論</b> <strong>這邊有個問題，就是要顯示錯誤訊息反而花更多成本，但用 @ 屏蔽的確在邏輯上會多做一些事情</strong></p>
<p><b>原文</b> Error messages are expensive<br />
<b>翻譯</b> 錯誤訊息代價昂貴<br />
<b>結論</b> <strong>請對照上一條原則.. XD</strong></p>
<p><b>原文</b> $row[’id’] is 7 times faster than $row[id]<br />
<b>翻譯</b> $row['id']的效率是$row[id]的7倍<br />
<b>結論</b> <strong>效率會比較高，但不明顯，我覺得更重要的是 $row[’id’] 可讀性好多了！</strong></p>
<p><b>原文</b> Do not use functions inside of for loop.<br />
<b>翻譯</b> 盡量不要在for循環中使用函數，比如for ($x=0; $x < count($array); $x) 每循環一次都會調用count()函數<br />
<b>結論</b> <strong> 非常正確 </strong></p>
<p><b>原文</b> Just declaring a global variable without using it in a function also slows things down (by about the same amount as incrementing a local var). PHP probably does a check to see if the global exists.<br />
<b>翻譯</b> 僅定義一個局部變量而沒在函數中調用它，同樣會減慢速度（其程度相當於遞增一個局部變量）。PHP大概會檢查看是否存在全局變量<br />
<b>結論</b> <strong> 在 function 內有差，在 Class 內沒有顯著差別。 </strong></p>
<p><b>原文</b> Method invocation appears to be independent of the number of methods defined in the class because I added 10 more methods to the test class (before and after the test method) with no change in performance.<br />
<b>翻譯</b> 方法調用看來與類中定義的方法的數量無關，因為我（在測試方法之前和之後都）添加了10個方法，但性能上沒有變化<br />
<b>結論</b> <strong> 差距並不顯著 </strong></p>
<p><b>原文</b> When working with strings and you need to check that the string is either of a certain length you&#8217;d understandably would want to use the strlen() function. This function is pretty quick since it&#8217;s operation does not perform any calculation but merely return the already known length of a string available in the zval structure (internal C struct used to store variables in PHP). However because strlen() is a function it is still somewhat slow because the function call requires several operations such as lowercase &#038; hashtable lookup followed by the execution of said function. In some instance you can improve the speed of your code by using an isset() trick. Ex. if (strlen($foo) < 5) { echo "Foo is too short"; } vs. if (!isset($foo{5})) { echo "Foo is too short"; } Calling isset() happens to be faster then strlen() because unlike strlen(), isset() is a language construct and not a function meaning that it's execution does not require function lookups and lowercase. This means you have virtually no overhead on top of the actual code that determines the string's length.<br />
<b>翻譯</b> 當操作字元串並需要檢驗其長度是否滿足某種要求時，你想當然地會使用strlen()函數。此函數執行起來相當快，因為它不做任何計算，只返回在zval 結構（C的內置數據結構，用於存儲PHP變量）中存儲的已知字元串長度。但是，由於strlen()是函數，多多少少會有些慢，因為函數調用會經過諸多步驟，如字母小寫化（譯註：指函數名小寫化，PHP不區分函數名大小寫），會跟隨被調用的函數一起執行。在某些情況下，你可以使用isset() 技巧加速執行你的程式。  舉例如下: if (strlen($foo) < 5) { echo "Foo is too short"; } 與 if (!isset($foo{5})) { echo "Foo is too short"; }<br />
<b>結論</b> <strong>是的，跑 1000 次的情況下效能差了 10% 。</strong></p>
<p>如果有興趣深入探討，可以看更多 <a href="http://www.phpbench.com/">The PHP Benchmark</a> 的測試，最後附上我寫的測試程式碼：</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php

require('/usr/share/php/Benchmark/Timer.php');

$timer = new Benchmark_Timer();

$timer-&gt;start();

class TestCase
{
    public static function get_something_static($string)
    {
		return sha1($string);
    }
	public function get_something($string)
	{
		return sha1($string);
	}

}

$timer-&gt;setMarker('StaticMethodStart');

for($i = 0; $i &lt; 1000; $i++)
	$str = TestCase::get_something_static('Hello World');

$timer-&gt;setMarker('StaticMethodStop');
$timer-&gt;setMarker('MethodStart');

for($i = 0; $i&lt; 1000; $i++)
	$str = TestCase::get_something('Hello World');

$timer-&gt;setMarker('MethodStop');

$timer-&gt;setMarker('PrintStart');

for($i = 0; $i&lt; 1000; $i++)
	print('Hello World');

$timer-&gt;setMarker('PrintStop');

$timer-&gt;setMarker('EchoStart');

for($i = 0; $i&lt; 1000; $i++)
	echo 'Hello World';

$timer-&gt;setMarker('EchoStop');

$timer-&gt;setMarker('Start: Echo + , ');
for($i = 0; $i&lt; 1000; $i++)
	echo 'Hello World' . 'Hello World' . 'Hello World' . 'Hello World';
$timer-&gt;setMarker('End: Echo + , ');

$timer-&gt;setMarker('Start: Echo + . ');
for($i = 0; $i&lt; 1000; $i++)
	echo 'Hello World', 'Hello World', 'Hello World' , 'Hello World';
$timer-&gt;setMarker('End: Echo + . ');

$timer-&gt;setMarker('Start: Switch');

for($i = 0; $i&lt; 10000; $i++)
{
	$k = 3; // try test $k=&quot;asdad&quot;;
	switch ($k) {
		case 0:
			echo &quot;i equals 0&quot;;
			break;
	    case 1:
			echo &quot;i equals 1&quot;;
			break;
		case 2:
			echo &quot;i equals 2&quot;;
			break;
	}
}
$timer-&gt;setMarker('End: Switch');

$timer-&gt;setMarker('Start: if');
for($i = 0; $i&lt; 10000; $i++)
{
	$k = 3; // try test $k=&quot;asdad&quot;;
	if ($k == 0)
		echo &quot;i equals 0&quot;;
	elseif ($k == 1)
		echo &quot;i equals 1&quot;;
	elseif ($k == 2)
		echo &quot;i equals 2&quot;;
}
$timer-&gt;setMarker('End: if');

$timer-&gt;setMarker('suppress start');
for($i = 0; $i&lt; 100; $i++)
	@include(&quot;example.txt&quot;);
$timer-&gt;setMarker('suppress end');

$timer-&gt;setMarker('no suppress start');
for($i = 0; $i&lt; 100; $i++)
	include(&quot;example.txt&quot;);
$timer-&gt;setMarker('no suppress end');

$timer-&gt;setMarker('start string convert');
$row = array('id' =&gt; 'value');
for($i = 0; $i&lt; 10; $i++)
	print $row[id];
$timer-&gt;setMarker('end string convert');

$timer-&gt;setMarker('start no string convert');
$row = array('id' =&gt; 'value');
for($i = 0; $i&lt; 10; $i++)
	print $row['id'];
$timer-&gt;setMarker('end no string convert');

for($i = 0; $i &lt; 1000; $i++)
{
	$row[$i] = $i;
}

$timer-&gt;setMarker('start no functions inside of for loop');
for($i = 0; $i &lt; 1000; $i++)
	echo $i;
$timer-&gt;setMarker('end no functions inside of for loop');

$timer-&gt;setMarker('start using functions inside of for loop');
function f_count($array) { echo 'call f_count();'; return count($array);}
for($i = 0; $i &lt; f_count($row); $i++)
	echo $i;
$timer-&gt;setMarker('end using functions inside of for loop');

$foo = &quot;Hello World.&quot;;

$timer-&gt;setMarker('start strlen');
for($i = 0; $i &lt; 1000; $i++)
	if (strlen($foo) &lt; 5) { echo &quot;Foo is too short&quot;; }
$timer-&gt;setMarker('end strlen');

$timer-&gt;setMarker('start isset');
for($i = 0; $i &lt; 1000; $i++)
	if ( ! isset($foo{5})) { echo &quot;Foo is too short&quot;; }
$timer-&gt;setMarker('end isset');

$timer-&gt;setMarker('start declare many methods');
class TestCase2
{
	public function get_something1($string) { return sha1($string);}
	public function get_something2($string) { return sha1($string);}
	public function get_something3($string) { return sha1($string);}
	public function get_something4($string) { return sha1($string);}
	public function get_something5($string) { return sha1($string);}
	public function get_something6($string) { return sha1($string);}
	public function get_something7($string) { return sha1($string);}
	public function get_something8($string) { return sha1($string);}
	public function get_something9($string) { return sha1($string);}
	public function get_something10($string) { return sha1($string);}
	public function get_something11($string) { return sha1($string);}
	public function get_something12($string) { return sha1($string);}
	public function get_something13($string) { return sha1($string);}
	public function get_something14($string) { return sha1($string);}
	public function get_something15($string) { return sha1($string);}
	public function get_something16($string) { return sha1($string);}
	public function get_something17($string) { return sha1($string);}
	public function get_something18($string) { return sha1($string);}
	public function get_something19($string) { return sha1($string);}
	public function get_something20($string) { return sha1($string);}
	public function get_something21($string) { return sha1($string);}
	public function get_something22($string) { return sha1($string);}
	public function get_something23($string) { return sha1($string);}
	public function get_something24($string) { return sha1($string);}
	public function get_something25($string) { return sha1($string);}
	public function get_something26($string) { return sha1($string);}
	public function get_something27($string) { return sha1($string);}
	public function get_something28($string) { return sha1($string);}
	public function get_something29($string) { return sha1($string);}
	public function get_something30($string) { return sha1($string);}
	public function get_something31($string) { return sha1($string);}
	public function get_something32($string) { return sha1($string);}
	public function get_something33($string) { return sha1($string);}
	public function get_something34($string) { return sha1($string);}
	public function get_something35($string) { return sha1($string);}
	public function get_something36($string) { return sha1($string);}
	public function get_something37($string) { return sha1($string);}
	public function get_something38($string) { return sha1($string);}
	public function get_something39($string) { return sha1($string);}
	public function get_something40($string) { return sha1($string);}
	public function get_something41($string) { return sha1($string);}
	public function get_something42($string) { return sha1($string);}
	public function get_something43($string) { return sha1($string);}
	public function get_something44($string) { return sha1($string);}
	public function get_something45($string) { return sha1($string);}
	public function get_something46($string) { return sha1($string);}
	public function get_something47($string) { return sha1($string);}
	public function get_something48($string) { return sha1($string);}
	public function get_something49($string) { return sha1($string);}
	public function get_something50($string) { return sha1($string);}
}
for($i = 0; $i &lt; 1000; $i++)
	TestCase2::get_something1('Hello World');
$timer-&gt;setMarker('end  declare many methods');

$timer-&gt;setMarker('start declare one method');
class TestCase3
{
	public function get_something1($string) { return sha1($string);}
}
for($i = 0; $i &lt; 1000; $i++)
	TestCase3::get_something1('Hello World');
$timer-&gt;setMarker('end declare one method');

$timer-&gt;setMarker('start define redundancy variable');
class TestCase4
{
	var $test1;
	protected $test2;
	public $test3;
	private $test4;
	var $test5;
	protected $test6;
	public $test7;
	private $test8;
	var $test9;
	protected $test10;
	public $test11;
	private $test12;
	// try to remove variables in this method and run it again.
	public function get_something($string) { (int) $test; (int) $test2; (int) $test3; (int) $test4; (int) $test5;  return sha1($string);}
}
for($i = 0; $i &lt; 1000; $i++)
	TestCase4::get_something('Hello World');

$timer-&gt;setMarker('end define redundancy variable');

$timer-&gt;setMarker('start no variable');
class TestCase5
{
	public function get_something($string) { return sha1($string);}
}
for($i = 0; $i &lt; 1000; $i++)
	TestCase5::get_something('Hello World');

$timer-&gt;setMarker('end no variable');

$timer-&gt;timeElapsed('StaticMethodStart', 'StaticMethodStop') . &quot;\n&quot;;
$timer-&gt;timeElapsed('MethodStart', 'MethodStop') . &quot;\n&quot;;

$timer-&gt;timeElapsed('PrintStart', 'PrintStop') . &quot;\n&quot;;
$timer-&gt;timeElapsed('EchoStart', 'EchoStop') . &quot;\n&quot;;

$timer-&gt;timeElapsed('Start: Echo + , ', 'End: Echo + , ') . &quot;\n&quot;;
$timer-&gt;timeElapsed('Start: Echo + . ', 'End: Echo + . ') . &quot;\n&quot;;

$timer-&gt;timeElapsed('Start: Switch', 'End: Switch') . &quot;\n&quot;;
$timer-&gt;timeElapsed('Start: if', 'End: if') . &quot;\n&quot;;

$timer-&gt;timeElapsed('suppress start', 'suppress end') . &quot;\n&quot;;
$timer-&gt;timeElapsed('no suppress start', 'no suppress end') . &quot;\n&quot;;

$timer-&gt;timeElapsed('start string convert', 'start no string convert') . &quot;\n&quot;;
$timer-&gt;timeElapsed('start no string convert', 'end no string convert') . &quot;\n&quot;;

$timer-&gt;timeElapsed('start using functions inside of for loop', 'end using functions inside of for loop') . &quot;\n&quot;;
$timer-&gt;timeElapsed('start no functions inside of for loop', 'end no functions inside of for loop') . &quot;\n&quot;;

$timer-&gt;timeElapsed('start strlen', 'end strlen') . &quot;\n&quot;;
$timer-&gt;timeElapsed('start isset', 'end isset') . &quot;\n&quot;;

$timer-&gt;timeElapsed('start declare many methods', 'end define redundancy variable') . &quot;\n&quot;;
$timer-&gt;timeElapsed('start declare one method', 'end declare one method') . &quot;\n&quot;;

$timer-&gt;timeElapsed('start define redundancy variable', 'end strlen') . &quot;\n&quot;;
$timer-&gt;timeElapsed('start no variable', 'end no variable') . &quot;\n&quot;;

$timer-&gt;stop();
$timer-&gt;display();
</pre>
<p>測試環境</p>
<pre>
marker                                     time index            ex time         perct
---------------------------------------------------------------------------------------
Start                                      1271166102.63234900   -                0.00%
---------------------------------------------------------------------------------------
StaticMethodStart                          1271166102.63238600   0.000037         0.03%
---------------------------------------------------------------------------------------
StaticMethodStop                           1271166102.63430300   0.001917         1.46%
---------------------------------------------------------------------------------------
MethodStart                                1271166102.63431500   0.000012         0.01%
---------------------------------------------------------------------------------------
MethodStop                                 1271166102.63781100   0.003496         2.66%
---------------------------------------------------------------------------------------
PrintStart                                 1271166102.63782000   0.000009         0.01%
---------------------------------------------------------------------------------------
PrintStop                                  1271166102.65499800   0.017178        13.09%
---------------------------------------------------------------------------------------
EchoStart                                  1271166102.65501200   0.000014         0.01%
---------------------------------------------------------------------------------------
EchoStop                                   1271166102.66703400   0.012022         9.16%
---------------------------------------------------------------------------------------
Start: Echo + ,                            1271166102.66704600   0.000012         0.01%
---------------------------------------------------------------------------------------
End: Echo + ,                              1271166102.68043400   0.013388        10.20%
---------------------------------------------------------------------------------------
Start: Echo + .                            1271166102.68044700   0.000013         0.01%
---------------------------------------------------------------------------------------
End: Echo + .                              1271166102.69948600   0.019039        14.50%
---------------------------------------------------------------------------------------
Start: Switch                              1271166102.69951200   0.000026         0.02%
---------------------------------------------------------------------------------------
End: Switch                                1271166102.70379900   0.004287         3.27%
---------------------------------------------------------------------------------------
Start: if                                  1271166102.70381000   0.000011         0.01%
---------------------------------------------------------------------------------------
End: if                                    1271166102.70807200   0.004262         3.25%
---------------------------------------------------------------------------------------
suppress start                             1271166102.70808100   0.000009         0.01%
---------------------------------------------------------------------------------------
suppress end                               1271166102.71372200   0.005641         4.30%
---------------------------------------------------------------------------------------
no suppress start                          1271166102.71373400   0.000012         0.01%
---------------------------------------------------------------------------------------
no suppress end                            1271166102.72371200   0.009978         7.60%
---------------------------------------------------------------------------------------
start string convert                       1271166102.72373200   0.000020         0.02%
---------------------------------------------------------------------------------------
end string convert                         1271166102.72392700   0.000195         0.15%
---------------------------------------------------------------------------------------
start no string convert                    1271166102.72393900   0.000012         0.01%
---------------------------------------------------------------------------------------
end no string convert                      1271166102.72407900   0.000140         0.11%
---------------------------------------------------------------------------------------
start no functions inside of for loop      1271166102.72462000   0.000541         0.41%
---------------------------------------------------------------------------------------
end no functions inside of for loop        1271166102.73778500   0.013165        10.03%
---------------------------------------------------------------------------------------
start using functions inside of for loop   1271166102.73779700   0.000012         0.01%
---------------------------------------------------------------------------------------
end using functions inside of for loop     1271166102.74494600   0.007149         5.45%
---------------------------------------------------------------------------------------
start strlen                               1271166102.74495800   0.000012         0.01%
---------------------------------------------------------------------------------------
end strlen                                 1271166102.74547700   0.000519         0.40%
---------------------------------------------------------------------------------------
start isset                                1271166102.74548900   0.000012         0.01%
---------------------------------------------------------------------------------------
end isset                                  1271166102.74571200   0.000223         0.17%
---------------------------------------------------------------------------------------
start declare many methods                 1271166102.74572100   0.000009         0.01%
---------------------------------------------------------------------------------------
end  declare many methods                  1271166102.74899700   0.003276         2.50%
---------------------------------------------------------------------------------------
start declare one method                   1271166102.74900600   0.000009         0.01%
---------------------------------------------------------------------------------------
end declare one method                     1271166102.75232400   0.003318         2.53%
---------------------------------------------------------------------------------------
start define redundancy variable           1271166102.75233400   0.000010         0.01%
---------------------------------------------------------------------------------------
end define redundancy variable             1271166102.75980700   0.007473         5.69%
---------------------------------------------------------------------------------------
start no variable                          1271166102.75981600   0.000009         0.01%
---------------------------------------------------------------------------------------
end no variable                            1271166102.76346600   0.003650         2.78%
---------------------------------------------------------------------------------------
Stop                                       1271166102.76362100   0.000155         0.12%
---------------------------------------------------------------------------------------
<strong>total                                      -                     0.131272       100.00%</strong>
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/04/php-%e4%bd%bf%e7%94%a8-echo-%e7%9a%84%e6%95%88%e8%83%bd/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Fork child processes 在 PHP 的寫法</title>
		<link>http://blog.roga.tw/2010/03/php-%e8%a3%a1%e9%9d%a2-fork-%e7%9a%84%e5%af%ab%e6%b3%95/</link>
		<comments>http://blog.roga.tw/2010/03/php-%e8%a3%a1%e9%9d%a2-fork-%e7%9a%84%e5%af%ab%e6%b3%95/#comments</comments>
		<pubDate>Fri, 26 Mar 2010 15:56:41 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2373</guid>
		<description><![CDATA[有人在 PTT 問了一個問題： 作者 kovenkoven (小沃) 看板 PHP 標題 [請益] 幾個Plurk機器人的問題 時間 Thu Mar 25 18:51:22 2010 ─────────────────────────────────────── ..略.. 由於最近對在嘗試寫Plurk的回訊機器人 ..略.. 我想在使用者發出關鍵字的噗之後才開啟回訊機器人進行動作 之後在回覆的地方抓使用者輸入的控制選項 例如A使用者發出了&#8221;呼叫OOO&#8221;的訊息 機器人會在該篇噗做出等待使用者下命令的訊息之後等待使用者的回覆 接著使用者可能會輸入&#8221;氣象&#8221;or&#8221;新聞&#8221;等等的訊息讓機器人做出相對應的事情 直到使用者發出&#8221;結束OOO&#8221;才停止函式 可是該怎麼做才能實現主程式持續抓時間河上的訊息且同時服務已呼叫機器人的使用者? 在win作業系統下有支援類似thread的功能嗎? 另外目前我所使用的方法是利用while搭配sleep讓它持續運作 有什麼方法可以讓我做個分頁，裡面有控制按鈕去讓他起動or停止嗎? 譬如利用while($key)，讓我的按鈕可以去控制$key之類的 感謝各位^^ 這個概念其實不難，雖然說用 PHP 實做不見得是個好主意。比較有效率的方法, 分成兩隻程式來跑，第一隻負責找最新的噗的關鍵字，找到之後把 plurk_id 存起來。第二隻負責去撈 plurk_id 來回應關鍵字。 table scheme id &#124; plurk_id &#124; start_time &#124; hooked pk 整數 抓到的時間 TRUE/FALSE 在 [...]]]></description>
			<content:encoded><![CDATA[<p>有人在 PTT 問了一個問題：<br />
<span id="more-2373"></span></p>
<blockquote><p>
作者  kovenkoven (小沃)                                            看板  PHP<br />
標題  [請益] 幾個Plurk機器人的問題<br />
時間  Thu Mar 25 18:51:22 2010<br />
───────────────────────────────────────<br />
..略..<br />
由於最近對在嘗試寫Plurk的回訊機器人<br />
..略..<br />
我想在使用者發出關鍵字的噗之後才開啟回訊機器人進行動作<br />
之後在回覆的地方抓使用者輸入的控制選項<br />
例如A使用者發出了&#8221;呼叫OOO&#8221;的訊息<br />
機器人會在該篇噗做出等待使用者下命令的訊息之後等待使用者的回覆<br />
接著使用者可能會輸入&#8221;氣象&#8221;or&#8221;新聞&#8221;等等的訊息讓機器人做出相對應的事情<br />
直到使用者發出&#8221;結束OOO&#8221;才停止函式<br />
可是該怎麼做才能實現主程式持續抓時間河上的訊息且同時服務已呼叫機器人的使用者?<br />
在win作業系統下有支援類似thread的功能嗎?<br />
另外目前我所使用的方法是利用while搭配sleep讓它持續運作<br />
有什麼方法可以讓我做個分頁，裡面有控制按鈕去讓他起動or停止嗎?<br />
譬如利用while($key)，讓我的按鈕可以去控制$key之類的<br />
感謝各位^^
</p></blockquote>
<p>這個概念其實不難，雖然說用 PHP 實做不見得是個好主意。比較有效率的方法, 分成兩隻程式來跑，第一隻負責找最新的噗的關鍵字，找到之後把 plurk_id 存起來。第二隻負責去撈 plurk_id 來回應關鍵字。<br />
<!--more--></p>
<pre>
    table scheme
    id   | plurk_id | start_time | hooked
    pk     整數       抓到的時間   TRUE/FALSE
</pre>
<p>在 fork 的同時把該筆 plurk_id 對應的欄位 hooked 設定為 FALSE</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/*
    先去撈資料庫的資料, 找出有幾筆 plurk_id 要回,
    假設你撈出來的資料已經處理好了，像是下面醬子.
*/

$data = array(
    array(&quot;plurk_id&quot; =&gt; 1, &quot;tpye&quot; =&gt; '打掃', &quot;hooked&quot;, FALSE),
    array(&quot;plurk_id&quot; =&gt; 2, &quot;type&quot; =&gt; '搥背', &quot;hooked&quot;, FALSE),
    array(&quot;plurk_id&quot; =&gt; 3, &quot;type&quot; =&gt; '偷懶', &quot;hooked&quot;, FALSE),
    array(&quot;plurk_id&quot; =&gt; 4, &quot;type&quot; =&gt; '喝茶', &quot;hooked&quot;, FALSE),
);

$total_num = count($data);

for($i = 0; $i&lt; $total_num; $i++)
{
    $pid = pcntl_fork();
    if($pid == -1)
    {
        exit('hey! orange.');
    }
    else if($pid)
    {
        echo &quot;這邊可以把該筆 plurk_id 的資料標記為已經處理\n&quot;;
        echo &quot;製造出第 &quot; . $data[$i]['plurk_id'] .&quot; 個 child process\n&quot;;
        // &quot;UPDATE TABLE SET hooked = TRUE WHERE plurk_id = $data[$i]['plurk_id']&quot;;
    }
    else
    {
        break;
    }
}

if($pid)
{
    echo &quot;end of parent process\n&quot;;
}
else
{
    test_case($data[$i]['plurk_id']);
}

function test_case($plurk_id)
{
    echo &quot;你目前在分析 $plurk_id 。\n&quot;;
    sleep(rand(10, 20));
    echo &quot;分析完畢 $plurk_id\n&quot;;
}

exit();
?&gt;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/03/php-%e8%a3%a1%e9%9d%a2-fork-%e7%9a%84%e5%af%ab%e6%b3%95/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>php-plurk-api 1.4</title>
		<link>http://blog.roga.tw/2010/02/php-plurk-api-1-4/</link>
		<comments>http://blog.roga.tw/2010/02/php-plurk-api-1-4/#comments</comments>
		<pubDate>Wed, 03 Feb 2010 20:17:50 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2357</guid>
		<description><![CDATA[目前 php-plurk-api 推出到 1.4.1 版本，當然，最新的版本還是放在 trunk 下面，從 SVN 取出最好。 雖然說版號一直演進，但其實都是在做 bug fix 和極小幅度的 enhancement ，所以說版號跳得很心虛。 最近在 issue 上面看到不少建議和回報，熱心的網友真的很多，真的很令人感動，而且測試之後的回報往往是督促成長最大的動力！ 最近工作比較繁忙，所以大概不會有什麼大幅度的更新，頂多就是照著 Plurk API 公佈的規格走，至於 status 在 accept 的 issue ，應該這幾天有空會改寫相關的程式 ^_^]]></description>
			<content:encoded><![CDATA[<p>目前 <a href="https://code.google.com/p/php-plurk-api/">php-plurk-api</a> 推出到 1.4.1 版本，當然，最新的版本還是放在 trunk 下面，從 SVN 取出最好。</p>
<p>雖然說版號一直演進，但其實都是在做 bug fix 和極小幅度的 enhancement ，所以說版號跳得很心虛。</p>
<p>最近在 <a href="http://zzb.bz/WbUo8">issue </a> 上面看到不少建議和回報，熱心的網友真的很多，真的很令人感動，而且測試之後的回報往往是督促成長最大的動力！</p>
<p>最近工作比較繁忙，所以大概不會有什麼大幅度的更新，頂多就是照著 <a href="http://www.plurk.com/API">Plurk API</a> 公佈的規格走，至於 status 在 accept 的 issue ，應該這幾天有空會改寫相關的程式 ^_^ </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2010/02/php-plurk-api-1-4/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Release: php-plurk-api 1.2 Beta.</title>
		<link>http://blog.roga.tw/2009/12/release-php-plurk-api-1-2-beta/</link>
		<comments>http://blog.roga.tw/2009/12/release-php-plurk-api-1-2-beta/#comments</comments>
		<pubDate>Fri, 18 Dec 2009 08:58:33 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2341</guid>
		<description><![CDATA[Plurk 在台灣是個流行的網路服務，也有不少應用程式是針對 Plurk 所設計，包括機器人的撰寫、手機應用程式、 桌面應用程式&#8230;等等。由於以往 Plurk 並未公佈 API ，所以應用程式必須自己模擬登入、解譯、傳遞資料，在開發上 有一定的難度。但好消息是於 2009 年 12 月份，噗浪公佈了官方的 API 規格，此一規格釋出，可以方便程式設計師更 專注於程式面的開發，設計更多好用、有趣的服務。 因此，我寫了 php-plurk-api 這個 PHP Client API 的架構，並且放到 Google Code 上面。當然，它是一個完全免費的開放原始碼專案，授權方式是 New BSD License，幸運的是，我的朋友 Whatup 以及 AppleBOY 在發起後就加入了專案並且貢獻了許多的程式碼，我們在很短時間內就把 Plurk 官方公佈的 API 規格實做完畢。之後陸續一週內，也加入了不少願意幫忙的朋友，包括了 ChrisLiu 在 Comment 上面做了補充了不少地方，並且與官方公佈的文件對照，讓 phpDocumentor 得以順利產生文件，另外他也寫了 RLPLurkAPI 相容的 method 以及 Limit 也幫 php-plurk-api 補充了 Example 的部份，讓使用的人有範例可以參考。 備註：目前 [...]]]></description>
			<content:encoded><![CDATA[<p>Plurk 在台灣是個流行的網路服務，也有不少應用程式是針對 Plurk 所設計，包括機器人的撰寫、手機應用程式、 桌面應用程式&#8230;等等。由於以往 Plurk 並未公佈 API ，所以應用程式必須自己模擬登入、解譯、傳遞資料，在開發上 有一定的難度。但好消息是於 2009 年 12 月份，噗浪公佈了官方的 API 規格，此一規格釋出，可以方便程式設計師更 專注於程式面的開發，設計更多好用、有趣的服務。</p>
<p>因此，我寫了 <a href="http://plurk.lab3.tw" title="php-plurk-api">php-plurk-api</a> 這個 PHP Client API 的架構，並且放到 Google Code 上面。當然，它是一個完全免費的開放原始碼專案，授權方式是 New BSD License，幸運的是，我的朋友 Whatup 以及 AppleBOY 在發起後就加入了專案並且貢獻了許多的程式碼，我們在很短時間內就把 Plurk 官方公佈的 API 規格實做完畢。之後陸續一週內，也加入了不少願意幫忙的朋友，包括了 ChrisLiu 在 Comment 上面做了補充了不少地方，並且與官方公佈的文件對照，讓 phpDocumentor 得以順利產生文件，另外他也寫了 RLPLurkAPI 相容的 method 以及 Limit 也幫 php-plurk-api 補充了 Example 的部份，讓使用的人有範例可以參考。</p>
<p>備註：目前 php-plurk-api 依然在 Beta 階段，等到所有 method 都驗證測試 完畢之後才會釋出 rc 以及 stable 版本。</p>
<p>網站在這邊：<a href="http://plurk.lab3.tw" title="php-plurk-api">php-plurk-api website.</a>.<br />
程式碼在這邊：<a href="https://code.google.com/p/php-plurk-api/" title="php-plurk-api source code">php-plurk-api source code</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/12/release-php-plurk-api-1-2-beta/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>PHP Plurk API 專案開始</title>
		<link>http://blog.roga.tw/2009/12/rogas-plurk-api/</link>
		<comments>http://blog.roga.tw/2009/12/rogas-plurk-api/#comments</comments>
		<pubDate>Wed, 09 Dec 2009 11:52:45 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2338</guid>
		<description><![CDATA[最近 Plurk 釋出 API，原本小籤籤、小歌手、rogabot 的程式都是共用我改寫自 RLPlurkAPI 的版本，換言之，幾乎都是硬幹出來的。 我正在進行一個專案，會依照 Plurk 公佈的 API 標準重新實做，畢竟這才是王道，而且少了一大堆針對頁面的 regex 硬幹法，程式會好維護許多。 專案網址：php-plurk-api 如果有問題請在 Google Code 上面提出，如果要參與開發，也請通知我。]]></description>
			<content:encoded><![CDATA[<p>最近 Plurk 釋出 API，原本<a href="http://www.plurk.com/chance_deliver">小籤籤</a>、<a href="http://www.plurk.com/song_deliver">小歌手</a>、<a href="http://www.plurk.com/rogabot">rogabot</a> 的程式都是共用我改寫自 RLPlurkAPI 的版本，換言之，幾乎都是硬幹出來的。</p>
<p>我正在進行一個專案，會依照 Plurk 公佈的 API 標準重新實做，畢竟這才是王道，而且少了一大堆針對頁面的 regex 硬幹法，程式會好維護許多。</p>
<p>專案網址：<a href="https://code.google.com/p/php-plurk-api/">php-plurk-api</a></p>
<p>如果有問題請在 Google Code 上面提出，如果要參與開發，也請通知我。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/12/rogas-plurk-api/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>PageRank 改變計算方式</title>
		<link>http://blog.roga.tw/2009/08/pagerank-%e6%94%b9%e8%ae%8a%e8%a8%88%e7%ae%97%e6%96%b9%e5%bc%8f/</link>
		<comments>http://blog.roga.tw/2009/08/pagerank-%e6%94%b9%e8%ae%8a%e8%a8%88%e7%ae%97%e6%96%b9%e5%bc%8f/#comments</comments>
		<pubDate>Thu, 20 Aug 2009 01:55:37 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2287</guid>
		<description><![CDATA[最近 PageRank 計算的公式改變，已經無法用舊的 HASH 值取回正確的 PR ，會出現錯誤訊息 一般來講，網路上取回 PR 的程式都是算出一串 URL 後丟給 Google ，然後把解讀回傳值，會像是這樣： http://www.google.com/search?client=navclient-auto&#038;ch=6-9223293982612316911&#038;features=Rank&#038;q=info:roga.tw 可以拆解成幾個部份來看： 接著把 $url 用 curl 或是 fopen 之類的方法取回即可。 裡面最重要的就是計算 checksum 這組 hash value 的方法，舊有的方法如下，我們可以看到幾個地方有 Magic Number ，像是上面 $url 字串中包含的 &#038;ch=6 以及後面接 $checksum，以及下面程式碼中計算 checksum 的 $init = 0xE6359A60 我後來在 http://wpcn.googlecode.com 找到一個新的 Google PageRank checksum 算法，它是針對 WP 寫的，稍微改寫一下就可以使用了。 最重要的 $checksum 計算方法如下： 裡面一樣有 [...]]]></description>
			<content:encoded><![CDATA[<p>最近 PageRank 計算的公式改變，已經無法用舊的 HASH 值取回正確的 PR ，會出現錯誤訊息</p>
<p><img src="http://gallery.roga.tw/d/38079-1/Screenshot-3.png" alt="" /><br />
<span id="more-2287"></span><br />
一般來講，網路上取回 PR 的程式都是算出一串 URL 後丟給 Google ，然後把解讀回傳值，會像是這樣：</p>
<p><code>http://www.google.com/search?client=navclient-auto&#038;ch=6-9223293982612316911&#038;features=Rank&#038;q=info:roga.tw</code></p>
<p>可以拆解成幾個部份來看：</p>
<pre class="brush: php; title: ; notranslate">
$site = 'http://www.example.com'; /* 你的 url */
$info = 'info:' . urldecode($site);
$checksum = $this-&gt;checksum($this-&gt;strord($info));
$url = &quot;http://www.google.com/search?client=navclient-auto&amp;ch=6{$checksum}&amp;features=Rank&amp;q={$info}&quot;;
</pre>
<p>接著把 <code>$url</code> 用 curl 或是 fopen 之類的方法取回即可。</p>
<p>裡面最重要的就是計算 checksum 這組 hash value 的方法，舊有的方法如下，我們可以看到幾個地方有 Magic Number ，像是上面 $url 字串中包含的 <code>&#038;ch=6</code> 以及後面接 <code>$checksum</code>，以及下面程式碼中計算 <code>checksum</code> 的 <code>$init = 0xE6359A60</code></p>
<pre class="brush: php; title: ; notranslate">
    /**
     * Pagerank checksum hash emulator
     */
	function checksum ($url, $length = null, $init = 0xE6359A60)
	{
		if (is_null($length))
		{
			$length = sizeof($url);
		}
		$a = $b = 0x9E3779B9;
		$c = $init;
		$k = 0;
		$len = $length;

		while($len &gt;= 12)
		{
			$a += ( $url[$k+0] + ( $url[$k+1] &lt;&lt; 8 ) + ( $url[$k+2] &lt;&lt; 16 ) + ( $url[$k+3] &lt;&lt; 24 ));
			$b += ( $url[$k+4] + ( $url[$k+5] &lt;&lt; 8 ) + ( $url[$k+6] &lt;&lt; 16 ) + ( $url[$k+7] &lt;&lt; 24 ));
			$c += ( $url[$k+8] + ( $url[$k+9] &lt;&lt; 8 ) + ( $url[$k+10] &lt;&lt; 16 ) + ( $url[$k+11] &lt;&lt; 24 ));
			$mix = $this-&gt;mix($a,$b,$c);
			$a = $mix[0]; $b = $mix[1]; $c = $mix[2];
			$k += 12;
			$len -= 12;
		}

		$c += $length;

		switch($len)
		{
			case 11: $c += ($url[$k + 10] &lt;&lt; 24);
			case 10: $c += ($url[$k + 9] &lt;&lt; 16);
			case 9: $c += ($url[$k + 8] &lt;&lt; 8);
			case 8: $b += ($url[$k + 7] &lt;&lt; 24);
			case 7: $b += ($url[$k + 6] &lt;&lt; 16);
			case 6: $b += ($url[$k + 5] &lt;&lt; 8);
			case 5: $b += ($url[$k + 4]);
			case 4: $a += ($url[$k + 3] &lt;&lt; 24);
			case 3: $a += ($url[$k + 2] &lt;&lt; 16);
			case 2: $a += ($url[$k + 1] &lt;&lt; 8);
			case 1: $a += ($url[$k + 0]);
		}

		$mix = $this-&gt;mix($a, $b, $c);

		return $mix[2];

    }
    /**
     * Converts number to int 32
     * (Required for pagerank hash)
     */
	function to_int_32 (&amp;$x)
	{
		$z = hexdec(80000000);
		$y = (int) $x;
		if($y ==- $z &amp;&amp; $x &lt;- $z)
		{
			$y = (int) ((-1) * $x);
			$y = (-1) * $y;
		}
		$x = $y;
	}

    /**
     * Fills in zeros on a number
     * (Required for pagerank hash)
     */
	function zero_fill ($a, $b)
	{
		$z = hexdec(80000000);
		if ($z &amp; $a)
		{
			$a = ($a &gt;&gt; 1);
			$a &amp;= (~$z);
			$a |= 0x40000000;
			$a = ($a &gt;&gt; ($b - 1));
		}
		else
		{
			$a = ($a &gt;&gt; $b);
		}
		return $a;
	}

    /**
     * Pagerank hash prerequisites
     */
	function mix($a, $b, $c)
	{
		$a -= $b; $a -= $c; $this-&gt;to_int_32($a); $a = (int)($a ^ ($this-&gt;zero_fill($c,13)));
		$b -= $c; $b -= $a; $this-&gt;to_int_32($b); $b = (int)($b ^ ($a&lt;&lt;8));
		$c -= $a; $c -= $b; $this-&gt;to_int_32($c); $c = (int)($c ^ ($this-&gt;zero_fill($b,13)));
		$a -= $b; $a -= $c; $this-&gt;to_int_32($a); $a = (int)($a ^ ($this-&gt;zero_fill($c,12)));
		$b -= $c; $b -= $a; $this-&gt;to_int_32($b); $b = (int)($b ^ ($a&lt;&lt;16));
  		$c -= $a; $c -= $b; $this-&gt;to_int_32($c); $c = (int)($c ^ ($this-&gt;zero_fill($b,5)));
		$a -= $b; $a -= $c; $this-&gt;to_int_32($a); $a = (int)($a ^ ($this-&gt;zero_fill($c,3)));
		$b -= $c; $b -= $a; $this-&gt;to_int_32($b); $b = (int)($b ^ ($a&lt;&lt;10));
		$c -= $a; $c -= $b; $this-&gt;to_int_32($c); $c = (int)($c ^ ($this-&gt;zero_fill($b,15)));
		return array($a,$b,$c);
	}
    /**
     * ASCII conversion of a string
     */
    function strord($string)
    {
    	for($i = 0; $i &lt; strlen($string); $i++)
    	{
    		$result[$i] = ord($string{$i});
    	}
    	return $result;
    }

    /**
     * Number formatting for use with pagerank hash
     */
    function format_number ($number='', $divchar = ',', $divat = 3)
    {
    	$decimals = '';
    	$formatted = '';

    	if (strstr($number, '.'))
    	{
    		$pieces = explode('.', $number);
    		$number = $pieces[0];
    		$decimals = '.' . $pieces[1];
    	}
    	else
    	{
    		$number = (string) $number;
    	}

    	if (strlen($number) &lt;= $divat)
    		return $number;

    	$j = 0;

    	for ($i = strlen($number) - 1; $i &gt;= 0; $i--)
    	{
    		if ($j == $divat)
    		{
    			$formatted = $divchar . $formatted;
    			$j = 0;
    		}
    		$formatted = $number[$i] . $formatted;
    		$j++;
    	}
    	return $formatted . $decimals;
    }
</pre>
<p>我後來在 <a href=" http://wpcn.googlecode.com/">http://wpcn.googlecode.com</a> 找到一個新的 <a href="http://code.google.com/p/wpcn/downloads/detail?name=wp-pr.zip&#038;can=2&#038;q=">Google PageRank checksum</a> 算法，它是針對 WP 寫的，稍微改寫一下就可以使用了。</p>
<pre class="brush: php; title: ; notranslate">
$info = urlencode(&quot;info:&quot;.$site);
$checksum = $this-&gt;CheckHash($this-&gt;HashURL($site));
$url = &quot;http://www.google.com/search?client=navclient-auto&amp;ch=$checksum&amp;features=Rank&amp;=$info&quot;
</pre>
<p>最重要的 <code>$checksum</code> 計算方法如下：</p>
<pre class="brush: php; title: ; notranslate">

	//convert a string to a 32-bit integer
	function StrToNum($Str, $Check, $Magic) {
		$Int32Unit = 4294967296;  // 2^32
		$length = strlen($Str);
		for ($i = 0; $i &lt; $length; $i++) {
			$Check *= $Magic;
			//If the float is beyond the boundaries of integer (usually +/- 2.15e+9 = 2^31),
			//  the result of converting to integer is undefined
			//  refer to http://www.php.net/manual/en/language.types.integer.php
			if ($Check &gt;= $Int32Unit) {
				$Check = ($Check - $Int32Unit * (int) ($Check / $Int32Unit));
				//if the check less than -2^31
				$Check = ($Check &lt; -2147483648) ? ($Check + $Int32Unit) : $Check;
			}
			$Check += ord($Str{$i});
		}
		return $Check;
	}

	//genearate a hash for a url
	function HashURL($String) {
		$Check1 = $this-&gt;StrToNum($String, 0x1505, 0x21);
		$Check2 = $this-&gt;StrToNum($String, 0, 0x1003F);
		$Check1 &gt;&gt;= 2;
		$Check1 = (($Check1 &gt;&gt; 4) &amp; 0x3FFFFC0 ) | ($Check1 &amp; 0x3F);
		$Check1 = (($Check1 &gt;&gt; 4) &amp; 0x3FFC00 ) | ($Check1 &amp; 0x3FF);
		$Check1 = (($Check1 &gt;&gt; 4) &amp; 0x3C000 ) | ($Check1 &amp; 0x3FFF);	

		$T1 = (((($Check1 &amp; 0x3C0) &lt;&lt; 4) | ($Check1 &amp; 0x3C)) &lt;&lt;2 ) | ($Check2 &amp; 0xF0F );
		$T2 = (((($Check1 &amp; 0xFFFFC000) &lt;&lt; 4) | ($Check1 &amp; 0x3C00)) &lt;&lt; 0xA) | ($Check2 &amp; 0xF0F0000 );

		return ($T1 | $T2);
	}

	//genearate a checksum for the hash string
	function CheckHash($Hashnum) {
		$CheckByte = 0;
		$Flag = 0;
		$HashStr = sprintf('%u', $Hashnum) ;
		$length = strlen($HashStr);

		for ($i = $length - 1;  $i &gt;= 0;  $i --) {
			$Re = $HashStr{$i};
			if (1 === ($Flag % 2)) {
				$Re += $Re;
				$Re = (int)($Re / 10) + ($Re % 10);
			}
			$CheckByte += $Re;
			$Flag ++;
		}

		$CheckByte %= 10;
		if (0 !== $CheckByte) {
			$CheckByte = 10 - $CheckByte;
			if (1 === ($Flag % 2) ) {
				if (1 === ($CheckByte % 2)) {
					$CheckByte += 9;
				}
				$CheckByte &gt;&gt;= 1;
			}
		}
		return '7'.$CheckByte.$HashStr;
	}
</pre>
<p>裡面一樣有 Magic Number ，像是 CheckHash() 的 return value。接著一樣把 <code>$url</code> 用 curl 或是 fopen 之類的方法取回即可。</p>
<p>唉，我還是不懂為什麼 Google 不提供取回 PR 的 API 。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/08/pagerank-%e6%94%b9%e8%ae%8a%e8%a8%88%e7%ae%97%e6%96%b9%e5%bc%8f/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>The Last PHP Project.</title>
		<link>http://blog.roga.tw/2009/07/the-last-php-project/</link>
		<comments>http://blog.roga.tw/2009/07/the-last-php-project/#comments</comments>
		<pubDate>Thu, 23 Jul 2009 02:16:10 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2276</guid>
		<description><![CDATA[maybe you should will not waste your spare time.]]></description>
			<content:encoded><![CDATA[<p>maybe you should will not waste your spare time.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/07/the-last-php-project/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IP 判斷方式</title>
		<link>http://blog.roga.tw/2009/06/ip-%e5%88%a4%e6%96%b7%e6%96%b9%e5%bc%8f/</link>
		<comments>http://blog.roga.tw/2009/06/ip-%e5%88%a4%e6%96%b7%e6%96%b9%e5%bc%8f/#comments</comments>
		<pubDate>Thu, 11 Jun 2009 04:16:51 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2226</guid>
		<description><![CDATA[一般我們知道的 IP 都是長得像： 192.168.0.1 不過對電腦來說，IP 是用 2 進位來表示，一個 IP 由四組數字組成，每組大小為 8 bit (每個 bit 可以代表二進位的 0 或是 1 )，所以上面的 IP 其實是長這樣： 11010000.10101000.00000000.00000001 然後 2 進位就可以和我們的子網路遮罩做運算了，分別可以算出 Network 和 Boadcast 分別是多少 (好，梗鋪完了)。 在網路上大家都是用 IP 表示自己的位置，由於我們會想知道對方是哪來的，所以就有某些人把 IP 分配給哪些國家這檔事情記錄下來再拿這個資訊賣錢，像是 MaxMind 這間公司。這個公司提供的 IP 資料庫格式會像是下面這樣： "222.250.0.0","222.251.255.255","3740925952","3741057023","TW","Taiwan" 通常這些資料都會被匯入資料庫中，在查詢的時候，我們會轉換成十進位再用 SQL 的 BETWEEN 敘述來查詢(上面資料的第三、四欄位)，二進位轉換成十進位的方法如下： 第一組數字 * 2 的 24 次方 + 第二組數字 * 2 [...]]]></description>
			<content:encoded><![CDATA[<p>一般我們知道的 IP 都是長得像：</p>
<p><code>192.168.0.1</code><br />
<span id="more-2226"></span><br />
不過對電腦來說，IP 是用 2 進位來表示，一個 IP 由四組數字組成，每組大小為 8 bit (每個 bit 可以代表二進位的 0 或是 1 )，所以上面的 IP 其實是長這樣：</p>
<p><code>11010000.10101000.00000000.00000001</code></p>
<p>然後 2 進位就可以和我們的子網路遮罩做運算了，分別可以算出 Network 和 Boadcast 分別是多少 (好，梗鋪完了)。</p>
<p>在網路上大家都是用 IP 表示自己的位置，由於我們會想知道對方是哪來的，所以就有某些人把 IP 分配給哪些國家這檔事情記錄下來再拿這個資訊賣錢，像是 <a href="http://www.maxmind.com/">MaxMind</a> 這間公司。這個公司提供的 IP 資料庫格式會像是下面這樣：</p>
<p><code>"222.250.0.0","222.251.255.255","3740925952","3741057023","TW","Taiwan" </code></p>
<p>通常這些資料都會被匯入資料庫中，在查詢的時候，我們會轉換成十進位再用 SQL 的 BETWEEN 敘述來查詢(上面資料的第三、四欄位)，二進位轉換成十進位的方法如下：</p>
<p><code>第一組數字 * 2 的 24 次方 + 第二組數字 * 2 的 16 次方 + 第三組數字 * 2 的 8 次方 + 第四組數字 * 2 的 0 次方。</code></p>
<p>為了方便程式計算，可以速記如下：</p>
<p><code> sum = 16777216 * ip[0] + 65536 * ip[1] +  256 * ip[2] + ip[3]</code></p>
<p>寫成程式大概可以像這樣：</p>
<pre class="brush: php; title: ; notranslate">
$ip ='192.168.0.1';
$array = explode('.', $ip);
$sum =  $array[0] * 16777216 + $array[1] * 65536 + $array[2] * 256 + $array[3];

/* 然後取出的 SQL 語法： */
&quot;SELECT `country`, `name` FROM `ip_country` WHERE $sum BETWEEN `begin_num` AND `end_num` LIMIT 1
</pre>
<p>用這個方法這樣就可以解出來國家名稱了！</p>
<p>在 <a href="http://www.maxmind.com/app/geoip_country">MaxMind &#8211; GeoLite Country | Open Source IP Address to Country Database</a> 有提供免費 IP 資料庫下載(但是免費版本的涵蓋率比較低)。</p>
<p>&#8211;</p>
<p>UPDATE 2010/06/17</p>
<p>結果後來發現 IP 轉整數可以用 ip2long() ，所以不需要自己寫這個方法來轉換。</p>
<p>例如</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
    $ipv4 = '192.168.0.1';
    $result = sprintf(&quot;%u&quot;, ip2long($ipv4));
?&gt;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/06/ip-%e5%88%a4%e6%96%b7%e6%96%b9%e5%bc%8f/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Gallery 3 beta 1 發布</title>
		<link>http://blog.roga.tw/2009/06/gallery-3-beta-1-%e7%99%bc%e5%b8%83/</link>
		<comments>http://blog.roga.tw/2009/06/gallery-3-beta-1-%e7%99%bc%e5%b8%83/#comments</comments>
		<pubDate>Wed, 10 Jun 2009 03:23:34 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2222</guid>
		<description><![CDATA[在 6/5 Gallery 官方發布了 beta 1 版本，Gallery 3 擺脫過去使用 php smaty template engine 的&#8217;特色&#8217;，整體架構改為採用 Kohana 這套 php 的 Framework (這是從 CI 衍生出來的，很類似)。 我一直覺得套用 Framework 開發大型站台是一個趨勢，畢竟高速的生產力，省略繁瑣的檢核機制，這些都是必要的。 當然，採用 Kohana 的好處是可以加入的人更多了！畢竟有使用 Kohana 經驗的人可以迅速投入，不論是 debug, patch, commit 新玩意兒上去 或是單純做一些 &#8220;workaround&#8221; 。對玩過 Kohana 的人(或是其他類似的 Framework) 來說也可以省下了大量的 Code Tracing 時間。 在 Gallery 3 的 FAQ 裡面，有提到為什麼最後是選 Kohana 而不是 CakePHP 或是 ZendFramework [...]]]></description>
			<content:encoded><![CDATA[<p>在 6/5 Gallery 官方發布了 beta 1 版本，Gallery 3 擺脫過去使用 php smaty template engine 的&#8217;特色&#8217;，整體架構改為採用 Kohana 這套 php 的 Framework (這是從 CI 衍生出來的，很類似)。<br />
<span id="more-2222"></span><br />
我一直覺得套用 Framework 開發大型站台是一個趨勢，畢竟高速的生產力，省略繁瑣的檢核機制，這些都是必要的。</p>
<p>當然，採用 Kohana 的好處是可以加入的人更多了！畢竟有使用 Kohana 經驗的人可以迅速投入，不論是 debug, patch, commit 新玩意兒上去 或是單純做一些 &#8220;workaround&#8221; 。對玩過 Kohana 的人(或是其他類似的 Framework) 來說也可以省下了大量的 Code Tracing 時間。</p>
<p>在 Gallery 3 的 <a href="http://codex.gallery2.org/Gallery3:FAQ">FAQ</a> 裡面，有提到為什麼最後是選 Kohana 而不是 CakePHP 或是 ZendFramework 以及其他重量級產品(吊詭的是他沒有說為什麼不選 CI )。</p>
<p>希望之後可以有完整的 Migration Tool 出來協助 Gallery 2 的使用者轉換到 Gallery 3 ，目前最大的問題在於網址結構，而且我有大量使用 Mode Rewrite 來重新改寫網址，如果之後網址結構換掉的話，得花上不少時間修改舊有網址。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/06/gallery-3-beta-1-%e7%99%bc%e5%b8%83/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>所謂的爆炸</title>
		<link>http://blog.roga.tw/2009/06/%e6%89%80%e8%ac%82%e7%9a%84%e7%88%86%e7%82%b8/</link>
		<comments>http://blog.roga.tw/2009/06/%e6%89%80%e8%ac%82%e7%9a%84%e7%88%86%e7%82%b8/#comments</comments>
		<pubDate>Thu, 04 Jun 2009 06:08:58 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2212</guid>
		<description><![CDATA[大概就是這樣吧&#8230; 為了日後回應方便，我讓 Google Reader 去收我寫的討論區的 RSS ，結果沒想到，一個晚上就這麼多的問題回報！ 這就是所謂的爆炸吧！ (比起長輩級的爆炸，這還是很小咖就是了&#8230;科科)]]></description>
			<content:encoded><![CDATA[<p>大概就是這樣吧&#8230;<br />
<span id="more-2212"></span><br />
<a href="http://gallery.roga.tw/d/37727-1/Screenshot_002.png" rel="lightbox"><img src="http://gallery.roga.tw/d/37728-2/Screenshot_002.png" alt="" /></a></p>
<p>為了日後回應方便，我讓 Google Reader 去收我寫的討論區的 RSS ，結果沒想到，一個晚上就這麼多的問題回報！</p>
<p>這就是所謂的爆炸吧！</p>
<p>(比起長輩級的爆炸，這還是很小咖就是了&#8230;科科)</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/06/%e6%89%80%e8%ac%82%e7%9a%84%e7%88%86%e7%82%b8/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>SiteStates 免費計數器 新版上線</title>
		<link>http://blog.roga.tw/2009/06/sitestates-%e6%96%b0%e7%89%88%e4%b8%8a%e7%b7%9a/</link>
		<comments>http://blog.roga.tw/2009/06/sitestates-%e6%96%b0%e7%89%88%e4%b8%8a%e7%b7%9a/#comments</comments>
		<pubDate>Wed, 03 Jun 2009 09:36:07 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2210</guid>
		<description><![CDATA[新的計數器的樣式 模式 0 模式 1 模式 2 模式 3 模式 4 模式 5 標準模式，有 PageRank 資訊 精簡模式，沒有 PageRank 資訊 PageRank 模式 總瀏覽人次模式 小折線圖模式 (顯示當月到訪人次) 隱藏模式 * 顏色、邊框(可選擇要不要顯示邊框)、背景(可選擇要不要背景透明)都可以在後台自訂，把邊框顏色設定和你的網站背景一樣，看起來就會像&#8221;無邊框&#8221;效果 自動產生 RSS Feed 可供訂閱，發送內容為討論區文章以及系統公告。 每一帳號可以管理 10 個計數器，每個網址均可以分別紀錄「顯示模式」、「背景顏色」、「邊框顏色」、「文字顏色」。 可以使用透明背景 舊網址可以繼續沿用，但不建議，另外現在不允許從網址傳入變色和顯示參數，必須經由後台修改。 每一網址可以紀錄 2000 筆訪客，我會視系統狀況動態調整上限。 從現在起可以紀錄每月訪客人次，舊的也不會消失，也可以依照月份查詢單月每日訪客人次(趕工中) 獨立的小型討論區，顯示支援 Gravatar。 忘記密碼功能 網址：SiteStates]]></description>
			<content:encoded><![CDATA[<p>新的計數器的樣式<br />
<span id="more-2210"></span></p>
<table border="0" width="100%" cellpadding="0" cellspacing="10" bordercolor="#FFFFFF">
<tr>
<th>模式 0</th>
<th>模式 1</th>
<th>模式 2</th>
<th>模式 3</th>
<th>模式 4</th>
<th>模式 5</th>
</tr>
<tr>
<td align="center"><img src="http://sitestates.com/sitestates/files/images/mode0.png" alt="sitestates.com" style="border: 0px" /></td>
<td align="center"><img src="http://sitestates.com/sitestates/files/images/mode1.png" alt="sitestates.com" style="border: 0px" /></td>
<td align="center"><img src="http://sitestates.com/sitestates/files/images/mode2.png" alt="sitestates.com" style="border: 0px" /></td>
<td align="center"><img src="http://sitestates.com/sitestates/files/images/mode3.png" alt="sitestates.com" style="border: 0px" /></td>
<td align="center"><img src="http://sitestates.com/sitestates/files/images/mode4.png" alt="sitestates.com" style="border: 0px" /></td>
<td align="center"><img src="http://sitestates.com/sitestates/files/images/mode5.png" alt="sitestates.com" style="border: 0px" /></td>
</tr>
<tr>
<td align="center">標準模式，有 PageRank 資訊</td>
<td align="center">精簡模式，沒有 PageRank 資訊</td>
<td align="center">PageRank 模式</td>
<td align="center">總瀏覽人次模式</td>
<td align="center">小折線圖模式 (顯示當月到訪人次)</td>
<td align="center">隱藏模式</td>
</tr>
</table>
<p>* <strong>顏色、邊框(可選擇要不要顯示邊框)、背景(可選擇要不要背景透明)都可以在後台自訂，把邊框顏色設定和你的網站背景一樣，看起來就會像&#8221;無邊框&#8221;效果</strong></p>
<ul>
<li>自動產生 RSS Feed 可供訂閱，發送內容為討論區文章以及系統公告。</li>
<li>每一帳號可以管理 10 個計數器，每個網址均可以分別紀錄「顯示模式」、「背景顏色」、「邊框顏色」、「文字顏色」。</li>
<li>可以使用透明背景</li>
<li>舊網址可以繼續沿用，但不建議，另外現在不允許從網址傳入變色和顯示參數，必須經由後台修改。</li>
<li>每一網址可以紀錄 2000 筆訪客，我會視系統狀況動態調整上限。</li>
<li>從現在起可以紀錄每月訪客人次，舊的也不會消失，也可以依照月份查詢單月每日訪客人次(趕工中)</li>
<li>獨立的小型討論區，顯示支援 Gravatar。</li>
<li>忘記密碼功能</li>
</ul>
<p>網址：<a href="http://sitestates.com/ ">SiteStates</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/06/sitestates-%e6%96%b0%e7%89%88%e4%b8%8a%e7%b7%9a/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>計數器新版本開發計畫</title>
		<link>http://blog.roga.tw/2009/05/sitestates-%e6%96%b0%e7%89%88%e6%9c%ac%e9%96%8b%e7%99%bc%e8%a8%88%e7%95%ab/</link>
		<comments>http://blog.roga.tw/2009/05/sitestates-%e6%96%b0%e7%89%88%e6%9c%ac%e9%96%8b%e7%99%bc%e8%a8%88%e7%95%ab/#comments</comments>
		<pubDate>Wed, 27 May 2009 06:52:59 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2203</guid>
		<description><![CDATA[兩年前我寫了一個計數器：SiteStates 來玩，一直都還算相安無事，不過最近因為我自己伺服器主機虛擬化的問題，以及日漸龐大的資料量，我不得不正式著手解決資料庫的問題。 之前儲存使用者的到訪紀錄，是採用一個 row 直接存取，把到訪紀錄(ip, time, 反解)存成一個二維 array ，將之 serialize 之後存入一個 text 欄位，要取用的時候，先取出該欄位，再 unserialize 、刪掉最頂端元素、插入最底端元素，並且控制陣列大小維持在 500 個元素。 這樣的作法在小站其實沒什麼大問題，也可以簡化某些程序，在程式上也還算方便取出資料(最少我不需要 join table)，但缺點就是當資料量很龐大時，一但在寫入時當機那筆資料就沒了。而且這種作法，反而比對資料表做正規化還要佔空間。 現在新的作法是： 把 user 的資料單獨儲存在一個 table 叫做 users 。 把 user 擁有的 url 單獨儲存在一個 table 叫做 users_url，每個 user 可以擁有多組 url ，當然，每一組 url 可以單獨設定自己的計數器顏色、底色、邊框顏色、以及顯示模式(背景透明死否、邊框顯示與否、顯示資訊多寡)。 把所有到訪紀錄單獨存在一個 table 叫做 users_url_history ，使用 url_id 和 users_url 關聯，如此就可以知道每個 url 的個別到訪紀錄，資料筆數也沒有上限，一次新增也只需要動用一筆資料(刪除異同)，這樣也大大降低了弄丟資料的風險。 目前正在撰寫中，等到上線的時候，使用者必須先進行「網址轉換帳號」的動作，才能繼續使用服務(我會把整個程序設計得非常簡單，花不了五分鐘！) 最近上班工作忙碌，改寫這個系統又得花不少精神，如果有 [...]]]></description>
			<content:encoded><![CDATA[<p>兩年前我寫了一個計數器：<a href="http://sitestates.com" title="免費計數器">SiteStates</a> 來玩，一直都還算相安無事，不過最近因為我自己伺服器主機虛擬化的問題，以及日漸龐大的資料量，我不得不正式著手解決資料庫的問題。<br />
<span id="more-2203"></span><br />
之前儲存使用者的到訪紀錄，是採用一個 row 直接存取，把到訪紀錄(ip, time, 反解)存成一個二維 array ，將之 serialize 之後存入一個 text 欄位，要取用的時候，先取出該欄位，再 unserialize  、刪掉最頂端元素、插入最底端元素，並且控制陣列大小維持在 500 個元素。</p>
<p>這樣的作法在小站其實沒什麼大問題，也可以簡化某些程序，在程式上也還算方便取出資料(最少我不需要 join table)，但缺點就是當資料量很龐大時，一但在寫入時當機那筆資料就沒了。<strong>而且這種作法，反而比對資料表做正規化還要佔空間。</strong></p>
<p>現在新的作法是：</p>
<p>把 user 的資料單獨儲存在一個 table 叫做 users 。<br />
把 user 擁有的 url 單獨儲存在一個 table 叫做 users_url，每個 user 可以擁有多組 url ，當然，每一組 url 可以單獨設定自己的計數器顏色、底色、邊框顏色、以及顯示模式(背景透明死否、邊框顯示與否、顯示資訊多寡)。<br />
把所有到訪紀錄單獨存在一個 table 叫做 users_url_history  ，使用 url_id 和 users_url 關聯，如此就可以知道每個 url 的個別到訪紀錄，資料筆數也沒有上限，一次新增也只需要動用一筆資料(刪除異同)，這樣也大大降低了弄丟資料的風險。</p>
<p>目前正在撰寫中，等到上線的時候，使用者必須先進行「網址轉換帳號」的動作，才能繼續使用服務(我會把整個程序設計得非常簡單，花不了五分鐘！)</p>
<p>最近上班工作忙碌，改寫這個系統又得花不少精神，如果有 bug 或是設計不好的地方還請大家多多見諒。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/05/sitestates-%e6%96%b0%e7%89%88%e6%9c%ac%e9%96%8b%e7%99%bc%e8%a8%88%e7%95%ab/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>所以說寫程式就是不能懶惰&#8230;</title>
		<link>http://blog.roga.tw/2009/02/%e6%89%80%e4%bb%a5%e8%aa%aa%e5%af%ab%e7%a8%8b%e5%bc%8f%e5%b0%b1%e6%98%af%e4%b8%8d%e8%83%bd%e6%87%b6%e6%83%b0/</link>
		<comments>http://blog.roga.tw/2009/02/%e6%89%80%e4%bb%a5%e8%aa%aa%e5%af%ab%e7%a8%8b%e5%bc%8f%e5%b0%b1%e6%98%af%e4%b8%8d%e8%83%bd%e6%87%b6%e6%83%b0/#comments</comments>
		<pubDate>Wed, 25 Feb 2009 12:04:01 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2163</guid>
		<description><![CDATA[前幾天寫了一個 bot ，他沒什麼大問題，但有個缺點就是會 OP 。其實 OP 是人為疏失，因為我沒把它寫得嚴謹一些。 以下說明為甚麼會 OP ： 假設我們取得一個列表，叫做美女列表，以下面為例，有五個美女，分別是 A, B, C, D, E： A -> 0 B -> 1 C -> 2 D -> 3 E -> 4 然後 0, 1, 2, 3, 4 是她們香閨的門牌。我們如果要每個小時取出一個美女，可以用這種方法取出： index = web.getGirl(now_time) 當我們把「now_time」傳入 getGirl() 這個方法，可以取得一個數字叫做 index ，而 getGirl() 這個方法的作用就是回傳 24 小時制的「時」來當作列表的索引。舉例說明： 把半夜十二點這個時間放到 getGirl() 裡面，他會回傳 0 。 [...]]]></description>
			<content:encoded><![CDATA[<p>前幾天寫了一個 bot ，他沒什麼大問題，但有個缺點就是會 OP 。其實 OP 是人為疏失，因為我沒把它寫得嚴謹一些。</p>
<p>以下說明為甚麼會 OP ：<br />
<span id="more-2163"></span><br />
假設我們取得一個列表，叫做美女列表，以下面為例，有五個美女，分別是 A, B, C, D, E：</p>
<p>A -> 0<br />
B -> 1<br />
C -> 2<br />
D -> 3<br />
E -> 4</p>
<p>然後 0, 1, 2, 3, 4 是她們香閨的門牌。我們如果要每個小時取出一個美女，可以用這種方法取出：</p>
<p><code>index = web.getGirl(now_time)</code></p>
<p>當我們把「<code>now_time</code>」傳入 <code>getGirl()</code> 這個方法，可以取得一個數字叫做 <code>index</code> ，而 <code>getGirl()</code> 這個方法的作用就是回傳 24 小時制的「時」來當作列表的索引。舉例說明：</p>
<p>把半夜十二點這個時間放到 getGirl() 裡面，他會回傳 0 。<br />
把清晨五點這個時間放到 getGirl() 裡面，他會回傳 5 。<br />
把中午十二點這個時間放到 getGirl() 裡面，他會回傳 12 。<br />
把下午五點這個時間放到 getGirl() 裡面，他會回傳 17 。</p>
<p>參考上面的列表，<br />
在 0 點的時候，會取出 A 。<br />
在 1 點的時候，會取出 B 。<br />
在 2 點的時候，會取出 C 。<br />
在 3 點的時候，會取出 D 。</p>
<p>接下來切入問題核心：假設半夜 3 點時，列表長這樣：</p>
<p>A -> 0<br />
B -> 1<br />
C -> 2<br />
D -> 3<br />
E -> 4</p>
<p>但是三點十五的時候列表更新了，變成這樣：變成這樣：</p>
<p>Z -> 0<br />
A -> 1<br />
B -> 2<br />
C -> 3<br />
D -> 4<br />
E -> 5</p>
<p>前面多了一個 Z 插隊。由於我們索引規則沒變，所以在 4 點的時候，照原本的規定，應該是取出 E ，但由於列表變動過了，所以依照 4 這個索引取出的會是 D ，於是產生 OP 的情形。當然實際情況沒這麼單純，加上當時程式寫得有點隨便而且沒有判斷是否有重複發表的情況&#8230;</p>
<p>當然這樣下去不是辦法，所以我新增了一個資料表來紀錄發過的圖片，結構如下：</p>
<p><img src="http://gallery.roga.tw/d/36398-1/op.png" alt="" /><br />
這個資料表可以紀錄縮圖、相簿網址、相簿主人帳號以及是否被機器人送出過。</p>
<p><a href="http://gallery.roga.tw/d/36401-1/page.png" rel="lightbox"><img src="http://gallery.roga.tw/d/36402-2/page.png" alt="" /></a></p>
<p>程式判斷 op 部份的流程大致如下：</p>
<ul>
<li>從正妹牆的 RSS 取得列表</li>
<li>解析 RSS 取出有用的元素並依序塞入資料庫內，每塞入一行之前均進行檢查該本相簿是否已經存在於資料庫中。如果有就略過，沒有就新增進資料庫，並將該筆資料標示為「未用」。</li>
<li>整點的時候，從資料表隨便挑選一筆標示為「未用」的資料秀出來，在這把該筆資料標示為「用後不理」。</li>
</ul>
<p><strong>目前可能會有極少數 OP 的情況，因為以前發過的相簿網址來不及存入資料庫中，沒得判斷，但過個兩天這個情況應該可以被完全解決。</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/02/%e6%89%80%e4%bb%a5%e8%aa%aa%e5%af%ab%e7%a8%8b%e5%bc%8f%e5%b0%b1%e6%98%af%e4%b8%8d%e8%83%bd%e6%87%b6%e6%83%b0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>早安</title>
		<link>http://blog.roga.tw/2009/02/%e6%af%8f%e5%a4%a9%e5%92%8c%e4%bd%a0%e8%aa%aa%e6%97%a9%e5%ae%89/</link>
		<comments>http://blog.roga.tw/2009/02/%e6%af%8f%e5%a4%a9%e5%92%8c%e4%bd%a0%e8%aa%aa%e6%97%a9%e5%ae%89/#comments</comments>
		<pubDate>Tue, 24 Feb 2009 03:18:45 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=2159</guid>
		<description><![CDATA[每天早上說早安..]]></description>
			<content:encoded><![CDATA[<pre class="brush: php; title: ; notranslate">

function morning_call()
{
	$morning = array(
			&quot;God dag - (瑞典語/早安)&quot;,
			&quot;Bon matin - (法語/早安)&quot;,
			&quot;早上好 - (中國語)&quot;,
			&quot;Good morning - (英文/早安)&quot;,
			&quot;おはよう - (日語/早安)&quot;,
			&quot;Buenos días - (西班牙語/早安)&quot;,
			&quot;Goede morgen - (荷蘭文/早安)&quot;,
			&quot;Buon Giorno - (義大利文/早安)&quot;,
			&quot;哩厚！勞炸！ - (台灣話/早安)&quot;,
			&quot;Guten morgen - (德文/早安)&quot;,
			&quot;manhã - (葡萄牙語/早安)&quot;,
			&quot;God morgen - (挪威語/早安)&quot;,
			&quot;안녕하세요  - (韓文/早安)&quot;,
			&quot;хорошее утро - (俄文/早安)&quot;,
			&quot;Guten morgen - (德語/早安)&quot;,
			&quot;صباح الخي - (阿拉伯語/早安)&quot;,
			&quot;안녕하세요 - (韓文/早安)&quot;,
			&quot;सुप्रभात - (印度文/早安)&quot;,
			&quot;שַׁחֲרִית  - (希伯來文/早安)&quot;,
			&quot;Buenos días - (西班牙文/早安)&quot;,
			&quot;Selamat pagi - (印尼語/早安)&quot;);
	$index = (int) date(&quot;z&quot;) % count($morning);
	return $morning[$index];
}
</pre>
<p>每天早上說早安..</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/02/%e6%af%8f%e5%a4%a9%e5%92%8c%e4%bd%a0%e8%aa%aa%e6%97%a9%e5%ae%89/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>CakePHP 使用不同 Data Source 的方法</title>
		<link>http://blog.roga.tw/2009/01/cakephp-%e4%bd%bf%e7%94%a8%e4%b8%8d%e5%90%8c-dao-%e7%9a%84%e6%96%b9%e6%b3%95/</link>
		<comments>http://blog.roga.tw/2009/01/cakephp-%e4%bd%bf%e7%94%a8%e4%b8%8d%e5%90%8c-dao-%e7%9a%84%e6%96%b9%e6%b3%95/#comments</comments>
		<pubDate>Tue, 13 Jan 2009 14:25:14 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1986</guid>
		<description><![CDATA[在 CakePHP 中，常常都是用單一資料庫當作資料來源。不過去年我遇到了一個問題，就是我必須新增其他的資料來源(也就是存取不同的資料庫)，但是這個問題，一直無解，直到今天晚上狂翻線上文件才發知道解決的辦法(居然這麼簡單)&#8230; 如果有不同 DAO 要切換的話，必須這樣做： 在某個要使用該資料庫的 Model 中宣告： 而是 Controller 內的使用方法則不變（這邊就不用講了）： 更詳細的用法請見： Model Attributes]]></description>
			<content:encoded><![CDATA[<p>在 CakePHP 中，常常都是用單一資料庫當作資料來源。不過去年我遇到了一個問題，就是我必須新增其他的資料來源(也就是存取不同的資料庫)，但是這個問題，一直無解，直到今天晚上狂翻線上文件才發知道解決的辦法(居然這麼簡單)&#8230;<br />
<span id="more-1986"></span><br />
如果有不同 DAO 要切換的話，必須這樣做：</p>
<pre class="brush: php; title: ; notranslate">
    var $default = array('driver' =&gt; 'mysql',
        'host' =&gt; '127.0.0.1',
        'login' =&gt; 'cakephpuser',
        'password' =&gt; '',
        'database' =&gt; 'mysql_project');

    var $alternate = array('driver' =&gt; 'mssql',
        'host' =&gt; '127.0.0.2',
        'persistent' =&gt; true,
        'login' =&gt; 'cakephpuser',
        'password' =&gt; '',
        'database' =&gt; 'mssql_project',
        'port' =&gt; '1443',
        'encoding' = &gt; 'utf8',
        'prefix' =&gt; '');
        /* 在 DB config 中新增一組連線叫做 alternate 連接到 127.0.0.2 的 MSSQL 資料庫 */
</pre>
<p>在某個要使用該資料庫的 Model 中宣告：</p>
<pre class="brush: php; title: ; notranslate">
class Example extends AppModel {
    var $useDbConfig = 'alternate'; /* 使用 DB 設定檔中的 $alternate 連線設定 */
    var $name = 'Example'; /* 在 Controller 被叫用的名字，如不設定則依內定命名規則對應命名 */
    var $useTable = 'members'; /* 使用 members 資料表，如不設定則依內定命名規則找資料表 */
    var $tablePrefix = 'alternate_'; /* 資料表前面的 prefix 字串 */
    var $primaryKey = 'example_id'; /* 預設命名規則使用 id 當主鍵，如果使用別的欄位名稱當主鍵則需特別指定 */
}
</pre>
<p>而是 Controller 內的使用方法則不變（這邊就不用講了）：</p>
<pre class="brush: php; title: ; notranslate">
    $this-&gt;Example-&gt;find('all');
</pre>
<p>更詳細的用法請見：<a href="http://book.cakephp.org/view/71/Model-Attributes"> Model Attributes</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2009/01/cakephp-%e4%bd%bf%e7%94%a8%e4%b8%8d%e5%90%8c-dao-%e7%9a%84%e6%96%b9%e6%b3%95/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PDT</title>
		<link>http://blog.roga.tw/2008/12/pdt-%e9%96%8b%e7%99%bc%e5%b7%a5%e5%85%b7%e5%88%9d%e6%ad%a5%e4%bb%8b%e7%b4%b9/</link>
		<comments>http://blog.roga.tw/2008/12/pdt-%e9%96%8b%e7%99%bc%e5%b7%a5%e5%85%b7%e5%88%9d%e6%ad%a5%e4%bb%8b%e7%b4%b9/#comments</comments>
		<pubDate>Fri, 19 Dec 2008 21:22:03 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1780</guid>
		<description><![CDATA[先講結論，如果你在找一套 JAVA 的開發工具，那找 Eclipse 就對了！ 但如果你要找 PHP 的開發工具呢？沒錯！也是 Eclipse ！接下來要大力推薦的這套開發工具的全名叫做 PHP Development Tools framework for the Eclipse platform. 一樣也是基於 Eclipse 平台上的酷炫玩意兒！ 之前還是學生的時候，在 Windows 下寫程式都是用 Notepad++, PSPad 等文字編輯器去寫然後編譯， Linux 下面則是用很多人愛用的 VIM (說來丟臉，博大精深的 VIM 我也只會一點點)。不過也因此我對整合式開發環境(Integrated Development Environment, IDE) 的接觸和認識幾乎是零 。上班之後因為要寫 JAVA 所以開始學習 Eclipse ，用了快要三個月之後，我的感想是 Eclipse 真的是&#8230;好！， 在實際用了 PDT 兩天之後，有些心得&#8230; 介面部份都是英文，沒有中文。 Eclipse 有中文語系外掛，但是很難找，我兩個多月前原本想找，但後來習慣了，也就算了。 文件都是英文，可能有些翻譯作品，但我沒找到 PDT 支援 SVN 版本控制(這非常重要)，但必須透過 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://gallery.roga.tw/d/36057-2/PDT.png" rel="lightbox"><img src="http://gallery.roga.tw/d/36058-2/PDT.png" alt="" align="right" /></a></p>
<p>先講結論，如果你在找一套 JAVA 的開發工具，那找 <a href="http://www.zdnet.com.tw/enterprise/technology/0,2000085680,20096842,00.htm">Eclipse</a> 就對了！ 但如果你要找 PHP 的開發工具呢？沒錯！也是 Eclipse ！接下來要大力推薦的這套開發工具的全名叫做 PHP Development Tools framework for the Eclipse platform. 一樣也是基於 Eclipse 平台上的酷炫玩意兒！<br />
<span id="more-1780"></span><br />
之前還是學生的時候，在 Windows 下寫程式都是用 Notepad++, PSPad 等文字編輯器去寫然後編譯， Linux 下面則是用很多人愛用的 VIM (說來丟臉，博大精深的 VIM 我也只會一點點)。不過也因此我對整合式開發環境(Integrated Development Environment, IDE) 的接觸和認識幾乎是零 。上班之後因為要寫 JAVA 所以開始學習 Eclipse ，用了快要三個月之後，我的感想是 Eclipse 真的是&#8230;<strong>好</strong>！，</p>
<p>在實際用了 PDT 兩天之後，有些心得&#8230;</p>
<ol>
<li>介面部份都是英文，沒有中文。 Eclipse 有中文語系外掛，但是很難找，我兩個多月前原本想找，但後來習慣了，也就算了。</li>
<li>文件都是英文，可能有些翻譯作品，但我沒找到</li>
<li> PDT 支援 SVN 版本控制(這非常重要)，但必須透過 PDT 的 Software Updater 去安裝 Subclipse ，也就是 PDT 下的 SVN Client ，網址是<a href="http://subclipse.tigris.org/">http://subclipse.tigris.org/</a>。</li>
<li>我有稍微用了一下 NetBean 6.5 ，我還是覺得 PDT 比較好。雖然 NetBean 6.5 內建 SVN ， 但是Commit, Checkout 的時候，只能看到一條 Status Bar 在掃來掃去，沒辦法像 PDT 一樣看到實際傳輸狀況，我不喜歡這樣的感覺。我因為這個原因把 NetBean 6.5 移除安裝。</li>
<li>我也有安裝 Aptana ，但是我忘記什麼原因我又把它移除掉了，搞不好也和 SVN 有關系&#8230;</li>
<li>PDT 可以整合 Zend Debugger 或是 XDebug ，但因為授權的關係，必須分開下載安裝或是到 Zend 下載整合安裝的版本。</li>
<li>至於其他功能，我用得還不夠深入，所以沒辦法更進一步介紹，但是開啟專案時 PDT 掃描專案的速度很快，建立專案檔也事是在工作區 (wordspace) 的 .metadta 裡面，檔案不會四處分散亂存(NetBean 預設分開來存，有點討厭)，在掃描完專案之後，那個 CLASS 有哪個 Method 或是 Instance 它都可以找得出來，語法錯誤也是即時標示，鍵入變數符號 $ 的時候，還會自動列出清單讓我選擇，而且它免安裝，點了直接就可以用，放在隨身碟還可以帶著走 <img src='http://blog.roga.tw/wp-includes/images/smilies/icon_evil.gif' alt=':evil:' class='wp-smiley' />  </li>
</ol>
<p>備註：(上面連結點選放大後為 600&#215;480 縮圖，<a href="http://gallery.roga.tw/d/36056-1/PDT.png">點此</a>可看原圖)，圖案是我的桌面抓圖，畫面上的原始碼就是我之前在寫給 WP 用的 get your plurk 外掛。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/12/pdt-%e9%96%8b%e7%99%bc%e5%b7%a5%e5%85%b7%e5%88%9d%e6%ad%a5%e4%bb%8b%e7%b4%b9/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The Cookbook &#8211; in Simplified Chinese</title>
		<link>http://blog.roga.tw/2008/12/the-cookbook-in-%e7%b0%a1%e9%ab%94%e4%b8%ad%e6%96%87/</link>
		<comments>http://blog.roga.tw/2008/12/the-cookbook-in-%e7%b0%a1%e9%ab%94%e4%b8%ad%e6%96%87/#comments</comments>
		<pubDate>Fri, 12 Dec 2008 17:36:38 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1644</guid>
		<description><![CDATA[CakePHP 手册 &#8211; 簡體中文 不知道多久沒注意，現在有簡體版本了，讀起來比英文再快些。只要真的有把整個手冊都讀一遍，我想基本用法應該就都會了。 但是說真的，和 CodeIgniter 的手冊交互看了兩個小時，我發現 CodeIgniter 的手冊比較合我的胃口。 而且 CakePHP 的網站現在一直吐空白頁出來，有點討厭。]]></description>
			<content:encoded><![CDATA[<p><a href="http://book.cakephp.org/cn">CakePHP 手册 &#8211; 簡體中文</a></p>
<p>不知道多久沒注意，現在有簡體版本了，讀起來比英文再快些。只要真的有把整個手冊都讀一遍，我想基本用法應該就都會了。</p>
<p>但是說真的，和 CodeIgniter 的手冊交互看了兩個小時，我發現 <a href="http://codeigniter.org.cn/user_guide/index.html">CodeIgniter</a> 的手冊比較合我的胃口。</p>
<p>而且 CakePHP 的網站現在一直吐空白頁出來，有點討厭。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/12/the-cookbook-in-%e7%b0%a1%e9%ab%94%e4%b8%ad%e6%96%87/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>to join two table and rotate.</title>
		<link>http://blog.roga.tw/2008/12/to-join-two-table-and-rotate/</link>
		<comments>http://blog.roga.tw/2008/12/to-join-two-table-and-rotate/#comments</comments>
		<pubDate>Mon, 08 Dec 2008 17:32:59 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1568</guid>
		<description><![CDATA[最近遇到一個 SQL 的問題，假設原本有兩個資料表如下： Delares 資料表 ________________________ declare_no declare_name 01 台北 02 台中 03 台南 04 高雄 05 墾丁 Members 資料表 ________________________ name type 某甲 02 某甲 03 某乙 05 某乙 03 某丙 04 某丁 05 路人 01 男人 02 女人 02 要把兩張資料表合併，並且統計各地人數，可以用這個方法： 但是產出的是 declare_no declare_name result ___________________________________ 01 台北 1 02 台中 3 03 台南 [...]]]></description>
			<content:encoded><![CDATA[<p>最近遇到一個 SQL 的問題，假設原本有兩個資料表如下：<br />
<span id="more-1568"></span></p>
<pre>
Delares 資料表
________________________
declare_no  declare_name
  01        台北
  02        台中
  03        台南
  04        高雄
  05        墾丁

Members 資料表
________________________
name       type
某甲       02
某甲       03
某乙       05
某乙       03
某丙       04
某丁       05
路人       01
男人       02
女人       02
</pre>
<p>要把兩張資料表合併，並且統計各地人數，可以用這個方法：</p>
<pre class="brush: sql; title: ; notranslate">
SELECT
Declares.declare_no, Declares.declare_name, COUNT(Members.type) AS result
FROM Members, Declares
WHERE Members.type = Declares.declare_no
GROUP BY Declares.declare_no, Declares.declare_name
</pre>
<p>但是產出的是</p>
<pre>
declare_no  declare_name result
___________________________________
  01        台北       1
  02        台中       3
  03        台南       2
  04        高雄       1
  05        墾丁       2
</pre>
<p>可是我想要以下這種結果，這樣對報表來說比較直覺。</p>
<pre>
表頭   台北  台中   台南  高雄   墾丁
______________________________________
資料   人數  人數   人數  人數   人數
</pre>
<p>可是我不知道該怎麼下語法，所以就問了一下，<strong>很感謝 grence@ptt 幫我解答這個問題。</strong></p>
<p>他是用 CASE WHEN 來達成：</p>
<pre class="brush: sql; title: ; notranslate">
SELECT
SUM(CASE declare_no WHEN '台北' THEN 1 ELSE 0 END)[台北],
SUM(CASE declare_no WHEN '台中' THEN 1 ELSE 0 END)[台中],
SUM(CASE declare_no WHEN '台南' THEN 1 ELSE 0 END)[台南],
SUM(CASE declare_no WHEN '高雄' THEN 1 ELSE 0 END)[高雄],
SUM(CASE declare_no WHEN '墾丁' THEN 1 ELSE 0 END)[墾丁]
-- 當然，這邊可以再加 column
FROM Members
LEFT JOIN Declares ON TYPE=declare_no
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/12/to-join-two-table-and-rotate/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Filter Functions</title>
		<link>http://blog.roga.tw/2008/12/use-regex-to-validate-string/</link>
		<comments>http://blog.roga.tw/2008/12/use-regex-to-validate-string/#comments</comments>
		<pubDate>Mon, 08 Dec 2008 15:50:24 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1559</guid>
		<description><![CDATA[一些實用的 filter functions ，某些地方可以取代 regex 驗證 filter_has_var — Checks if variable of specified type exists filter_id — Returns the filter ID belonging to a named filter filter_input_array — Gets external variables and optionally filters them filter_input — Gets a specific external variable by name and optionally filters it filter_list — Returns a list of all [...]]]></description>
			<content:encoded><![CDATA[<p>一些實用的 filter functions ，某些地方可以取代 regex 驗證<br />
<span id="more-1559"></span></p>
<ol>
<li><a href="http://www.php.net/manual/en/function.filter-has-var.php">filter_has_var</a> — Checks if variable of specified type exists</li>
<li><a href="http://www.php.net/manual/en/function.filter-id.php">filter_id</a> — Returns the filter ID belonging to a named filter</li>
<li><a href="http://www.php.net/manual/en/function.filter-input-array.php">filter_input_array</a> — Gets external variables and optionally filters them</li>
<li><a href="http://www.php.net/manual/en/function.filter-input.php">filter_input</a> — Gets a specific external variable by name and optionally filters it</li>
<li><a href="http://www.php.net/manual/en/function.filter-list.php">filter_list</a> — Returns a list of all supported filters</li>
<li><a href="http://www.php.net/manual/en/function.filter-var-array.php">filter_var_array</a> — Gets multiple variables and optionally filters them</li>
<li><a href="http://www.php.net/manual/en/function.filter-var.php">filter_var</a> — Filters a variable with a specified filter</li>
</ol>
<pre class="brush: php; title: ; notranslate">
/* for an example: */

var_dump(filter_var('http://example.com', FILTER_VALIDATE_URL));
var_dump(filter_var('example@example.com', FILTER_VALIDATE_EMAIL));
var_dump(filter_var('example.com', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED));
</pre>
<p>今天才知道這個東西，以後如果有機會要驗證表單的話，很多常用的欄位都不需自己再寫 regex 了(詳見官方文件)。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/12/use-regex-to-validate-string/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TopDN &#124; 免費短網址 &#124; 免費轉址 &#8211; 問題與回應</title>
		<link>http://blog.roga.tw/2008/12/topdn-%e5%85%8d%e8%b2%bb%e7%9f%ad%e7%b6%b2%e5%9d%80-%e5%85%8d%e8%b2%bb%e8%bd%89%e5%9d%80-%e5%95%8f%e9%a1%8c%e8%88%87%e5%9b%9e%e6%87%89/</link>
		<comments>http://blog.roga.tw/2008/12/topdn-%e5%85%8d%e8%b2%bb%e7%9f%ad%e7%b6%b2%e5%9d%80-%e5%85%8d%e8%b2%bb%e8%bd%89%e5%9d%80-%e5%95%8f%e9%a1%8c%e8%88%87%e5%9b%9e%e6%87%89/#comments</comments>
		<pubDate>Sun, 07 Dec 2008 06:49:20 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1542</guid>
		<description><![CDATA[這篇文章專門提供 TopDN 的 問題與回應 ，在使用上不管有什麼問題，都可以在這邊提出！ 請直接寫下您的問題，我有看到就會回答！]]></description>
			<content:encoded><![CDATA[<p>這篇文章專門提供 <a href="http://topdn.net/" title="轉址服務">TopDN</a> 的 <strong>問題與回應</strong> ，在使用上不管有什麼問題，都可以在這邊提出！</p>
<p>請直接寫下您的問題，我有看到就會回答！</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/12/topdn-%e5%85%8d%e8%b2%bb%e7%9f%ad%e7%b6%b2%e5%9d%80-%e5%85%8d%e8%b2%bb%e8%bd%89%e5%9d%80-%e5%95%8f%e9%a1%8c%e8%88%87%e5%9b%9e%e6%87%89/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Web MVC &#8211; CakePHP 教學實做 &#8211; 簡介</title>
		<link>http://blog.roga.tw/2008/11/web-mvc-cakephp-%e6%95%99%e5%ad%b8%e5%af%a6%e5%81%9a-%e7%b0%a1%e4%bb%8b/</link>
		<comments>http://blog.roga.tw/2008/11/web-mvc-cakephp-%e6%95%99%e5%ad%b8%e5%af%a6%e5%81%9a-%e7%b0%a1%e4%bb%8b/#comments</comments>
		<pubDate>Sat, 15 Nov 2008 17:13:38 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1178</guid>
		<description><![CDATA[雖然我最近都在寫 JAVA ，但我還是想撥空把前陣子寫 CakePHP 的心得和大家分享，而以下文章則針對 CakePHP 說明。 MVC 是 Model View Controller 的縮寫，這是一種設計模式，方便把頁面和商業邏輯分開來處理。而 CakePHP 則是使用 PHP 實做這種設計模式的一套 Framework 。在 CakePHP 中， Model 代表的是資料庫、 Controller 代表的是商業邏輯的判斷處理，View 代表的則是使用者呈現的頁面。另外 CakePHP 實做了一個很重要的觀念叫做 ORM (Object-Relational Mapping)，在 CakePHP 中 ORM 把每一個資料庫宣告成一個類別 (Class) ，接著我們在透過 Controller 來處理資料庫的資料，避免直接使用 SQL 語言進行查詢，增加程式的彈性和可維護性。 建議在看這篇文章之前，先把 CakePHP 使用手冊 讀一遍，最好的狀況是能夠跟著教學實做一遍。因為跟著做過一次之後，會發現其實並不那麼困難，以下的例子是仿照 tutorial 寫成的，當然還有依據實際情況修改一些東西。我會陸續貼出來。 在學這套 Framework 之前，我們必須先大略瞭解 CakePHP 的目錄結構，在安裝之後，一開始我們會看到三個目錄： /app - 所有東西都放這邊！ [...]]]></description>
			<content:encoded><![CDATA[<p>雖然我最近都在寫 JAVA ，但我還是想撥空把前陣子寫 CakePHP 的心得和大家分享，而以下文章則針對 CakePHP 說明。</p>
<p>MVC 是 Model View Controller 的縮寫，這是一種設計模式，方便把頁面和商業邏輯分開來處理。而 CakePHP 則是使用 PHP 實做這種設計模式的一套 Framework 。在 CakePHP 中， Model 代表的是資料庫、 Controller 代表的是商業邏輯的判斷處理，View 代表的則是使用者呈現的頁面。另外 CakePHP 實做了一個很重要的觀念叫做 ORM (Object-Relational Mapping)，在 CakePHP 中 ORM 把每一個資料庫宣告成一個類別 (Class) ，接著我們在透過 Controller 來處理資料庫的資料，避免直接使用 SQL 語言進行查詢，增加程式的彈性和可維護性。</p>
<p>建議在看這篇文章之前，先把 <a href="http://www.ezluk.org/">CakePHP 使用手冊</a> 讀一遍，最好的狀況是能夠跟著教學實做一遍。因為跟著做過一次之後，會發現其實並不那麼困難，以下的例子是仿照 tutorial 寫成的，當然還有依據實際情況修改一些東西。我會陸續貼出來。<br />
<span id="more-1178"></span><br />
在學這套 Framework 之前，我們必須先大略瞭解 CakePHP 的目錄結構，在安裝之後，一開始我們會看到三個目錄：</p>
<pre>
/app       - 所有東西都放這邊！
/cake      - CakePHP 的函式庫，不用去動
/venders   - 3rd party 的東西
index.php  - 改寫網址用
.htaccess  - 改寫網址用，Apache 必須掛載 mod_rewrite
</pre>
<p>而這系列的文章，都會專注在 /app 的這個資料夾下面。</p>
<p>如果對 MVC 架構有興趣，請仔細拜讀這兩篇文章：</p>
<p><a href="http://osteele.com/archives/2004/08/web-mvc">Web MVC &#8211; Oliver Steele</a><br />
<a href="http://kiwi.csie.chu.edu.tw/blog/archives/39"> 進階 php 程式設計 介紹 MVC 與 Cakephp</a></p>
<p>而 CakePHP 的流程是這樣的：</p>
<p><img src="http://book.cakephp.org/img/typical-cake-request.gif" alt="" /></p>
<p>在 CakePHP 的環境中，程式對網址規則有嚴格的要求，例如：</p>
<p>http://example.com/posts/index/</p>
<p>就代表他用的 Controller 叫做 Posts_Controller ，而 index 則是 Controller 裡面的一個 method 。</p>
<p>http://example.com/posts/index/30</p>
<p>則表示把 30 這個參數用 GET Method 傳入 index 這個 method 裡面。</p>
<p>我實了一個 Valerossi.net 的網站，裡面放賽車手 Rossi 的自傳，我把實做的過程一步一步記錄下來和大家分享，也希望對 CakePHP 有興趣的人，可以不吝指教。</p>
<p>先修改連線設定檔，如果資料庫是 utf8 編碼，請最後加入鍵名 encoding 和鍵值 utf8：</p>
<pre class="brush: php; title: ; notranslate">
/* /app/config/database.php */
var $default = array('driver' =&gt; 'mysql',
                          'connect' =&gt; 'mysql_connect',
                          'host' =&gt; 'localhost',
                          'login' =&gt; 'NAME',
                          'password' =&gt; 'PASSWORD',
                          'database' =&gt; 'cake',
                          'prefix' =&gt; '',
                          'encoding' =&gt; 'utf8'); // 請注意這一行！
</pre>
<p>在 app/Model/post.php 裡面宣告一個類別，代表要操作的資料庫</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
class Post extends AppModel
{
    var $name = 'Post';
    // var $useTable = 'articles';  也可以這樣去指定資料表
}
?&gt;
</pre>
<p>這代表了新增一個 Model ，我們採用預設的命名規則，資料庫的名稱叫做 posts (複數)。當然，我們也可以不用預設命名規則，如上所示。</p>
<p>接下來建立 app/controllers/posts_controller.php 當作 Controller 。</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
	class PostsController extends AppController
	{
		var $name = 'Posts';
		function index()
		{
			$this-&gt;set('posts', $this-&gt;Post-&gt;findAll($conditions = null,
								$fields = &quot;&quot;,
								$order = 'ID ASC',
								$limit = null,
								$page = 1,
								$recursive = null));
		} // 這邊要依照 ID 排序，$condition 代表的是 SQL 的 WHERE 條件

		function view($id = null)
		{
			$this-&gt;Post-&gt;id = $id;
			$this-&gt;set('post', $this-&gt;Post-&gt;read());
		} // 這是閱讀單篇文章用的
}
?&gt;
</pre>
<p>Controller 裡面的$this->set() 就是用來把變數「傳遞」到 view 中，例如</p>
<pre class="brush: php; title: ; notranslate">
//在某個 Controller 裡面的 method
$hello = &quot;Hello World&quot;
$this-&gt;set('post', $hello);
</pre>
<p>這個動作可以把 Hello World 這個字串傳到 view ，變成 $post 變數，當在 view 中執行 echo $post 的時候，會印出 Hello World 。</p>
<p>至於 view 的寫法非常簡單，</p>
<pre class="brush: php; title: ; notranslate">
&lt;h1&gt;文章列表&lt;/h1&gt;
&lt;table&gt;
	&lt;tr&gt;
	&lt;th&gt;標題&lt;/th&gt;&lt;th&gt;時間&lt;/th&gt;&lt;th&gt;譯者&lt;/th&gt;
	&lt;/tr&gt;
	&lt;?php foreach ($posts as $post): ?&gt;
	&lt;tr&gt;
		&lt;td&gt;&lt;?php echo $html-&gt;link($post['Post']['post_title'], &quot;/posts/view/&quot;.$post['Post']['ID']); ?&gt;&lt;/td&gt;
		&lt;td&gt;&lt;?php echo $post['Post']['post_date']; ?&gt;&lt;/td&gt;
		&lt;td&gt;&lt;?php echo $post['Post']['post_author']; ?&gt;&lt;/td&gt;
		&lt;td&gt;&lt;/td&gt;
	&lt;/tr&gt;
	&lt;?php endforeach; ?&gt;
&lt;/table&gt;
</pre>
<p>這樣就是一個 view 。而 view 擺放的路徑則因 Controller 而定， view 的檔名則隨著 Controller 的 Method 而定，例如 Posts::index() 的 view 就是 /views/posts/index.thtml ，而 Posts::view() 的 view 則是 /view/posts/view.thtml。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/11/web-mvc-cakephp-%e6%95%99%e5%ad%b8%e5%af%a6%e5%81%9a-%e7%b0%a1%e4%bb%8b/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Magic Quotes 的問題</title>
		<link>http://blog.roga.tw/2008/11/magic-quotes-%e7%9a%84%e5%95%8f%e9%a1%8c/</link>
		<comments>http://blog.roga.tw/2008/11/magic-quotes-%e7%9a%84%e5%95%8f%e9%a1%8c/#comments</comments>
		<pubDate>Wed, 05 Nov 2008 05:58:26 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1135</guid>
		<description><![CDATA[以前不是 UTF8 編碼的時候，常常會有特殊字元(功蓋許)，或是 &#8216; 字元的問題，而 Magic quotes 可以幫你在遇到特殊字元的時候加上 \ 這個跳脫字元，避免特殊字元影響語法或是 SQL Injection。但在 UTF8 的環境中，這個功能卻不是那麼必要。只要在 DB Driver 處理的好，其實 Magic Quotes 在 PHP 內的設定可以關閉，避免語法判斷錯誤。 在新版的 PHP5 內，預設 Magic Quotes 是開啟的(我習慣用Debian)，要關起來的方法是從 php.ini 內設定 ; Magic quotes for incoming GET/POST/Cookie data. magic_quotes_gpc = On 請設定為 Off ，這影響到 $_POST, $_GET 傳入的資料。 另外 ; Magic quotes for runtime-generated data, e.g. data [...]]]></description>
			<content:encoded><![CDATA[<p>以前不是 UTF8 編碼的時候，常常會有特殊字元(功蓋許)，或是 &#8216; 字元的問題，而 Magic quotes 可以幫你在遇到特殊字元的時候加上 \ 這個跳脫字元，避免特殊字元影響語法或是 SQL Injection。但在 UTF8 的環境中，這個功能卻不是那麼必要。只要在 DB Driver 處理的好，其實 Magic Quotes 在 PHP 內的設定可以關閉，避免語法判斷錯誤。</p>
<p><span id="more-1135"></span></p>
<p>在新版的 PHP5 內，預設 Magic Quotes 是開啟的(我習慣用Debian)，要關起來的方法是從 php.ini 內設定</p>
<p>; Magic quotes for incoming GET/POST/Cookie data.<br />
magic_quotes_gpc = On</p>
<p>請設定為 Off ，這影響到 $_POST, $_GET 傳入的資料。</p>
<p>另外<br />
; Magic quotes for runtime-generated data, e.g. data from SQL, from exec(), etc.<br />
magic_quotes_runtime = Off</p>
<p>也請設定為 Off ，這會影響到 mysql_query() 和 exec() 的資料。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/11/magic-quotes-%e7%9a%84%e5%95%8f%e9%a1%8c/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>todo list&#8230;</title>
		<link>http://blog.roga.tw/2008/10/todo-list/</link>
		<comments>http://blog.roga.tw/2008/10/todo-list/#comments</comments>
		<pubDate>Tue, 28 Oct 2008 04:51:21 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=1081</guid>
		<description><![CDATA[這週末要做的事情： 1. 新增 SiteStates 的統計功能：訪客數目排行榜、網址所屬 gLTD/ccLTD 分類(regex)以及百分比分析、無效網址判別、維護介面。 2. 以 CakePHP 小型實做 &#8211; 2 (Guestbook, CSS layout) 3. 領薪水了，所以要組一台新的電腦 (？) 4. 好想養小貓陪我，但怕牠太無聊抓壞我的衣服和網路線。]]></description>
			<content:encoded><![CDATA[<p>這週末要做的事情：</p>
<p>1. 新增 SiteStates 的統計功能：訪客數目排行榜、網址所屬 gLTD/ccLTD 分類(regex)以及百分比分析、無效網址判別、維護介面。</p>
<p>2. 以 CakePHP 小型實做 &#8211; 2 (Guestbook, CSS layout)</p>
<p>3. 領薪水了，所以要組一台新的電腦 (？)</p>
<p>4. 好想養小貓陪我，但怕牠太無聊抓壞我的衣服和網路線。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2008/10/todo-list/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>SiteStates 免費計數器的問題與回應</title>
		<link>http://blog.roga.tw/2007/10/sitestates-v03-beta/</link>
		<comments>http://blog.roga.tw/2007/10/sitestates-v03-beta/#comments</comments>
		<pubDate>Sat, 06 Oct 2007 20:04:26 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=521</guid>
		<description><![CDATA[在發問之前，如果有空請先閱讀 使用方法 瞭解詳細的語法和功能，沒空的話，直接寫下您的問題，我有看到就會回答！ 本篇留言目前禁止回應，請移駕討論區]]></description>
			<content:encoded><![CDATA[<p>在發問之前，如果有空請先閱讀 <a href="http://sitestates.com/faq">使用方法</a> 瞭解詳細的語法和功能，沒空的話，直接寫下您的問題，我有看到就會回答！</p>
<p><strong>本篇留言目前禁止回應，請移駕<a href="http://sitestates.com/forum">討論區</a></strong></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2007/10/sitestates-v03-beta/feed/</wfw:commentRss>
		<slash:comments>157</slash:comments>
		</item>
		<item>
		<title>SiteStates &#8211; v0.2 beta</title>
		<link>http://blog.roga.tw/2007/09/sitestates-v02-beta/</link>
		<comments>http://blog.roga.tw/2007/09/sitestates-v02-beta/#comments</comments>
		<pubDate>Mon, 24 Sep 2007 02:12:24 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=502</guid>
		<description><![CDATA[前幾天我寫好了一隻程式叫做 SiteStates ，現在新增了一些功能(粗體字表示新增功能)，主要簡述如下： * 支援 PageRank 資訊，並可自訂是否顯示。 * 線上即時人數、當日瀏覽人數、累計總瀏覽人數 * 支援自訂邊框、背景、文字顏色 * 支援隱藏模式 * 支援近 30 日訪客人數紀錄 (大折線圖 580&#215;180 ,小折線圖 110&#215;30) * 支援以 JavsScript 語法列出在貴站的即時線上使用者的 IP * 支援簡易密碼重置和自訂訪客人數 * 紀錄最近的到訪者紀錄(IP &#038; 時間 &#038; DNS 反解) 自訂訪客人數的想法是來自於 PTT的 ritandy 和 mewmi 兩位板友，因為他們說： ritandy:「請問可以新增一個讓計數歸零的的功能嗎？」 mewmi:「另外, 我希望可以自己填&#8221;開始人數&#8221;&#8230;因為也許有人本來就有個total, 轉移到你的計數器使用&#8230;」 我覺得很有道理，所以就花了半個晚上寫好自訂人數以及密碼重設的功能。 另外，這篇文章的重點在下面，主要是討論實做遭遇到的問題和解決方法，如果覺得無聊可以跳過 1. Google PageRank 的 Cache 問題 眾所皆知，取得 PR [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://sitestates.com"><img src="http://gallery.roga.tw/d/29465-2/ss.png" alt="" /></a></p>
<p>前幾天我寫好了一隻程式叫做 SiteStates ，現在新增了一些功能(粗體字表示新增功能)，主要簡述如下：</p>
<p>* 支援 PageRank 資訊，<strong>並可自訂是否顯示。</strong><br />
* 線上即時人數、當日瀏覽人數、累計總瀏覽人數<br />
* 支援自訂邊框、背景、文字顏色<br />
* 支援隱藏模式<br />
* <strong>支援近 30 日訪客人數紀錄 (大折線圖 580&#215;180 ,小折線圖 110&#215;30)</strong><br />
* <strong>支援以 JavsScript 語法列出在貴站的即時線上使用者的 IP</strong><br />
* <strong>支援簡易密碼重置和自訂訪客人數</strong><br />
* 紀錄最近的到訪者紀錄(IP &#038; 時間 &#038; DNS 反解)</p>
<p>自訂訪客人數的想法是來自於 PTT的 ritandy 和 mewmi 兩位板友，因為他們說：</p>
<p>ritandy:「請問可以新增一個讓計數歸零的的功能嗎？」</p>
<p>mewmi:「另外, 我希望可以自己填&#8221;開始人數&#8221;&#8230;因為也許有人本來就有個total, 轉移到你的計數器使用&#8230;」</p>
<p>我覺得很有道理，所以就花了半個晚上寫好自訂人數以及密碼重設的功能。</p>
<p>另外，這篇文章的重點在下面，主要是討論實做遭遇到的問題和解決方法，如果覺得無聊可以跳過  <img src='http://blog.roga.tw/wp-includes/images/smilies/icon_smile.gif' alt=':smile:' class='wp-smiley' /> </p>
<p><b>1. Google PageRank 的 Cache 問題</b></p>
<p>眾所皆知，取得 PR 最快的方法就是用第三方 API 套在程式裡面直接去問 Google ，但是這個方法很糟糕，我實際測試過後，發現問一次大概要一秒左右，如果每更新一頁面都真的去問一次，那 Google 大概會第一個封鎖我吧。所以必須有 Cache 機制，還好 popstats 一併解決了這個問題，但不支倒是不是作者故意的，這隻程式另外也留下其他的問題，所以我只好手動 Hacking 一下。第一個問題是問不到 PR 回傳 FLASE 的時候，他不會把結果 Cache 起來，更要命的是他會把這件事情記錄下來，所以可想而知， log 檔肥大的速度相當之快。因為毎遇到一個 PR=0 的網站，馬上 log 比數 = 瀏覽次數 = 我的程式去打擾 Google 的次數。自然這樣效率就不會高，所以我把 Cache 的預設時間延長，並且改寫了一下，讓 PR=0 的時候一併寫入 Cache ，更新週期為 7 天(預設是 1 天, 86400秒) 。如此這個問題算是暫告一段落。</p>
<p><b>2. 儲存日期資料的問題</b></p>
<p>我之前有考慮過一陣子，要用 explode/implode 還是 serialize/unserialize ，最後決定聽從 whapup 兄的建議，用 serialize/unserialize ，因為這算是真正正規的作法，雖然轉成 String 的時候長度會更長一些，但這影響僅限於儲存和後端操作，對於使用者來說是沒有感覺的(因為看不到)。原本我想多加幾個欄位，或是把陣列設大一點，儲存多一些天數的訪客人次，但後來考慮到這個資訊似乎不是那麼必要 (畢竟用 Analytics 更完整不是嗎？)，所以就僅儲存近 30 日的紀錄，增加方式同 MRTG 的 Grow Right ，因為我覺得這樣流動的圖表，很漂亮。</p>
<p><b>3. 重設密碼的問題</b></p>
<p>由於我想把系統註冊儘量簡化 (其實最理想是用 OpenID ，之前有提過了) ，可是我遭遇到一些問題，最大的困難在於字串的取得和判斷，後來我想出一個方法，就是讓使用者修改網頁標題，在其中加入我雜湊(HASH)出來的一串數值，這樣我在用程式去讀取他的頁面，以利判斷是否為網站所有人修改密碼。但這衍生出了兩個問題，第一個是正規表示法(Regex)來頗析字串的問題，第二個是利用 file_get_contents() 函數的問題。</p>
<p>第一個問題我就不說了，書上寫很多，資源很豐富，我自己也找很久 XD<br />
第二個是因為 file_get_contents() 必須透過 php.ini 開啟選項(儘管預設似乎是開的)，但我覺得不太喜歡這樣，可是又在想用 fopen() 和 file_get_contents() 哪個會比較好，雖然兩者的速度可能僅在伯仲之間。</p>
<p>我在 PHP 問了個問題：</p>
<pre>
$encode_string = '&lt;title&gt;' . $encode_string;
                  ^^^^^^^ 加上這個是為了防止被插其他地方
$long_string = file_get_contents(urldecode($url));
if(eregi($match_string ,$long_string)) { 開始做事 }

這似乎有危險，如果有人留下含有 $encode_string 的 comment
這樣或許可能會出現問題，儘管機率不高。
</pre>
<p>結果以前中正的 dinos 學長幫我回答了，他採用 fopen 的解法，比較漂亮的解法是不用 retrive 整個 content 回來，只要讀到了第一次 match 到正規表示法的地方迴圈就會 break ，然後進行更進一步的處理：</p>
<pre>
function getRemoteTitle($url){
  $fr=@fopen($url,"r");
  if(!$fr)return false;
  $content='';
  while(($line=fgets($fr))!=false){
    content.=trim($line);
    if(preg_match('/&lt;title\s*&gt;[^&lt;]*&lt;\/title&gt;/i',$line) ||
      strstr(strtolower($line),'&lt;/title&gt;')){
      break;
    }
  }
  fclose($fr);
  preg_match('/&lt;title\s*&gt;[^&lt;]*&lt;\/title&gt;/i',$content,$matches);
  if(isset($matches[0][1]))return $matches[0][1];
  else return false;
}
</pre>
<p>這樣可以減少傳輸的位元數，更快的取得結果，而且第一次 Match 到的一定是標題(當然，用 file_get_contents() retrive 回來的整份文件中，可能包含不只一次 HASH 的字串(在其他地方被惡意插入)。所以在 preg_match() 的時候，只要判斷第一個結果就好(array[0][1]) ，但這樣始終回傳太多資料了，我覺得不需要比對這麼多資料。</p>
<p><b>4. 自訂人數的問題：</b></p>
<p>在自訂人數上倒是還好，就是把資料表的欄位重新 UPDATE 即可，我另外寫了一個小函數專於做 Error Handler ，並且 echo 出錯誤訊息，登入錯誤、 Session 錯誤、認證碼錯誤..等等。</p>
<p><b>5. 折線圖的問題：</b></p>
<p>在用程式繪製折線圖上，其實我之前就做過類似的嘗試，也畫出來了，但是困難的地方在於分配欄位以及配置，這也是花我最多時間的地方，最後我決定畫兩張折線圖，大張的可以看出詳細資訊，小張的則是像 Site Meter 的小圖那樣，可以看個大概(不過即時產生小張圖片程式的我還沒寫好)。</p>
<p><b>6. 系統效率與記憶體的問題：</b></p>
<p>我用 free 看了系統的記憶體使用量，發現 Debian Linux 並不會把所有的記憶體都 Buffer 起來，在系統總共 2GB 的記憶體中，實際使用約 100MB ，然後 Buffer 大概 1GB 左右，還有 1GB 左右的記憶體閒置。至於 SWAP 使用率一直都在 0% 。</p>
<p><b>7. 流量的問題：</b></p>
<p>經過觀察，發現流量大約平均在 10KB 左右，這算是意料中的事情，因為動態生成一張圖片大概也才 1KB 多而已，所以在人少的時候流量還不至於太高影響到我平時的網路速度。</p>
<p><b>8. Apache2 效能的問題：</b></p>
<p>之前我搞不太清楚 MaxKeepAliveRequests 和 MaxClients 的關係，以及 MinSpareServers 和 MaxSpareServers 的關係，所以在效能的調校上一直無法動手，另外我也想知道有沒有拒絕 DDoS 的 module 。這方面 dinos 學長也一併解決了我的問題。</p>
<blockquote><p>
MaxClients 是伺服器啟動時要啟動多少個 httpd 來等待連接，在 apache2 預設值裡，每多一個大約會多使用 20~40MB ，對於同時連線人數越多的站是開越多越好。</p>
<p>MaxKeepAliveRequests 只有在 KeepAlive On 時才有效， KeepAlive 是說在一個 httpd 每處理完一個 MaxRequestsPerChild 後要不要繼續等待下一個子請求，所以 MaxKeepAliveRequests 就是指定每個 httpd 在等待期間可以處理多少個子請求。</p>
<p>MinSpareServers 和 MaxSpareServers 這兩個是指備用的 httpd 數量，也就是除了被用掉的以外還要開啟多少個。當然一定會影響效能囉，對於載入多個 mod 的 Apache httpd 而言，每要啟動一個 httpd 是要花上很多時間(約莫 3~8 seconds)，所以設置一些備用的減少當使用者連結上時需要等待 httpd 啟動的時間</p>
<p>另外對於 DDoS 的阻斷服務攻擊，則是可以用 mod_evasive 來嘗試保護系統。
</p></blockquote>
<p>這次的問題檢討大致是以上幾點。</p>
<p>refer:<br />
1. <a href="http://sitestates.com">SiteStates 網站</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2007/09/sitestates-v02-beta/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>簡述 SiteStates 開發規劃</title>
		<link>http://blog.roga.tw/2007/09/sitestates-%e8%a8%88%e6%95%b8%e5%99%a8-%e6%8e%a2%e6%b8%ac%e5%99%a8-%e9%96%93%e8%ab%9c-%e5%b0%8f%e7%8e%a9%e5%85%b7-%e8%a8%aa%e5%ae%a2%e4%ba%ba%e6%95%b8/</link>
		<comments>http://blog.roga.tw/2007/09/sitestates-%e8%a8%88%e6%95%b8%e5%99%a8-%e6%8e%a2%e6%b8%ac%e5%99%a8-%e9%96%93%e8%ab%9c-%e5%b0%8f%e7%8e%a9%e5%85%b7-%e8%a8%aa%e5%ae%a2%e4%ba%ba%e6%95%b8/#comments</comments>
		<pubDate>Sat, 15 Sep 2007 18:59:53 +0000</pubDate>
		<dc:creator>roga</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.roga.tw/?p=497</guid>
		<description><![CDATA[我最近寫了一隻小程式，原本是今年學期末就要寫好的，因為早就答應人家了，不過我暑假(暑修)實在太忙加上想說既然要寫，就寫的完整一點，所以拖到現在才完成這隻程式。 SiteStates 是個 多功能 簡單的計數器, 也可以稱為探測器 或是 間諜，程式部份大概幾百行而已，所以只能算是小玩具，它主要是可以統計 訪客人數 和 線上人數 以及顯示 到訪紀錄 其實要寫一個會動的訪客記錄器，對一般會寫的人來說，大概是一小時或是幾十分鐘的事情。因為寫出這類的東西並不困難，但是週邊一堆困擾的事情，倒是很麻煩，從介面的修改，資料庫的規劃(和以後的可擴充性)..等等。 不過在寫這隻程式的時候，我還是遇到了一些瓶頸，主要是牽扯到效能的問題，這邊和大家分享一下。 取得同時在線人數的方法？ 建立一個 table ，裡面設定三個欄位， id 、 ip 以及 lastvisit 。id 是使用者的代號，ip 則是訪客來源， lastvisit 則是 unix 的 timestamp 。如此遇到訪客的時候，先檢查有沒有存在，如果沒有，就 INSERT 一筆資料進去，如果存在，就把原本的那筆資料 UPDATE ，然後每次跑這個 script ，都先把 timestamp 過期的 records 刪除。 我曾經在 PTT 的 PHP 板提出作法如下 建立一個 table 叫做 online 然後以下欄位： serial - [...]]]></description>
			<content:encoded><![CDATA[<p>我最近寫了一隻小程式，原本是今年學期末就要寫好的，因為早就答應人家了，不過我暑假(暑修)實在太忙加上想說既然要寫，就寫的完整一點，所以拖到現在才完成這隻程式。</p>
<p><a href="http://sitestates.com/">SiteStates</a> 是個 <del datetime="2007-09-15T18:33:38+00:00">多功能</del> 簡單的<a href="http://sitestates.com">計數器</a>, 也可以稱為<a href="http://sitestates.com">探測器</a> 或是 <a href="http://sitestates.com">間諜</a>，程式部份大概幾百行而已，所以只能算是<a href="http://sitestates.com">小玩具</a>，它主要是可以統計 <a href="http://sitestates.com">訪客人數</a> 和 <a href="http://sitestates.com">線上人數</a> 以及顯示 <a href="http://sitestates.com">到訪紀錄</a></p>
<p>其實要寫一個會動的訪客記錄器，對一般會寫的人來說，大概是一小時或是幾十分鐘的事情。因為寫出這類的東西並不困難，但是週邊一堆困擾的事情，倒是很麻煩，從介面的修改，資料庫的規劃(和以後的可擴充性)..等等。</p>
<p><span id="more-497"></span></p>
<p>不過在寫這隻程式的時候，我還是遇到了一些瓶頸，主要是牽扯到效能的問題，這邊和大家分享一下。</p>
<p><strong>取得同時在線人數的方法？</strong><br />
建立一個 table ，裡面設定三個欄位， id 、 ip 以及 lastvisit 。id 是使用者的代號，ip 則是訪客來源， lastvisit 則是 unix 的 timestamp 。如此遇到訪客的時候，先檢查有沒有存在，如果沒有，就 INSERT 一筆資料進去，如果存在，就把原本的那筆資料 UPDATE  ，然後每次跑這個 script ，都先把 timestamp 過期的 records 刪除。</p>
<p><strong>我曾經在 PTT 的 PHP 板提出作法如下</strong><br />
<code><br />
建立一個 table 叫做 online 然後以下欄位：</p>
<p>serial    - 序號.自動增加<br />
id        - 屬於哪個站台的紀錄<br />
ip        - 進入的使用者來源<br />
lastvisit - 進入時間</p>
<p>首先刪除所有逾時的人 (假設 300 秒逾時)<br />
$sql = "DELETE FROM online WHERE unix_timestamp() - lastvisit >= 300 AND id = '$id'";</p>
<p>使用者是否造訪過<br />
$sql = "SELECT id FROM online WHERE ip = '$ip' AND id = '$id'";</p>
<p>計算 mysql_query($sql) 是為 0</p>
<p>如果是 0 的話，插入一筆新的資料：<br />
$sql = "INSERT INTO online (id, ip, lastvisit) VALUES ('$id','$ip',unix_timestamp())";</p>
<p>如果不是 0 的話，把 time stamp 延後<br />
$sql = "UPDATE users_online SET lastvisit = unix_timestamp() WHERE id = '$id' AND ip = '$ip'";</p>
<p>最後計算該站台有多少人<br />
$sql = "SELECT count(id) FROM online WHERE id='$id'";<br />
</code><br />
<strong>另外有位 allanshen 兄，回覆了一個更好的作法：</strong><br />
<code><br />
建立一個 table - online 有3個欄位 id、ip、lastvisit，id 跟 ip 合併設定為 unique key<br />
刪除所有逾時的人跟您的方式一樣<br />
然後使用者到訪時用<br />
$sql = "REPLACE INTO online (id,ip,lastvisit) VALUES ('$id','$ip',unix_timestamp())";</p>
<p>計算人數一樣用 $sql = "select count(id) FROM online where id='$id'";</p>
<p>這樣跟您原來的方式省了一次 select<br />
<b>儘在此再次謝謝他的意見。</b><br />
<code></p>
<p><strong>取得 PageRank 的方法？</strong><br />
Google Code 裡面有一個叫做 <a href="http://code.google.com/p/popstats/">Popstats</a> 的函式庫，目前是可以動的，根據 <a href="http://blog.wildcat.tw">Wildcat</a> 哥的可靠消息， <a href="http://www.hotscripts.com/Detailed/37856.html">PHP Google PageRank Calculator</a> 已經無法運作了。</p>
<p><strong>到訪紀錄的 Maintain？</strong><br />
這是我最頭痛的事情，不過還好也是迎刃而解。原本我想了複雜的方法，但是 wildcat 哥和我說了個比較簡單的作法：<br />
</code><br />
<b>wildcat 說：</b></p>
<p>喔，我會建議你把來源用 text or blob 存，反正一百筆大概也才 15x100 = 1600 , + 100(分隔號) = 1600 bytes，這種大小的字串給 php 處理其實非常非常迅速…(大概就長這樣 192.168.100.100|192.168.200.200|.... ) 要讀出來的時候再把它 split or explode 就好了。</p></blockquote>
<p><strong>實際作法大致如下：</strong></p>
<pre>
/*
建立一個叫做 users 的 table ：
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL auto_increment,
  `url` text NOT NULL,
  `password` varchar(32) NOT NULL,
  `from_ip` text,
  UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
*/

function add_ip_to_user($id, $ip) /* 新增一筆 ip 紀錄 */
{
    require('database.php');
    $sql = "SELECT from_ip FROM users WHERE id = $id";
    $result = lab3_query($sql);
    list($from_ip) = mysql_fetch_row($result);

    $array = array();
    $array = unserialize($from_ip);

    $size  = count($array);

    if($from_ip == '')
    {
        $array[0][0] = gethostbyaddr($ip);
        $array[0][1] = date('Y-m-d H:i:s');
        $array[0][2] = $ip;
    }
    else
    {
        $array[$size][0] = gethostbyaddr($ip);
        $array[$size][1] = date('Y-m-d H:i:s');
        $array[$size][2] = $ip;
    }

    if($size >= 500) /* 最多幾筆 */
        array_shift($array);

    $from_ip = serialize($array);

    $sql = "UPDATE users SET from_ip = '$from_ip' WHERE id = $id";
    lab3_query($sql);
}
</pre>
<p>用 serialize()、unserialize() 或是 implode()、 explode() 來搭配存取 from_ip 這個 text 欄位。以上大概是比較需要討論的地方，其他需要具備的條件就是對於繪圖函式的使用、陣列處理、以及一般字串處理和網路函式(像是取得 IP 和 DNS 反解)的應用，另外 CSS 和 HTML 的技術則是基本必須具備的，這邊著重於 PHP 和資料庫處理的部份，所以使用者介面的部份我就不提了。</p>
<p>如果有建議或是問題也很歡迎在此提出，非常感謝！ <img src='http://blog.roga.tw/wp-includes/images/smilies/icon_surprised.gif' alt=':eek:' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.roga.tw/2007/09/sitestates-%e8%a8%88%e6%95%b8%e5%99%a8-%e6%8e%a2%e6%b8%ac%e5%99%a8-%e9%96%93%e8%ab%9c-%e5%b0%8f%e7%8e%a9%e5%85%b7-%e8%a8%aa%e5%ae%a2%e4%ba%ba%e6%95%b8/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>

