AngularJS+PHP+MySQLでCRUD入門

## AngularJS+PHP+MySQL

AngularJSとPHPを組み合わせたCRUDのサンプルです。なるべくシンプルになるように作ってみました。

![スクリーンショット 2014-03-22 19.08.11.png](https://qiita-image-store.s3.amazonaws.com/0/32030/abdb570d-7891-f4e2-08af-609b0a454bd4.png)

## 全ソースコード

[https://github.com/naga3/angular-php-basic](https://github.com/naga3/angular-php-basic)

以下のデータベースとテーブルを作成して、それ以下の3ファイルを入力してindex.htmlを開くと動きます。

### SQL

```mysql
CREATE DATABASE school;
USE school;
CREATE TABLE students(
  id SERIAL PRIMARY KEY,
  name VARCHAR(255),
  age INT,
  comment TEXT
);
```

学生テーブルがひとつだけのシンプルなDBです。

### HTML

```html:index.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>&nbsp;</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>&nbsp;</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

```js:controller.js
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:students.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
<html lang="ja" ng-app="app">
```

AngularJSはタグの入れ子構造がそのままスコープとして使うことが出来て直感的です。ng-appの付いている要素内がAngularJSの管轄となります。

```html
<body ng-controller="MainCtrl">
```

body要素内をコントローラ`MainCtrl`で制御します。

```html
    <tr ng-controller="DetailCtrl" ng-repeat="student in students">
```

tr要素内をコントローラ`DetailCtrl`で制御します。`students`はJavaScript側で設定した変数`$scope.students`を参照します。そこに`students`テーブルの内容が入りますので要素を`$scope.student`に代入しつつループします。

```html
      <td>{{student.id}}</td>
```

オブジェクト`$scope.student`のプロパティ`id`をこの場にバインドしています。JavaScript側で`id`の値を変更すると自動的に反映されます。

```html
      <td><input ng-model="student.name"></td>
      <td><input ng-model="student.age"></td>
      <td><input ng-model="student.comment"></td>
```

テキストフィールドの内容とオブジェクト`$scope.student`のプロパティを双方向バインドしています。テキストフィールドの内容を変更するとプロパティの値が変わるのはもちろん、JavaScript側でプロパティの値を変更しても自動的にテキストフィールドの内容が変わります。

```html
      <td><button ng-click="update()">更新</button></td>
      <td><button ng-click="delete()">削除</button></td>
```

ng-clickでボタンをクリックしたときに呼び出される関数を指定します。

```html
  <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の解説

```js
var app = angular.module('app', ['ngResource']);
```

モジュール宣言です。ngResourceを使えるようにしています。

```js
app.controller('MainCtrl', function($scope, $resource, $window) {
```

HTML側でbody要素に割り当てたコントローラMainCtrlの中身を定義しています。
`$scope`によってコントローラが定義された場所にスコープを割り当て、データバインディングを可能にします。
`$resource`でngResourceの機能をここから呼び出せます。

```js
  var Student = $resource('students.php', {id: '@id'});
```

REST APIを呼び出すURLを`students.php`とします。`{id: '@id'}`の部分はAPI側に渡されるデフォルトのキーと値です。この設定では渡されるオブジェクトに`id`というキーがあったら自動的にそれが付加されます。

```js
  $scope.students = Student.query();
```

queryメソッドはAPI側をGETで呼び出し配列で結果を受け取ります。ここではstudents.phpから`students`テーブルのレコード全てをJSONで貰い、オブジェクト`$scope.students`に格納します。この`students`プロパティはHTML側の`MainCtrl`配下の`students`とバインディングされます。

```js
  $scope.add = function() {
    Student.save($scope.new_student, function() {
      alert("追加しました。");
      $window.location.reload();
    });
  };
```

追加ボタンを押したときは、saveメソッドによりPOSTでPHPを呼び出し、レコードを挿入してリロードしています。

```js
app.controller('DetailCtrl', function($scope, $window) {
```

HTML側でtr要素に割り当てたコントローラ`DetailCtrl`の中身を定義しています。

```js
  $scope.update = function() {
    $scope.student.$save(function() {
      alert("更新しました。");
    });
  };
```

更新ボタンを押したときは、該当の行をDBに保存します。`$scope.student.$save()`は`$resource('students.php').save($scope.student)`とほぼ同じです。saveメソッドによりPOSTでPHPを呼び出し、レコードを更新します。

```js
  $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提供に徹するのがナウい作りだそうなので、それに従います。

```php
switch ($_SERVER['REQUEST_METHOD']) {
```

メソッドの種類で分岐します。

```php
case 'GET':
```

`$resource`の`query`メソッドが呼ばれた場合はここに来ます。レコード一覧を返しています。

```php
case 'POST':
```

`$resource`の`save`メソッドが呼ばれた場合はここに来ます。

```php
  $in = json_decode(file_get_contents('php://input'), true);
```

`save`メソッドの場合は渡されたパラメータがリクエストボディにJSON形式で入るので配列として取得します。

```php
  if (isset($in['id'])) {
```

idが設定されている場合はUPDATE、設定されていない場合はINSERTに分岐します。

```php
case 'DELETE':
```

JavaScript側から`delete`メソッドが呼ばれた場合はここに来ます。指定されたidのレコードを削除しています。

## まとめ

* AngularJSを使うと驚くほどスッキリとデータバインディングの仕組みが記述できることが分かりました。
* エラーチェックは全くやっていません。PHP側でステータスコードを返すのがRESTの作法だと思います。正常の場合もレスポンスの内容がないときは204を返したほうが良いのかも知れません。