2018年6月9日 星期六

裝 Gentoo,可以學到什麼呢

最近在教小孩安裝 Gentoo,有時真的被被問的有點煩,可是也從中看到過去自己學習安裝的情形。

首先,在安裝時,就和其他作法不同。它只提供一個最簡單的開機 image,或是久久才出一次的 Live DVD。甚至可以用其他系統,如 Ubuntu,來安裝。

更深入的瞭解 Linux的架構細節,理念

開機
相依性
系統的細節
選擇 Service 管理
出現問題,該重裝嗎?

2018年5月28日 星期一

惱人的 javascript context 和 this

寫 Javascript 的程式,被它的 this 搞得實在惱火,寫個小程式來測一下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
測試 javascript this 所指的 context
</body>
<script type="text/javascript">
    var tmodel = (function ()
    {
        var myself;

        var set_myself = function() {
            myself = this;
        };

        var self;
        var v1 = 'tmodel v1';

        var tst = {
            self: this,
            fun1: function () {
                console.log('tst.fun1()');
                console.log(this);
                console.log(this.self);
                if (typeof v1 !== "undefined") {
                    console.log(v1);
                } else {
                    console.log('v1 is undefined!');
                }
            }
        };

        var fun2 = function () {
            console.log('tmodel.fun2()');
            console.log(this);
            console.log(myself);
            console.log(tst.self);
            if (typeof v1 !== "undefined") {
                console.log(v1);
            } else {
                console.log('v1 is undefined!');
            }

            tst.fun1();
        };

        var fun3 = function () {
            self = this;
            var v1 = 'fun3 var';
            console.log('tmodel.fun3()');
            console.log(this);
            console.log(tst.self);
            if (typeof v1 !== "undefined") {
                console.log(v1);
            } else {
                console.log('v1 is undefined!');
            }
            fun2();
        };

        /*
         * fun1.this => tst
         * tst.this => Window,這個不會改變
         * tmodel.v1
         */
        tst.fun1();

        return {
            set_myself: set_myself,
            fun2: fun2,
            fun3: fun3
        }
    })();

    tmodel.set_myself();

    /*
     * fun2.this => tmodel
     * 在執行 tst.fun1 中
     * fun1.this => tst
     */
    tmodel.fun2();

    /*
     * fun3.this => tmodel
     * fun3.v1 => local variable
     * 在執行 fun2 中
     * fun2.this => Window
     * 可直接存取 tmodel.v1
     * 無法存取 fun3.v1
     * 在執行 tst.fun1 中
     * fun1.this => tst
     * 可直接存取 tmodel.v1
     * 無法存取 fun3.v1
     */
    tmodel.fun3();

    console.log('tmodel');
    console.log(tmodel);

</script>
</html>

js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:36 tmodel.fun2()
js_this_tst.php:37 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:38 undefined
js_this_tst.php:39 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:41 tmodel v1
js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:52 tmodel.fun3()
js_this_tst.php:53 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:54 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:56 fun3 var
js_this_tst.php:36 tmodel.fun2()
js_this_tst.php:37 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:38 undefined
js_this_tst.php:39 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:41 tmodel v1
js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:74 內部執行 BEGIN 
js_this_tst.php:75 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:36 tmodel.fun2()
js_this_tst.php:37 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:38 undefined
js_this_tst.php:39 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:41 tmodel v1
js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:52 tmodel.fun3()
js_this_tst.php:53 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:54 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:56 fun3 var
js_this_tst.php:36 tmodel.fun2()
js_this_tst.php:37 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:38 undefined
js_this_tst.php:39 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:41 tmodel v1
js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:78 -- 內部執行 END --
js_this_tst.php:36 tmodel.fun2()
js_this_tst.php:37 {set_myself: ƒ, fun2: ƒ, fun3: ƒ}
js_this_tst.php:38 {set_myself: ƒ, fun2: ƒ, fun3: ƒ}
js_this_tst.php:39 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:41 tmodel v1
js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:52 tmodel.fun3()
js_this_tst.php:53 {set_myself: ƒ, fun2: ƒ, fun3: ƒ}
js_this_tst.php:54 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:56 fun3 var
js_this_tst.php:36 tmodel.fun2()
js_this_tst.php:37 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:38 {set_myself: ƒ, fun2: ƒ, fun3: ƒ}
js_this_tst.php:39 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:41 tmodel v1
js_this_tst.php:24 tst.fun1()
js_this_tst.php:25 {self: Window, fun1: ƒ}
js_this_tst.php:26 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
js_this_tst.php:28 tmodel v1
js_this_tst.php:111 tmodel
js_this_tst.php:112 {set_myself: ƒ, fun2: ƒ, fun3: ƒ}

最需要特別注意的是,在 fun3 中呼叫 fun2,fun2 的 this 指到 Window。

為了在 tmodel 中,能夠存取自身的 context,用一個 myself 的變數來存放自身的 context。但是因為在被呼叫之前,此 object 尚未建立,因此無裡面指定 myself,只能在它的外面執行。可能有更好的解決辦法,但我目前只能找到此方法。

這是為了解決 KnockoutJS 的 computed 變數的問題。



2018年5月19日 星期六

自訂 PHPstorm 的 BrowserRefresh 功能

If You are developing a PHP web site using PhpStorm in this approach currently, i.e. save the changes, and then switch to the browser and refresh the page, here is a quicker way for you. You can press a key-combination, Ctrl+Shift+R, for example, to save the change and refresh the browser.

雖然 PHPstorm 有個 LiveEdit,好像很強大,但是不會用。就參考先前用過的 Sublime 的 BrowserRefresh 功能,想辦法把它搬到 PHPstorm 來用。

我的作法,主要是參考 Sublime Text 的 BrowserRefresh 功能,在 Linux 下,使用 xdotool 來送出瀏覽器的 refresh 指令。在 Sublime Text 下,要有程式底子,才能看懂它在做什麼。但是在 PHPstorm,就簡單多了,只要建好 script 檔,確定功能正常,然後點一點滑鼠就 OK 了。

先建立可透過 xdotool 送出 refresh 指令的 script,並測試確定功能正常。

在 [Settings] > [Tools] > [External Tools] 下,新增 BrowserReferesh,並且,在 Program 欄位,填上 script 的 full path。

然後,在  [Settings] > [Keymap] > [External Tools] > [External Tools] 下,指定快捷鍵,例如,我是指定 Ctrl+Shift+R。雖然,這個已被使用,但幾乎我要用的組合,都被用了,也就不管,用自己習慣的吧。



BrowserReferesh 的程式碼
(chromium-refresh)
#!/bin/bash
xdotool search --sync --onlyvisible --class 'chromium' windowfocus key 'F5'  windowactivate


(firefox-refresh)
#!/bin/bash
xdotool search --sync --onlyvisible --class 'firefox' windowfocus key 'F5'  windowactivatet 

對於 Chrome,有些困擾,會更新到 LINE 的擴充功能,就 chromium 了。因為 Chrome 和 Chromium 才有 Knockout JS 的 extension 可用。而 Firefox 改版之後,一些舊的 extension 都不能用了,另外,在我的 Linux 下,可能是設定的關係,版型變得怪怪的,要改網頁都抓不準,也是促成改用 chromium 的原因。

嗯,自從改用 PHPstorm 寫 PHP 程式,再也回不去了,現在根本就不想用 Sublime Text 了。

2018年4月17日 星期二

ThinkPad USB 小紅點鍵盤更換

更換步驟摘要

  • 拆掉螺絲,有兩個在標籤下
  • 用拆手機的撬棒,撬開卡筍
  • 拆掉新的鍵盤上方的蓋子,電源鍵要拔掉,不然會卡住
  • 拆下USB模組,鎖到新的鍵盤上
  • 鍵盤模組與下列筆電相容 ThinkPad T400S T410 T410S T410i T420 T420S T420i T510 T510i W510 W520 X220 X220S X220T
另外,鍵盤貼紙使用黑底白字,例如 Fujiei PQ0133 霧面黑底白字電腦鍵盤貼紙


聯想將先前的外接 ThinkPad USB 小紅點鍵盤 (ThinkPad USB Keyboard with TrackPoint) 改款了,先前不小心買到新款的,發覺真的是太難用了。只好上網找舊版的,運氣還不錯,還有存貨,買到一個。舊款的型號為 SK-8855,FRU 55Y9010,或繁體中文的 FRU 為 55Y9060。
有了新的之後,就可以很放心的把舊的拿來解剖,看看能不能找個筆電的鍵盤來更換。

總共有 8 顆螺絲,位置如紅圈所標示的位置,很好拆,其中有兩顆在標籤下。標籤破壞之後,就失去保固,我想會想要拆鍵盤的人,應該也過保固了。

8 顆螺絲拆下後,還是打不開來的。但也別再亂找了,懷疑在標籤下,或在塑膠墊下,還藏有螺絲。別找了,塑膠墊撕下後,雙面膠粘性就變差了。

如紅圈所標示的位置,是有卡筍的地方,可以找兩支小的一字起子,有耐心點,慢慢翹。若是翹斷了,外殼合不起來,也就廢了。

不過多年之後,偶然知道有拆卡筍的翘片,只要用翘片沿着邊框走一圈,上下殼就分開来了。可是自己沒有,就試著用厚一點的保特瓶,剪2片來用。順著邊框塞進去移動一下,卡筍就分開來了,還真方便。自製的翹片,有時用起來不是很順,再加個一字小起子撐一下,也也很就拆開,也不怕弄壞卡筍。

後記 (2021-11-30):專業的作法,用拆手機的翘棒,可以很方便的翘開卡筍,尤其淘寶可以找到金屬翘棒,非常好用。



接下來,就是按圖索驥,到網拍上,找接腳和按鍵長得一模一樣的筆電鍵盤來換。其實也不用啦,因為已經有人研究過了,只要是給 ThinkPad T400S 就可以了。

請參考 Inside ThinkPad USB Keyboard with TrackPoint,這一款鍵盤也可用在下列這些筆電,ThinkPad T400S T410 T410S T410i T420 T420S T420i T510 T510i W510 W520 X220 X220S X220T。其為控制桿為第四代 (TrackPoint IV)。

雖然如此,買的時候,還是再確認一下,確定正面和背面都長得一樣,再下單購買會比較安心。有很多賣家,都說他的是正廠的,也有的說,不用管,有很多家在代工做鍵盤,都是一樣的。是的,聽說 ThinkPad 的鍵盤都是群光 (Chicony) 代工的,可能也有 NMB 和 ALPS 的吧。可以參考「看看你們筆電的鍵盤是誰家OEM的」

(http://www.mobile01.com/topicdetail.php?f=240&t=543557)

另外,外接 USB 鍵盤多了一個 USB 模組,用的是光寶 (Lite-On) 的晶片),如紅圈所標示,用一字起子在排線接頭的下面,左右,左右,慢慢的頂,就可以把接頭拆下。再用小一號的十字起子,注意,不是最小的,拆下 USB 模組。


鍵盤控制電路和USB傳輸電路板。

新的鍵盤模組上,會有兩個凸出的螺絲座,一個用來鎖 USB 模組,另一個沒用,就留著吧,在塑膠外殼有為它留位置了。


要記得把那個頂端的藍色按鍵的蓋子拆下,不然會卡住。常忘了拆,裝上後才發現又卡住,又要重拆一次。

和普通鍵盤的差別在於,紅圈旁邊那一塊鐵,裡面就 Trackpoint 控制板,是 IBM 在 1996 年取得的美國專利。不過,IBM 這種大公司,真的不在乎賺這種小錢,想買這種鍵盤,還真不容易。雖然,所有的東西都是台灣和大陸做的,IBM只負責收錢。

真希望別家也能做有 trackpoint 的鍵盤,像要是 Cherry 鍵盤配上 Trackpoint,那會多棒啊。等 2016 年之後,專利期過了,會有人做嗎?


來一張連接線的照片,鍵盤模組只是一堆開關,沒有電路。只有指桿控制器有電路,送出十條左右的控制線,合併到那一堆密密麻麻的連線。


筆電鍵盤基本知識 (2016-02-05 摘自某網拍說明,供大家參考)
※ 筆電鍵盤總的來說分為新的、舊的、和翻新的三大類,而這三大類裏又分各個文字版本,比如英文、韓文、日文、歐文、及繁中等等,很多買家會有一些誤區:
一:關於中文注音問題:
很多賣場都說自己的鍵盤是原裝中文注音,但事實是市面上原裝出產的繁中並不多,只有幾個型號。因為繁中的需求量並不是非常的大,所以工廠沒必要花高成本去製作,而那些賣場所說的中文注音都是後期鐳射列印上去的。有些賣場宣稱使用的精密的鐳射列印,效果接近原廠,加工成本每片接近 200 元,或強調其為一手貨源所以比其他賣場型號更全,能以比其他賣場更低的價格提供給顧客更好的產品。
有些型號的機器,因為工廠的鍵盤庫存已經沒有此型號的存貨了,而市場上此型號的 US 鍵盤也基本買不到,那麼迫不得已之下只好買其他文字版本的,即國外對應機器用的鍵盤,如韓文,日文,歐文等等。
二,關於新舊,原裝非原裝問題:
鍵盤只有新舊之分,沒有原裝和非原裝之分,筆電鍵盤因為開模成本高,而且一個模具只能生產一個型號的鍵盤,所以現在還沒有出現 OEM 鍵盤.鍵盤工廠也不是只有一家,鍵盤基本上由精元,達方,群光,等鍵盤代工廠生產。所以買鍵盤,不能因為買來的鍵盤有一點點小細節和原裝機器的不一樣 (比如原裝機器鍵盤反面有錫紙,買來的沒有),就說買的鍵盤是 OEM,這是錯誤的。生產廠家不一樣,某些小細節方面也會不一樣的。說不是原裝還有另外一種情況,即買的鍵盤是代用的,代用和原裝的區別就是功能鍵的圖示印刷,鍵盤反面的螺絲柱位置等可能有區別。換句話說,就是拿這個機器的原裝鍵盤裝在另外一個型號的機器上。

小紅點加長

小紅點的霝敏度和加速,必需要設定,不然很難推。另外,把小紅點加長,也會好推一點。之前是用鐵衣架的塑膠帽來延伸,效果不是很好,但在找不到好的方法前,就這樣用了好久。後來買了一堆小紅帽,就想辦法用快乾膠把兩個小紅帽黏在一起,效果還不錯。


使用心得 2018-04-18

雖說沒有原裝與非原裝之後,但使用之後,覺得號稱原廠 3 色印刷的鍵盤,觸感比較好,也比較耐用。搜尋網上的討論,好不好用,好像是要看是那一家產的。不過,ThinkPad 落入中國的手中,品質真的是越來越爛,好壞只能看運氣。最近,有一個是滑鼠的中間的按鍵,卡筍斷了,只好再找一個來換。

後來買了宣稱「最好打的NMB 7列中文鍵盤」,試試看用起來如何。可惜鍵帽和其他的不合,雷射中文輸入又很不清楚,只好用貼紙。但貼紙又很難看,久了還會溢膠。可是,打久了,手感真的有那麼一點差別。

各家號稱全新鍵盤,價格也差很多,從台幣 1,400 到 2,500 都有。現在,變成鍵盤買得到,但外殼搶手,淘寶有不少人再徵舊的 USB 外接 Thinkpad 鍵盤。


七列鍵盤停產,2019-03-15

使用所謂的「最好打的NMB 7列中文鍵盤」將近一年,感覺還不錯,但是網拍的賣家說,七列鍵盤已停產,沒有貨了,只好作罷。真的是,有錢也買不到啊。


2018年4月5日 星期四

在中國,使用 shadowsocks 連上 Facebook 和 Line

偶爾去中國玩,不能連上平常習慣使用的 Facebook 和 Line,實在很不方便。第一次去玩時,在當地和團員學會了使用 VPN 使用 Facebook 和 Line,但不方便,也不穩,最近好像又被封鎖了不少。

這次出去前,原來想辦中華電信的漫遊,可惜預付卡不能辦,只好另想辦法。看到有人介紹 shadow-socks (ss),中文叫影梭,好像不錯。

在網路上找到 docker 的 image,目前有在維護的是 shadowsocks/shadowsocks-libev。就直接建個 docker-compose.yml,直接執行,很簡單,內容如下。
---------------------------------------
shadowsocks:
  image: shadowsocks/shadowsocks-libev
  ports:
    - "443:8388"
  environment:
    - PASSWORD=xxyyzz2388
  restart: always
---------------------------------------
使用的是辦公室的網站,網站只用 80 port,去中國玩的時候,就暫時把 443 port 拿來當跳板用,這樣不用費心地在防火牆打洞。

手機裝的是 Shadowocks client for Android 4.5.3。雖然在手機測試,可以連接,但還是不太確定只開 tcp port 能不能用,只能到實地再測試了。

到了張家界,連上 wifi,開啟影梭,連接一直有問題。在測試時,有時會出現 "無法解析 google.com" 的訊息。改變 "遠程 DNS" 為可以直接連上的辦公室的 DNS SERVER,仍來不行。後來,看到有設定 "個別應用程式的 VPN",就把它打開,並設定 "Chrome 瀏覽器",然後測試連線能力就成功了。再設定 LINE 和 Facebook,都可以順利的連上。

在旅程的其餘的日子裡,只要透過導遊的分享器上網,就能自在的在 FB 打卡,和自己的朋友互動,整個旅程也就變得有趣多了。還有一些的遺憾,大概就是無法幫同團的朋友裝 Shadowocks client,無法讓他們也能用 LINE。

2018年3月22日 星期四

使用 Knockout.js 與 Vue.js 的抉擇

程式開發,對於新技術,總是會猶豫著要不要在自己的專案中使用。新技術總能帶來一些好處,但又怕要改很多,或者馬上就被淘汰。

記得在第一次看到 Codeigniter 的介紹時,心動

Knockout.js,很早就出現了,但我是到最近才接觸。在聽到它的名字之前,已經聽過 AngularJS,然後是最新出現的 Vue.js,最後,才知道 Knockout.js。也就是說,我知道 Knockout.js 時,基本上,它已被判死刑。

2018年3月7日 星期三

PHP 連接 MS SQL Server 2000 的考量

Microsoft SQL Server 2000,雖然已有將近 20 年,但使用起來很方便,尤其現今電腦硬體強大,跑起來,更是非常有效率。

但是,使用 PHP,微軟提供的官方版本,只支援連接 SQL Server 2005 以上的版本。有人重新編譯,將限制拿掉,雖然可以連接 SQL Server 2000,但是使用上會有問題。

例如 $rs = DB::select("SELECT * FROM ifcrfcotmp where id=:p1",  ['p1' => '']);
會出現 Invalid precision value 的錯誤。使用 Query builer 也有同樣的問題,ORM 我沒有用,所以沒測過。
這種問題,使用得在應用上,很不方便。因為使用 parameter 或 Query builer,可以處理特殊字元,如單引號,尤其可以避免 sql injection 的攻擊。

在 Linux 下面,PHP 7.0 以上的版本,可以使用 dblib 來連接,也不會有上述問題。

在 Windows 的平台,Laravel 5.2 之後,可以透過 ODBC 來連接 SQL Server 2000,但是無法傳回 UTF-8 編碼的字串,必須轉換。

現在辦公室,還在大量的使用 Windows Server 2003,只能跑 PHP 5.4,也就只能用 Laravel 5.0。Laravel 5.2 中,連接 ODBC 在單一的程式中,其在 Laravel 5.0 也可以使用,可在 composer.json 中,"scripts" 的 "post-update-cmd" 部分,加上下述命令,在 update 之後,以 5.2 的程式覆蓋。
"cp SqlServerConnector_52.php vendor/illuminate/database/Connectors/SqlServerConnector.php"

Laravel 的 database 設定檔,內容如下
'connections' => [
    'odbc' => [
        'driver'   => 'sqlsrv',
        'database' => '',  // 需保留,可空白
        'username' => 'uid',
        'password' => 'pwd',
        'charset'  => 'utf8',  // 這設定是沒用的
        'prefix'   => '',
        'odbc' => true,  // Tell the driver to use ODBC connection
        'odbc_datasource_name' => 'Driver={SQL Server};Server=db_host;Database=db_name;',  // Only the name, don't include 'odbc:' prefix
        ],
],

因此,難以抉擇的是,該用那一種方式。以前都是用微軟的driver,碰到錯誤再處理,或將長度為0的字串改為長度為1的空白。或偷懶,直接串 sql 指令。這些都不是好的解法。

若改為使用 odbc 來連接,雖然要在 big5 與 utf8 之間轉換,麻煩些。但比較麻煩的問題是,轉換之後,有些字會不見,如簡體字。
例如 " 机器人公民索菲亚",轉成 big5,再轉回 utf8,就變成 "机器人公民索菲?",最後一個字變問號了。

小結

整理這篇文章,供自己日後參考,以免以後又再做重複的比較,浪費時間。
很顯然的,要連接 SQL 2000,一個是用 Linux + PHP 7.0 + dblib,若使用 Windows server 2003 + PHP 5.4 + sqlsrv driver,就麻煩點,處理好長度為0的字串。odbc 不予考慮,少字的問題,是無法控制的。

網誌存檔