Ajaxの雛型 | CakePHP,JSON,JQuery

CakePHP環境でAjaxを利用する雛型。
$.ajaxを用いて、JSONデータを送受信する。

実行URLの例
http://localhost/animal/devloper/index

CakePHP:コントローラ:DevloperController.php


class DevloperController extends AppController {
	public $name = 'Devloper';
	public $uses = ['Devloper'];

	function index() {
		$csrf_token = CrudBaseU::getCsrfToken('developer_edit');
		
		$this->set([
			'csrf_token' => $csrf_token,
		]);
	}
	

	/**
	 * 〇〇 | Ajax 非同期通信
	 * @return string
	 */
	public function ajax_reg(){
		$this->autoRender = false;//ビュー(ctp)を使わない。
		
		// CSRFトークンによるセキュリティチェック
		if(CrudBaseU::checkCsrfToken('developer_edit') == false){
			return '不正なアクションを検出しました。';
		}
		
		// 通信元から送信されてきたパラメータを取得する。
		$param_json = $_POST['key1'];
		$param=json_decode($param_json,true);//JSON文字を配列に戻す
		
		//データ加工や取得
		$res = ['success'=>1,'yagi'=>'山羊','kani'=>'蟹','same'=>'鮫'];
		
		// JSONに変換し、通信元に返す。
		$json_str = json_encode($res, JSON_HEX_TAG | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_HEX_APOS);
		return $json_str;

	}


}
	

CakePHP:ビュー:index.ctp

<?php
	$this->assign('script', $this->Html->script(['Devloper/index',]));
	//※jqueryはdefalut.ctpにてインクルード済み
?>

<div id="err"></div>
<input type="hidden" id="csrf_token" value='<?php echo $csrf_token; ?>' />
	

JS:index.js

class Animal{

	regAction(){
		let sendData={neko_name:'cat&dog%',same:{hojiro:'ホオジロザメ',shumoku:'シュモクザメ'}};
		
		// データ中の「&」と「%」を全角の&と%に一括エスケープ(&記号や%記号はPHPのJSONデコードでエラーになる)
		sendData = this._escapeAjaxSendData(sendData);
		// sendData = this._ampTo26(sendData); // PHPのJSONデコードでエラーになるので、&を%26に一括変換する
		
		let fd = new FormData();
		
		let send_json = JSON.stringify(sendData);//データをJSON文字列にする。
		fd.append( "key1", send_json );
		
		// CSRFトークンを取得
		let csrf_token = jQuery('#csrf_token').val();
		fd.append( "csrf_token", csrf_token );
		
		let ajax_url = "developer/ajax_reg";
		
		// AJAX
		jQuery.ajax({
			type: "post",
			url: ajax_url,
			data: fd,
			cache: false,
			dataType: "text",
			processData: false,
			contentType : false,
		})
		.done((res_json, type) => {
			let res;
			try{
				res =jQuery.parseJSON(res_json);//パース
			}catch(e){
				jQuery("#err").append(res_json);
				return;
			}
			console.log(res);
		})
		.fail((jqXHR, statusText, errorThrown) => {
			let errElm = jQuery('#err');
			errElm.append('アクセスエラー');
			errElm.append(jqXHR.responseText);
			alert(statusText);
		});
	}
	
	
	/**
	 * データ中の「&」と「%」を全角の&と%に一括エスケープ
	 * 
	 * @note
	 * PHPのJSONデコードでエラーになるので、&記号をエスケープ。%記号も後ろに数値がつくとエラーになるのでエスケープ
	 * これらの記号はMySQLのインポートなどでエラーになる場合があるのでその予防。
	 * @param mixed data エスケープ対象 :文字列、オブジェクト、配列を指定可
	 * @returns エスケープ後
	 */
	_escapeAjaxSendData(data){
		if (typeof data == 'string'){
			data = data.replace(/&/g, '&');
			data = data.replace(/%/g, '%');
			return data;

		}else if (typeof data == 'object'){
			for(var i in data){
				data[i] = this._escapeAjaxSendData(data[i]);
			}
			return data;
		}else{
			return data;
		}
	}
	
	
	/**
	 * Ajax送信データ用エスケープ。実体参照(&lt; &gt; &amp; &)を記号に戻す。
	 * 
	 * @param any data エスケープ対象 :文字列、オブジェクト、配列を指定可
	 * @returns エスケープ後
	 */
	_escapeForAjax(data){
		if (typeof data == 'string'){
			if ( data.indexOf('&') != -1) {
				data = data.replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
				return encodeURIComponent(data);
			}else{
				return data;
			}
		}else if (typeof data == 'object'){
			for(var i in data){
				data[i] = _escapeForAjax(data[i]);
			}
			return data;
		}else{
			return data;
		}
	}
	
	/**
	 * データ中の「&」と「%」を一括エスケープ
	 * @note
	 * PHPのJSONデコードでエラーになるので、&記号をエスケープ。%記号も後ろに数値がつくとエラーになるのでエスケープ
	 * 
	 * @param mixed data エスケープ対象 :文字列、オブジェクト、配列を指定可
	 * @returns エスケープ後
	 */
	_ampTo26(data){
		if (typeof data == 'string'){
			if ( data.indexOf('&') != -1) {
				return data.replace(/&/g, '%26');
			}else if(data.indexOf('%') != -1){
				return data.replace(/%/g, '%25');;
			}else{
				return data;
			}
		}else if (typeof data == 'object'){
			for(var i in data){
				data[i] = _ampTo26(data[i]);
			}
			return data;
		}else{
			return data;
		}
	}
}
	

CrudBaseU.php


class CrudBaseU{

	/**
	 * CSRFトークンを取得
	 * セッションまわりの処理も行う。
	 * @return string CSRFトークン
	 */
	public static function getCsrfToken($page_code)
	{
		
		$ses_key = $page_code . '_csrf_token'; // セッションキーを組み立て
		$csrf_token = self::random();
		$_SESSION[$ses_key]  = $csrf_token;
		
		return $csrf_token;
	}
	
	
	/**
	 * ランダム文字列を作成
	 * @param number $length
	 * @return string
	 */
	public static function random($length = 8)
	{
		return base_convert(mt_rand(pow(36, $length - 1), pow(36, $length) - 1), 10, 36);
	}


	/**
	 * CSRFトークンによるセキュリティチェック
	 * @return boolean true:無問題 , false:不正アクションを確認!
	 */
	public static function checkCsrfToken($page_code){
		
		// Ajaxによって送信されてきたCSRFトークンを取得。なければfalseを返す。
		$csrf_token = null;
		if(!empty($_POST['_token'])) $csrf_token = $_POST['_token'];
		
		if($csrf_token == null){
			if(!empty($_POST['csrf_token'])) $csrf_token = $_POST['csrf_token'];
		}
		
		if($csrf_token == null){
			if(!empty($_GET['_token'])) $csrf_token = $_GET['_token'];
		}
		
		if($csrf_token == null){
			if(!empty($_GET['csrf_token'])) $csrf_token = $_GET['csrf_token'];
		}
		
		if($csrf_token == null) return false;
		
		// セッションキーを組み立て
		$ses_key = $page_code . '_csrf_token';
		$ses_csrf_token = $_SESSION[$ses_key];

		if($csrf_token == $ses_csrf_token){
			return true;
		}
		
		return false;
	}
	
	
}
	


Ajaxの雛型その2

CakePHPとJSクラスを組み合わせたAjaxのひな形。
サーバー側(CakePHP)でエラーがあった場合、ダンプ出力するようにしている。
クライアント側とサーバー側のデータやりとりはJSONで統一。
TestController(例:YagiController)
index.ctp
index.js
ajax_action.js(例:YagiAction)

ビューに共通用のビューを埋め込む | echo $this->element('test');

ビューであるctpファイル中に、別のctpファイルを埋め込むことができる。
ただし埋め込む別のctpファイルは、ディレクトリの指定位置に配置せねばならない。
配置場所はapp/View/Elements。

sample.ctpに、app/View/Elements内にあるxxx.ctpを埋め込む場合、以下のように記述する。
	<?php echo $this->element('xxx');?>
	

埋込ctpファイルに引数を渡す

<例>index.ctpにanimal.ctpを埋込と同時に引数を渡す場合

index.ctp

	<?php echo $this->element('animal',array(
		'animal_name'=>'ラクダ'
	)); ?>	
	

animal.ctp

	<div>
		動物の名前は<strong><?php echo $animal_name;?></strong>です。
	</div>
	

クロスドメインによるAjax

クロスドメイン技術を使うとAjaxでjs側からドメインが異なるサーバサイドを呼び出すことができる。
cakephpでクロスドメインを行う場合、コントローラの認証をOFF設定にすること。はじめから認証がないページの場合は問題ない。
異なるドメイン間だけでなく、ローカル環境のjs側からサーバー環境のサーバーサイドを呼び出すことができる。

クロスドメインは、名前からしてドメインが異なるサーバー同士のやりとりのように見えるが、実際はオリジン(Origin)の異なる同士のやりとりのようである。
オリジンは、プロトコル、ドメイン、ポートの3つの組み合わせた情報の呼称である。


コントローラのソースコード(サーバーサイド)
	class DemoController extends AppController {
		public $name = 'Demo';
		public $components=null;//ログイン認証不要
	
		public function test_c(){
	 		$this->autoRender = false;//ビュー(ctp)を使わない。
			
	 		$this->header('Access-Control-Allow-Origin: *');
	 		$this->header('Access-Control-Allow-Methods: POST');
	 		
	 		$referer=$_SERVER['HTTP_REFERER'];//リファラ取得
	 		$json_param=$_POST['key1'];
			return 'hello world  '.$json_param;
		}
	}
	
※クロスドメインでも遷移元URLであるリファラを取得できる。

動かない場合、「$this->header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');」を追加する手もある。
	$this->header('Access-Control-Allow-Origin: *');
	$this->header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
	$this->header('Access-Control-Allow-Methods: POST');
	


js側のajaxからの呼出し例
	var data={'neko':'ネコ','same':{'hojiro':'ホオジロザメ','shumoku':'シュモクザメ'},'xxx':111};
	var json_str = JSON.stringify(data);//データをJSON文字列にする。
	var url="http://example.org/cake_demo/demo/test_c";

	//☆AJAX非同期通信
	$.ajax({
		type: "POST",
		url: url,
		data: "key1="+json_str,
		cache: false,
		dataType: "text",
		success: function(str_json, type) {
			alert(str_json);
		},
		error: function(xmlHttpRequest, textStatus, errorThrown){
			$('#xxx').html(xmlHttpRequest.responseText);//詳細エラーの出力
			alert(textStatus);
		}
	});
	


通常のphpの場合(cakephp未使用)
	header('Access-Control-Allow-Origin: *');
	header('Access-Control-Allow-Methods: POST');
	$json_param=$_POST['key1'];
	echo 'hello world2  '.$json_param;
	



リダイレクトでパラメータを送る

redirect_with_param2にリダイレクトする際、$text1と$text2をパラメータとして送る。
    public function redirect_with_param(){
    	$text1='やぎ';
    	$text2='yagi';
    	$this->redirect(array(
    			'controller' => 'test1',
    			'action' => 'redirect_with_param2',
    			$text1,
    			$text2,
    	));
    }
    

リダイレクト先。
受取パラメータはメソッドの引数に記述する。引数の名称は何でもよい。
    public function redirect_with_param2($t1=null,$t2=null){
    	Debugger::dump($t1);
    	Debugger::dump($t2);
    }
	
出力
	'やぎ'
	'yagi'
リダイレクト先のURLは以下のようになっている。
	http://amaraimusi.sakura.ne.jp/sample/cake_smp/cakephp/test1/redirect_with_param2/%E3%82%84%E3%81%8E/yagi
	
サンプル 参考1 参考2

リンク、アンカーボタン、サブミットボタンのHTMLヘルパー | link,button,submit

リンク
	echo $this->Html->link(
			'TOPへ',
			'/pages/index',
			array('class' => 'btn btn-primary', 'target' => '_blank')
	);
	
HTMLソース
<a href="/shch/pages/index" class="btn btn-primary" target="_blank">TOPへ</a>

アンカーボタン
	echo $this->Form->button('ボタン1', 
		array(
				'type' => 'button',
				'class' => 'btn btn-primary',
				'onclick'=>"alert('Hello world!')",
				
		));
	
HTMLソース
<button type="button" class="btn btn-primary" onclick="alert('Hello world!')">ボタン1</button>

サブミットボタン
	echo $this->Form->submit('検索', array(
			'name' => 'search',
			'class'=>'btn btn-success',
	));
	
HTMLソース
<input name="search" class="btn btn-success" type="submit" value="検索">


SQLをダンプしてデバッグする

SQLをダンプする場合は下記のメソッドを使う。
echo $this->element('sql_dump');
本来ctpファイル内で使うものであるが、コントローラやモデルでも使える。
モデルのfindメソッドやsaveメソッドの後に記述する良い。

上記の方法でダンプできない場合の別の方法

		$sqlLog = $this->モデル名->getDataSource()->getLog();
		debug($sqlLog);
		



CakePHPの静的ファイルの配置とパス | htmlファイルの配置とURL

webroot配下に配置するだけです。

webrootにanimalフォルダを作り、その中にneko.htmlを配置した場合、以下のURLでアクセスできます。
http://localhost/cake_demo/animal/neko.html

CakePHPエラー:システムのタイムゾーンの設定に依存することは安全ではありません

バグ

...Cake/Cache/CacheEngine.php on line 60

発生原因

PHP5.2系からPHP5.3,PHP5.4,PHP5.6に変更すると発生。
タイムゾーン未設定によるエラー。

修正方法

app/Config/core.phpを開き、タイムゾーンの設定をする。
	//date_default_timezone_set('UTC');
	↓
	date_default_timezone_set( 'Asia/Tokyo');
	

参考

メール送信 | CakeEmail

gmailを例に手順を示しているが、gmail側でブロックされてしまう。
よって以下の手順はメール送信できるサーバーが用意されていることを前提とする。

手順

① app/Configを開き、「email.php.defult」を「email.php」に名前変更
② email.phpに以下の設定を記述する。
			class EmailConfig {
			
				// Gmailによる送信例
				public $gmail = array(
						'host' => 'ssl://smtp.gmail.com',
						'port' => 465,
						'username' => '○○○○@gmail.com', //送信元メールアドレス
						'password' => '○○○○',           // パスワード
						'transport' => 'Smtp',
						'tls' => true
				);
			}
			
③ 以下のコードでメール送信する。
			App::uses( 'CakeEmail', 'Network/Email');
			$email = new CakeEmail( 'gmail');
			$email->from( array( '○○○○@gmail.com' => 'Sender'));	// 送信元メールアドレス
			$email->to( '○○○○@gmail.com');	// 送信先
			$email->subject( 'テストの件です');	// タイトル
			$email->send( 'メール内容です。いろはにほへとちりぬるをわかよたれそつねならむ');
			
参考サイト