僕が開発に携わっているbaserCMSで Advent Calendar をやる事になったので、もう更新する事もないかなと思っていたこのブログで書いてみる事にしました。
Advent Calendar は、だいたい技術系の内容が多いのですが、今回はちょっとそれたお話しになりそうです。
baserCMSの開発の経緯とか、今後どうなっていく事を目指しているかとかそういった事が伝わればよいかなと思います。
baserCMSについて極論を言うと、僕がフリーランス時代に溜め込んだ「大きめのライブラリ」です。
プログラマの特性として、「一回書いたものは二度と書きたくない」という事で、どうしても使いまわして楽をしたかった。
フリーランス時代、商品検索システムだとか、店舗検索システムだとかWeb系の更新システムの開発に携わる事が多かったのですが、当時は管理画面もその都度最初から作っていました。
ある程度デザイン的なものは使いまわすにしても、その作業が煩雑で面倒。
Web系の更新システムは、公開サイドのデザインとも密接に関わる事がほとんどなのですが、せめて、Webサイトの雛形を作りたいなというのが始まりです。
そこに、プラグインという形で機能を追加できたり、スクラッチにしてもライブラリとは完全に分離できれば便利になるなと。
プレゼンテーション層に限りなく近いところまでをフレームワーク化する。まさにWebサイトのフレームワークを想像してました。
僕のフレームワークの経験としては、Smarty → 自前フレームワーク → Mojavi → CakePHP といった感じですが、CakePHPはサイズ的にちょうどよく、規約もしっかりしていて、かつ、逃げ道が色々と用意されているという印象で、一気にハマり込みました。
といった事から、作ったライブラリ群は、CakePHP の app にボンボン放り込んでいってました。
別のWebサイトの案件が始まると、そのライブラリ込みのCakePHPをコピーして使うのですが、別サイトで利用した「いらない部分」があったりするので、まず面倒。
また、ライブラリに致命的なバグが見つかった場合は、納品済のサイト全部にコードベースで修正を加えていく必要がありました。
当時は小さいプログラムを色んなサイトに納品していたので、ひどい時は、20〜30サイト以上も一気にメンテするハメに。
ライブラリのバージョン管理はしていたのですが、納品先ごとのバージョン管理はちゃんとやってなかったので、どこからどこまでを反映させたかどうかなんて考えると気が狂いそうでした。
保守費用をちゃんともらってない上、立場の弱いフリーランスのプログラマなもので修正費用も請求しにくいという。。
単純に、当時の僕のビジネススキル不足だっただけかもしれませんが。
そこで、当時、ライブラリと拡張部分のフォルダ分けをしているEC-CUBEを参考に、ライブラリ部分を app から分離する事にしました。
分離したフォルダの名称が baser です。
ただ、その当時は、EC-CUBEを参考にしクラスを継承する仕組みを採用してましたので、appフォルダ内がかなりチラかるハメとなり、コアライブラリのバージョンアップ自体は、上書きだけで楽に行えるようになったものの、app内は、どのファイルがカスタマイズしているファイルなのか全てをチェックする必要がありました。ああ、結局、面倒。
そこで他に何かいい方法がないかとCakePHPのソースコードを読み漁ったところ、Configure クラスで、include するクラスの候補を管理している事に気づきました。
CakePHPでは、cake というフォルダ内にCakePHPの全てのライブラリが詰まっているのですが、app というフォルダ内の指定の場所にコピーするとそちらを優先する仕組みを備えています。
では、app と cake の間にもうひとつ候補を挟めないかという思いつきでしたが、それができる仕組みをなんと CakePHP は既に用意していました。
この辺の話は、12月12日の kanjihtmt さんが書いてくださっているのでそちらを見られてください。
※ CakePHPの最新バージョン(現在 2.2.4)では、App::build というメソッドで提供されています。
この仕組みがあったおかげで、baserCMSは、フレームワークであるCakePHPの上にフレームワークを載せる事に成功しました。
バージョンアップの問題も解決し、何かあった場合のメンテナンスもだいぶん楽になりました。
書き始めると長くなってきましたが、次は何故オープンソースにしたか。
baserCMSをオープンソースにした理由
さて、自分なりに素晴らしい仕組みと思えるものができたので、次はどうしようかと考えました。
まずは「ソフトウェア販売」を考えましたが、2つの問題が頭をよぎりました。当時の僕はお金もない1人のただのフリーランス。
広告にかけれるお金がない、保守を提供できる体制がない。
この状態で販売してうまくいくか。自分がこれまでに取引をしてきたお客さんだけに提供するのであればまだなんとかなるかもしれないが広がりがない。出資を受けるには勇気がなさ過ぎた。
そんな時、ある人物よりこんなアドバイスを受けました。
「オープンソースにしたらどう?」
それまでオープンソースを使った事は何度も何度もあるけど、自分が提供する側という発想はありませんでした。
そこからオープンソースへの興味が強くなり、オープンソースに関する色んな情報を収集しました。
結果、僕なりに5つの事がわかりました。
なんて素晴らしい仕組み。これで広告の問題も当面の保守の問題も解決しました。
さらに、プロダクト自体をもっと良くする事ができるし、幅広く使ってもらえる。
後は、マネタイズの問題のみ。
これがその後一番の足かせとなるのですが、当時の僕は、利用者が増えれば仕事は後からどうとでもなると思っており、オープンソースしかないと考えていました。
そこからが茨の道の始まりです。
現在に至るにあたり、従業員雇用、事務所開設、離婚、破産寸前、解散、と色々な事がありました。
色んなところでエピソードを話す度に、「自虐ネタ」とお褒めの言葉?を頂いているのですが、無償のパッケージで利用者と協力者を増やすまでがホント大変でした。
忙しいのにお金がない。まさに貧乏ヒマなしというヤツですね。
なんとか乗り切り、今では以前に比べ、利用者も協力者も少しずつ増えてきています。
数カ所のホスティングサービスに簡単インストールとして採用され、少しずつ認められてきました。
仕事の方もbaserCMSをカスタマイズするお仕事がだいぶ増えてきており、スタッフも増え、最近、法人化する事ができました。
これからが正念場。
普及活動の成果もあり、使ってもらえるようになってきたので、パッケージ自体をよくしていかないと。
パッケージ自体をよくしていく為にも開発者を増やさなければなりません。
現在は、CakePHPの最新版に対応したら、開発者の方も増えると信じて CakePHPの最新版への対応をすすめています。(現行のbaserCMSの最新版のCakePHPは1.2系です)
それと同時進行で、baserCMSの次期バージョン 2.1.0 の開発もすすめています。
今年中のリリースには間に合わないかもしれませんが、さらに使いやすくなる予定ですのでお楽しみに!
また、実現可能かどうかまだハッキリしていないですが、書籍の出版もやり取りをすすめています。
そして、テーマやプラグインの販売サイトの企画もすすめています。
会社では社長1年生という事でやる事が多すぎて、まだまだ貧乏ヒマなし状態で、なかなかbaserCMSにかける時間がとれずにいますが、ここを乗り切れば、baserCMSは次のフェーズを迎えられると信じています。みなさん、見捨てないでねっ><
今日の教訓
みなさん、マネタイズはしっかりとやりましょうね!
baserCMS をリリースしてから色んな事がありました。
歓喜、離婚、挫折、復活、忙殺と、とにかく走り続けてます。
落ち着いたらまた更新していきます。
]]>
国産オープンソースとしてCakePHPベースのCMS、「BaserCMS」の公式サイトをオープンしました。
このCMSはどちらかというと制作者向けのCMSとなっており、WEBサイトのベースとして利用できるようになっています。
特別これといった目立った機能はなく、至ってシンプル構成でまだまだバグも含んでいますが、これまでの実務経験から培ったCakePHPに関するノウハウもしっかり詰め込まれています。
土台の分離化や、プラグインフックなども一例です。
是非一度ダウンロードしてみてください!
で、「ことえり」の辞書がインポートしたくなってインポートしてみたがフォーマットが違うみたいでうまくいかない。
テキストエディタで以下の正規表現を使えば変換できるので変換してエンコーディングUTF-8、改行タイプLFで保存すると大丈夫。
"(.+?)","(.+?)","(.+?)"\n
↓
\1\t\2\t名詞\n
※ \1 \2 は各エディタの後方参照の記号に置き換える
※ 最後の品詞を名詞固定としているのは、Googleの品詞にいちいち置き換えるのがめんどいから。
※ 単語に改行が入ってるとダメ
ポイントはパスの引継ぎ。 以下を記述したファイルをbinなんかにおいて利用する。
(Windows側でgvimのパスを通しておく)
#!/bin/bash gvim $(cygpath -aw $*) &]]>
rake redmine:migrate_from_trac RAILS_ENV=production
※ productionの部分は移行先のRedmine環境名
通常、認証機能などで、Session を利用した場合、ブラウザを閉じると保持していた Session 情報は消える。
これは、php.ini の session.cookie_lifetime に「0」を設定した場合の挙動だが、 CakePHP で、認証機能を実装した場合、session.cookie_lifetime を「0」に設定していてもセッションが消えない場合がある。
]]> 調べてみると、どうやら app/config/core.php で、Security.level の値を high 以外を設定した場合、 Sessionクラスで session.cookie_lifetime が上書きされてしまう様子。知らんかった。。この余計?な機能のお陰で、月末のクソ忙しい時に半日潰したよ。
という事で、ブラウザを閉じた際にセッション情報を消して欲しい場合は、Security.level を high に!
ただ、high に設定した場合、セッションのタイムアウトまでが極端に短いので、Session.timeout で調整が必要っす。
]]>コマンドプロンプトやターミナルで対象のフォルダに移動し
Windows
for /R %i in (.svn) do rd /Q /S "%i"
Mac
rm -rf `find ./ -type d -name .svn ! -regex \.svn/. -print`]]>
関東や関西の方ではけっこう開催されていたようだけど、福岡ではCakePHPをメインとした勉強会がなかなか開催されなかったので本当についに!という感じです。
なんとCakePHPガイドブックの著書の方が発表されるらしいです!
]]> 日時は以下のとおり2009年3月13日(金) 18:30より
福岡市NPO・ボランティア交流センター「あすみん」会議室
福岡市中央区大名2-6-46 福岡市立青年センター5F
http://www.fnvc.jp/access.html
詳細はコチラから
懇親会もあるようなので楽しみ〜♪]]>細かい変更点は確認してないけど、既存のCakeアプリに投入したところ問題なく動作した。
RC2、3のような大幅な変更はないのかな?
もうすぐ stable !楽しみ~。
実装方法としては、上記記事のタイトル通り、CakePHPのコンポーネントとして実装させる。
上記記事のコンポーネントを参考にもっと理解しやすいようにシンプルに書いてみたので参考にどうぞ。
以下よりSabreAMFをダウンロードし、解凍後、ディレクトリ名をSabreAMFにリネームし[app/vendors]に設置する。
2008/11/24時点でのバージョンは、1.2.203
以下のソースをコピーして、[app/controllers/components/sabre_amf.php]として設置する。
set_include_path(APP.'vendors' . PATH_SEPARATOR . get_include_path()); App::import('Vendor', 'SabreAMF_CallbackServer', array('file'=>'SabreAMF/CallbackServer.php')); class SabreAmfComponent extends Object { function startup(&$controller) { if ($controller->action == 'gateway') { global $_cakeController; $_cakeController = $controller; Configure::write('debug', 0); // コールバックサーバーを立ち上げる $controller->autoRender = false; $server = new SabreAMF_CallbackServer(); $server->onInvokeService = array($this,'amfCallBack'); $server->exec(); exit; }elseif(empty($controller->amfExclude) || !in_array($controller->action,$controller->amfExclude)){ exit(); } } function amfCallBack($service, $method, $data) { global $_cakeController; $res = null; if ($_cakeController) { if (strpos($method, "_") !== 0) { // _(アンダーバー)で始まるmethodはエラー。 if (method_exists($_cakeController, $method) && !in_array($_cakeController->action,$_cakeController->amfExclude)) { // CakePHPのメソッドを呼び出せるように変換をかける。 $res = call_user_func_array( array( $_cakeController, $method ), $data ); } else { $res = "not found action."; } } else { $res = "invalid method name."; } } else { $res = "not found controller."; } return $res; } }
コンポーネントのstartupメソッドで、CallbackServerを立ち上げ、
CallbackServerがリクエストを解析後、コールバックメソッドでCakePHPのメソッドを呼び出すという流れだ。
そうすると、mxml側からCakePHPのコントローラーを引数つきで呼び出せる。
以下のソースを[app/controllers/amfs_controller.php]として設置する
amfExclude = array('normal'); } //これは通常表示できるアクション function normal() { $this->set("hoge", date("Y-m-d H:i:s")); } function gettext($message){ $ret = date("H:i:s")." ".$message; return $ret; //各action で return した値が flashに返る。 } //配列返却 function getarr($data) { return $data; } } ?>
コントローラー側では、$componentsにSabreAmfを指定するだけで利用可能となる。
また、beforeFilterメソッドで$this->amfExcludeに配列で除外対象のアクションを指定するとAMFでは利用できず、
通常のアクションとして利用できるようになる。
$this->amfExclude = array('normal');
以下のコードを元にmxmlファイルを作成しコンパイルする。
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="240" height="408">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.utils.ArrayUtil;
import mx.collections.ArrayCollection;
import mx.rpc.events.*;
[Bindable]
private var myDataItem:ArrayCollection = new ArrayCollection;
private function sendMessage():void {
ro1.gettext(msg.text);
msg.text = "";
}
private function resultHandler1(e:ResultEvent):void {
log.text = e.result.toString() + "\n" + log.text;
}
private function resultHandler2(evt:ResultEvent):void {
myDataItem.addItem(evt.result);
}
private function faultHandler(evt:FaultEvent):void {
Alert.show("Fault: " + evt.fault + " Msg: " + evt.message);
}
]]>
</mx:Script>
<!--
<RemoteObject設定項目>
endpoint = "http://ホスト名/コントローラー名/gateway"
destination = "送信先名"(内容な任意だが必須)
result = "成功時の返信後処理"
fault = "失敗時の返信後処理"
gatewayファイルを設置した場合は、endpointでなく、sourceでクラスを指定する事もできる。
endopointなどをservices-config.xmlで設定する事もできる。
その場合、コンパイルオプションに、-services="services-config.xml"を追加する。
-->
<!-- 文字列読み込み例 -->
<mx:RemoteObject id="ro1"
endpoint = "http://localhost/amftest/amfs/gateway"
destination="amftest"
result="resultHandler1( event )"
fault="faultHandler( event )">
</mx:RemoteObject>
<mx:TextArea id="log" width="200" height="152" x="10.5" y="40"/>
<mx:TextInput id="msg" x="8.5" y="10"/>
<mx:Button label="send" click="sendMessage()" x="176.5" y="10"/>
<!-- 配列読み込み例 -->
<mx:RemoteObject id="ro2"
endpoint="http://amftest.localhost/amfs/gateway"
destination="amftest"
result="resultHandler2(event)"
fault="faultHandler(event)">
</mx:RemoteObject>
<mx:Button label="array load test" click="ro2.getarr({col1:'1',col2:'abc'})" x="10" y="360"/>
<mx:DataGrid id="myData" dataProvider="{myDataItem}" x="10" y="200">
<mx:columns>
<mx:DataGridColumn headerText="列 1" dataField="col1"/>
<mx:DataGridColumn headerText="列 2" dataField="col2"/>
</mx:columns>
</mx:DataGrid>
</mx:WindowedApplication>
mxml側の処理としては、URLのフルパスをRemoteObjectのendpointとし、その際のactionをgatewayとする仕様とした。
(例)http://localhost/コントローラー名/gateway
その代わり、通常サービスを指定するsorceは指定しない。
CakePHPの場合、dispatcherから呼び出されたコントローラーを操作できなくては、CakePHPの恩恵にあずかれない為、
gatewayを呼び出した際に、同時に呼び出されたコントローラーを処理対象のコントローラーとした方が処理効率がよいからだ。
そして、後はボタンのクリックイベントなどに、RemoteObject.action()という形式で実装する事でCakePHPのアクションを呼び出せる。