続き
今回使用したソフトウェアのバージョン
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はインストールされている。
はたしてどうなる。
PHP4ではprivateや__constructといったオブジェクト指向的書き方に必要な要素が使えないので、今回からの内容はPHP5以上の話となります。
クラスを使って何が便利かというと、クラス内の関数でデータを共有できる点です。
普通にサブルーチンとして関数を使用した場合、必要なパラメータをすべて引数として渡さなければなりません。
その点、クラスの場合は一旦適当なsetParameterを呼び出せば、プログラム終了までその値を持ち続けてくれます。
HTTPの限界上、さすがにリロードされたりすると消えてしまいますが、使いこなせば非常に便利です。
はっきり言ってデータと手続きの一体化とか、ポリモフィズムとか、上に比べるとどうでもいい話です。
どうせ規模が大きくなったらまたわからなくなりますし。
とりあえず図形の面積を求めるクラスを作ってみましょう。
area.php
class Area{ //変数宣言 private $base=0; private $height=0; //セッター public function setBase($base){ $base=$base+0; $this->base=$base; } public function setHeight($height){ $height=$height+0; $this->height=$height; } //ゲッター public function getTriangle(){ return $this->base*$this->height/2; } public function getQuadrangle(){ return $this->base*$this->height; } public function getOval(){ return $this->base*$this->height*M_PI/4; } } |
まず変数宣言部分で、クラス内で共通して使う変数を定義します。
privateをつけると、クラス外からその値を直接参照することが出来なくなります。
セッターメソッドは、クラス内変数に値を設定するメソッドです。
上記のようにprivate変数はクラス外から変更することが出来ないため、setBase等のpublic関数を通じて行わなければなりません。
引数として渡される値は数値を想定していますが、必ず数値が渡されるとは限りません。
うっかり、あるいは故意に文字列やスクリプトが渡されるかもしれません。
それを防ぐためにバリデートが必須となります。
今回は手っ取り早く+0とすることで数値化しています。
PHPのデータ型の扱いは極めてアバウトなので、文字列だろうがなんだろうが+0すると数値にしてくれます。
まあ配列やオブジェクトが来ると死にますが、今回はそこまでやっていません。
ゲッターメソッドは、クラスから値を受け取るメソッドです。
クラス内のprivate変数の値を参照したい場合など、このメソッドを通じて値の引取りを行います。
このクラスを呼び出す側で、
$area=new Area();としてインスタンス化し、
$area->setBase(10);$area->setHeight(20);
としてクラス内の変数$baseと$heightに値をセットしてあげます。
その後はいつでも$area->getTriangle()や$area->getOval()で値を取り出すことが出来るようになります。
値を変更したくなったら再度$area->setBase(50);等としてやればいいです。
index.php
include_once('area.php'); //インスタンス化 $area=new Area(); //セッター $area->setBase(10); $area->setHeight(20); //ゲッター print($area->getTriangle().'<br />'); print($area->getQuadrangle().'<br />'); print($area->getOval().'<br />'); //値を変化させてみる $area->setBase(50); print($area->getTriangle().'<br />'); |
以上のクラスで行ったことを、関数で行うと以下のようになります。
index2.php
$Base=10; |
量的には関数のほうが少なくてすみますが、呼び出す側では毎回getTriangle($base,$height)というふうに引数を入れてやらなければならなかったり、関数毎に引数のチェックをしたりしなければならなかったりと手間がかかります。
ただまあ、タイトルに反していますが、これが実用的なクラスかといわれると断じて否と言えます。
面積程度のことならば直接書いたほうがよっぽど早いですし。
次回はもう一歩だけ実用に近づいていくとしましょう。
KWICを作ってみるの続きです。
あちらでフロント部分を作成したので、今回はバックエンド部分の作成です。
整形は面倒ですが、プログラム的には特に難しいことを行っているわけではありません。
innerHTMLがGET形式で送られてくるので、それを受け取ってテキストを作成して返す、それだけです。
返すといっても、行うことはブラウザに表示するのと全く同じです。
ブラウザからのリクエストの場合、表示した文はブラウザの画面上に表示されますが、XMLHttpRequestからのリクエストへの返答はresponseTextの中に入ります。
'gingatetsudono_yoru.txt'は、青空文庫から拾ってきてください。
このプログラムの場合、読み込むファイル名をHTML側で決めているので、簡単に詐称が可能です。
隠しておきたいサーバ内のファイルを読み出されたりしかねませんので、サーバ側で必ず引数のチェックを行ってください。
以下の例の場合は拡張子がtxtかどうかというだけの緩い制限になっていますが、本来は許可するファイルのリストを作り、それ以外であれば不許可にすべきです。
特に、以下のコードそのままだとディレクトリトラバーサルの格好の餌食となります。
それ以前に、HTMLにファイル名を出さなくていい方法もありますので、実用する際はできるだけセキュリティ的に安全な方法を選びましょう。
inclemental_server.php
<?php |
一見面倒ですが、やっていることはテキストからHTML文を作成しているだけです。
XMLHttpRequestはUTF-8しか受け付けません。
そのために表示文字列の文字コードをUTF-8にする必要があるのですが、PHPファイルがEUC-JP、テキストファイルとjsはUTF-8、そしてHTMLファイルはSJISで書かれているという不健康極まりない状態です。
テキストファイルのUTF-8データと、PHPで付け足したEUC-JPのデータを、一見無用な文字コード変換を入れることで無理矢理統一しています。
まあ揃えられるなら全部揃えたほうが安全です。
さあ、これでリロードを伴わない動的な検索サービスを作成することができました。
http://yuubiseiharukana.creativeroot.jp/js2/2-5-1.html
さて、今回は直接HTMLタグまで書き込んだ上で返信を行っていますが、これはMVCの観点からあまり正しい方法とは言えません。
データの送受信はJSONで行い、jsとcssで装飾を行ったほうがレイアウト変更等にも対処しやすく便利です。
が、個人的にJavaScriptでのパースが苦手なのでPHPで行っています。
XML?
企業同士ならともかく、個人ユースでXMLは資源の無駄としか言い様が。
クラスの特徴のひとつが、継承です。
継承とは、あるクラスを引き継いで、新たな別のクラスを作成することができるという機能です。
まあ早速ですが作ってみましょう。
とりあえず乗り物クラスでも作ってみましょう。
class Vehicle{ |
名前だけしか決まってないクラスです。
さて乗り物だけでは範囲が広すぎるので、次は飛行機クラスを作ってみましょう。
この場合、一から飛行機クラスを作るのではなく、さきほど作った乗り物クラスを引き継いで作成することができます。
class Airplane extends Vehicle{ |
extendsで親クラスを引き継ぐことができます。
Airplaneクラスにはどこにも$aNameプロパティ、setNameメソッドおよびgetNameメソッドがありませんが、
親クラスであるVehicleクラスで定義されているため、きちんと使用することができます。
もちろん飛行機クラスをさらに引き継いで、戦闘機クラスを作ることもできます。
乗り物クラスを継承して自動車クラスや船クラスを作ったり、このように元のクラスを利用してどんどん拡張していけるのがクラスの特徴です。
ただ、あんまりやりすぎると、絶対使わないような死にクラスだらけになったり、仕様変更で使えなくなったり、同名なのに動作が違うメソッドが存在したりというJavaみたいな悲惨なことになるので程々にしましょう。
PHPのクラスにはいくつか問題点が存在します。
オーバーロードができません。
引数の有無で振り分ける等の対応をする必要があります。
子クラスにコンストラクタが存在する場合、親クラスのコンストラクタは実行されません。
親クラスのコンストラクタを実行させたい場合、super()的な指定方法が存在しないので明示的に指定せねばならず微妙に不便。
前回も挙げましたが、クラスの外から自由にオブジェクトを操作できてしまうという問題点があります。
その方向を突き詰めたプロトタイプベースという書き方もありますが(JavaScriptが筆頭)、PHP5では逆にJavaやC++のような実装が追加されました。
クラスはデータベースに接続するときのDB種類による差異を吸収したり、ユーザのデータを簡単に取り扱いたい場合などに使用すると便利です。
ただし、継承を繰り返したりしていると、後から見て何をやっているのか絶対にわからなくなりますので、コードよりヘルプが多いくらいの勢いで注釈を書くようにしましょう。
Javaのせいで難しいとレッテルを貼られてしまったクラスですが、さほど難しいものではありません。
ただ単に、幾つかの関数を纏めて一塊にしてみました、それだけのことです。
特にPHPのクラスは実のところ連想配列そのものです。
継承など面倒な概念も幾つかありますが、まあそれはおいおい。
ちなみに以下はPHP4での話です。
PHP5でも基本は同じだと思いますが、よりオブジェクト指向的な書き方ができるようになっています。
さて、さっそくクラスを作ってみましょう。
作り方は簡単です。
関数を作る要領で、functionのかわりにclassで全体を囲むだけです。
また、クラスの外部から操作を行うためのメソッドというものを書く必要がありますが、こちらは関数そのものです。
まあとりあえず作ってみましょう。
class Animal{ //名前を決めるメソッド //名前を取得するメソッド |
クラスを初めて触ったときに一番わかりにくい概念が、インスタンス化だと思います。
クラスは関数のように書いただけでは使用することができず、インスタンス化という儀式を通さないといけません。
$AnimalA = new Animal;
こうすることでAnimalクラスが$AnimalAという名前で実体化されました。
以降は$AnimalAという実体化されたクラスに対して操作を行うことができます。
Animalクラスの中のsetName関数に触ってみましょう。
$AnimalA->setName("dog");
クラスとクラス内関数や変数との繋ぎは「->」で表します。
さて、上の関数でどうなったかというと、クラス内の変数$aNameに、setName関数で与えた引数"dog"が設定されました。
同様に、
$AnimalA->getName();
という関数を実行すると$AnimalAクラス内の変数$aName、すなわち先ほど設定した"dog"が返ってきます、
以後クラス内の変数には、直接触るのではなく、常に関数を通じてアクセスすることになります。
今度はもうすこし複雑なクラスを作ってみます。
class Circle{ var $radius; //半径をセット //面積を取得 |
pow($this->radius,2)*M_PIは半径から面積を計算しています。
M_PIは円周率を表す定数(=3.14159265358979323846)、pow(a,b)はaのb乗です。
関数の恐ろしく豊富なPHPですが、何故か^は使えません(何故かxorにアサインされている)
$circle1=new Circle;
とすることで$circle1をCircleクラスとして扱うことができるようになります。
$a->setRadius("3");
で半径3をセットし、
$a->getArea();
で先ほどセットした半径から計算した円の面積を取得することができます。
さてコンストラクタという見慣れぬ名前の関数があります。
クラス名と同名の関数は、クラスのインスタンス化と同時に実行される、という特徴があります。
この場合、$circle1=new Circle;と書いた時点で自動的にfunction Circleも実行されるのです。
Circle関数が何をやってるかというと、引数を半径にセットしています。
つまり、
$circle2=new Circle(3);
と書けば最初から半径に3がセットされるというわけです。
コンストラクタは主に変数の初期化等に使われます。
クラスを使って何が便利かというと、同じ定義を使いまわせることです。
例えばAnimalクラスは以下の連想配列とほぼ同義です。
$AnimalA=array("aName",$name);
しかし、同じ定義の連想配列を他で使用したくなった場合も再度同じことを書かなければなりません。
$AnimalB=array("aName",$name);
クラスならば最初に定義してしまえば、何度でも使いまわすことができます。
$AnimalA = new Animal;
$AnimalB = new Animal;
この場合は中身がひとつだけなのでたいして変わりませんが、キーが何十個もあるような複雑な連想配列を考えてみるとクラスの便利さがわかるでしょう。
同じ処理は関数に纏めましょう、というのと同じように、同じオブジェクトはクラスに纏めましょう、ということです。
さて、PHPのクラスはほぼ連想配列なので、何気に連想配列的な操作を行うことができます。
例えば$AnimalA->aName="dog";と書くと、メソッドを介さずに直接変数をセットできてしまいます。
同様にprint_r($AnimalA);と書いてしまうとクラス変数の中身が全部表示されてしまいます。
それどころか$AnimalA->aBark="wanwan";などと元のクラスに存在しないプロパティを作成できてしまったりとやりたい放題です。
オブジェクト指向としては非常に正しくない実装なので大問題です。
PHP5ではprivateなどのアクセス権を設定できるようになっており、上記の問題点は改善されつつあります。
さて次回はクラスの継承についてと言いたいのだが、そこまでやったところで個人レベルじゃどうせ使わないんだよなあ継承。
継承すればするほどオブジェクト指向の目的から外れていくと思うのは私だけでしょうか。
index.php
<form method="POST" action="./validate.php"> 氏名:<input type="text" name="shimei" maxlength="64"><br /> メアド:<input type="text" name="mail" maxlength="64"><br /> 性別: <input type="radio" name="sex" value="1">男 <input type="radio" name="sex" value="2">女<br /> <input type="submit" name="soushin" value=" 確認 "> </form> |
まあよくあるフォームです。actionを指定したのでvalidate.phpを作らなくてはなりません。
何をするかというと入力値のチェックです。
validate.php
#初期設定 #入力をチェック #エラーの有無で分岐 |
簡単に流れを書くと以上のようになります。
性別は入る値が1か2に決まっているので、判定も1か2かで行います。
radio、checkbox、hiddenは最初から決められている値以外に変更することはできないように見えますが、実は簡単に行えます。
外部から入ってくるデータは基本的に汚染されているものだと考えてチェックを行う必要があります。
保存部分は簡単に作成します。
保存部分は「,」で区切っているだけなので、入力値に「,」を入れられると困ったことになりますが、今回はまあパスします。
form.txtは予め作成しておかねばなりませんが、これで保存部分ができました。
validate_ok.php
$data=$shimei.','.$mail.','.$sex; $fp = fopen("form.txt","r+"); flock($fp,LOCK_EX); fputs($fp,$data); fclose($fp); |
さて、入力のエラーがあった場合もとの入力画面に戻されるのはよくある光景ですが、このときにせっかく登録した内容が消えていたりすると非常にがっかりです。
入力した内容を保持しておいて、入力画面に戻ってきた場合には最初から表示されるようにしましょう。
新たに以下のようなエラー画面を用意して、validate.phpに組み込んであげればよいことになります。
内容的には、valueにフォームから送信されてきた値をセットしているだけです。
htmlspecialcharsという不思議な関数ですが、入力値に「"><script>alert("XSS");</script>」と入れてみるとその理由がわかります。
validate_error.php
<?php print($error_message_array); ?> <form method="POST" action="./validate.php"> 氏名:<input type="text" name="shimei" maxlength="64" value="<?php print(htmlspecialchars($shimei)); ?>"><br /> メアド:<input type="text" name="mail" maxlength="64" value="<?php print(htmlspecialchars($mail)); ?>"><br /> 性別: <input type="radio" name="sex" value="1"<?php if($sex==1){print(" checked");} ?>>男 <input type="radio" name="sex" value="2"<?php if($sex==2){print(" checked");} ?>>女<br /> <input type="submit" name="soushin" value=" 確認 "> </form> |
しかしindex.phpとほとんど同じ内容なのに、わざわざもう一度作るのも馬鹿みたいです。
多くの言語と違い、PHPはデフォルトの設定では、未定義の変数を呼び出したとき中身が空として扱ってくれます。
従ってvalidate_error.phpをそのままindex.phpにリネームすれば何事もなく動いてくれます。
ただしerror_reporting等で厳しくエラーチェックしているとエラーが発生します。
その場合はまた変数の存在確認などの処理を行ったりしなければなりません。
面倒なので全部纏めてしまいましょう。
index2.php
<?php <?php print($error_message_array); ?> <?php |
フォームのデータを自分自身に送っています。
$_POSTの値が存在すればそれを代入し、存在しなかった場合は空として扱ってくれます。
1ページで投稿フォームを作成することができました。
細かな部分でまだ問題がありますが、ひとまずは以上で完成です。
次回は投稿確認画面でも作りましょうか。
ところでこのブログって横幅どうにかならないんですかね?
実用プログラムの第一歩として、よくあるアクセスカウンターを作ってみましょう。
その前にファイルの扱いについて簡単に説明しておきます。
とりあえず初心者が躓く点として、PHPはファイルを直接扱うことができず、ファイルポインタという変数を通して扱わなければならないことがあげられます。
$fp = fopen("counter.txt","r+");
というふうに、counter.txtを$fpという変数に代入し、以後変数を通してファイルを操作することになります。
PHPによるファイルの操作はけっこう不自由で面倒なので、今後は徐々にデータベースを使用するようにしていきます。
以下の2ファイルを用意します。
counter.txt
0 |
counter.php
<?php //ファイルを書き込み可能状態で開く $fp = fopen("counter.txt","r+"); //ファイルから1行読み込む $count = fgets($fp); //カウンターを1増やす ++$count; //ファイルポインタを戻す rewind($fp); //ファイルに書き込む fputs($fp,$count); //ファイルを閉じる fclose($fp); ?> <html> <head></head> <body> アクセスカウンター<br /> <div align="center"><?php print($count); ?> </div> </body> </html> |
counter.txtは改行を入れず、アップロード時にパーミッションを666に変更します。
注意すべきは一点、rewindです。
ファイルポインタは、今自分がどの場所にいるかという情報、要するにカーソルの位置を記憶しています。
fgets等でファイルの内容を取得すると、その場所までファイルポインタの位置が移動します。
この場合「1」というファイルの中身を読んだ後、「1」の後ろの部分になるということです。
その状態で書き込みを行ってしまうと、ファイルの中身は「12」ということになってしまうので、一旦rewindでファイルポインタを最初に戻しています。
さて、上のプログラムはとりあえず動くのですが、問題があります。
ほぼ同時に二人がアクセスした場合を考えてみます。
A | B |
fopen | |
fgets(=1) | fopen |
$count++(=2) | fgets(=1) |
fputs(=2) | $count++(=2) |
fclose | fputs(=2) |
fclose |
二人がアクセスしたにもかかわらず、カウントが1しか増えていません。
同時にアクセスされた場合の処理を考えていないからです。
アクセスカウンター程度なら一人や二人ずれたところでどうでもいいですが、これがたとえば銀行の送金処理で起こったりしたらとんでもないことになります。
このような問題を防ぐためには、同時に一人しかファイルにアクセスできないようにすればいいわけです。
PHPには簡単にファイルロックを行える関数flockがあるので使用することにしましょう。
counter2.php
<?php //ファイルロック |
こうすることで、ほぼ同時にリクエストが来た場合でも後者が待たされるので、正しい結果を出すことができます。
A | B |
fopen | |
fgets(=1) | ↓ |
$count++(=2) | ↓ |
fputs(=2) | ↓ |
fclose | fopen |
fgets(=2) | |
$count++(=3) | |
fputs(=3) | |
fclose |
ちなみにロック解除のflock($fp, LOCK_UN)は、fcloseの際に自動的に行われるので書かなくてもいいです。
上のプログラムでまあとりあえず配布可能レベルにはなっていますが、できれば「ファイルを作成してパーミッションを云々」というような余計な手間をかけさせないほうがいいでしょう。
自動作成するようにしてみます。
defineは定数を定義するもので、ディレクトリ名やファイル名等、ファイル全体で使用し、定義後は変更しないデータを突っ込んでおきます。
counter3.php
<?php //カウンターファイルが存在するかどうかで分岐 |
ファイルが存在しない場合のfopenのみエラー処理を行っている理由は、パーミッションの関係でファイルの作成が行えない可能性があるからです。
fopen,flock,fgets,fputs,fcloseは返り値に成功失敗を渡すので、本格的にプログラム開発をするならば各関数についてそれぞれエラーチェックを行わなければならないのですが、まあさすがにそこまではいいでしょう。
カウンターひとつでいろいろと面倒ですね。
動かすだけならもっと適当でもいいのですが、不用意なバグを防ぐためにも丁寧な書き方をお勧めします。
まあ、人のことは言えませんが。