cakephp webroot以外を非公開ディレクトリに移動

参考:CakePHP webroot以外を非公開ディレクトリに移動する

webrootの中身をパブリックなディレクトリに入れます。
それ以外を非公開ディレクトリに入れます。

今回さくらだったので、wwwをパブリックにして、その上にhogeとつくってこれを非公開としました。www内(元webrootディレクトリ内)のindex.php内の各パス設定を変更します。

define( 'ROOT', dirname( dirname( __FILE__ ) )  . DS . 'hoge');
define( 'APP_DIR', 'app' );
define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'lib');

これでできました。

cakephp – ログインが必要なajaxでログインが切れちゃった場合の挙動

authコンポーネントつかってるので、authコンポーネントでログイン状態を確認したときに、ajaxだった場合は、ログイン画面にリダイレクトする変わりに、ログインしてねというメッセージをviewにセットして、終了したい。

cakephpのcookbookみたら、ちゃんとそういう変数があるらしい。便利だ、なんでもある。
http://book.cakephp.org/2.0/ja/core-libraries/components/authentication.html#AuthComponent::$ajaxLogin

$ajaxLoginにviewの名前をいれたらいいらしい。
viewというかエレメントのようだ。

AuthComponentのstartupメソッドでログイン状態とか、ログインしてなくてもアクセスさせてよいかなどを実際にチェックしており、その際にリクエストがajaxで、指定したアクションにアクセスできない場合に、$ajaxLoginにエレメントを指定しておけばそれを表示して終わってくれる。

authコンポーネントのstartupメソッドは、beforFileterより後に実行されるので、beforeFileterで何やら色々やってる場合は注意が必要だなと思った。

capistrano3でcakephpのmigrations pluginを実行させる

cookpadの人がridgepoleというのを作られまして、これはべきとうせいが保証されているらしい。ちなみにべきとうせいという読み方は間違っているかもしれない。漢字が変換で出てこない。どうもテーブル一覧つくってそれを更新するだけでいいっぽくて、その一覧をみながらないやつを足したり、いらないやつを削除したりしてくれるっぽい。migrations pluginみたいに、ここではaテーブルを追加し、次はbテーブルを削除しつつ、cテーブルを追加する、とかだと、もうaテーブルが既に存在する時点で終了になったりするのですごく便利な気がする。

といいつつ、今回はmigrations pluginをcapistranoで実行させます。サブモジュールがデプロイできない。下記の真似したらできた。自分は拡張子が.capで大丈夫だった。確かに拡張しはCapfileに書いてあった。

capistrano3 + git で submodule も一緒にデプロイしたい場合

上記にて、migrations pluginを展開した後に、下記を実行すればいい。

execute "#{fetch :deploy_to}html/app/Console/cake Migrations.migration run all"

EC2 cakePHPでMemcachedつかう

AmazonLinuxにMemcachedを設定します。

インストールと設定

インストールします。
参考:さくらの VPS 設定覚書(4)PHP

yum --enablerepo=remi,epel,rpmforge install libevent libevent-devel memcached php-pecl-memcache php-pecl-memcached

設定します。

vim /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="4096"
CACHESIZE="2048"
OPTIONS=""

Memcached を起動します。

service memcached start

自動起動の設定をします。

chkconfig --add memcached
chkconfig memcached on
chkconfig --list memcached

“0:off 1:off 2:on 3:on 4:on 5:on 6:off”と出れば、設定成功です。

memcache.iniを設定します。
http://php.net/manual/ja/memcache.ini.php

vim /etc/php.d/memcache.ini
memcache.chunk_size=32768
memcache.default_port=11211
session.save_path="tcp://localhost:11211"

Apacheを再起動します。
service httpd restart

php -R ‘ phpinfo(); exit(); ‘ | grep “memcache”
で、反映してるか確認できます。

EC2でポートをあける

Custom TCP Rule, tcp, 11211, セキュリティグループ

cakePHPでつかえるようにする

core.phpで下記がコメントアウトされてるので、コメントを外します。

Cache::config('default', array(
	'engine' => 'Memcache', //[required]
	'duration' => 3600, //[optional]
	'probability' => 100, //[optional]
	'prefix' => Inflector::slug(APP_DIR) . '_', //[optional]  prefix every cache file with this string
	'servers' => array(
		'127.0.0.1:11211' // localhost, default port 11211
	), //[optional]
	'persistent' => true, // [optional] set this to false for non-persistent connections
	'compress' => false, // [optional] compress data in Memcache (slower, but uses less memory)
));

あとは、キャッシュを使いたいところで、App::uses(‘Cache’, ‘Cache’);を書いて、Cache::read(‘hoge’);とか、Cache::write(‘hoge’, 123);とかやります。

memcachedは、apacheを再起動しても消えないけど、memcachedを再起動したら消えます。あとcakePHPのCacheは、モデルが追加、編集、削除されたときにモデルに関するキャッシュを消すそうです。でもroutes.phpでURL変えてると消えない場合があるようです。

cakephp mpdf A4横でブラウザ表示

前の記事:cakePHP2.3 mPDFを使ってPDFを出力する(レイアウト・ビューを使ってPDFを作成しサーバに保存する)

上記で大体できますが、A4横にしたりするのがわからなかったから調べた。ブラウザ表示自体は、$mpdf->Output();で普通にブラウザに表示されますので簡単です。

A4横は、$mpdf = new mPDF(‘ja’, ‘A4-L’);とやればよかった。
改ページは、$mpdf->AddPage();

ちなみに、cakePHPで改ページするときに、$Viewを毎回初期化しないとエラーになった。

$View = new View();
$View->viewPath = 'Hoge';
$View->viewVars = $vars;
$html = $View->render('hoge_1', 'pdf');

$View = new View();
$View->viewPath = 'Hoge';
$View->viewVars = $vars;
$html2 = $View->render('hoge_2', 'pdf');
$css = file_get_contents(CSS . 'pdf.css');

$mpdf = new mPDF('ja', 'A4-L');
$mpdf->writeHTML($css, 1);
$mpdf->writeHTML($html);
$mpdf->AddPage();
$mpdf->writeHTML($html2);
$mpdf->Output();

twitterプロフィール画像をFilebinderに関連づけつつ保存する

全然汎用的じゃないけどtwitter画像を保存してみた。

/**
 * Twitter画像の取得関数
 */
public function save_twitter_profile_img($model, $twitter_img_url, $user_id){
	$args = array(array(18, 18), array(48, 48)); //サムネイルのサイズ

	$save_dir_path = '/upload/User/' . $user_id . '/img';
	$dir = new Folder(WWW_ROOT . $save_dir_path);
	if($dir->find('.*')) return true;
	$dir->create(WWW_ROOT . $save_dir_path);

	$url = str_replace('_normal', "", $twitter_img_url); //_normalを削除
	$url_info = parse_url($url);
	$path_info = pathinfo($url_info['path']);
	$save_path = WWW_ROOT . $save_dir_path . '/' . $path_info['filename'] . '.' . $path_info['extension'];

	if($this->resize_image_from_out($model, $url, $save_path, 100, 100)){ //100*100の画像
		if($this->create_thumbnail($model, $save_path, $args)){
			//FileBinderのattachmentsテーブルに登録
			$Attachment = $this->_getModelObject('Attachment', 'attachments');
			$data = array(
				'Attachment' => array(
					'model' => 'User',
					'model_id' => $user_id,
					'field_name' => USER_IMG_FIELD_NAME,
					'file_name' => $path_info['filename'] . '.' . $path_info['extension'],
					'file_content_type' => 'image/' . $path_info['extension'],
					'file_size' => 10
				)
			);
			$Attachment->create();
			$Attachment->save($data);
			return true;
		}
	}

	return false;
}

いやー汎用的じゃない。あとまだしっかりチェックしてない。でもできた。$this->resize_image_from_outとかは、GDでリサイズする関数に渡してる。$this->_getModelObjectは、BakeのModelTaskに書いてあったテーブルをモデルとして扱えるようにできる関数だから使ってみた。

/**
 * テーブルをモデルにしてくれる関数
 * @param      $className
 * @param null $table
 *
 * @return Model
 */
public function _getModelObject($className, $table = null) {
	if (!$table) {
		$table = Inflector::tableize($className);
	}
	$object = new Model(array('name' => $className, 'table' => $table, 'ds' => null));
	$fields = $object->schema(true);
	foreach ($fields as $name => $field) {
		if (isset($field['key']) && $field['key'] === 'primary') {
			$object->primaryKey = $name;
			break;
		}
	}
	return $object;
}

jquery ui sortable で並び順を取得する(serialize)

参考:http://api.jqueryui.com/sortable/#method-serialize

ここで、item_1、item_2といった感じで連番ふるよといいましたが、どちらかというと並び替えの場合、並び替え対象でDBのIDで管理しないといけないと思いますので、item_idという感じにした方がよかったです。

cakePHPだと、下記のような感じにするといいのではないでしょうか。

item_<?php echo h($item['Item']['id'])?>

$(this).sortable(‘serialize’);で下記のようなデータが得られます。

item[]=2&item[]=1

2と1がIDであれば、これをそのままAjaxでsortアクションなんかに渡します。
以下は例です。

function save_sort(sort_data){
	$.ajax({
		type:'post',
		url: '<?php echo $this->Html->url(array( 'controller' => 'hogehoge', 'action' => 'sort'))?>',
		data: sort_data,
		dataType: 'html',
		async: true,
		success: function(data){
			alert('sort ok!');
		}
	});
}

$(function(){
	$('#sortable').sortable({
		cursor: 'move',
		opacity: 0.7,
		placeholder: "placeholder",
		forcePlaceholderSize: true,
		cancel:false,
		handle:'.move_btn',
		update: function(event, ui){
			save_sort($(this).sortable('serialize'));
		}
	});
});

これでhogehogeコントローラーのsortアクションにアクセスしてくれて、$this->request->dataの中にソートデータが下記のように入っております。Formのシリアライズと一緒です。便利です。

スクリーンショット 2014-06-25 0.49.26

cakePHP SecurityコンポーネントのBlackhole

セキュリティ的にかなり便利なようで、javascriptでフォーム書き換えしたりbackボタン使ったりできなくなるのはいいですが、そのかわりかなりBlackholeにはまります。

参考サイト:
Cakephp2.xでのBlackHoleまとめ
[CakePHP2] Security Component を使って SSL を強制する

『Cakephp2.xでのBlackHoleまとめ』にかなりしっかりまとまってるので有り難い。Ajaxのときは、$this->Security->validatePost = false;を設定するだけでもどうもだめでしたが、下記を設定したらいけました。

public function beforeFilter() {
	if ($this->RequestHandler->isAjax()) {
		$this->Security->csrfCheck = false;
	}
}

PHPから自動でサブドメインつくる (cakePHP)

よくあるアカウント登録してサブドメイン名登録すると、ユーザ専用のサイトを作成できるようにしたいがやったことがない。

サーバはEC2のAmazonLinuxを使っています。ドメインはRoute53で管理してます。
Route 53のRecord Setsで、*.hoge.comとやれば、どんなサブドメインでもhoge.comと同じように扱うことができます。
後はcakePHPのルーティングでうまく処理できれば終了なんじゃないかと思います。

AppControllerとかでサブドメインの文字列を取得して、その文字列内容に応じて処理を切り替えれば、route.phpの設定なども必要ないんじゃないでしょうか。

private function get_subdomain(){
	$url = explode('.',env('HTTP_HOST'));
	$this->subdomain = $url[0];
}

最初のドットまでを取得しているので、サブドメインなしとか、IPアドレスでアクセスとか、wwwアクセスとかも、最初のドットまでを取得してしまう。それを省けば大丈夫っぽい。あとは$this->subdomainの内容に応じて表示内容を変えればoKじゃないでしょうか?

FileBinderの設定

引用:CakePHPの超便利なファイルアップロードプラグイン、FileBinderプラグインの使い方をまとめてみた。

public $actsAs = array(
        'Filebinder.Bindable' => array(
            'model' => 'Attachment', // ファイル情報を保存するモデル名
            'filePath' => WWW_ROOT . 'img' . DS, // ファイルを保存するディレクトリ(絶対パス)
            'dbStorage' => true, // ファイルをバイナリデータとしてデータベースに保存するか
            'beforeAttach' => null, // フック関数(ファイル保存前)
            'afterAttach' => null, // フック関数(ファイル保存後)
            'withObject' => false, // 検索結果にファイルのバイナリデータを付加するか
        )
    );

cakePHPをcomposerでインストールする

参考:
http://book.cakephp.org/2.0/ja/installation/advanced-installation.html
https://getcomposer.org/download/

mkdir compo1
cd compo1
touch composer.json
vim composer.json
{
    "name": "compo1",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ],
    "require": {
        "pear-cakephp/cakephp": ">=2.3.4"
    },
    "config": {
        "vendor-dir": "Vendor/"
    }
}
curl -sS https://getcomposer.org/installer | php
php composer.phar install
Vendor/bin/cake bake project sample1

これで、/compo1/sample1にプロジェクトが作成された。

bake projectの結果
スクリーンショット 2014-05-08 11.49.01

デフォルトでは、 bake は CAKE_CORE_INCLUDE_PATH をハードコードするようになっています。 アプリケーションの移植性を高めるためには、 webroot/index.php を修正し、 CAKE_CORE_INCLUDE_PATH を相対パスに変更しましょう:

って書いてあるし何かする必要があるらしい。
CAKE_CORE_INCLUDE_PATHは、ルートの lib ディレクトリへのパスです。

webroot/index.phpには、CAKE_CORE_INCLUDE_PATHは下記のように設定されている。

define('CAKE_CORE_INCLUDE_PATH',  DS . 'vagrant' . DS . 'html' . DS . 'compo1' . DS . 'Vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

これを相対化すればいいらしい。ああそうかそうしないとgithubとかで共有できないのじゃな。いやー勉強なった。
きっとcakePHP3だと相対化してくれるんだろう。

define(
    'CAKE_CORE_INCLUDE_PATH',
    ROOT . '/Vendor/pear-pear.cakephp.org/CakePHP'
);

PHP 全角半角どちらでもOKで、空白区切りで絞り込む検索

$keyword = mb_convert_kana($keyword, "s");
$keyword = trim(preg_replace('/\s+/', ' ', $keyword));
$words = explode(' ', $keyword);

foreach($words as $idx => $word){
    $word_h = mb_convert_kana($word, 'ak');
    $word_z = mb_convert_kana($word, 'AK');

    $options['conditions']['Hoge.hoge LIKE'] = "%{$word_h}%";
    .........
}

$options[‘conditions’]のところはANDとかORとかを設定する

cakePHP CSVヘルパー

列の追加
$this->Csv->addField(‘hoge’);

改行
$this->Csv->endRow();

レンダリング(ファイル名指定、文字コードをUTF-8からSJISに変換)
Csv->render(‘hoge.csv’, ‘SJIS’, ‘UTF-8’)?>

試してないけど、$this->Csv->setFilename(‘支払.csv’); とかでファイル名指定もできるっぽい。
$this->Csv->render(false);で、ダウンロードではなくブラウザにcsvデータが表示されるっぽい。
今はもうできないという可能性はある。

参考
http://liginc.co.jp/programmer/archives/705

cakePHP コントローラーで手動でバリデーションエラー登録

こんな感じで登録できる。hogeはフィールドです。

$this->Blog->invalidate('hoge', '変な値です');

モデルの場合はこうです。

$this->invalidate('hoge', '変な値です');

ブログ投稿後にトラックバック送信して、送信エラーがあったらブログを登録しないようにするときに使った。

$this->Blog->begin();
if($this->Blog->save($this->request->data)){
	if($this->Trackback->send($this->Blog->read())){
		$this->Blog->commit();
		return;
	}else{
		$this->Blog->invalidate('hoge', 'トラックバックに失敗しました。');
	}
}