DataBase(MySQL)

MySQL

MySQLとはオープンソースRDBMSです。オープンソースといっても高速性や信頼性に定評があり、多数の商用サイトにも用いられている実績の高いDBソフトウエアです。現在の最新バージョンは5.1(08/12現在)であり、5系よりViewやトリガーなども利用できるため、新規に利用する場合は5系を利用したらよいでしょう.(4系の安定バージョンの4.1の利用もまだ多い)
また、MySQLは非同期レプリケーションの構築が容易でこの機能により複数台のサーバにDBのレプリカを作ることが可能です。大量のアクセスのあるアプリケーションもDB負荷分散の技術で処理を行うことができます。

MySQLの基本操作

MAMPで利用できるMySQLはuser:root pass:root port:8889で立ち上がっています。MySQLのコマンドは
/Applications/MAMP/Library/bin/
以下に色々あります。試しに接続してみます

bash-3.2$ /Applications/MAMP/Library/bin/mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.0.41 Source distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema | 
| mysql              | 
| test               | 
+--------------------+
3 rows in set (0.00 sec)

デフォルトのDBがあります。prog9というDBを作ってみます

bash-3.2$ /Applications/MAMP/Library/bin/mysqladmin -u root -p create prog9

もう一度接続して show databases; すると prog9が増えているはずです.もう少し実験してみましょう。簡単なテーブルを作成します

mysql> create table sample ( id int auto_increment, name varchar(64), age int, primary key(id));
Query OK, 0 rows affected (0.00 sec)

mysql> show tables;
+-----------------+
| Tables_in_prog9 |
+-----------------+
| sample          | 
+-----------------+
1 row in set (0.00 sec)

mysql> show fields from sample;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment | 
| name  | varchar(64) | YES  |     | NULL    |                | 
| age   | int(11)     | YES  |     | NULL    |                | 
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

作ったテーブルに値を入れてみます

mysql> insert into sample (name,age) values ('tanaka', 25);
Query OK, 1 row affected (0.00 sec)

mysql> insert into sample (name,age) values ('yamada', 30);
Query OK, 1 row affected (0.00 sec)

mysql> select * from sample;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | tanaka |   25 | 
|  2 | yamada |   30 | 
+----+--------+------+
2 rows in set (0.00 sec)

perlからMySQLを操作する

perlからDBを操作する場合は DBIモジュールとMySQL用のドライバDBD-MySQLが必要です。下の例はmac-portsからインストールした環境(perl5.8, p5-dbi, p5-dbd-mysql)での例です

bash-3.2$ cat test.pl 
#!/opt/local/bin/perl

use strict;
use warnings;
use Data::Dumper;
use DBI;

my $dbh = DBI->connect('DBI:mysql:prog9:127.0.0.1:8889', 'root', 'root');
my $sth = $dbh->prepare("SELECT * FROM sample");
$sth->execute;
while(my $ref = $sth->fetchrow_hashref) {
    print Dumper($ref);
}
$sth->finish;
$dbh->disconnect;
bash-3.2$ 
bash-3.2$ ./test.pl 
$VAR1 = {
          'name' => 'tanaka',
          'id' => '1',
          'age' => '25'
        };
$VAR1 = {
          'name' => 'yamada',
          'id' => '2',
          'age' => '30'
        };

PHPからMySQLを操作する

phpinfoの画面で組み込む済みモジュール一覧でmysqlまたはmysqliがあれば利用できますmysqliはmysql拡張モジュールです (マニュアル: mysql , mysqli )
pearライブラリにはDB(サポート終了.非推奨)やMDB2がありますが,標準のmysqliも使いやすいです。以下はmysqliのサンプル例です

bash-3.2$ cat test.php 
<?php
    $mysqli = new mysqli("127.0.0.1", "root", "root", "prog9", 8889);
    $result = $mysqli->query("SELECT * FROM sample");
    while($row = $result->fetch_array(MYSQLI_ASSOC)) {
        print_r($row);
    }
    $mysqli->close();
?>
bash-3.2$ /Applications/MAMP/bin/php5/bin/php test.php 
Array
(
    [id] => 1
    [name] => tanaka
    [age] => 25
)
Array
(
    [id] => 2
    [name] => yamada
    [age] => 30
)

地図APIを使ったアプリケーション プログラム演習8

今回はすこし、ウエブ検索APIから離れて最近流行の地図を使ったアプリケーションを作ってみましょう。地図(MAP)をサイトに掲載する方法で代表的なものは

が代表的です。どちらも JavaScript版とflash版がありますが、一般的にはGoogleJavaScript版の利用が多いようです。
今回は、あえて?Yahoo!地図API JavaScript版を利用した地図アプリを作ってみますが、Google版でも多少の細かい処理の違いはありますが、簡単に移植可能です。今回作成するアプリケーションは下記の画面イメージです

仕様は以下の通りです

【仕様】

  • 画面は地図部と住所表示部と最寄り駅リスト表示部から構成されます
  • 住所表示部は現在標示されている地図の中心の住所(文字列)が表示されます
  • 最寄り駅リスト表示部は地図中心地点から最寄りの駅20件が近い順位リストされます
  • 地図部では中心点から最寄りの駅20件の駅にアイコンが表示されます
  • 地図はマウスで移動可能で移動のたびに上の情報が更新されます

中心地点の住所、および最寄り駅情報は同じくヤフー地図APIローカルサーチAPIを利用します。

汎用的なProxyの作成

前回の演習でも説明したajaxのクロスドメイン制約が今回も立ちはだかります。今回は汎用的に使えるProxyを作成しましょう。また、今回のアプリではajaxのデータの形式としてJSONを用います。外部APIから受信したXMLJSONに変換できるようにProxyを作成します

bash-3.2$ cat prog8/proxy.php 
<?php
    require_once('XML/Unserializer.php');

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $_GET['url']);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $xml = curl_exec($curl);
    curl_close($curl);

    if(!isset($_GET['output']) || $_GET['output'] == 'xml') {
        header("Content-type: text/xml; charset=utf-8");
        print $xml;
    } else {
        $unserializer = new XML_Unserializer(array(
                     XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE => 'parseAttributes'
                                                   ));
        $unserializer->unserialize($xml);
        $hash = $unserializer->getUnserializedData();
        if($_GET['output'] == 'json') {
            header("Content-type: application/json; charset=utf-8");
            print json_encode($hash);
        } else if($_GET['output'] == 'debug') {
            header("Content-type: text/html; charset=utf-8");
            print "<pre>\n";
            print_r($hash);
            print "</pre>\n";
        }
    }
?>

作成したProxyは単純にURLに

url=取得したい(外部)API&output=[xml|json|debug]

という形を取ります. output=jsonjson形式. output=debugでprint_r(hash)形式で出力します。json形式への変換はjson_encodeというPHPの組み込み拡張関数を用います.json_encodeは演習4で紹介したphpinfoで

と表示されていればそのまま使えます。ない場合は pear(に登録予定の) Services_JSONで代用できます。またXMLからHashに変換する必要があるのでそこは同じく演習4で紹介したXML_Unserializerを用いました。これで

http://localhost:8888/prog8/proxy.php?output=json&url=http%3A%2F%2Fmap.yahooapis.jp%2FLocalSearchService%2FV1%2FLocalSearch%3Fappid%3Dp3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG%26dist%3D10%26n%3D20%26category%3Dstation%26lat%3D35.67777222222222%26lon%3D139.77048055555557
http://localhost:8888/prog8/proxy.php?output=json&url=http%3A%2F%2Fmap.yahooapis.jp%2FLocalSearchService%2FV1%2FLocalSearch%3Fappid%3Dp3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG%26dist%3D10%26n%3D1%26category%3Daddress%26lat%3D35.67777222222222%26lon%3D139.77048055555557

で、最寄り駅(category=station)および中心住所(category=address)が取得できます

アプリ作成

上の完成例の画面を元に雛形となるHTMLを次のように作成します

bash-3.2$ cat prog8/index.html 
<html>
<head>
<title>Sample Program 8 JavaScript-1</title>
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<script src="./jquery-1.2.6.min.js"></script>
<script src="http://map.yahooapis.jp/MapsService/js/V2/?appid=p3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG"></script>
<script src="./prog8.js"></script>
</head>
<body>

<div id="address"></div>
<div id="map" style="width:600px;height:400px;float:left;"></div>
<div id="station" style="font-size:10pt;"></div>

</body>
</html>

jqueryjavascript版地図apiをhead部に読み込んでます。作成するjsファイルはprog8.jsです.アドレス表示部はdivのid="address" 最寄り駅リスト部はdivのid="station" です。配置およびレイアウトは任意でOKです。
prog8.jsは以下のようになります

bash-3.2$ cat prog8/prog8.js 
var appid = "p3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG";
var localApi = 'http://map.yahooapis.jp/LocalSearchService/V1/LocalSearch?appid=' + appid;
var _map;

function getStations() {
    var center = _map.getCenter();

    var api = localApi + '&dist=10&n=20&category=station';
    api += '&lat=' + center.lat;
    api += '&lon=' + center.lon;

    var proxyParam = 'output=json&url=' + encodeURIComponent(api);
    $.ajax( {
                type: "get",
                url: "./proxy.php",
                dataType: "json",
                data: proxyParam,
                success: function(data) {
                    _map.clearIcon();
                    var stationList = "";
                    for(var i=0;i<data.Item.length;i++) {
                        _map.addIcon(   i,
                                        data.Item[i].DatumTky97.Lat + ',' + data.Item[i].DatumTky97.Lon,
                                        data.Item[i].Title,
                                        'L2',
                                        data.Item[i].Title
                                        );
                        stationList += data.Item[i].Title + "<br>";
                    }
                    $("#station").html(stationList);
                }
    });
}

function getAddress() {
    var center = _map.getCenter();

    var api = localApi + '&dist=10&n=1&category=address';
    api += '&lat=' + center.lat;
    api += '&lon=' + center.lon;

    var proxyParam = 'output=json&url=' + encodeURIComponent(api);
    $.ajax( {
                type: "get",
                url: "./proxy.php",
                dataType: "json",
                data: proxyParam,
                success: function(data) {
                    $("#address").html(data.Item.Title);
                }
    });
}

$(window).load(function() {
    _map = new YahooMapsCtrl("map", "35.40.39.980,139.46.13.730", 4);
    _map.addEvent(YEventType.MAP_MOVED, getStations);
    _map.addEvent(YEventType.MAP_MOVED, getAddress);
});

ものすごく簡単に実現できることが分かると思います。 windowのloadで地図の生成. ドラック時に読み込む関数を2つ(最寄り駅を取得表示する getStation関数, 現在住所を取得表示するgetAddress関数)登録します。
それぞれの関数は jQueryajax機能でProxy経由にて取得します。

dataType: "json"

が今回のポイントです. 受信データは json形式をevalされた状態でなります。すなわちJavaScriptのオブジェクトとして即時アクセス可能です。オブジェクトがどのような形かを確かめるためにproxyのoutput=debugを使うと良いでしょう。Jsonの形式で扱うと、前回面倒だった getElementsByTagName などの記述が不要になり、シンプルなコードがかけることが分かります。

今回のプログラム一式は
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog8
にありますので、必要に応じて参照してください。

tips(緯度経度と測地系
地図で緯度経度をあつかうアプリケーションを作成する場合、ピンデータ緯度経度の測地形式が地図の測地系と一致していない場合は正確な地図の位置にピンが立たない現象が発生します。これは

という2つの測地系があるためです。たとえばヤフーの地図はGoogleの地図は世界測地系をデフォルトで記述されています。ヤフーのローカルサーチAPIの応答の各ピンの座標値を日本測地系を使った場合は正しい位置にピンが立ちません。ローカルサーチAPIは2つの測地系どちらも応答できる機能を持っていますが、その他のものはどちらか一方であることが多いので、ピンの位置があわない場合はこの測地系を疑うと良いでしょう
ヤフーの地図APIはデフォルトでは日本測地系です。YahooMapsCtrlで地図オブジェクト生成時に世界測地系を指定することも可能です(参照)

Ajaxを用いたウエブページ プログラム演習7

前回のJavaScriptの説明をふまえて、ウエブAPIを用いたCGI版のアプリケーションと同様の機能を持つajaxの仕組みを利用したウエブ検索アプリケーションを作成してみましょう。

クロスドメインの制約

JavaScriptAjaxを用いて外部のリソースを取得する場合、その「外部リソースは同じドメインでなければいけない」というクロスドメインの制約があります。クロスドメイン制約を回避する方法としてiframeやJSONPを用いる方法がありますが、今回はその制約の元、アプリケーションを作成してみましょう。
といってもヤフーAPIは外部ドメインであるため、Proxyの役割をもつ中継サーバをアプリケーションを動作させるドメインと同じ場所にたてる必要があります。イメージ的には

|APIサーバ(外部ドメイン)|<---PHPなど--->|Proxyサーバ(同ドメイン)|<---ajax--->|JavaScriptアプリ|

という形です。Proxyサーバ演習6で利用したアプリケーションを拡張して代用させましょう。

演習6のアプリケーションをProxyとして拡張する

演習6のアプリケーションのURL仕様はqueryにキーワードをactionにはウエブ(web)か画像(image)かの選択をしてHTMLをoutputするものでした。URL例:

http://localhost:8888/prog6/index.php?query=dvd&action=web

これを拡張して outputのパラメータを追加しこれがxmlであればXMLとして応答を返すように拡張しましょう。すなわち

http://localhost:8888/prog6/index.php?query=dvd&action=web&output=xml

とすると同内容のコンテンツがXML形式で応答させます。
プログラムの変更箇所は
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/changeset/11
になります。肝の部分は Viewを管理する以下のクラスです。

bash-3.2$ cat prog6/VIEW/ViewManager.class.inc 
<?php
    class ViewManager {
        public static function viewSmarty($template, $params = array(), $output = 'html') {
            if($output == "html" || $output =="") {
                require_once('Smarty/Smarty.class.php');
                $SMARTY_TEMPLATE_DIR = './VIEW/';

                $smarty = new Smarty();
                $smarty->template_dir = $SMARTY_TEMPLATE_DIR;
                $smarty->compile_dir  = $SMARTY_TEMPLATE_DIR . 'cache/';
    
                $smarty->assign('p', $params);
                header("Content-type: text/html; charset=utf-8");
                $smarty->display($template);

            } elseif($output == "xml") {
                require_once('XML/Serializer.php');
                $serializer = new XML_Serializer(array(
                                                       "rootName" => "myXML",
                                                        XML_SERIALIZER_OPTION_DEFAULT_TAG => 'item'
                                                    ));
                if($serializer->serialize($params)) {
                    header("Content-type: text/xml; charset=utf-8");
                    print $serializer->getSerializedData();
                }
            }
        }
    }
?>

$outputの引数を追加しそれがxmlであった場合はSmartyでのoutputではなくXML_Serializerを用いて$paramをそのままXML化し出力させています。
これで準備が完了です。

ajaxを用いた検索アプリケーション (ライブラリを用いない場合)

では、JavaScriptのアプリを作ってみましょう.前回、JavaScriptライブラリを用いると簡易に開発できることを紹介しましたが、まずはこれを用いない場合にどのようになるかを体験するために、ライブラリは用いずに作成することにします
基本となるHTMLは

bash-3.2$ cat prog7/index-1.html 
<html>
<head>
<title>Sample Program 7 JavaScript-1</title>
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<script src="./prog7-1.js"></script>
</head>
<body>

<form>
<input id="searchQuery" type="text">
<select id="searchOpt">
    <option>web</option>
    <option>image</option>
</select>
<input id="searchBtn" type="button" value="search">

<div id="searchResult"></div>

</body>
</html>

JavaScriptで操作する各要素にidを付与しています。またmetaにjavascriptを追加。script srcに 今回作成する jsファイルを読み込むようにしています。では javascriptファイル prog7-1.jsを説明します

bash-3.2$ cat prog7/prog7-1.js 
function createHttpRequest() {
    if(window.ActiveXObject) {
        try {
            return new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
            try {
                return new ActiveXObject("Microsoft.XMLHTTP");
            } catch (e2) {
                return null
            }
        }
    } else if(window.XMLHttpRequest) {
        return new XMLHttpRequest();
    } else {
        return null
    }
}

function getAction() {
    var selectList = document.getElementById("searchOpt");
    for(var i=0; i<selectList.options.length;i++) {
        if(selectList.options[i].selected) {
            return selectList.options[i].value;
        }
    }
}

function doSearch() {
    var query = document.getElementById("searchQuery").value;
    var action = getAction();

    var httpObj = createHttpRequest();
    httpObj.open("GET", "/prog6/index.php?query="+query+"&action="+action+"&output=xml", true);
    httpObj.onreadystatechange = function () {
        if(httpObj.readyState == 4) {
            displayResult(httpObj.responseXML);
        }
    }
    httpObj.send("");
}

function displayResult(data) {
    var action = data.getElementsByTagName("action")[0].firstChild.nodeValue;
    if(action == "web") {
        displayWebResult(data.getElementsByTagName("resultSet")[0]);
    } else if (action == "image") {
        displayImageResult(data);
    }
}

function displayWebResult(data) {
    var resultSet = data.getElementsByTagName("item");
    var resultCount = resultSet.length;
    var resultHtml = "";
    for(var i=0; i<resultCount; i++) {
        resultHtml += '<li><a href="';
        resultHtml += resultSet[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue;
        resultHtml += '">';
        resultHtml += resultSet[i].getElementsByTagName("Title")[0].firstChild.nodeValue;
        resultHtml += '</a>';
    }
    document.getElementById("searchResult").innerHTML = resultHtml;
}

function displayImageResult(data) {
    var resultSet = data.getElementsByTagName("item");
    var resultCount = resultSet.length;
    var resultHtml = "";
    for(var i=0; i<resultCount; i++) {
        resultHtml += '<a href="';
        resultHtml += resultSet[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue;
        resultHtml += '"><img src="';
        resultHtml += resultSet[i].getElementsByTagName("ThumbnailUrl")[0].firstChild.nodeValue;
        resultHtml += '"></a>';
    }
    document.getElementById("searchResult").innerHTML = resultHtml;
}

function addEvent(element, eventName, func) {
    if(element.attachEvent) {
        element.attachEvent("on"+eventName, func);
    } else {
        element.addEventListener(eventName, func, false);
    }
}

function init() {
    addEvent(document.getElementById("searchBtn"), "click", doSearch);
}

addEvent(window, "load", init);

各関数は以下の機能を持ちます

createHttpRequest ajaxで通信するためのリクエストクラスを作成します.ブラウザの違いにより、そのオブジェクトの生成方法が異なるため、クロスブラウザの処理が含まれています
getAction web imageのselectがどちらを選択しているかを取得します。(この部分IEに対応してません.修正予定)
doSearch 検索ボタンを押されたときの動作です.createHttpRequestでリクエストオブジェクトを生成してajax通信を行います
displayResult ajaxで受信したData(XMLObject)を元に検索結果を描画します(実際はweb,imageの判別を行い、それぞれの各描画関数にdataを渡します)
displayWebResult ウエブの検索結果を描画します
displayImageResult 画像の検索結果を描画します
addEvent ブラウザのイベントを登録します. ここもブラウザ毎に登録方法の差異があるため、クロスブラウザの処理があります
init HTMLが読み込まれた時点で一度読み込まれる初期化処理を記述しています。実際には検索ボタンをクリックしたときにdoSearch関数をコールするように登録しています

doSearch関数のhttpObj.open で作成したProxyのURLを指定します。受信のXMLはこのProxyからの結果になります。

ajaxを用いた検索アプリケーション (JQueryライブラリを利用)

次にJQueryライブラリを用いて上と同じアプリを作ってみましょう. HTMLの部分は読み込むJQueryのサイトからダウンロードしたライブラリを読み込む部分が追加となるだけであとは同じです

<script src="./jquery-1.2.6.min.js"></script>
<script src="./prog7-2.js"></script>

次にprog7-2.jsのJavaScript部分です JQueryを用いているのでかなりシンプルになります

bash-3.2$ cat prog7/prog7-2.js 
function doSearch() {
    $("#searchResult").html("");
    $("#searchResult").fadeOut("fast");
    var apiParam = "query=" + $("#searchQuery").val();
    apiParam += '&action=' + $("#searchOpt").val();
    apiParam += '&output=xml';

    $.ajax( {
                type: "get",
                url: "/prog6/index.php",
                dataType: "xml",
                data: apiParam,
                success: function(data) {
                    displayResult(data); 
                }
    });
}

function displayResult(data) {
    var action = data.getElementsByTagName("action")[0].firstChild.nodeValue;
    if(action == "web") {
        displayWebResult(data.getElementsByTagName("resultSet")[0]);
    } else if (action == "image") {
        displayImageResult(data);
    }
}

function displayWebResult(data) {
    var resultSet = data.getElementsByTagName("item");
    var resultCount = resultSet.length;
    var resultHtml = "";
    for(var i=0; i<resultCount; i++) {
        resultHtml += '<li><a href="';
        resultHtml += resultSet[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue;
        resultHtml += '">';
        resultHtml += resultSet[i].getElementsByTagName("Title")[0].firstChild.nodeValue;
        resultHtml += '</a>';
    }
    $("#searchResult").html(resultHtml);
    $("#searchResult").fadeIn("slow");
}

function displayImageResult(data) {
    var resultSet = data.getElementsByTagName("item");
    var resultCount = resultSet.length;
    var resultHtml = "";
    for(var i=0; i<resultCount; i++) {
        resultHtml += '<a href="';
        resultHtml += resultSet[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue;
        resultHtml += '"><img src="';
        resultHtml += resultSet[i].getElementsByTagName("ThumbnailUrl")[0].firstChild.nodeValue;
        resultHtml += '"></a>';
    }
    $("#searchResult").html(resultHtml);
    $("#searchResult").fadeIn("slow");
}

$(window).load(function() {
    $("#searchBtn").click(function() {
        doSearch();
    });
});

ajaxの部分は JQueryライブラリで提供される

$.ajax 

で処理しています document.getElementById("ID") で DOMのノードを特定する処理も JQuery

$("#ID")

で行えます。innerHTMLの処理は

$("#ID").html(挿入したいHTML文字列);

検索ボタンをクリックした場合の処理は

$("#searchBtn").click(function() {ここに処理を書く});

です。クロスブラウザを意識した処理はすべてJQuery内部で行われているので処理が簡潔になります。
JQueryはその他にもエフェクト処理を簡易に行う関数も提供されています。上で一部使用しました

$("#searchResult").fadeIn("fast");
$("#searchResult").fadeIn("slow");

の部分がそれに当たります。これもJQueryを用いない場合はTimerオブジェクトを生成してエレメントのCSS:alpha(透明度)の値を増減させる処理の記述が必要になりますが JQueryを用いると簡単に実現できます。

受信したXMLの解析部分がまだまだスリムとはいえません(XPathを利用する方法もありますが)。この部分は受信データをXMLではなく JSON形式のデータにすればさらに、プログラムが簡易になります。これは次の章で説明することにします。

今回のプログラム一式は
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog7
にありますので、必要に応じて参照してください。

JavaScript

JavaScriptとは

JavaScriptとはブラウザ上で動作するスクリプト言語です。JavaScriptを用いるとサーバサイドでページを生成する動的なウエブアプリとは対照的に、クライアント側でそのページの一部を変更するなどの動的な処理を行うことができます。
最近では、クライアントでのJavaScriptロジックの内部に、外部リソースを通信で取得し動的にページを書き換えるajaxと呼ばれる技術も流行っています。また、JavaScriptはHTMLウエブページをXML-DOMドキュメントとして取り扱い、操作することが可能であったり、また、CSSと呼ばれるウエブページのスタイルの定義も同じく動的に操作可能です。これを応用することでJavaScriptを用いると、ディスクトップアプリケーションに匹敵するインタラクティブなウエブページを表現することが可能なため、利用者にも直感的で使いやすいUIを提供することが可能になります。
JavaScriptとHTML内部にそのソースを記述する場合は scriptタグ内部にそのコードを記述します

<script type="text/javascript">
<!--
document.write("Hello World!!");
//-->
</script>

または,JavaScriptのコードのみで構成される別のファイルを用意し

<script type="text/javascript" src="test.js">

と呼び出すことも可能です

とほほのJavaScriptリファレンス

firebug

JavaScriptは以前はブラウザ製品毎に多少の仕様の違いがあり、統一的にサポートするウエブアプリケーションを開発することが困難な時代がありました。現在ではECMAScriptと呼ばれる標準化Scriptの策定と、それをサポートしたブラウザがリリースされています。
JavaScriptを開発する際は、一般的にスクリプト型のサーバプログラムと同様にテキスト編集&ブラウザでの実行動作確認(debug)を繰り返します。現在、最もこの開発作業を簡易に行う方法として人気が高いのは firefoxfirebug と呼ばれる拡張機能をインストールしてこの作業を行う方法です。

firebugJavaScriptをデバックするためだけのアドオンではなく多機能です。これを用いると、既存のウエブページが内部でどのようなJavaScriptを実行しているか、HTMLのデザインがどのように構成されているかが簡単に確認できます。
firebugを起動するにはFirefoxの右下の虫のアイコンをクリックします。動的に画面が書き変わるようなサイト(下の例はgooの地図情報の画面)でfirebugのXHRの画面を開き、ブラウザを動作(地図をグリグリ)させてみましょう。XHRにURLが順次表示されると思います。これはユーザのアクションの度に、サーバの情報をHTTPで取得しています。実際にその通信の中身も確認できます.

firebugはHTMLの構造を視覚的に確認することが可能です。firebugの調査という項目をクリックして、ブラウザの画面をなぞってみましょう。カーソルが乗った箇所のHTMLの構成部分がfirebugの画面に表示されます。その他,その箇所のCSS定義なども確認できます。HTML/CSSfirebugの画面で動的に書き換えることも可能です

firebugはブラウザの表示パフォーマンスを計測する際も利用されます。接続という項目をクリックして、ページを読み込んでみましょう。そのページで必要なリソースがどのような順番で読み込まれるのか。各項目のリソースの取得にどれぐらいの時間がかかっているのか。ページ全体を読み終わるのにどれぐらいの時間がかかっているのかが分かります。

firebugの機能で上であげたものはほんの一部です。 「firebug 使い方」で検索するとさまざまなサイトで利用方法を解説しています。参考にしてみてください。

JavaScriptライブラリ

JavaScriptは上で解説したECMAScriptによって標準化の方向でブラウザ間での差異は無くなってくる方向ですが、現状はまだ、ブラウザ毎に一部の機能で文法(関数)が異なったりします。そのような差異を開発者が意識すること無くクロスブラウザ(どのブラウザでも同様の機能を提供すること)を意識したプログラムを書けるようにさまざまなJavaScriptライブラリが提供されています。また、クロスブラウザの対応だけでなく、JavaScriptで行いたい処理を簡単に実現するためのライブラリとしての機能もあります。JavaScriptは単純に記述するとコード量が膨大になりがちですがライブラリを利用することで汎用的な部分をライブラリ内で処理してくれるので、非常に開発が楽になります。
一般的に良く利用されているJavaScriptライブラリは

などがあります。どのライブラリも日本語で解説したウエブページも多数ありますので、自分にあったライブラリを選んでマスターすると良いでしょう。

ウエブセキュリティ

ウエブアプリケーションを外部に公開する場合、本来そのサイトが持つ機能を逸脱した使われ方が行われる可能性があるサイトを脆弱性を持つサイト呼びます。脆弱性のあるサイトはセキュリティリスクが伴います。たとえ、サイトが個人情報などを保有しないサイトであっても、サーバ自体の侵入を許可したり、公開を意図しないサーバ内のファイルが漏洩されたりする可能性があります。また、知らない間に、サイトの内容が書き換えられ、他のサイトを攻撃する踏み台サーバとして書き換えられたりすることもあります。ウエブサイトを公開する場合はそのサイトの内容に問わず、セキュリティを意識したプログラムを開発することが求められます。
以下では、ウエブアプリケーションにおける、脆弱性の代表例を解説します

XSS (クロスサイトスクリプティング)

XSSとは、ユーザが入力した文字列が直接HTMLで出力されてしまう箇所があった場合、その入力値がJavaScriptの実行コードとして動作してしまう脆弱性のことです。よく発生するケースとして

  • 掲示板などの書き込み型サービス
  • 検索結果の画面で、「○○で検索した結果」と表示する部分
  • 申し込みフォームなどの確認画面

などは要注意です。
JavaScriptが実行されるとどのようなリスクが発生するかを例を用いて解説します。

  1. ある別の悪意のあるサイトが脆弱性をもつサイトへのリンクをJavaScriptが実行される引数をもって貼付けます
  2. ユーザがそのリンクをクリックした場合、脆弱性のあるサイトへ遷移します。遷移時に脆弱性のあるサイトでJavaScriptが実行されます。
  3. JavaScriptには脆弱性のあるサイトのCookieを取得し別のサーバへ転送する仕掛けが組み込まれています。(CookieについてはWebの基本を参照)
  4. 悪意のあるユーザが、そのサイトをでCookieの送信を受信することで、脆弱性を持つサイトへ被害者のCookieを用いて(なりすまして)アクセスすることが可能になります。

XSSを防止する第一歩は、ユーザが入力した文字列の画面出力時にはHTMLをエスケープすることを徹底することです。

SQLインジェクション

Databaseを利用するアプリケーションでwhere句での条件値をユーザの入力値をそのまま利用した場合にSQLインジェクションと呼ばれる脆弱性が発生する可能性があります。例えば、ユーザ認証であるユーザのパスワードが一致している場合にログイン可能とする処理で

SELECT * FROM user_table WHERE user=[入力されたユーザ値] AND passwd=[入力されたパスワード値] 

とした場合 入力されたパスワードが

適当な文字列 OR 1=1

であった場合

SELECT * FROM user_table WHERE user=[入力されたユーザ値] AND passwd=適当な文字列 OR 1=1

となり、意図しない結果になると思います。(場合によってはログイン成功と処理されるでしょう)。またDBの中身が意図しない形で画面に出力される可能性もコーディングによってはあります。
対策としては、SQLを実行する際は prepared statementを用いたり、入力値をチェック(エスケープ)したりの処理が必要です

CSRF(クロスサイトリクエストフォージェリ)

CSRFとは、サービスの利用を中断してもCookie等を用いてログイン状態を一定期間保つ機能をもつサイトに対して、被害を受ける可能性がある脆弱性です。悪意のあるサイトにアクセスした際に、そのHTML内に脆弱性のあるサイトでの処理内容を自動的に実行するコードが埋め込まれ、意図してない処理を被害ユーザが行ってしまうことをさせます。意図せず行わせる被害ケースとしては掲示板への自動書き込み、パスワードの変更処理など多岐に及びます。
これを防ぐ手段としては、ユーザに重要な操作を行わせる際には、確認画面を経由させ、更新処理時にその画面からの遷移であるかを確認する仕組みをもうけることや、CAPTCHAなどの認証キーを求めることがあげられます。リファラを利用することは対策としては不十分です。


Webアプリケーションを作る前に知るべき10の脆弱性(@IT)
PHPでのセキュリティ施策についてのメモ

拡張性の高いプログラムを作る プログラム演習6

プログラム演習4ではヤフーのウエブ検索APIを利用して簡単なウエブ検索サイトを作りました。次にこのサイトを以下の仕様で拡張することを考えてみます

【拡張仕様】

  • 検索キーワード入力窓と検索ボタンの間に web, image, both の3種が選択できる選択プルダウンを設置する
  • webが選択された場合は演習4の結果と同様の動きをする
  • imageが選択された場合はヤフー画像検索APIからキーワードに関連する画像を検索してサムネイル画像を表示する
  • bothが選択された場合は、ウエブ検索、画像検索の両方を行い、上下に疎の結果を表示する

という仕様とします。たとえば「紅葉」というキーワードでbothが選択された場合の出力は以下のような画面が期待されます。

【演習】
MVCや再利用を意識しないで、単純に演習4の延長で上の機能を実現するサイトを開発してみましょう。

おそらく、webが検索できるプログラムを基本として拡張する場合、最終的な出来上がりは同じようなロジックが点在する形になるでしょう。それを改善するために共通の処理を関数化したりとすれば、1つ機能が増えるごとのコードのステップ数(行数)はある程度押さえることがかのうです。では、「そのサイトにさらにブログ検索結果を...」という要求がきた場合、どの程度の修正量が必要かを考えてみましょう。 MVC的な発想でプログラムを開発すると、関数化でのアプローチとは異なる形での開発となります。
次に紹介するプログラムは一般的なMVCのモデルとは若干外れていますが、筆者自身が普段簡易なサイトを開発する際に使っている自己流のフレームワークを紹介します。
今回はプログラム一式から紹介します。以下から閲覧可能です
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog6
ファイル構造は以下のようになります

|-- ACTION  (動作処理の定義)
|   |-- Action_Base.class.inc     <--- あらゆる処理の基本となる動きを定義しています
|   |-- Action_both.class.inc     <--- both が選択しされたときの動きを定義しています
|   |-- Action_image.class.inc   <--- imageが選択されたときの動きを定義しています
|   |-- Action_main.class.inc     <---初めの画面の処理を定義しています
|   `-- Action_web.class.inc      <---webが選択されたときの動きを定義しています
|-- COMMON  (汎用的に使える機能)
|   |-- Config.inc    <--- 色々な設定値などを記入しておきます
|   |-- HttpApiRequest.class.inc    <--- HTTPでAPIからXMLを取得する機能を提供します
|   |-- Logs.class.inc     <--- error_logに必要な情報を書き込む処理を受け持ちます
|   `-- RequestManager.class.inc    <---クライアントからのURL引数を取得したりする機能を提供します
|-- MODEL (データを取得する部分)
|   |-- YahooImageSearch.class.inc   <--- ウエブ検索結果の取得&管理します  
|   `-- YahooWebSearch.class.inc  <---画像検索結果の取得&管理します
|-- VIEW  (テンプレート)
|   |-- ViewManager.class.inc   <--- Smartyでの画面描画を管理します
|   |-- cache  <--- Smartyのcacheディレクトリ
|   |-- footer.tpl  <--- ページフッタテンプレート
|   |-- header.tpl   <---ページヘッダテンプレート
|   `-- main.tpl   <---メインテンプレート
`-- index.php <---エントリーファイル

このサイトのURLは queryとactionの2種類のみの引数を受付ます(Config.incのline6で定義)

/prog6/index.php?query=[キーワード]&action=[web, image, bothのいずれか]

という仕様で動きます.
index.phpの処理が重要なのでその部分は以下に解説します。

<?php
    require_once('COMMON/Config.inc');
    require_once('VIEW/ViewManager.class.inc');

    $request = new RequestManager($ACCEPT_URL_ARGS);

    $actionClassName = 'Action_'.$request->get('action');
    if(!include_once("./ACTION/{$actionClassName}.class.inc")) {
        $actionClassName = "Action_main";
        include_once("./ACTION/{$actionClassName}.class.inc");
    }
    $action = new $actionClassName($request);
    $action->execute();

    ViewManager::viewSmarty($action->template, $action->templParams);

    exit;
?>

処理を順を追って解説すると

  1. RequestManagerというURL引数を管理するクラスにユーザからのリクエストを解析させます。
  2. 処理を決定するactionという引数をもとに、動作を行うクラス名を動的に決定させます ($actionClassName)
  3. 動作を行う処理が記述された(クラス)ファイルを読み込みます. (無い場合actionが空など. はAction_mainというクラスファイルを選択します)
  4. ACTIONクラスを生成(インスタンス化)します. 各クラスはAction_Base.class.incを継承させているのでexecuteメソッドは実装されてることを保証します
  5. ececuteを実行します。当然Actionのクラスごとに処理内容は異なります。
  6. 最後にSmartyで描画します

という処理をこのindex.phpで行っています。処理の解説のなかで、ウエブ検索や画像とかという記述はありません。よって基本的に、機能の拡張に対してこのファイルを修正する必要はないように作られています。
各ActionクラスのexecuteメソッドをAction毎に実装することになります。executeが終了した段階で

$action->template にこのActionの場合どのテンプレートを利用するか。今回は全部main.tpl
$action->templParams にテンプレートに渡したい変数(郡)をセット

するように実装します
Viewでは指定のテンプレートをSmarty書式で渡された変数を用いて画面をつくります。
データを取得する部分はMODELとしてACTIONとは別に定義しています。こうすることによって、データの処理をActionに依存することなく保守性,再利用性が向上します(bothの処理を参照)
このような形でプログラムすることで、たとえば、「ブログ検索結果の追加」という要求がきた場合はAction_blog.class.inc と MODELにブログ検索を取得するclassを追加すればすぐに拡張できます。

一般的に提供されているフレームワークを用いると、さらに汎用的なデザインパターンで開発することができますが、フレームワーク自体の利用の理解が必要になったりと敷居が高く感じられる方も多いと思います。そのような方は多少MVCとはいえない部分もありますが、上で紹介したような自己流フレームワークを一つ武器として持っておくと良いと思います。

番外: バージョン管理(ソース管理)システム

今回のプログラムコードは以下のURLで紹介しました。
http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog6
これはSubversionと呼ばれるオープンソースバージョン管理システムです。バージョン管理システムを利用すると、プログラムはリポジトリと呼ばれるDBのような場所に一括管理され、ファイル毎の更新情報(世代管理)などが可能になります。リポジトリへの操作(追加,更新,ダウンロード)は専用のコマンドを利用して行います。バージョン管理システムSubversionの他に,CVSと呼ばれるものがあります。最近はtrucと呼ばれるプロジェクト管理とバグ管理も含めたツールに組み合わせて使うことができるSubversionが人気があるようです。
自分のPCにSubversionリポジトリを構築して管理することもできます。企業などでは専用の管理サーバに対して複数の開発者が利用する形態がおおいです。また、リポジトリの構築は多少の手間がかかるので無料で使えるオンラインサービス版のSubversionもあります。無料オンライン版は

などがあります。今回は assemblaを利用しました(unfuddleは管理したものをシェアすることが無料ではできないので)。オンライン版を利用するとsubversionのコマンド(svn)が利用できる環境を構築すれば、それを使えるのでプログラム開発する場合はあわせてこのようなシステムを利用することを検討すると良いでしょう。(公開設定すると、あらゆる誰にでもその情報の閲覧機会を与えてしまうので、無料オンラインのものを利用する場合はその登録内容に注意しましょう(個人情報など))
PHP開発におけるSubversionを用いたバージョン管理
意外と使われていない「個人用trac」活用のすすめ

テンプレートエンジン (PHP Smarty) プログラム演習5

ウエブプログラムで厄介な点の一つは、デザイン(UI/HTML)部分のコーディングがプログラム内部のロジックに混入するすることで可読性が低下するだけでなく、メンテナンス性が落ちるケースが多々あることです。また、UIはHTML/CSS技術として一つの技術体系があるため、ウエブデザイナーとしても分業されているケースもあります。そうなると、プログラムとデザインの(ソース上の)分離が必要になります。それを実現する手段としてテンプレートエンジンがあり、PHPではSmartyと呼ばれるライブラリが一般的に利用されています。今回は少し本題と外れますが、このテンプレートエンジンの利用をマスターしましょう。
Smartypearで提供されるライブラリではないため、本家のサイトからダウンロードします。ダウンロードしたファイルを展開すると、たくさんのファイルが展開されますが、
とありますが、実際に利用するのは Smarty-2.x.x/lib 以下です このlib を php の include_path で参照できる場所ににコピーします

MAMPの場合は
bash-3.2$ cp -r libs /Applications/MAMP/bin/php5/lib/php/Smarty

これで

require_once('Smarty/Smarty.class.php');

でライブラリを読み込むことができます。簡単な例として、課題1で開発したPHPサイトをSmarty対応してみます。プログラム部分は以下のようになります。

bash-3.2$ cat prog5/index.php
<?php
    require_once('Smarty/Smarty.class.php');
    error_log('script start');

    $smarty = new Smarty();
    $smarty->template_dir = './template/';
    $smarty->compile_dir  = './template/cache';

    $templateParams = array();
    $templateParams['query'] = $_GET['query'];
    $templateParams['scriptname'] = basename($_SERVER['SCRIPT_NAME']) ;

    $smarty->assign('p', $templateParams);
    $smarty->display('smarty_sample.tpl');

    error_log('query=' . $_GET['query']);
    error_log('script end');

    exit;

?>

テンプレート部分は以下になります

bash-3.2$ cat prog5/template/smarty_sample.tpl 
<html>
<head>
<title>Sample Program 5 php-1</title>
<meta http-equiv="Content-Type" content="text/html" charset="utf-8">
</head>
<body>

<form action="./{$p.scriptname}">
<input type="text" name="query" value="{$p.query}">
<input type="submit" value="search">
</form>

</body>
</html>

プログラム部分でSmartyのクラスとインスタンス化します。

$smarty = new Smarty();

次に、テンプレートの保存場所、また、一度読み込まれたテンプレートは中間コードとしてcacheされるのでその保管場所を指定します(apacheから書き込みできる権限を与えてあらかじめ作成しておく)。

$smarty->template_dir = './template/';
$smarty->compile_dir  = './template/cache';

今回はプログラム直下にディレクトリを作りましたが、本来であればapacheのDocumentRootから外れた場所の方が望ましいです)
次にSmartのテンプレートに渡す変数をラベルとともにassignします。ラベルは複数assin可能で1ラベルごとに1変数とできますが、プログラムとテンプレートの変数受け渡しの仕様を明確にする上で1つのラベルで1つのHASH変数(複数変数)とする形を私はよくとります。

$smarty->assign('p', $templateParams);

最後に、displayではテンプレートを指定すると変数をテンプレートに割当て出力まで行います。

$smarty->display('smarty_sample.tpl');

テンプレート内から変数にアクセスする場合は ラベルを用いてアクセスします。この場合はラベルがHASHなので

{$p.scriptname}
{$p.query}

でそれぞれ値を取り出すことが可能です。

以上のようにHTMLの記述部分がプログラム本体と分離していることが分かります。デザインの変更はテンプレートファイルの修正のみで対応できます。Smartyにはその他たくさんの機能があり柔軟なテンプレート制御が可能です(条件分や繰り返し文,プラグインなど)。使い方によってはテンプレート内である程度のロジックも組めるのでそれを利用するのも有効です。

perlでも同じようにテンプレートエンジンとして

があります。
テンプレートエンジンは理解するまで多少は苦労ですが、覚えておくと非常に有効なのでこの時点でぜひマスターしてください。

【演習】
ウエブ検索アプリケーションで作成したプログラムをHTMLテンプレートを用いいて書き換えてみましょう。