ITの隊長のブログ

ITの隊長のブログです。Rubyを使って仕事しています。最近も色々やっているお(^ω^ = ^ω^)

【CakePHP3】Componentで呼び元のControllerのインスタンスを利用

initialize()で、$this->_registry->getController()を使えばいける。

<?php
  // ... 省略
  public function initialize(array $config)
  {
      $this->controller = $this->_registry->getController();
      parent::initialize($config);
  }

動的にインスタンスを追加したら、それを他のメソッドで使えるようになった。

<?php
  // ... 省略
  public function main()
  {
    $this->controller->autoRender = false;
    $this->controller->response->charset('UTF-8');
    $this->controller->response->type('json');
  }

【CakePHP3.x】Auth Componentのセッションが切れた時にAjaxされたら、JavaScript側でリダイレクトさせるためのBeforeFilterを書く

タイトル長い

タイトル通りの話。

Authのセッションが切れた後にAjaxでアクセスするとエラーが返ってきて、それ以上動作できない。

さらに、エラーのviewも返り、なんかキモいので、自分で作成したエラーメッセージとリダイレクトURLをJSONで渡せないかなと思ったのがきっかけ。

結構ハマったのでログに残す。

AngularJSのrequestをキャッチする

「あー、$this->request->is('ajax')でキャッチすればいけんじゃね?」と思ったそこのあなた! 私ですかね?

できませんでした。理由はわかりませんが、以前似たようなことでハマったので、これではできないと(なんとなく)確信していました。

www.aipacommander.com

ただ、$this->request->input()を使えば、postされた値は取れると。さらにjson_decodeをコールバックで渡してあげたら、もし値がjsonであれば、配列が返ってきます。

$angularAjax = $this->request->input('json_decode', true);

これでよし。通常のPOSTはname_1=test&name_2=test2みたいな値で飛んで来るので、json_decodeではnullで返ってきます。それを利用して、リクエストが配列であれば、AngularJSからのリクエストという判定ができます。

これにプラス。認証しているかどうかを判定すればおk.認証の確認はセッションからAuthの情報を取り出します。

<?php
$isAuth = $this->request->session()->read('Auth.User');

もし認証しているのなら、認証ユーザーの情報が配列で入っているはず。

完成したコードはこちら。これをAppControllerbeforeFilterで記述すればおk.

<?php

class AppController extends Controller
{
  // ... 省略
    public function beforeFilter(Event $event)
    {
        $isAuth = $this->request->session()->read('Auth.User');
        $angularAjax = $this->request->input('json_decode', true);
        if (is_array($angularAjax) && ! $isAuth) {
            $this->autoRender = false;
            $this->response->charset('UTF-8');
            $this->response->type('json');
            $this->response->statusCode(400);
            echo json_encode(['message' => __('You must be login.')]);
            return $this->response;
        }
        return parent::beforeFilter($event);
    }
  // ... 省略
}

$this->responseを組み立てて最後にreturnで返します。これでおk.これでできました。

ちなみに。。。書いてていくつか思ったこと。

そもそも、400ステータスは確認できていたので、そのままリダイレクトすればよかったんじゃ。。。。?

まぁでもやっぱしエラーのviewがブラウザのコンソールに流れてくるので、やっぱりこれでいいかな。

あー、そういえば、HTTP GETメソッドをAjaxで飛ばされたらこれキャッチできないんじゃね・・・・? できないと思います。。。これは一旦確かめてもしできなかったら、この記事に追記します。こうご期待(?)

追記 2016/09/14

やっぱりGETメソッドでは上のコードは動きませんでした。。。。さてどうすべ?と悩んでいました。

よく考えると、PHPAjaxを判定する場合はどのパラメータを確認できればいいのか? などなど考えているといくつか記事に出会いました。

note.onichannn.net

jQuery、prototypeなどのライブラリを利用してAjax通信をした場合、 リクエストヘッダーに「X-Requested-With:XMLHttpRequest」がセットされるため

AngularJSで来たリクエストを確認したら

<?php

// null
env('HTTP_X_REQUESTED_WITH');

(´・ω・`)

次にHTTP_X_REQUESTED_WITHは何故AngularJSで取れないの?と思ったので、angularjs HTTP_X_REQUESTED_WITHで検索したところ

qiita.com

おおー!と感激していたら、コメントに

$httpProvider でデフォで X-Requested-With が付くようにするのでも良いかもしれませんね

なんと! AngularJSでは意図的に送信しないようになっていたのね。

github.com

ということでconfigで設定するようにしました。

/**
 * Added X-Requested-With Default
 */
.config(['$httpProvider', function($httpProvider){
    $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]);

そしたら取れたキタ━━━━(゚∀゚)━━━━!!

【CakePHP3】Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails ~

なんかエラーが発生

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`a-curation`.`post_metas`, CONSTRAINT `post_metas_ibfk_1` FOREIGN KEY (`id`) REFERENCES `posts` (`id`))

どうやら外部キーの制約がおかしいよ

qiita.com

なお、ここで外部キーとして整合性のないデータがarticleテーブルに入っていると ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails … といったエラーが出るため、そういった不整合なデータは削除するなど何らかの対処をする必要がある

どうやら、postsテーブルとの制約がはられていないらしい。

つーことで、Phinxでmigrationファイルを作成して実行する。

$ ./bin/cake bake migration RepasteForeignKeyPostMetas
<?php
use Migrations\AbstractMigration;

class RepasteForeignKeyPostMetas extends AbstractMigration
{

    public function up()
    {
        $this->table('post_metas')
            ->addForeignKey(
                'posts_id',
                'posts',
                'id',
                [
                    'update' => 'RESTRICT',
                    'delete' => 'RESTRICT'
                ]
            )
            ->save();
    }

    public function down()
    {
        $this->table('post_metas')
            ->dropForeignKey('posts_id');
    }
}
$ ./bin/cake migrations migrate

外部キーが増えているのを確認

mysql> show create table post_metas \G
*************************** 1. row ***************************
       Table: post_metas
Create Table: CREATE TABLE `post_metas` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `posts_id` bigint(20) NOT NULL,
  `posts_meta_input_type_id` bigint(20) NOT NULL,
  `posts_meta_status` tinyint(4) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `posts_meta_input_type_id` (`posts_meta_input_type_id`),
  KEY `posts_id` (`posts_id`),
  CONSTRAINT `post_metas_ibfk_2` FOREIGN KEY (`posts_meta_input_type_id`) REFERENCES `posts_input_types` (`id`),
  CONSTRAINT `post_metas_ibfk_3` FOREIGN KEY (`posts_id`) REFERENCES `posts` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8
1 row in set (0.01 sec)

すると、保存もうまくいくようになりました。よかった。

【CakePHP3.x】jsonを返すapiを作りたい

追記 2016/12/22

この記事うまくいかないかもしれません。

でかいデータを入れると何故かうまくいかないことが増えました。

www.aipacommander.com

↑のほうがいいかも。追記終わりです。


teratail.com

これの$this->autoRender = false;を使ったほうでやりました。

ただ、echoで返すとそこで処理が中断されないので、そのあとにreturn;が必要だと思います。

<?php
// ... 省略
    public function addItem()
    {
        $this->autoRender = false;
        $this->response->charset('UTF-8');
        $this->response->type('json');
        // ... 省略
            try {
                if ($this->Post->save($posts)) {
                    echo json_encode($posts);
                    return;
                }
            } catch (\PDOException $e) {
            }
        }
        // ... 省略
        echo json_encode([]);
        return;
    }

CakePHP2.xの時はreturnを使って返していたので、若干ハマりました。。。

CakePHP3でPluginを自作しようとしてハマる

プラグイン

チュートリアルを見ながらやったのね。

$ ./bin/cake bake plugin TestPlugin # bakeでpluginディレクトリを作成
$ ./bin/cake bake controller --plugin LoadAssets Test # bakeでcontroller作成
$ ./bin/cake bake component --plugin LoadAssets Test  # bakeでcomponent作成

おーさくさく進む進む!

# autoloaderにpluginを追加
$ composer dumpautoload

すばらしい!!

ブラウザにアクセスしてみると

Missing Controller

orz

これで1時間半ハマった。

原因としてはこれ。

github.com

  • ~/app/config/bootstrap.php
Plugin::load('LoadAssets', ['autoload' => true, 'bootstrap' => true, 'routes' => true]);

oh...

明示的にautoloadを使うって指定しないとdumpしただけじゃダメだってさ。。。

おかげさまでCakePHPの初期動作をまんべんなく読むことができたわ(理解はしていないけど)

ほんとよくできていますね。

おわり(´;ω;`)