Symfony Advent Calendar 2014 20日目の記事です。
Symfony2プロジェクト内でAngularJSアプリケーション
フロントエンドもバックエンドもSymfonyプロジェクトで済ませてしまいたい、そんな人向けの記事です。
SymfonyはAsseticがある関係で生成したファイルをバージョン管理しない流儀だと勝手に思っているので、gruntやgulpで生成とかは一切なしです。
- バックエンド
- フロントエンド
の順に書かれています。
バックエンド
SymfonyでREST APIを作る時の話です。
使用するバンドル一覧
リクエスト Content-Type: application/json を受け取る
AngularJSの$http
サービスはデフォルトでJSONのままデータ送信を行うため、そのままではSymfony側でデータが受け取れません。
REQUEST_METHOD
が PUT
, DELETE
, PATCH
のいずれかであればRequest Payloadからデータを読込んでくれるのですが、そうでなければ手動で行うしかありません。そうであってもJSONのままです。
<?php
// 手動でRequest Payloadから読込んでJSONをデコード
json_decode(file_get_contents('php://input'), true);
そこでFOSRestBundle
にデコードさせる設定をしておけば、デコード済みJSONが受け取れるようになります。
fos_rest:
body_listener:
decoders:
json: fos_rest.decoder.json
簡単ですね!
フォームのCSRF問題
めでたくJSONリクエストを受け取れるようになりましたが、AngularJSではsymfony/form
で作成したフォームをレンダリングする機会がないのでトークンをブラウザに渡す事が出来ません。
ブラウザに渡ってないので、当然サーバーに送信される事も無くcsrfでフォームがエラーになってしまいます。
それでもフォームは便利なので使いたいですよね。
通常ではcsrf_protection
を無効にするかトークンを手動で渡すかの2択ですが、DunglasAngularCsrfBundle
を使うとトークンの受け渡し等々を自動的にやってくれます。
これだけだと黒魔術感がありますが、AngularJSには元々トークンの受け渡しを行う仕組みが備わっていて DunglasAngularCsrfBundle
はその仕組みを組み込んでくれているという訳です。
設定は至ってシンプルで、どのURLでトークンをブラウザに送信するか と どのURLでcsrfのトークンチェックを行うか ぐらいなので適当にドキュメントを見れば済むと思います。
フロントエンド
Symfonyプロジェクト内でAngularJSアプリケーションを作る時の話です。
使用するライブラリ一覧
バンドル
AngularJS
AngularJS(テスト)
Bowerコンポーネントのバージョン管理
bower.json
をバンドル内に持っておけば、そのバンドルの公開ディレクトリにコンポーネントをインストールしてくれる代物です。
Bowerを使っているなら特に困る事も何も無いので説明する事も無いですね。
サーバーとのやり取り
生のAngularJSだとほぼ$http
か$resource
を使うかと思いますが、restangular が結構使えるのでおすすめです。
URLの生成にレスポンスのIDを使っているので、子のオブジェクト毎にIDのキーが違ったりすると結構厄介なので注意が必要ですが、レスポンスオブジェクトからリクエストが行えるのがかなり便利だと思ってます。
// GET: /usrs.json
Restangular.all('users').getList().then(function (users) {
// GET: /users/1/posts.json
users[0].getList('posts');
});
ただし、URLの生成がわりとルーズなので好みが分かれるのが少し問題です。 その上、SymfonyでREST APIを作る際にもRestangularに合わせたURL構成(とはいっても一般的な構成)にしないと真価を発揮しません。 カッチリ派の方はFOSJsRoutingBundleを使って、、という方が好みかもしれません。
AngularJSへSymfonyの情報を渡す
テストの事もあるので、config
モジュール等をapp
モジュールから参照するようにすると割と便利かなと思ってます。
テストの時は固定のconfig
モジュールを作り、実アプリの時はtwigテンプレート内でconfig
モジュールを作ります。
<!-- 実アプリ用 -->
<script type="text/javascript">
(function (angular) {
angular
.module('config', [])
.const('BASE_URL', '{{ app.request.baseUrl }}');
})(angular);
</script>
// テスト用
angular
.module('config', [])
.const('BASE_URL', 'http://localhost')
;
これでSymfonyの情報(BaseUrl)がAngularJSで参照できるようになります。
// 例えば前述のRestangularにBaseUrlを設定する
angular
.module('app', ['config'])
.config(['RestangularProvider', 'BASE_URL', function (RestangularProvider, BASE_URL) {
RestangularProvider.setBaseUrl(BASE_URL + '/api');
}])
;
AngularJSテンプレートのURL解決
JmikolaJsAssetsHelperBundle
を使えば、twig内のasset()
と同じURLがjsからも取得できるようになるのでそれを使います。
app
.module('app', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: AssetsHelper.getUrl('/bundles/acmedemobundle/js/views/index.html')
});
}])
;
この方法だと、テンプレートの取得にHTTPリクエストが使われるのでそれが嫌な場合はHshnAngularBundle
を使うと、生成された$templateCacheからテンプレートが読込まれるため高速です。
AngularJSテンプレートを別ファイルにした際のユニットテスト
何も準備していないとAngularJSがテンプレートを取得する為にHTTPリクエストを飛ばしてしまいます。
ユニットテスト用にローカルサーバーを立てていれば話は別ですが、通常はテストが通りません。
なのでテスト実行前に$templateCache
上にテンプレートをセットしておきます。
karma-ng-html2js-preprocessor
は $templateCache
のキーが合うように設定さえすればキャッシュを生成してくれるのでおすすめです。
オプションが複数あり、指定方法はいくつかあるのですが基本的に下記の通りにすればOKです。
-
JmikolaJsAssetsHelperBundle
を使っている場合 =>AssetsHelper.getUrl()
の戻り値とキーが合うように -
HshnAngularBundle
を使っている場合 =>templateUrl
とキーが合うように
最後に
役に立つか分かりませんが、何かしらの参考になれば幸いです!