まずはタグ用のJavaサーブレット本体から。
helloTag.java
| package tag; import java.io.IOException; import javax.servlet.jsp.tagext.TagSupport; public class helloTag extends TagSupport { public int doStartTag() { try { pageContext.getOut().write("Hello,world"); } catch (IOException e) { e.printStackTrace(); } return SKIP_BODY; } } |
カスタムタグはTagSupportクラスをextendsして作成します。
TagSupportクラスにあるメソッドのうち、doStartTag、doAfterBody、doEndTagの各メソッドを上書きして実行します。
各メソッドは名前のとおり、タグ開始時点、タグボディが終わったあと、タグの終了時点で行う処理を意味しています。
デフォルトでは各メソッドとも何もしません。
今回は文字を書くだけなのでdoStartTagを上書きします。
各メソッドは返り値を返さなければなりません。
EVAL_PAGEやEVAL_BODY_INCLUDE等色々ありますが、今回はタグボディを気にしないのでSKIP_BODYを返します。
次にタグを呼び出すJSPです。こちらは簡単。
helloTag.jsp
| <%@ taglib uri="http://localhost:8080/example/tags/helloTag" prefix="hw" %> <hw:helloworld /> |
http://localhost:8080/example/tags/helloTagって何処のこと?
気にせずに次はweb.xml
| <jsp-config> <taglib> <taglib-uri>http://localhost:8080/example/tags/helloTag</taglib-uri> <taglib-location>/WEB-INF/lib/hello.tld</taglib-location> </taglib> </jsp-config> |
実はhttp://localhost:8080/example/tags/helloTagではなく何でもいいのです。
命名規則のようなものはありますが比較的どうでもいいです。
web.xmlで<taglib-uri>と<taglib-location>を結び付けるという意味なのです。
<taglib-location>には実在するtldファイルを置かねばなりません。
http://yuubiseiharukana.blog.shinobi.jp/Entry/40/
で作成したタグの使用法が、
<%@taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>
となっていたのも、web.xml(のようなもの)にそのように書かれていたからです。
というわけで最後にhello.tld。
/WEB-INF/lib/hello.tld
| <?xml version="1.0" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd" version="2.0"> <tlib-version>1.0</tlib-version> <jsp-version>2.0</jsp-version> <short-name>hw</short-name> <tag> <name>helloTag</name> <tag-class>tag.helloTag</tag-class> <body-content>empty</body-content> </tag> </taglib> |
JSPによるタグファイルの設計図は<%@tag%><%@attribute%>等で記述していたのに対し、tldファイルはタグの設計図をxml形式で記入します。
両者は概ね共通していますが、
<tag-class>にパッケージ付きのフルパス、<name>はタグのクラス名を指定します。
さて、(のようなもの)に嫌な予感を感じた方はいませんか。
上記を見てのとおりカスタムタグを一個追加するために複数のファイルを書かねばなりません。
しかもweb.xmlまで書き換える必要があります。
web.xmlはJavaアプリケーションの根幹であり、PHPでいうところのphp.iniのようなものです。
たかだかタグ追加のためだけにわざわざ編集するなどという面倒なことはしたくありません。
また、自作のすごいカスタムタグを配布したい、という場合に、「導入する場合はweb.xmlを書き換えて」などとヘルプを書くのもどうかという感じです。
というわけでweb.xmlを書き換えずに済む方法があるのですがまあまたいつか。
とりあえず役に立つクラスを作成してみましょう。
PHPでは変数へのデータ保存は非常に簡単に行えます。
多くの言語では面倒な、セッションへのデータの保存や取り出しも無闇に簡単です。
しかしファイルに対する操作は多少の手間が必要です。
ファイルに保存するクラスを作って簡単に読み書きできるようにしてしまいましょう。
尤もこれが実用的かというと、すぐDBに移行するので微妙なところですが。
filereader.class.php
|
class fileReaderClass{ |
まず__constructですが、これはコンストラクタと呼ばれ、newでクラスをインスタンス化した時点で自動的に実行されます。
newするときにファイル名を与えると、そのファイルの中身が連想配列に読み込まれます。
その後、getParamおよびsetParamでファイルの読み書きを行います。
最後にデストラクタですが、これはクラスへの参照が無くなったときに自動的に実行されます。
「クラスを最後に使用したとき」ではなく「参照が無くなったとき」ですので、順番を厳密に管理するときは注意が必要です。
使用する側では以下のようにします。
file.php
|
require_once('./filereader.class.php'); |
sample.txt(空白ではなく、タブ区切りです)
| a sample1 b sample2 c sample3 |
$fp=new fileReaderClass('a.txt');
の行を実行した時点でコンストラクタが実行され、sample.txtが読み込まれ、クラス内のメンバ変数$contentsに入れられます。
次のgetParamでは、$contents['b']の値、すなわち'sample2'を取得します。
setParamで$contentsに('d'=>'sample4')という配列を追加しています。
unset($fp)でこのクラスへの参照を削除すると、同時にデストラクタが実行され、現在の$contentsの値をファイルに書き込みます。
これで一通りファイルを読み書きするクラスが完成しました。
タブを含む変数名、改行を含む変数内容は使用できませんが、とりあえずはいいでしょう。
さて、書き込み後のsample.txtを見てみます。
sample.txt
| a sample1 b sample2 c sample3 |
ってあれ?
デストラクタをちょこっと変更して確認してみます。
| public function __destruct(){ $tmp=''; foreach($this->contents as $key=>$val){ $tmp.=$key."\t".$val."\n"; } file_put_contents($this->file_name,$tmp); var_dump($this->file_name); var_dump(file_get_contents($this->file_name)); } |
string(8) "file.txt"
string(40) "a sample1 b sample2 c sample3 d sample4 "
あれれ?
きちんと取得できてますね。
| public function __destruct(){ $tmp=''; foreach($this->contents as $key=>$val){ $tmp.=$key."\t".$val."\n"; } file_put_contents('C:\xampp\htdocs\file.txt',$tmp); } |
file.txt
| a sample1 b sample2 c sample3 d sample4 |
フルパスを与えると保存できてます。
$this->file_nameって何処なんだ?
はて??
今度は呼び出し側を変更してみます。
file.php
| require_once('./filereader.class.php'); $fp=new fileReaderClass('sample.txt'); $a=$fp->getParam('b'); $fp->setParam('d','sample2'); unset($fp); print($a); |
file.txt
| a sample1 b sample2 c sample3 d sample4 |
ファイルポインタを明示的に破棄した場合、相対パスでもきちんと保存されるようです。
どういうことなのでしょう?
カスタムタグはJakartaTaglibsしか存在していないというわけではなく、自作することができます。
カスタムタグの部分はJavaの作成者たちが特に力を入れた部分であり、まさに正気の沙汰ではない実装になっています。
タグの作り方は何通りもあるのですが、とりあえず一番簡単な方法から行ってみます。
まずWEB-INF/tagsディレクトリを作成し、hello.tagという名前のテキストファイルを作成します。
これがタグの処理内容を示すファイルになります。
/WEB-INF/tags/tag1.tag
| <%@ tag pageEncoding="Shift_JIS" %> <p>Hello World</p> |
本当にただHelloWorldを出力しているだけです。
<%@ tag pageEncoding>には文字コードを指定します。
タグファイルは<filter>の範囲外なので、例によって毎回書かなければなりません。
JSPファイルでは、以下のような書き方で作成したタグを呼び出すことができます。
tag1.jsp
| <%@taglib tagdir='/WEB-INF/tags' prefix='tag' %> <tag:tag1 /> |
tagdirで設定したディレクトリの中にある.tagファイルが、ファイル名で自動的にタグとして使えるようになります。
実に簡単ですね。
属性の受け渡しは以下のようになります。
tag2.jsp
| <tag:tag2 data="aaaaa"/> |
/WEB-INF/tags/tag2.tag
| <%@ tag pageEncoding="Shift_JIS" %> <%@ attribute name="data" type="java.lang.String" required="true" rtexprvalue="true"%> |
${data}が渡されました。
JSP側では普通のHTMLタグのように名前と属性を指定するだけです。
タグファイル側では、<%@ attribute%>で属性の書式を設定します。
設定値は、nameが受け取る属性名、typeが属性の型(デフォルトはString)、requiredが必須かどうか(デフォルトはfalse)、rtexprvalueは属性値としてELを受け付けるかどうか(デフォルトはtrue)です。
デフォルトでは属性を書いても書かなくてもかまいませんが、required="true"を設定すると必ずその属性を記述しなければなりません。
デフォルトでは<tag:tag2 data="${ELdata}"/>といった書き方が可能ですが、rtexprvalue="false"が指定されているとエラーになります。
さて、以上のタグはタグボディを完全に無視していました。
<tag:tag1>ここがタグボディ</tag:tag1>と書いても、無かったことにされてしまうのです。
次はタグボディを評価してみます。
tag3.jsp
| <tag:tag3>${data}</tag:tag3> 帰ってきた値:${tag3request} |
/WEB-INF/tags/tag3.tag
| <%@ tag pageEncoding="Shift_JIS" %> <%@ tag body-content='scriptless' %> 渡された値:<jsp:doBody /> <jsp:doBody var="tag3request" scope="request" /> スコープに入れた値:${requestScope.tag3request}<br> |
独特な書式ですが、<jsp:doBody>でタグボディを取得することができます。
属性を何も記述しない場合、そのままその部分に文字列で出力します。
varを指定すると指定された変数に格納されます。
タグボディを加工したい場合などに利用します。
<%@ tag body-content%>の部分で、タグボディの評価方法を指定します。
上記のようにscriptlessを指定するとELを使用できます。
dataに入っている値が表示されます。
tagdependentを指定すると、タグボディが文字列として評価されます。
${data}という文字列のまま出力されます。
emptyと指定した場合、タグボディを記述することができません。
<br />等のボディなしで完結するタグ用ですが、タグボディを記述してしまうとエラーになるので不便です。
何れにせよ、指定にかかわらずスクリプトレットは常時使用できません。
tagdependentの場合は単に文字列になりますが、scriptlessの場合タグボディ内にスクリプトを書くとエラーになります。
このように、いくつか制限はありますがJSPでカスタムタグを作るのはわりと簡単です。
では次に、Javaサーブレットで/WEB-INF/tags/tag1.tagと同じものを作ってみましょう。
/helloTag.jsp
| <%@ taglib uri="http://localhost:8080/example/tags/helloTag" prefix="hw" %> <hw:helloTag /> |
src/tag/helloTag.java
|
package tag; |
/WEB-INF/web.xml
| <jsp-config> <taglib> <taglib-uri>http://localhost:8080/example/tags/helloTag</taglib-uri> <taglib-location>/WEB-INF/lib/hello.tld</taglib-location> </taglib> </jsp-config> |
/WEB-INF/lib/hello.tld
| <?xml version="1.0" ?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd" version="2.0"> <tlib-version>1.0</tlib-version> <jsp-version>2.0</jsp-version> <short-name>hw</short-name> <tag> <name>helloTag</name> <tag-class>tag.helloTag</tag-class> <body-content>empty</body-content> </tag> </taglib> |
これ考えたやつは何なの?馬鹿なの?死ぬの?
JSPは使ってはいけない、ELだけでは何もできないということで、さらにJSTLという拡張タグライブラリが作られました。
JakartaTaglibsというタグライブラリが一般的に使われているのでそれを使ってみましょう。
何故か最初から入っていないのでダウンロードしてくる必要があります。
http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi
解凍したら、中に入っているlib/jstl.jarとlib/standard.jarを、WEB-INF/libディレクトリにコピーします。
その後、タグライブラリを使用するJSPに
<%@taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c' %>
と書くと、JSPでライブラリを使用することができます。
何故実際にライブラリが入っている場所ではなくて変なURLを書かねばならないのかはまたいつか。
とりあえず使ってみましょう。
<c:out value="あいうえお">
ただでさえ<%out.println>、<%=>、${}と同じようなものがいっぱいあるのにまた増えるの?
また増えました。
特徴は
<c:out value="${sessionScope.data1}" default="データがありません" escapeXml=true>
というふうに何も無かった場合のデフォルトを設定できることと、HTMLタグのエスケープができることです。
これだけやってようやくhtmlspecialcharsに肩を並べることができました。
<c:set var="data2" value="<b>value2</>" scope="page">
setAttributeと何がちがうの?
さあ?
<c:import url="include.jsp" context="/example2">
<%@include file %><jsp:include page>があるのにまた増えました。
上記と違う点は、別のコンテキストに存在するJavaアプリケーションを取り込めることです。
もっともそんなことをしなければならない時点で設計を失敗しているわけですが。
以下は選択及び繰り返し処理の例です。
|
<c:forEach items="[1,2,3,4,5,6]" var="var" varStatus="varStatus"> |
Javaに限りませんが、Webプログラミングは、デザインからロジックを排除して排除して、その結果何もできなくなるのでこうやってスクリプトっぽいことを追加して、それでまたロジックがデザインに入り込んでくる、そんなことを何度も繰り返しています。
JSTLもまたそのような道に入り込んでいるように見えます。
PHPには多くのソート関数がありますが、その中でも便利でありながら使い方がわかりにくいもののひとつであるarray_multisortとusortについて調べてみます。
http://jp2.php.net/array_multisort
マニュアルを見てみても
「複数の配列を一度に、 または、多次元の配列をその次元の一つでソートする際に使用可能です。 この関数は、ソートの際にキーの相関を維持します。」
としか書かれておらず、なんのこったという感じです。
日本語訳すると、引数の前のほうの配列をソートして、そのついでに引数の後ろのほうの配列も同じ順番でソートする、ということになります。
| $a=array(3,5,1,4,2); $b=array('a','b','c','d','e'); array_multisort($a,$b); |
と書いた場合、
まず$aが普通にソートされて
$a=array(1,2,3,4,5);
となります。
それと同時に、$bが$aと同じ順番でソートされ、
$b=array('c','e','a','d','b');
となります。
さて、この関数をわかりにくくしている要因のひとつが引数の与え方です。
array_multisort($a,SORT_DESC,$b);
と書くと、$aが降順でソートされた上で$bがそれに従ってソートされます。
| $a1=array(2,2,1,1,1); $a2=array(1,2,5,4,3); $b=array('a','b','c','d','e'); array_multisort($a1,$a2,$b); |
とすると、まず$a1がソートされ、$a2と$bがその順番に従ってソートされます。
更にその後、$a1の順番を壊さない範囲で$a2がソートされ、$bも$a2に従ってソートされます。
最終的に
$a1=array(1,1,1,2,2);
$a2=array(3,4,5,1,2);
$b=array('e','d','c','a','b');
となります。
更に引数を増やして
array_multisort($a1,$a2,SORT_STRING,$a3,$b);
などと書いても動作してしまうのが、便利さと同時にわかりにくさを感じる一因となっていることでしょう。
さて、array_multisortで便利なのが、多重配列のソートです。
以下のようなよくある商品データがあったとして、これを価格順にソートしたい場合。
| $product=array( array('name'=>'みかん','price'=>'60'), array('name'=>'りんご','price'=>'180'), array('name'=>'バナナ','price'=>'48'), array('name'=>'いちご','price'=>'590'), array('name'=>'ぶどう','price'=>'298'), array('name'=>'ドリアン','price'=>'5000') ); |
必要なのは、ソート順を決定する配列と、ソート自体を行う配列です。
後者は出来てますので、前者を作ります。
| foreach($product as $tmp){$sort_array[]=$tmp['price'];} |
これでソート用の配列$sort_array=array('60','180','48','590','298','5000')が出来ました。
|
array_multisort($sort_array,$product); |
めでたしめでたし。
さて、array_multisortにはソートオプションが用意されており、SORT_DESCで降順、SORT_STRINGで文字列順、SORT_NUMERICで数値順とソート順を変更することが出来ます。
しかし定義された順ではなく任意の順番でソートしたい場合、どうすればよいでしょう。
usortという、ユーザでソートオプション的なものを設定できる関数が用意されています。
二つの引数をとり、前のほうにしたい場合は-1を返し、後ろに並べるときは1を返す関数を作ってあげます。
とりあえずやってみましょう。
|
usort($product,'compare'); |
バナナを一番上に持って行きたかっただけなのですが、うまくいっていません。
バナナ以外の部分が出鱈目なことになってしまっています。
一体どういう基準でソートされたんだこれ?
どのサイトを見ても
return ($a['price'] < $b['price']) ? -1 : (($a['price'] > $b['price']) ? 1 : 0);
といったわざわざ定義する必要ないじゃん的ソート関数しか置いてないんですよね(ちなみに↑は昇順)
はてさて。
これまで覚えてきたJSPはどうなるの?
ゴミです。
elsample.jsp
| <html> <head> <title>計算</title> </head> <body> ${100 * 100} ${applicationScope.data} </body> </html> |
計算結果の10000が表示されます。
アプリケーションスコープにセットされている変数dataの内容が表示されます。
見てのとおり、Smartyそのものです。
簡単ですか?
とんでもない。
HelloWorld.jsp
| <% String a="Hello,World"; %> <html> <head> <title>Hello, World!!</title> </head> <body> ${a} </body> </html> |
何も表示されません。
pageContext.setAttribute("a",a);
と、わざわざスコープにセットしないと、同一ページ内なのに読み取ることができません。
では問題です。
| <% pageContext.setAttribute("a","pageContext"); request.setAttribute("a","request"); session.setAttribute("a","session"); application.setAttribute("a","application"); %> <html><body> ${a} </body></html> |
なんと表示されるでしょう。
正解はpageContextです。
ELは、ページスコープ、リクエストスコープ、セッションスコープ、アプリケーションスコープの順で勝手に検索して、最初に見つかったオブジェクトを表示してくれます。
楽といえば楽ですが、別のスコープに対してうっかり同じ変数名を使ってしまうと誤動作してしまう可能性があります。
この場合、セッションスコープの変数を取得したければ${sessionScope.a}と記入します。
Javaの思想のひとつが厳密なスコープ管理だったはずなのですが、こんな中途半端にアバウトな仕様が混入したせいで台無しな趣があります。
ELの特徴のひとつとして、Map等連想配列の取り扱いがあります。
取得だけとはいえELではJavaScriptやPHPと同じくらい簡単に連想配列を扱うことができます。
${map.key}
${map["key"]}
${map['key']}
とJavaScriptそのものの書き方ができます。
連想ではない配列も、
${array[0]}
${array["0"]}
${array['0']}
で取得することができます。
何故か${map[key]}とか${array.0}とは書けません。
配列ではなくMapオブジェクトと呼べとかそういう異議は受け付けない。
実際ELだけでなにもかもを表示するのは、Smartyですべてを表示しようとするのと同じく却ってわかりにくくなるだけなので、結局のところJSPなり他の表現手段なりを併用することになります。
続き
今回使用したソフトウェアのバージョン
7wiki v1.1.1
pukiwiki 1.4.7_notb_utf8
個人的にBlogは好きではない。
カテゴリ記事を過去から順に読むという極めて需要の高い機能を何故かどのブログシステムも搭載していないからだ。
例えばこのブログ、カテゴリ別の記事をクリックしたときに期待する動きは、カテゴリ毎のタイトルと投稿日一覧である。
しかし実際には、ジャンルの記事が新しい順に表示されるだけ。
記事を時系列順に読むという行動が極めて行いにくいシステムになっている。
結果的にBlogは知識の蓄積に向いておらず、断片的な記述しか受け入れられない。
というわけでWikiだ。
Wikiとは要するに、昔はローカルでHTMLファイルを作って追記や削除があったら修正してFTPでアップして、なんてやってたことを全部Web上でやっちまおうというステキプロジェクトなのであって、辞書や纏めサイトなどにのみ使わなければならないなどという掟は別にない。
新しいページも簡単に作成できるし並び順も思いのまま、追記もコメントと同等のコスト(実装による)という便利なWikiを使わない手はないだろう。
というわけでとりあえず手始めに7Wikiを試してみる。
http://cm.xrea.cc/prog/7wiki.shtml
ソースコードはそこに載っているのが全て。
一行目を自分の環境に合わせれば終わりである。
index.pl
| #!/usr/bin/perl use CGI":all";charset$c="EUC-JP";$w='\b(([A-Z][a-z]+){2,})';$f=script_name;$/=( );($p)=path_info=~/$w/;$p||=FrontPage;request_method=~PO&!($m=param z)&&unlink$ p;open F,$m?">$p":$p;eval{flock F,2};print F$m||=<F>;$_=pre(escapeHTML$m).hr.ul map{li"".localtime((stat)[9]),$_}sort{-M$a<=>-M$b}grep/^$w$/&-f,<*>;s|$w|(!-f$1 &&$1).a{href,"$f/$1"},-f _?$1:"?"|eg;put header,start_html(-Title,$p,encoding,$ c,lang,ja),h1($p),startform(0,"$f/$p"),p(textarea(z,$m,6,60),br,submit),endform ,hr,$_,end_html |
Wikiとってもメモくらいにしか使えないが、この長さでこの性能は驚きだ。
解析しようと思ったら日が暮れるのは間違いない。
ただ、正規表現が何故([A-Z][a-z]+){2,}なのかはよくわからない。
さて手慰みはこれくらいにして、それでは本格的にPukiWikiを入れてみる。
http://pukiwiki.sourceforge.jp/
ごく普通にダウンロードして解凍、適当なフォルダに突っ込むだけである。
今回はC:\xampp\htdocs\src\wiki\pukiに置いてみた。
http://localhost/src/wiki/puki/
ブラウザからアクセスしてみると、何もしていないのにあっさり表示成功。
恐ろしいまでの敷居の低さである。
デフォルト設定では管理用パスなどが漏れ放題なので、そこらへんの設定を行う。
/pukiwiki.ini.php
| $modifier = 'NurseAngel'; $modifierlink = 'http://yuubiseiharukana.blog.shinobi.jp/'; $adminpass = '{x-php-md5}1a1dc91c907325c69271ddf0c944bc72' //'pass'のMD5ハッシュ |
以上で設定も終了である。
Linux環境であればパーミッションなどの設定が必要だが、今回はローカルなのでその必要もなし。
勿論インターネットに公開するのであればそこらの設定も必須である。
さて、Wikiと聞いて身構えてはいたものの、実際の導入コストは簡単と言われているCakePHPより遥かに易しいという驚きの結果が待っていた。
勿論設置後の運営が大変なのはWikiである。
とりあえず日記でも書いておけばいいブログと違って、中身がなければ見向きもされないからな。
前回の続き。
CakePHPは日本語ドキュメントがそこそこ充実しています。
たとえば公式にブログの作成方法が書いてあります。
http://www.cakephp.jp/doc/blog_tutorial.html
ただしCakePHPは1.2で大きな変更があったのでそのままでは動きません。
よくわからないまま作成してみます。
CakePHPをはじめ各フレームワークは、MVCというモデルに従って作成されています。
簡単に言うと、Controllerでリクエストを振り分けModelで処理を行いViewで表示する、という流れでプログラミングするという掟です。
CakePHPの場合、appフォルダ内にあるmodels、views、controllers各フォルダにそれぞれを記述していきます。
ということでとりあえずCakePHPのフォルダ構成の説明。
まずdocsはそのままドキュメントが入っていますが必要ないのでポイします。
cakeフォルダは基本的にシステムが使用するので変更はしません。
vendorsフォルダは外部ファイルを取り込むところで、他フレームワークのライブラリやPEARなどを使えたりするのですが、そんな高度なことはしないのでとりあえずパス。
appフォルダはユーザが作成したファイルを置いていく場所となります。
後述のヘルパーに機能を追加したい、といった場合はcakeフォルダ内の該当ファイルを直接いじるのではなく、appフォルダにコピーしてから変更することになります。
ではアプリを作る前に使用するDBの作成。
前回作成したPhpMyAdmin内のcakephpデータベースにテーブルを作成します。
cakePHPのお約束として、テーブル名をプログラム名と共通にすることが挙げられます(ただし複数形)
そうすることで特に何もしなくても自動的にプログラムとDBをリンクしてくれるのです。
凄すぎ。
テーブル作成は以下のSQL文をコピペするだけで終了。
見てのとおり公式そのままです。
| CREATE TABLE posts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(50), body TEXT, created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL ); INSERT INTO posts (title,body,created) VALUES ('The title', 'This is the post body.', NOW()); INSERT INTO posts (title,body,created) VALUES ('A title once again', 'And the post body follows.', NOW()); INSERT INTO posts (title,body,created) VALUES ('Title strikes back', 'This is really exciting! Not.', NOW()); |
さくっとプログラム作成。
こちらも公式をコピペしただけ。
app/models/post.php
| class Post extends AppModel{ var $name = 'Post'; } |
app/controllers/posts_controller.php
| class PostsController extends AppController{ var $name = 'Posts'; function index(){ $this->set('posts', $this->Post->findAll()); } } |
app/views/posts/index.thtml
| <h1>ブログの投稿</h1> <table> <tr> <th>Id <th>タイトル</th> <th>作成日</th> </tr> <?php foreach ($posts as $post): ?> <tr> <td><?php echo $post['Post']['id']; ?></td> <td> <?php echo $html->link($post['Post']['title'], "/posts/view/".$post['Post']['id']); ?> </td> <td><?php echo $post['Post']['created']; ?></td> </tr> <?php endforeach; ?> </table> |
foreach()~endforeachはforeach(){~}のエイリアスです。初めて知った。
しかし、postとpostsとPostとPostsはどう違うんだ?
早速ブラウザからアクセス。
http://localhost/src/php/fw/cake/posts/index
Error: The view for PostsController::index() was not found.
Error: Confirm you have created the file: C:\xampp\htdocs\src\php\fw\cake\app\views\posts\index.ctp
はて。
index.ctpが見つからないって言われても何でしょうそのファイル。
どうやらCakePHP1.2以降、Viewのファイル名がthtmlからctpに変更になったようです。
app/views/posts/index.htmlをindex.ctpをに変更すると今度はあっさり表示に成功。
動作の詳細を追ってみます。
まずアドレスのうち、http://localhost/src/php/fw/cake/までがCakePHPのパスです。
その後のposts/indexで、posts_controller.phpのindexメソッドにアクセスするということになります。
indexメソッドは変数'posts'にpost.phpで作成したPostクラスのfindAllメソッドの返り値を代入し、setメソッドでViewに送り込みます。
index.ctpは送り込まれた変数'posts'の内容を展開して表示しているだけです。
$html->linkで<a href~のタグを作成してくれるようです。
$html以外にも$formや$ajaxといった多くのクラスが定義されており、これらはヘルパーと呼ばれています。
cake\libs\view\helpersフォルダ内に置かれているので見てみるとよいかもしれません。
しかしModelが何をやっているのかよくわからないんだが。
よくわからないのはスルーして詳細表示をしてみることにします。
Controllerに新たにviewメソッドを追加します。
Viewにはview.ctpを新規作成します。
app/controllers/posts_controller.php
| function view($id = 1){ $this->Post->id = $id; $this->set('post', $this->Post->read()); } |
app/views/posts/view.ctp
| <h1><?php echo $post['Post']['title']?></h1> <p><small>作成日: <?php echo $post['Post']['created']?></small></p> <p><?php echo $post['Post']['body']?></p> |
http://localhost/src/php/fw/cake/posts/view/1とアクセスしてみると記事の詳細があっさり表示されました。
Post->idで読みたい記事のIDをセットした後、Post->readでその記事を取得できるようです。
相変わらずModelはよくわからないまま。
次に記事の追加削除機能を追加してみる。
Viewは新規作成し、ModelとControllerには追加します。
app/models/post.php
| var $validate = array( 'title' => VALID_NOT_EMPTY, 'body' => VALID_NOT_EMPTY ); |
app/controllers/posts_controller.php
| function add(){ if (!empty($this->data)){ if ($this->Post->save($this->data)){ $this->flash('投稿されました.','/posts'); } } } function delete($id){ $this->Post->del($id); $this->flash(' id: '.$id.' の投稿は削除されました。', '/posts'); } |
app/views/posts/add.tcp
| <h1>投稿の追加</h1> <form method="post" action="<?php echo $html->url('/posts/add')?>"> <p> タイトル: <?php echo $html->input('Post/title', array('size' => '40'))?> <?php echo $html->tagErrorMsg('Post/title', 'タイトルは必ず入力してください。') ?> </p> <p> 本文: <?php echo $html->textarea('Post/body', array('rows'=>'10')) ?> <?php echo $html->tagErrorMsg('Post/body', '本文は必ず入力してください。') ?> </p> <p> <?php echo $html->submit('保存') ?> </p> </form> |
app/views/posts/index.ctpに新規記事作成、記事削除へのリンクを追加する。
| <h1>ブログの投稿</h1> <p><?php echo $html->link('投稿の追加', '/posts/add'); ?></p> <table> <tr> <th>Id</th> <th colspan="2">タイトル</th> <th>作成日</th> </tr> <?php foreach ($posts as $post): ?> <tr> <td><?php echo $post['Post']['id']; ?></td> <td> <?php echo $html->link($post['Post']['title'], '/posts/view/'.$post['Post']['id']);?> </td> <td> <?php echo $html->link('削除',"/posts/delete/{$post['Post']['id']}",null,'本当に削除しますか');?> </td> <td><?php echo $post['Post']['created']; ?></td> </tr> <?php endforeach; ?> </table> |
見事にエラー。
コピペしているだけなのに何故?
CakePHP1.2からフォームの扱い方が変更になったようです。
$htmlではなく$formを使わなければならないらしい。
app/views/posts/add.ctp
| <h1>投稿の追加</h1> <form method="post" action="<?php echo $html->url('/posts/add')?>"> <?php echo $form->create( 'post',array('action'=>'add')) ?> <p> タイトル: <?php echo $form->input('Post/title',array('size' => 40,'error'=>'タイトルは必ず入力してください')) ?> </p> <p> 本文: <?php echo $form->input('Post/body',array('type'=>'textarea','error'=>'本文は必ず入力してください')) ?> </p> <p> <?php echo $form->end( '登録' ) ?> </p> </form> |
成功。
バリデーションは行うわ画面遷移後のデータ保持も行うわDBへのデータ保存も行うわで一体どうなってんのこれ?ってかんじです。
ModelのVALID_NOT_EMPTYで空白のチェックを行っているようです。
Controllerのaddメソッドは、フォームが空でなければPost->saveでDBの相当するカラムに内容を記入し、flashで完了画面を表示しています。
Viewの$form->inputは<input />に相当するわけですが、引数の内容によって自動的にtextareaになったりcheckboxになったりselect~optionになったりするという恐ろしい機能を持っています。
しかし$validateは一体どのタイミングで呼ばれているんだ?
さて一通り投稿ができるようにはなったのですが、日本語を入力するとDBに文字化けして登録されてしまいました。
'encoding' => 'utf8'は設定しているのに何故?
とりあえずPhpMyAdminから挿入してみる、とその時点で既に文字化けしているではありませんか。
テーブル構造を確認。
| SHOW CREATE TABLE posts CREATE TABLE `posts` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(50) default NULL, `body` text, `created` datetime default NULL, `modified` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
入れた覚えのないDEFAULT CHARSET=latin1なる文字列が入っていますよ?
というわけでテーブルを作り直し。
| CREATE TABLE `posts` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(50) default NULL, `body` text, `created` datetime default NULL, `modified` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 |
今度こそみごとに日本語入力成功。
めでたしめでたし。
最後は記事編集です。
app/controllers/posts_controller.phpに追加。
| function edit($id = null){ if (empty($this->data)){ $this->Post->id = $id; $this->data = $this->Post->read(); }else if ($this->Post->save($this->data['Post'])){ $this->flash('投稿を更新しました。','/posts'); } } |
app/views/posts/index.ctpの、"$html->link('削除')"の前の行に一行だけ挿入。
| <?php echo $html->link('編集', '/posts/edit/'.$post['Post']['id']);?> |
app/views/posts/edit.ctpを作成。
| <h1>投稿の編集</h1> <?php echo $form->create( 'post',array('action'=>'add')) ?> <?php echo $form->input('Post/id',array('type'=>'hidden')); ?> <p> タイトル: <?php echo $form->input('Post/title',array('size' => 40,'error'=>'タイトルは必ず入力してください')) ?> </p> <p> 本文: <?php echo $form->input('Post/body',array('type'=>'textarea','error'=>'本文は必ず入力してください')) ?> </p> <p> <?php echo $form->end('保存') ?> </p> </form> |
完成してしまいました。なんだこれ。
$form->input('Post/id',array('type'=>'hidden'))でhiddenフィールドを作成し、記事IDを持ちまわしています。
function editでは送信データがあれば保存し、なければ記事を拾ってきて表示しています。
さて、ここで存在しない記事IDを指定した場合、hiddenフィールドが何故か空白になっています。
そんなコード書いた記憶はないんだが一体何故に?
さて、最後にデフォルトページを変更しましょう。
http://localhost/src/php/fw/cake/
にアクセスするとCakePHPの情報が表示されてしまいます。
そこを飛ばして
http://localhost/src/php/fw/cake/posts/index
にアクセスさせてしまいましょう。
app\config\routes.phpの
| Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); |
こちらは/、すなわちhttp://localhost/src/php/fw/cake/にアクセスしてきたらpagesコントローラを起動せよ、という意味です。
つまり、以下のように書き換えましょう。
| Router::connect('/', array('controller' => 'posts', 'action' => 'index')); |
これで/にアクセスした際にもpostsコントローラのindexメソッドが実行されるようになりました。
以上で簡単なブログの作成が終了しました。
なんて簡単なのでしょう。
よかったよかった。
http://book.cakephp.org/ja/view/219/cakephp
に気が付いたのは全てが終わった後だったという。南無。
前回のXAMPPその他が導入されていることが前提。
今回使用したソフトのバージョン
CakePHP 1.2.0.7296 RC2
今回はPHPで有力なフレームワークのひとつであるところのCakePHPをインストールしてみる。
フレームワークとは何かというと、簡単に言うと簡単にブログを作成できるソフトである。
具体的にはMVCが云々とかO/Rマッパーがどうたらとかいう話になるのだが、そういうことはあまり気にしない。
一口にフレームワークといってもZend公式のZend Frameworkや純和製のEthna等多種あるのだが、その中でもCakePHPは導入までのコストが最も低いといわれている。
とはいえ幾つか躓くところがあるので試してみた。
まずPHP自体の設定を行う。
初期設定では困る部分があるので、php.iniを編集する必要があるのだが、XAMPPのphp.iniはC:\xampp\php\php5.iniでもC:\xampp\php\php4\php.iniでもない。
C:\xampp\apache\bin\php.iniである。
では前者は何のためにあるかというと、XAMPP付属のPHPSwitchを使用してPHP4とPHP5を入れ替えた際にC:\xampp\apache\bin\php.iniに上書きされるのだ。
従ってC:\xampp\apache\bin\php.iniを編集後、C:\xampp\php\php5.iniに上書きする。
ちなみにC:\xampp\php\php.iniはダミーだ。騙されるな。
該当部分を以下のように編集。
| magic_quotes_gpc = Off short_open_tag = Off error_reporting = E_ALL display_errors = On session.use_only_cookies = 1 mbstring.language = Japanese mbstring.internal_encoding = UTF-8 mbstring.encoding_translation = Off mbstring.http_input = pass mbstring.http_output = pass mbstring.substitute_character = none mbstring.detect_order = EUC-JP,UTF-8,SJIS,JIS |
CakePHPをダウンロード。
http://cakephp.org/
インストールはhtdocs以下に解凍するだけ。
今回はC:\xampp\htdocs\src\php\fw\cakeに保存した。
MTをインストールした際ディレクトリを適当に選んだせいでディレクトリ構成が美しくないことになっているが、まあ気にしない。
早速ブラウザからアクセス。
http://localhost/src/php/fw/cake/
「Your database configuration file is not present.」
「CAKEPHP_ROOT/app/tmp/cache/ is not writable [CORE/cake/libs/cache/file.php, line 263] Your tmp directory is NOT writable.」
以上のような2つのエラーが表示された。
まず前者はDBにアクセスできないということなので、その設定を行う。
C:\xampp\htdocs\src\php\fw\cake\app\config\database.php.defaultをdatabase.phpにコピー、そしての一番下を変更。
| var $default = array( 'driver' => 'mysql', 'persistent' => false, 'connect' => 'mysql_pconnect', 'host' => 'localhost', 'login' => 'login', 'password' => 'pass', 'database' => 'cakephp', 'prefix' => '', 'schema' => 'public', 'encoding' => 'utf8' ); |
ついでにXAMPPのPhpMyAdminからデータベース「cakephp」を作成しておく。
後者は、セキュリティ通信使用時の暗号化キーを設定せよという意味である。
C:\xampp\htdocs\src\php\fw\cake\app\config\core.phpの150行目付近、
| Configure::write('Security.salt', '***************************************'); |
となっている部分のキーを適当に変更する。
わかりやすい内容にしてしまうと簡単に解読されてしまうので注意。
適当にランダム文字列を作成した。
再度ブラウザからアクセスすると、無事にエラーメッセージが消えた。
Linuxの場合は一部ディレクトリのパーミッション設定が必要になるはずだが、今回はWindows環境なのでその必要はないようだ。
しかしスタイルシートやイメージが読み取れていない。
ソースを覗くと
<img src="/src/php/fw/cake/img/cake.power.gif">となっていた。
しかし実際にファイルが存在する場所はC:\xampp\htdocs\src\php\fw\cake\app\webroot\img\cake.power.gifである。
.htaccessでこのずれを修正するので、httpd.confで.htsccessによる操作を許可する。
C:\xampp\apache\conf\httpd.confの該当行を修正。
| LoadModule rewrite_module modules/mod_rewrite.so AllowOverride All |
ブラウザからアクセスすると、ようやくスタイルが適用されて見えるようになった。
本来なら.htaccessによる制御を書かねばならないのだが、CakePHPでは最初から書いてあるので何もせずとも問題はない。
今回はあっさり成功してしまった。
Windows環境でMTを動かす
以下のバージョンを使用しています。
XAMPP 1.6.6a
Perl 5.8.8-2.2.8
Movable Type 4.1
fport v2.0
リンク作成シェル拡張for Windows 2000/XP 1.42
http://www.sixapart.jp/movabletype/
MovableTypeをローカルで動かしてみることにした。
言うまでもなくWindowsではPerlは動かないので、Windows環境で動かすためにはインストールが必要である。
HTTP環境を作るためにapache、PerlとPHP、そしてデータを保存するためにMySQL、といろいろなものが必要で面倒なので、機能を一括でインストールしてくれるXAMPPを利用することにする。
http://www.apachefriends.org/jp/xampp-windows.html
とりあえずXAMPPのインストーラ版をインストールしたところ、apacheのインストールに失敗。
Port80、443が既に使用されていると言われてしまう。
fportで確認したところ、80番ポートはSkypeが使用中。
Skypeの設定→詳細→接続から使用ポートを変更。
何故デフォルトで80番ポートを使用するように設定されているのだ。
80番ポートは更にApacheおよびTomcatでも使用されていた。443もだ。
こちらのApacheはEclipseのために導入していたもの。
後からhttpd.confのListen 80を変更すればいいだけなのに、インストール自体行ってくれないから困る。
よく考えればApacheを止めればいいだけの話なのだが、何を血迷ったかApache毎削除という暴挙。
とりあえず無事にインストール成功。
Perlアドオンはzip版を使用。
解凍してC:\xamppフォルダに上書きするだけである。
ここまで来たらXAMPPモニターからApacheとMySQLを起動する。
XAMPPモニターの表示は時々おかしいが、http://localhost/にアクセスしてXAMPP管理画面が表示されれば成功である。
まず真っ先に行うのは、セキュリティタブからMySQLと.htaccessのパスワード入力である。
ついでにMySQLにログインし、MT4で使用するデータベースを作成する。
とりあえずDB名をmtとした。
テーブル等は特に作らない。
他はとりあえずどうでもいいので、Perlの設定に入る。
さて、現在の状態ではPerlのパスがC:\xampp\perl\binとなっている。
Perlスクリプトは冒頭にPerlのパス設定の記入が必要なのだが、殆どのサーバではそのパスは
/usr/bin/perl
である。
MTも御多分に洩れず、冒頭に
#!/usr/bin/perl
と記述してある。
現在の設定でPerlを使用するためには、その冒頭の文を
#!/xampp/perl/bin/perl
と書き換えなければならない。
ファイル数2000を超えるMTのパスを一々書き換えるのも馬鹿らしい話なので、#!/usr/bin/perlでC:\xampp\perl\binにアクセスできるようにシンボリックリンクを張ることにする。
ショートカットでは駄目なので、リンク作成シェル拡張を使用する。
C:\xampp\perl\binフォルダへのリンクをC:\に作成し、名前をusrに変更する。
#正しくはシンボリックリンクではなくジャンクションと言う
ではMTをインストールする。単に解凍してC:\xampp\htdocs以下のフォルダにコピーするだけである。
今回はC:\xampp\htdocs\src\php\mtに置いた。
こんなに深くする意味はあまりないのだが。
MTが提供するものはブログシステムだけなので、それとは別にブログの記事自体を置くフォルダを決める。
今回はC:\xampp\htdocs\blogとした。
この下にブログオーナー毎にadminとかaとかbとかのフォルダを作成する。
C:\xampp\htdocs\src\php\mt\mt-staticフォルダを、C:\xampp\htdocs\blogフォルダに丸ごとコピーする。
http://localhost/src/php/mt/にアクセス。
MTのログイン画面が現れたらとりあえずは一息である。
しかしログイン後、
「エラー: 'http:/localhost/src/php/mt/mt-static/'が見つかりませんでした。ファイルをmt-staticディレクトリに移動するか、設定を修正してください。 」
と表示され、正しいディレクトリを指定しても見つからない。
MTは殆どのことをブラウザ上から設定でき、MTの動作に必要なmt-staticフォルダも本来は設定可能である。
しかし、シンボリックリンクを使用してパスを作成した場合、何故かmt-staticフォルダを見つけられなくなってしまうらしい。
その場合ひとつのファイルを編集しなければならない。
ついでに/src/php/mtと深いことになっているブログのアドレスをもう少しわかりやすいところに移動することにする。
http://www.koikikukan.com/archives/2008/03/07-030000.php
C:\xampp\htdocs\src\php\mt\mt-config.cgi-originalをコピーし、mt-config.cgiに改名する。
CGIPathおよびStaticWebPathをそれぞれ正しいパスに書き換え、使用するデータベースの情報を記入する。
使用しないデータベース情報については削除してかまわない。
XAMPPはMySQL推奨なので、今回はMySQLを使用する。
当然環境によって変更は必要だが、今回は以下のようなセッティングになった。
CGIPath http://localhost/src/php/mt/
StaticWebPath http://localhost/blog/mt-static/
ObjectDriver DBI::mysql
Database mt #MySQL内で使用するデータベース名
DBUser root #MySQLのユーザ名
DBPassword root #MySQLのパスワード
DBHost localhost #localhost固定
MySQL以外のDBは使用しないのでコメントアウトしておく。
ここまで終了したらhttp://localhost/src/php/mt/にアクセス。
今度は無事にmt-staticディレクトリを発見することができた。
システムチェック画面をクリック。
CGIモジュールが見つからなければPerlのインストールが大失敗ということだ。
今回は無事だった。
データストレージモジュールはmysqlが必ず入っているはずなので問題ないはず。
オプションモジュールは、なくてもいいがあれば便利というモジュールだ。
今回は入っていなかったImage::Magick、Crypt::DSAおよびMail::Sendmailをインストールすることにする。
コマンドプロンプトから
cd C:\xampp\perl\bin
ppm
と入力。
ppmはCPANと同じようなものだが、GUIになっていて探しやすい。
しかしppmにデフォルトで登録されているサーバにはImage::Magickが入っていないので
http://www.bribes.org/perl/ppm
http://theoryx5.uwinnipeg.ca/ppms
両サイトを登録、そして足りないモジュールを検索してインストールする。
再度システムチェック画面を表示し、インストールされていれば無事成功である。
さて、無事にブログを作成できたが、今回の対処ではまだ問題がある、
インストール時に起きた「URLが不正です」エラーが、スタイルの選択画面でも発生するのだ。
http://sanahi.seesaa.net/article/101767549.html
ppmで確認してみたがLWPはインストールされている。
はたしてどうなる。