從昨天半夜開始,我就在寫 Kwiki::DB::ClassDBI 這個模組。想法是,因為我已經有一些 Kwiki plugin 有去使用 DBD::SQLite 了,不如就把架構搞好一點,弄個 Kwiki::DB 當做中介,而 Kwiki::DB::* 底下的東西,則是包裝大家已經可以在 CPAN 上見的到 DBI 引擎(這類的模組絕對是 CPAN 上最大宗的一種,幾乎是每個人都自已寫一套 DBI 引擎的程度。) Kwiki::DB::DBI 因為是沒做太多事情的極簡介面,所以 30 分鐘就生出來了。寫完之後就是想到,可以來包裝 Class::DBI。 Class::DBI 這個模組很有意思,它包裝 DBI 的方式,幾乎可以讓你不用再手刻任何一行 SQL。在使用時,每個資料庫的表格都會有一個對應的類別,而這個類別本身亦是一個存取器(Accessor, getter-setter,我倒底要怎麼叫這個玩意兒?)。只要在類別裡面先定義好欄位,就會自動生出跟欄位同名的方法,像是: package Music::CD; use base 'Music::DB'; Music::CD->table("cd"); Music::CD->columns(All => qw/title artist year/); 這樣子定義了 Music::CD 這個類別之後,就對應到 "cd" 這個表格,以及裡面的三個欄位。要取得欄位值,大致上的方法就是這樣: my $cd = Music::CD->retrieve("Easy Time"); print $cd->artist; # "Love Psychedelico" 果真是什麼 SQL 都沒看見。 但這模組最大的問題在於,它完全是使用「類別」來運做,所以我老是得打些很長的名字,像是 "Kwiki::UserName::Auth::DB::UserData" 這樣,才有辨法使用。所以我想替它包一下,可以用物件來使用,這樣子我愛用多短的名字就可以用多短的名字。 my $artist = $self->db->cd->retreive("Easy Time")->artist; # Love Physcdelico 而且,這樣寫起來也很 Spiffy! 不過這樣的事情比想像中難做,因為 "package" 後面只能接 bareword, 而包在 eval 裡面的話,scope 又只有在 eval 裡面,不會跑出來。所以比較難在 runtime 偷雞生出新的 package namespace。 目前想到的辦法是,運用 AUTOLOAD 機制,在建立物件時多給一些參數,讓 AUTOLOAD 去 eval "$class->$method(\@_)",這才算是解決了。雖然 AUTOLOAD 其實不很理想,不過還算是個辨法。或是 Source Filtering 也是一個辨法。最後生出來的程式碼只有 36 行,但是花了我 20 個小時。 這次在寫 Kwiki::DB::ClassDBI 時,雖然還是在用 print 偵錯,不過用上了 Devel::Symdump 這個模組。相當好用地,可以看到 symbol table 上的所有資訊。如果沒用它的話,大概是不會知道什麼地方錯了。在要偷玩一些把戲,或是多重繼承時,Devel::Symdump 的確會相當好用。