AngularJS+PHP+MySQL
AngularJSとPHPを組み合わせたCRUDのサンプルです。なるべくシンプルになるように作ってみました。
全ソースコード
https://github.com/naga3/angular-php-basic
以下のデータベースとテーブルを作成して、それ以下の3ファイルを入力してindex.htmlを開くと動きます。
SQL
CREATE DATABASE school;
USE school;
CREATE TABLE students(
id SERIAL PRIMARY KEY,
name VARCHAR(255),
age INT,
comment TEXT
);
学生テーブルがひとつだけのシンプルなDBです。
HTML
<!doctype html>
<html lang="ja" ng-app="app">
<head>
<meta charset="utf-8">
<title>名簿</title>
</head>
<body ng-controller="MainCtrl">
<table border="1">
<tr><th>ID</th><th>名前</th><th>年齢</th><th>コメント</th></tr>
<tr ng-controller="DetailCtrl" ng-repeat="student in students">
<td>{{student.id}}</td>
<td><input ng-model="student.name"></td>
<td><input ng-model="student.age"></td>
<td><input ng-model="student.comment"></td>
<td><button ng-click="update()">更新</button></td>
<td><button ng-click="delete()">削除</button></td>
</tr>
<tr>
<td> </td>
<td><input ng-model="new_student.name"></td>
<td><input ng-model="new_student.age"></td>
<td><input ng-model="new_student.comment"></td>
<td><button ng-click="add()">追加</button></td>
<td> </td>
</tr>
</table>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
<script src="controller.js"></script>
</body>
</html>
JavaScript
var app = angular.module('app', ['ngResource']);
app.controller('MainCtrl', function($scope, $resource, $window) {
var Student = $resource('students.php', {id: '@id'});
$scope.students = Student.query();
$scope.add = function() {
Student.save($scope.new_student, function() {
alert("追加しました。");
$window.location.reload();
});
};
});
app.controller('DetailCtrl', function($scope, $window) {
$scope.update = function() {
$scope.student.$save(function() {
alert("更新しました。");
});
};
$scope.delete = function(index) {
$scope.student.$delete();
alert("削除しました。");
$window.location.reload();
};
});
PHP
<?php
$pdo = new PDO('mysql:dbname=school', 'root');
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
$st = $pdo->query("SELECT * FROM students");
echo json_encode($st->fetchAll(PDO::FETCH_ASSOC));
break;
case 'POST':
$in = json_decode(file_get_contents('php://input'), true);
if (isset($in['id'])) {
$st = $pdo->prepare("UPDATE students SET name=:name,age=:age,comment=:comment WHERE id=:id");
} else {
$st = $pdo->prepare("INSERT INTO students(name,age,comment) VALUES(:name,:age,:comment)");
}
$st->execute($in);
break;
case 'DELETE':
$st = $pdo->prepare("DELETE FROM students WHERE id=?");
$st->execute([$_GET['id']]);
break;
}
MySQLのログインユーザーはroot、パスワードは無し(XAMPPのデフォルトの危ないやつ)を想定しています。
HTMLの解説
<html lang="ja" ng-app="app">
AngularJSはタグの入れ子構造がそのままスコープとして使うことが出来て直感的です。ng-appの付いている要素内がAngularJSの管轄となります。
<body ng-controller="MainCtrl">
body要素内をコントローラMainCtrl
で制御します。
<tr ng-controller="DetailCtrl" ng-repeat="student in students">
tr要素内をコントローラDetailCtrl
で制御します。students
はJavaScript側で設定した変数$scope.students
を参照します。そこにstudents
テーブルの内容が入りますので要素を$scope.student
に代入しつつループします。
<td>{{student.id}}</td>
オブジェクト$scope.student
のプロパティid
をこの場にバインドしています。JavaScript側でid
の値を変更すると自動的に反映されます。
<td><input ng-model="student.name"></td>
<td><input ng-model="student.age"></td>
<td><input ng-model="student.comment"></td>
テキストフィールドの内容とオブジェクト$scope.student
のプロパティを双方向バインドしています。テキストフィールドの内容を変更するとプロパティの値が変わるのはもちろん、JavaScript側でプロパティの値を変更しても自動的にテキストフィールドの内容が変わります。
<td><button ng-click="update()">更新</button></td>
<td><button ng-click="delete()">削除</button></td>
ng-clickでボタンをクリックしたときに呼び出される関数を指定します。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js"></script>
angular.min.jsがAngularJS本体、angular-resource.min.jsはAngularJSにngResourceという機能を追加します。これはREST APIによりバックエンドとのやりとりをサポートしてくれます。
コントローラJavaScriptの解説
var app = angular.module('app', ['ngResource']);
モジュール宣言です。ngResourceを使えるようにしています。
app.controller('MainCtrl', function($scope, $resource, $window) {
HTML側でbody要素に割り当てたコントローラMainCtrlの中身を定義しています。
$scope
によってコントローラが定義された場所にスコープを割り当て、データバインディングを可能にします。
$resource
でngResourceの機能をここから呼び出せます。
var Student = $resource('students.php', {id: '@id'});
REST APIを呼び出すURLをstudents.php
とします。{id: '@id'}
の部分はAPI側に渡されるデフォルトのキーと値です。この設定では渡されるオブジェクトにid
というキーがあったら自動的にそれが付加されます。
$scope.students = Student.query();
queryメソッドはAPI側をGETで呼び出し配列で結果を受け取ります。ここではstudents.phpからstudents
テーブルのレコード全てをJSONで貰い、オブジェクト$scope.students
に格納します。このstudents
プロパティはHTML側のMainCtrl
配下のstudents
とバインディングされます。
$scope.add = function() {
Student.save($scope.new_student, function() {
alert("追加しました。");
$window.location.reload();
});
};
追加ボタンを押したときは、saveメソッドによりPOSTでPHPを呼び出し、レコードを挿入してリロードしています。
app.controller('DetailCtrl', function($scope, $window) {
HTML側でtr要素に割り当てたコントローラDetailCtrl
の中身を定義しています。
$scope.update = function() {
$scope.student.$save(function() {
alert("更新しました。");
});
};
更新ボタンを押したときは、該当の行をDBに保存します。$scope.student.$save()
は$resource('students.php').save($scope.student)
とほぼ同じです。saveメソッドによりPOSTでPHPを呼び出し、レコードを更新します。
$scope.delete = function() {
$scope.student.$delete();
alert("削除しました。");
$window.location.reload();
};
削除ボタンを押したときは、該当の行をDBから削除しリロードします。$scope.student.$delete()
は$resource('students.php').delete({id: $scope.student.id})
とほぼ同じです。deleteメソッドによりDELETEでPHPを呼び出し、レコードを削除します。
PHPの解説
バックエンド側は完全にAPI提供に徹するのがナウい作りだそうなので、それに従います。
switch ($_SERVER['REQUEST_METHOD']) {
メソッドの種類で分岐します。
case 'GET':
$resource
のquery
メソッドが呼ばれた場合はここに来ます。レコード一覧を返しています。
case 'POST':
$resource
のsave
メソッドが呼ばれた場合はここに来ます。
$in = json_decode(file_get_contents('php://input'), true);
save
メソッドの場合は渡されたパラメータがリクエストボディにJSON形式で入るので配列として取得します。
if (isset($in['id'])) {
idが設定されている場合はUPDATE、設定されていない場合はINSERTに分岐します。
case 'DELETE':
JavaScript側からdelete
メソッドが呼ばれた場合はここに来ます。指定されたidのレコードを削除しています。
まとめ
- AngularJSを使うと驚くほどスッキリとデータバインディングの仕組みが記述できることが分かりました。
- エラーチェックは全くやっていません。PHP側でステータスコードを返すのがRESTの作法だと思います。正常の場合もレスポンスの内容がないときは204を返したほうが良いのかも知れません。