Techracho

CakePHP Containableビヘイビア

このエントリーをはてなブックマーク Share
2010.09.02    CakePHP, PHP, 芝原   タグ: , —    shibachan   

Containableビヘイビアをご存知でしょうか?
もし、使用していならばこれを機にぜひ導入してみてください。

公式ページによると

ContainableBehavior は CakePHP のコアの新機能です。このビヘイビアは find を実行するときに関連したモデルを選別したり限定したりするために使用します。コンテイナブル(Containable)は、データベース中の不要なものを削減し、アプリケーションの速度やパフォーマンスを改善します。このクラスを使うと、ユーザに対するデータの検索とフィルタを、美しく一貫した方法で行うこともできます。

と、魅力的に説明されています。

使用方法や動作は、公式ページをみるのが手っ取り早いので割愛しますm(_ _)m
#ググってください

このビヘイビアのメリットはたくさんあるのですが、
特に便利だと思う点が

  • recursiveやbindModelやunbindModelの記述がなくなって、ソースコードがきれいでわかりやすくなる
  • あとで関連を選別できるからモデルの関連をモリモリの最大で書いておくことができる

の2点で、とにかく便利です。

さらに以下のサンプルのようにモデルのrecursiveのデフォルト値を-1としておけば、
何をcontainすればいいのか、しているのかがソースコードからわかりやすくなってお勧めです。

class UserModel extends AppModel {

    public $recursive = -1;
    public $actsAs = array('Containable');
}

CakePHP 1.3でのプレフィックスルーティング(Prefix Routing)が簡単になった

このエントリーをはてなブックマーク Share
2010.08.24    CakePHP, PHP, 芝原   タグ: , , , , , , , —    shibachan   

CakePHPのPrefixRoutingは
例えばadmin_editという関数に対して、通常
/:controller/admin_edit
となるURLを
/admin/:controller/edit
とすることができるもの。

管理画面やWebAPIのために特別なURLを用意することができる。

このPrefixRoutingだが、設定の方法がCakePHP1.3より簡単なものに変更された。

そのやり方とは/app/config/core.phpで

Configure::write('Routing.prefixes', array('admin', 'api'));

とするだけ。
# サンプルとしてadminとapiというプレフィックスを設定

ね、かんたんでしょ。

CakePHP1.2の時は複数のプレフィックス設定するのはRoutesをいちいち書いて結構めんどくさかったなー

カタぞうの画像表示高速化

このエントリーをはてなブックマーク Share
2010.07.05    CakePHP, PHP, カタぞう, 馬場   タグ: , , , , —    baba   

Webプログラミングの超基本ですが、画像表示の最適化です。

カタぞうでは、画像を動的に出力(サイズを変えたりなど)するために、いったんPHPを通しています。

投稿時に縮小しておくやりかたもありますが、ディスク容量を圧迫するのと、今後違うサイズが求められる可能性があるため、基本的に最大サイズで保存しておいて出力時に縮小画像を生成する仕組みを採用しています。

ところでこの仕組み、CakePHPのアクション内で実行すると、恐ろしく時間がかかります。

単純に画像をfpassthruするだけのコードで、600ミリ秒程度もかかりました。

主な原因は、Routingやフレームワークの初期処理が300msec程度、DBへのDESCRIBEが100msec程度、各種コンポーネントの初期化が200msec程度でした。

さすがに苦しいため、ここだけ生PHPで書いたところ、平均5msecに短縮。
これなら、画像をwebrootにキャッシュしてApacheのキャッシュを使わせる・・・ 等の処理をしなくても、遜色ありません。

今後のスケールに向けてもう少し練らないといけませんが、やっぱり基本は、不要な複雑処理を省くところですね。

(Railsの高速化テクを見ていて、「とにかくRailsに到達させるな」という方針で悲しくなりましたが、それと同じ)

CakePHPで簡単Basic認証

このエントリーをはてなブックマーク Share
2010.06.30    CakePHP, PHP, 芝原      shibachan   

CakePHPで簡単な管理画面を作ったので、認証をかけたいけど、UserテーブルつくってAuthComponentっていうのはめんどくさい。
複数人のアカウント制御できる必要もないし、そんなのオーバースペックだ。

つい先ほど、まさにこの状態になって、簡単に認証を設定できないかと
ネットの海をさまよっていると、やはりありました。
CakePHP 1.2 の Basic 認証設定があまりにも簡単すぎる

SecurityComponentを使ってController#beforeFillterでベーシック認証を設定するという方法です。

class HogeController extends AppController {

    public $components = array('Security');

    public function beforeFilter() {
        $this->Security->loginOptions = array('type'=>'basic');
        $this->Security->loginUsers = array('username'=>'password');
        $this->Security->requireLogin('*');  // 全アクションを指定(特定アクションも設定できる)
    }
}

確かにすごい簡単でした。
パスワードが平文というのが少し気になりますが、簡単な認証でしたらこれで十分ですね。

OAuthライブラリが謎の停止

このエントリーをはてなブックマーク Share
2010.06.27    CakePHP, PHP, 馬場   タグ: , —    baba   

CakePHPでOAuthを使うのに、このライブラリが有名だと思います。

http://code.42dh.com/oauth/

MITライセンスなのが良いですよね。

ところが、これを使ったCakePHPのシステムで、debug=2の時は動くのに、debug=0にすると動かないという謎の問題に出くわしました。

止まる場所は、認証ページへのリダイレクトに使う、

$consumer->getRequestToken('http://twitter.com/oauth/request_token', $callback);

のあたりです。

ソースを追ってみたところ、OAuth.phpの以下の場所で止まっていました。

public function get_normalized_http_url() {
  $parts = parse_url($this->http_url);

  $port = @$parts['port']; // ←ここ!!
  $scheme = $parts['scheme'];
  $host = $parts['host'];
  $path = @$parts['path'];

  $port or $port = ($scheme == 'https') ? '443' : '80';

  if (($scheme == 'https' && $port != '443')
      || ($scheme == 'http' && $port != '80')) {
    $host = "$host:$port";
  }
  return "$scheme://$host$path";
}

いや、確かに、portというキーは無かったんですけど、
そのための@演算子だし、そもそもこんなのでFatal Errorになるはずもなく。

要するに、PHPエンジンの方のバグなのか、謎ですね・・・

解決策は以下。

public function get_normalized_http_url() {
  $parts = parse_url($this->http_url);

  //== add this ==
  if (!array_key_exists('port', $parts)) {
      $parts['port'] = (isset($parts['scheme']) && $parts['scheme'] == 'https') ? '443' : '80';
  }
  //== add this ==

  $port = @$parts['port'];
  $scheme = $parts['scheme'];
  $host = $parts['host'];
  $path = @$parts['path'];

  $port or $port = ($scheme == 'https') ? '443' : '80';

  if (($scheme == 'https' && $port != '443')
      || ($scheme == 'http' && $port != '80')) {
    $host = "$host:$port";
  }
  return "$scheme://$host$path";
}

CakePHP findの形式

このエントリーをはてなブックマーク Share
2010.06.22    CakePHP, PHP, 馬場   タグ: , —    baba   

CakePHPのfind自体は使いやすいのですが、返ってくるのが配列で、しかもへんてこな形式なので、結構めんどくさいですよね。

たとえば、Userテーブルでfind(’all’)すると、

array(
  'User' => array(
    'id' => 1,
    'name' => 'yamada'
  ),
  'User' => array(
    'id' => 2,
    'name' => 'tanaka'
  ),
  'User' => array(
    'id' => 3,
    'name' => 'suzuki'
  ),
);

ですが、

Group hasMany Users の場合、Groupをfindすると、

array(
  'Group' => array(
    'id' => 1,
    'name' => 'Group-A'
  ),
  'User' => array(
    array(
      'id' => 1,
      'name' => 'yamada',
    ),
    array(
      'id' => 2,
      'name' => 'tanaka',
    ),
    array(
      'id' => 3,
      'name' => 'suzuki',
    ),
  ),
);

となり、形式が全然違います。

そのため、配列を引数にとるelementを作って、Viewを効率的にまとめる、という当たり前のことが、非常にやりにくい。
こんなときは仕方ないので、後者の形式が来ることを期待して、前者の形式が来たら、以下のような姑息な手段が有効です。

if (isset($user['User'])) {
  $user+= $user['User'];
}

ほんっと、
Rails → コード書いている時楽しいけど、その後がめんどくさい
Cake → コード書いている時は苦痛だけど、インストールも運用も簡単
ですよね・・・

↑のようなことをTwitterでつぶやいていたら、中の人から、Set::mapやFilterを使うと良いとアドバイスを頂きました。感謝感謝です。
ただ、paginatorなどとの互換性を維持しつつ、望んでいる利便性を確保するには、少し工夫が必要そうですね。

PHPEclipseでTABをスペースにする設定

このエントリーをはてなブックマーク Share
2010.06.20    Eclipse, PHP, プログラミング言語, 馬場   タグ: , , , —    baba   

PHPでコードを書く場合、TABの代わりにスペースを使うコーディング規約を採用することが多いと思います。
Eclipose+PHPEclipseを使う場合、うっかりTABが入ってしまうことが多いので、以下の設定を忘れずにしておきましょう。

※以前の記事で、2番目の設定を忘れていました。これをやらないと、自動インデントがTABになってしまいます。

PHP Editor設定

PHP Editor設定

PHP Formatter設定

PHP Formatter設定

Text Editor設定

Text Editor設定

とくに2番目を忘れがちなので気をつけてください。
(3番目は、やらなくても良いはずですが、どうせなら念のため)

CakePHP hiddenでclassを設定できない

このエントリーをはてなブックマーク Share
2010.06.15    CakePHP, PHP, javascript, 馬場      baba   

Formヘルパーを使う際、inputタグなどなら、

$form->input('User.name', array('class' => 'test'));

のようにclassを指定できますが、

hiddenタグの場合、class指定は無視されます。

$form->input('User.id', array('type' => 'hidden', 'class' => 'test'));

これは、formヘルパーが以下のように意図的に無視している為で、仕様のようです。

function hidden($fieldName, $options = array()) {
        //(略)
	return sprintf(
		$this->Html->tags['hidden'],
		$options['name'],
		$this->_parseAttributes($options, array('name', 'class'), '', ' ')
	);
}

JavaScriptから制御する際など、hiddenにもclassを付けたいことは多々ありますが、考慮されていないようですね。

直接タイプしても面倒じゃないので、まあそれほど困らないのですが。

<input type="hidden" name="data[User][id]" class="test" />

CakePHP1.3 リンク付き画像の表示方法

このエントリーをはてなブックマーク Share
2010.06.14    CakePHP, PHP, 芝原      shibachan   

リンクのついた画像をHtmlヘルパーで表示する方法がCakePHP1.3から変更になりました。

CakePHP1.2までであればこれは

$html->link($html->image('sample.png'),
        array('action' => 'hoge'), null, null, false);

と書けました。
第5引数のタイトル文字のエスケープ設定にfalseをセットし、imgタグをエスケープせずに、そのまま出力しています。

ですが、
CakePHP1.3からはこのやり方ができなくなってしまいました。
1.2

HtmlHelper#link($title, $url = null, $options = array(), $confirmMessage = false, $escapeTitle = true);

1.3

HtmlHelper#link($title, $url = null, $options = array(), $confirmMessage = false);

と$escapeTitle引数が削除されてしまいました。

エスケープができないんじゃ、画像表示されないじゃん!!と早合点したあなた、安心してください。
CakeBookによれば$options['escape']を使用してくださいとのことです。
なるほど。

というわけで、前置きが長くなりましたがCakePHP1.3からはリンク付きの画像を出力するには

$html->link($html->image('sample.png'), $url, array('escape' => false));

のようにします。

すこし、スマートになった気がしていいですね。

CakePHPで、parent::beforeFilterの呼び忘れを防止

このエントリーをはてなブックマーク Share
    CakePHP, PHP, 馬場      baba   

CakePHPでは、AppControllerのbeforeFilter()で、Authコンポーネントの設定など色々な処理を書くと思います。

各コントローラでbeforeFilter()をオーバーライドした際、よくparent::beforeFilter()を呼び忘れてハマるのは、たぶん私だけではないですよね?

そこで、呼び忘れを防止する簡単な対策です。

class AppController extends Controller {
    public function __construct() {
        parent::__construct();
        $this->layout = 'error';
    }

    public function beforeFilter() {
        $this->layout = 'front';

        //ほにゃらら
    }
}

このようにすることで、各コントローラでparent::beforeFilter()を呼び忘れると、エラーレイアウトが表示されます。
目立って、すぐに分かりますね。

コンストラクタでerrorをセットすることで、各コントローラで

public $layout = 'front';

のようにやっていても上書きできます。

各コントローラのbeforeFilter()でlayoutをセットしていたら検知できないですが、簡易的な対策として、一定の効果はあるのでは無いでしょうか?

開発が終わったら、最適化のため消しておきましょう。

# 気づいていると思いますが、AndriodのSuperNotCalledExceptionのノリです

古い投稿 »

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.