読者です 読者をやめる 読者になる 読者になる

ITの隊長のブログ

ITの隊長のブログです。いや、まだ隊長と呼べるほどには至っていないけど、日々がんばります。CakePHPとPlayFrameworkを使って仕事しています。最近はAngular2をさわりはじめたお(^ω^ = ^ω^)

AngularJS1.4から、CakePHP2.xへリクエストを送ったら$this->requestでデータが取れない件

AngularJS CakePHP2.x AngularJS1.4.x CakePHP PHP

スポンサードリンク

AngularJS Hackathon

とあるプロジェクトで、AngularJSを使いました。

バックエンドはCakePHPを使いました。

例えば、AngularJSからデータをCakePHPにPostで送信した際に$this->request->dataに値が入っていませんでした。

なので調べた。

ちなみにAngularは触ってまだ2日目の超初心者です。

AngularJS1.4 => CakePHP2.xへ渡し方&受け取り方

環境

AngularJS側の実装

var app = angular.module('application', []);

app.controller('AppController', function($scope, $http) {
    $scope.updateButton = function() {
        var url = '/posts/save';
        var saveData = {
          post: '適当なデータ' 
        };
        $http.post(
          url,
          saveData,
          {}
        ).then(function(response) {
            // 成功処理
            console.log(response);
        }, function(response) {
            // 失敗処理
            console.log(response);
        });
    };
});

これでリクエストすることができた。メソッドはPostです。

が、何故かCakePHP$this->requestに値が入っておらん。

なぁーぜぇー?(’・ω・`)

色々調べた&教えてもらったこと

PHP: php:// - Manual

どうやらリクエストのbodyのデータはphp://inputで取得できるっぽい

<?php
...
$data = json_decode(file_get_contents('php://input'), true);

これで取得できるようになった。

が、せっかくCakePHPでリクエストクラスが用意されている&AngularJSと連携するControllerだけ書き方が変わるのはいかがなものかなと思った。

ホントはとれるんじゃない?なんてこと思ったので、CakePHPのLibraryを読んでみることにした。

CakePHP側でデバッグ

リクエスト側ってどんなして処理してんの?ってことでソースを読んでみました。

<?php
...
// 164行目ぐらい
  protected function _processPost() {
    if ($_POST) {
      $this->data = $_POST;
    } elseif (($this->is('put') || $this->is('delete')) &&
      strpos(env('CONTENT_TYPE'), 'application/x-www-form-urlencoded') === 0
    ) {
        $data = $this->_readInput();
        parse_str($data, $this->data);
    }

CakeRequest.phpのクラスがオブジェクト化される際のコンストラクタでCakeRequest->_processPost()が呼ばれる。

んで、そのコードはしょっぱなから、$_POST変数の存在をチェックしている。もし存在するなら$this->data、よく使う$this->request->dataに値をそのまま渡している。

AugularJSでリクエストを飛ばした場合の時、$_POSTに値が入っていないというよりは、$_POSTが宣言されていない。

はへぇーーーー°ω° そんなことってあるのね。

まぁそれはさておき。次の条件が気になった。

$data = $this->_readInput();<-これ。なかにジャンプしてみると、、、

<?php
...
// 1075行目ぐらい
  protected function _readInput() {
    if (empty($this->_input)) {
      $fh = fopen('php://input', 'r');
      $content = stream_get_contents($fh);
      fclose($fh);
      $this->_input = $content;
    }
    return $this->_input;
  }

おお!これじゃねーの? ここを処理させることができればええんじゃね?

っつーことで、条件を再度見直し。

($this->is('put') || $this->is('delete')) && strpos(env('CONTENT_TYPE'), 'application/x-www-form-urlencoded')

単純です。HTTPのメソッドがputdeleteで、且つ、content-typeの文字列でapplication/x-www-form-urlencodedが部分一致すれば良いとのこと。

では、そうなるようにAngularのほうを書き換える。

var app = angular.module('application', []);

app.controller('AppController', function($scope, $http) {
    $scope.updateButton = function() {
        var url = '/posts/save';
        var saveData = {
          post: '適当なデータ' 
        };
        // メソッドを`put`へ変更
        $http.put(
          url,
          saveData,
          {
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded'
            }
          }
        ).then(function(response) {
            // 成功処理
            console.log(response);
        }, function(response) {
            // 失敗処理
            console.log(response);
        });
    };
});

こうすれば、先ほどの$this->_readInput()への条件がtrueになった!

これが正解か!

<?php
...
// 164行目ぐらい
  protected function _processPost() {
    if ($_POST) {
      $this->data = $_POST;
    } elseif (($this->is('put') || $this->is('delete')) &&
      strpos(env('CONTENT_TYPE'), 'application/x-www-form-urlencoded') === 0
    ) {
        $data = $this->_readInput();   // ここでリクエストを取得
        parse_str($data, $this->data); // ? なにこれ?
    }

???(・ω・)

$this->request->dataには、値が入らないっぽい。

とりあえず、$this->request->_inputの中に値が入ることはわかった。

これで、AngularJSを使ったとしても、CakePHPのRequestクラスを使って実装することができる。...

でき。。。ない!?

<?php
...
// 125行目
  protected $_input = '';

( ゚∀゚)・∵. グハッ!!

これじゃとれねぇ。。。(外部クラスからアクセスできないから)

じゃあどうすれば。。。

ソース読んでいるとあることに気づいた。

<?php
...
// 1000行目ぐらい
  public function input($callback = null) {
    $input = $this->_readInput();
    $args = func_get_args();
    if (!empty($args)) {
      $callback = array_shift($args);
      array_unshift($args, $input);
      return call_user_func_array($callback, $args);
    }
    return $input;
  }

・・・・・・・・・・・・・。

googleで「CakePHP request input」で検索

リクエストとレスポンスオブジェクト

REST を採用しているアプリケーションではURLエンコードされていないpost形式でデータを交換することがしばしばあります。 CakeRequest::input() を使っているどんな形式であっても入力データを読み込むことができます。 デコード関数が提供されることでデシリアライズされたコンテンツを受け取ることができます。

Oh...

<?php

class Posts extends AppController
{
  public function save()
  {
    // 取れた!ヾ(*´∀`*)ノキャッキャ....orz
    $requestData = $this->request->input('json_encode', true);
  }
}

(°ω°;.....!

できました。

HTTPメソッドをPUTとか

ContentTypeとか指定しなくても

自分でphp://inputを指定しなくても

いけました。

(´;ω;`)ブワッ

読み飛ばしてた箇所の説明

リクエストメソッドがPUTで、ContentTypeを指定したところのparse_str()

<?php
...
        $data = $this->_readInput();   // ここでリクエストを取得
        parse_str($data, $this->data); // ? なにこれ?

application/x-www-form-urlencodedで明記されたルールのリクエストだと受け取ることができ、$this->request->dataへ書き込む処理だった。

application/x-www-form-urlencoded ‐ 通信用語の基礎知識

今回はjsonのデータだったのでうまくparseすることができなかった。

$_POSTが宣言されない謎

調べたけど、ようわからん。。。

まとめ

ドキュメントは一度は目を通すべし。