gihyo.jpさんにPHP Matsuri 2011のレポートが2本とも掲載され、やっと俺の祭りが終わった。結局締め切りギリギリになってしまいとても反省しています。

少し振り返ってみると、祭りが終わった直後からものすごい勢いでモチベーションが上がっていて、心の中で悶々としてたものが一気に整理がついたように思う。それは、yandoさんの行動力や日々進化していくhyoshiokさんの生き様を目の当たりにして、ハッカーとは何たるかが少しだけわかった気がするからでもある。ここ2年ぐらい、スーツとギークの狭間で葛藤していた気持ちがやっと晴れた感じがしてとても心地よい一方、仕事や社会生活において自分も「ハッカー」としてアウトプットしていかなきゃならないなと少しプレッシャーを感じてもいる今日この頃。

さて、PHP Matsuriでは一応cakephp-pjax-pluginという、CakePHPに簡単にpushstate+ajaxを実装できるプラグインを書いて発表した。pjaxはgithubなんかで使われてる素敵技術(このへんの説明がわかりやすい)なので興味があったら使ってやってください、デモはこちら。ただ、pjax_railsというrailsプラグインのパクリで、数行で出来てしまう簡単なものだったので欲求不満気味。しかも、CakePHP2.0を実際に動かした事が無かったのでCakePHP1.3対応というお粗末さ。

そこでこんなの作ってみた。一応のデザインを入れたり、Flickr APIとスライドショーライブラリのドキュメントと睨めっこしつつも7,8時間ぐらいで出来たのでハッカソンで作っても間に合うぐらいの規模感。もともとはひたすら猫の画像見続けるサイトが欲しいねって友達と話していてドメインだけ取っていたものなんだけど、PHP Matsuriを期にFlickrを使い始めたこともあり、Flickrのタグ検索に当たった写真をスライドショーにできるサービスにしてみた。それだけだと面白くも何ともないので、youtubeにアップされている動画のURLを一緒に登録することでBGMを流せるようにした。これでPHP Matsuri参加者には響くサービスになったんですよきっと!

というわけで、Flickrにアップした写真に”phpmatsuri2011″のタグを付けると上のサンプルにも追加されるはず。

まだ4点ほど問題があって、

  1. 最新のchromeとfirefoxでしか確認してない
  2. スライドショーのjavascriptがパフォーマンス的に厳しくて今は写真の枚数を30枚までに制限してる
  3. 肝心のスライドショーを作ったり管理したりの機能を作り込んでないのでまだ公開できるレベルじゃない
  4. IE爆発しろ

と取り急ぎ自己満足レベルにしかなってないんだけど、CakePHP2.0で何か作るっていう目的は達成できたし、とりあえず真新しいサービスでもないのでゆっくりやることにします。あと、これ作るついでにTwitterBootstrapをCake用にゴニョゴニョしたCakeBootstrapというプラグインも作りました。最初Themeとして作ってたのでREADMEが完全に間違ったままだったりしますが意外と便利なので良かったらどうぞ。特にCSS周りは荒削りなのでpull request貰えると泣いて喜びます。

 

DSC05701

PHP Matsuri 2011 #phpmatsuri に参加してきました。
参加は去年に引き続き2度目で、両方ともスタッフとして参加しましたが、これほどエキサイティングなPHPのイベントは他に無いんじゃないかと思うぐらい楽しめました。
今も余韻に浸っており、頭の中はEpic sax guy 10 hoursで一杯です。

プログラムの最後を飾る大LT大会では、次点賞として「Titanium Mobileで開発するiPhone/Androidアプリ」を頂きました。監修の増井さん(@masuidrive)にサインまで頂いてしまい大満足です。

さて、イベントレポートは後日書くとして、参加者の共通認識的に何かがこみ上げてくるポイントをいくつか紹介します。

参加者を次々と中毒に陥れたEpic sax guy

サブリミナル的に脳髄に埋め込まれたnyancat

Japanese Ninjaが大阪の夜を闇に包んだり

DSC05698

外国人がJapanese Ninjaにおびえてたり

DSC05701

でも仲間になったり

DSC05794

よしおかさん(@hyoshiok)がLTに失敗したり

DSC_0464

まとめ

コタツ(笑)の写真が撮れなかったのが残念ですが大変熱い二日間でした。
来年は福岡(!)開催かと思いますがまたお会いしましょう!

PS,
2日間のイベントレポートはレポート:PHP Matsuri 2011 レポート|gihyo.jp … 技術評論社にアップされる予定です。お楽しみに!

 

CakePHPのACLを使って権限を管理していると、権限がないアクションへのリンクを隠したくなることがあるので私はこんな感じにしてます。
※注: ユーザーロールによるACLを対象に書いてますので個々のユーザーによるACLだとそのままでは動きません

HtmlHelper::link()を叩く度にACLで権限チェックをしていると大変よろしくないので、まずは以下のようなComponentのinit()を叩いてログインユーザーの権限情報を整理してセッションに格納します。

class PermissionComponent extends Object {
  public $components = array('Session', 'Auth', 'Acl');
  private $C = null;

  public function startup(&$controller) {
    $this->C = &$controller;
  }

  public function init() {
    $this->clear();

    $aro = $this->Acl->Aro->find('first', array(
      'conditions' => array(
        'Aro.model' => 'Role',
        'Aro.foreign_key' => $this->Auth->user('role_id'),
      ),
    ));

    $permissions = $this->Acl->Aro->Permission->find('all', array(
      'conditions' => array(
        'Permission.aro_id' => $aro['Aro']['id'],
      ),
    ));

    foreach ($permissions as $p) {
      if ($p['Permission']['_create'] == 1 && $p['Permission']['_read'] == 1 &&
        $p['Permission']['_update'] == 1 && $p['Permission']['_delete'] == 1) {
        $allow = true;
      } else {
        $allow = false;
      }

      if (!empty($p['Aco']['alias']) && $p['Aco']['alias'] == 'controllers') {
        $this->Session->write('Auth.Permissions.controllers', $allow);
      } elseif(!empty($p['Aco']['parent_id'])) {
        $parent = $this->Acl->Aco->findById($p['Aco']['parent_id']);
        $key = 'Auth.Permissions.' . $parent['Aco']['alias'] . '.' . $p['Aco']['alias'];
        $this->Session->write($key, $allow);
      }
    }
  }

  public function clear() {
    $this->Session->delete('Auth.Permissions');
  }
}

このPermissionComponent::init()は、ユーザーがログインした時点で実行するようにしています。AuthComponentのデフォルト設定であれば以下のようになるはずです。

class UsersController extends AppController {
  public $components = array('Auth', 'Permission');

  public function login() {
    $user = $this->Auth->user();
    if ($user) {
      $this->Permission->init();
      $this->redirect($this->Auth->loginRedirect);
    }
  }

  public function logout() {
    $this->redirect($this->Auth->logout());
  }
}

次に、セッションに格納した権限情報をViewからチェックする為のHelperを作ります。
このPermissionHelper::check()は、ログインユーザーが引数に取ったURLへアクセスする権限を持つ場合にtrueを返します。

class PermissionHelper extends AppHelper {
  public $helpers = array('Session');

  public function check($path){
    if(!is_array($path)) {
      $path = Router::parse($path);
    }
    if(empty($path['controller'])) {
      $path['controller'] = $this->params['controller'];
    }
    if(empty($path['action'])) {
      $path['action'] = 'index';
    }

    if(!empty($path['prefix']) && $path['prefix'] == 'admin') {
      return true;
    }

    $allkey = 'Auth.Permissions.controllers';
    if($this->Session->check($allkey) && $this->Session->read($allkey) === true){
      return true;
    }

    $key = 'Auth.Permissions.' . Inflector::camelize($path['controller']) . '.' . $path['action'];
    if($this->Session->check($key) && $this->Session->read($key) === true){
      return true;
    }

    return false;
  }
}

しかし、これでいちいちチェックしてからHtmlHelper::link()も呼ぶのは非常にめんどくさいので、このPermissionHelperを内部で使うようにHtmlHelperを継承したMyHtmlHelperを作ります。

class MyHtmlHelper extends HtmlHelper {
  public $helpers = array('Permission');

  public function link($title, $url = null, $options = array(), $confirmMessage = false) {
    $options = array_merge(array(
      'escape' => false,
    ), $options);
    $url = ($url !== null) ? $url : $title;

    if($this->Permission->check($url)) {
      return parent::link($title, $url, $options, $confirmMessage);;
    }
    return null;
  }
}

このMyHtmlHelper::link()はリンクを出力する前にPermissionHelperを用いて権限チェックを行い、アクセス権限がある場合のみリンクを出力します。

使い方は通常のHtmlHelperと何ら変わりありません。
しかし、わざわざViewで$this->MyHtml->link();などと打つのは大変煩わしいので、@hiromi2424のHackPluginを貰ってきてAppControllerで以下のようにするとviewを変更する必要がなくなるのでとても幸せになれます。

class AppController extends Controller {
  public $helpers = array('Hack.alias' => array('Html' => 'MyHtml'));
}

全然関係ない宣伝ですが、今年もPHPMatsuriの準備が本格化してきてます。
PHP Matsuri 2011
今年はPHP5.4, Symfony2, CakePHP2.0, CodeIgniter2.0とPHP界隈が盛り上がってますし、PHP以外にもTitanium mobileやTDD関連のセッション、ワークショップも開催予定です。
協賛企業様も絶賛募集中ですのでぜひ一度お気軽にご連絡ください。

Cake2.0ももうすぐRCですし私もアクティビティAGEAGEで参ります。

 

AuthComponent関係ないけど。
CakePHPだとcore.phpの内容に応じてsessionの設定などをいろいろやってるので、
php.iniなんかでcookieの有効期限を0にするだけだとうまく動かない。

独自の設定を追加するには、core.phpに書いあるとおり以下の方法を使うといいらしい。

To define a custom session handler, save it at /app/config/<name>.php.
Set the value of ‘Session.save’ to to utilize it in CakePHP.

app/config/[Session.saveの値].phpを作ったらいいよだって。

というわけで実際にやってみる。
my_sessionの部分は適宜置き換えてください。

app/config/core.php line 127:

// ここを
Configure::write('Session.save', 'php');
// こう
Configure::write('Session.save', 'my_session');
?>

app/config/my_session.php

if (empty($_SESSION)) {
  session_set_cookie_params(0);
}

ちなみに、この my_session.php には他にもini_setなどでphpのセッション設定などいろいろできる。

 

# これめっちゃ間違ってるので訂正!
# s/$this->Security = false/$this->Security->enabled = false/g

SecurityComponentのAPI見ても全然書いてないけど。
あるコントローラとかアクションのみ、SecurityComponentを無効にしたい。
Controllerにて$this->Security->enabled = falseすればOK。

例えば、ExampleControllerのtestアクションのみ無効化したいとすると、

<?php
class ExampleController extends AppController {
  public $components = array('Security');
  public function beforeFilter() {
    parent::beforeFilter();
    if($this->params['action'] == 'test') {
      $this->Security->enabled = false;
    }
  } 

  public function test() {
    // のっぴきならない事情でSecurityComponentが使えない処理
  }
}

フォームで入力する値が動的に変わっちゃう上に項目数の多い画面だとdisableFieldsだけでえらいことになってしまうので。
もっと良い方法ありそうだけど力尽きた。
知ってる方いらっしゃったら教えて下さい。

 

現状、Apache+mod_phpをnginxで置き換えるとすると、nginx+spawn-fcgi+php-cgiを使ってる人が多いと思うんだけど、Ubuntuの公式リポジトリだとnginxのバージョンが低く(0.7系)、spawn-fcgiはinit.dスクリプトを書くかdaemontools使わなきゃいけないし、Apache+mod_phpみたいにインストール一発で動かせないので割とメンドクサイ。

同じこと思ってる人はnginxチームが提供しているaptリポジトリを使うと、nginx1.0.2+php5.3.5+php-fpmをaptでインストールできるので楽になりますよ。

既にnginxがインストールされている場合は念のためremoveしておく、spawn-cgiなどはkillしておきましょう。
以下、手順。

# nginxリポジトリを登録
$ sudo echo "deb http://ppa.launchpad.net/nginx/stable/ubuntu lucid main" > /etc/apt/sources.list.d/nginx-stable-lucid.list
$ sudo echo "deb http://ppa.launchpad.net/nginx/php5/ubuntu lucid main" > /etc/apt/sources.list.d/nginx-stable-lucid.list
$ sudo echo "deb-src http://ppa.launchpad.net/nginx/php5/ubuntu lucid main" > /etc/apt/sources.list.d/nginx-stable-lucid.list
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C300EE8C
# aptitudeのリストを更新
$ sudo aptitude update
# nginxをインストール
$ sudo aptitude install nginx
# PHP関連をインストール
$ sudo aptitude install php5 php5-cgi php5-cli php5-mysql php5-memcached php5-gd php5-curl php-pear php-apc php-fpm

インストールできたら、プロセスを確認してみる。

$ ps aux | grep nginx
root     14051  0.0  0.1  71092  1568 ?        Ss   May28   0:00 nginx: master process /usr/sbin/nginx
www-data 14052  0.1  0.2  72496  3568 ?        S    May28   3:47 nginx: worker process

$ ps aux | grep php
root     14238  0.0  0.3 182432  4944 ?        Ss   May28   0:25 php-fpm: master process (/etc/php5/fpm/main.conf)
www-data 14255  0.1  3.1 192428 48292 ?        S    May28   4:54 php-fpm: pool www
www-data 14256  0.1  4.0 207328 62988 ?        S    May28   4:43 php-fpm: pool www
www-data 14258  0.1  3.8 202720 59212 ?        S    May28   4:36 php-fpm: pool www
www-data 14291  0.1  3.6 199648 55496 ?        S    May28   4:39 php-fpm: pool www
www-data 14293  0.1  3.3 196576 52260 ?        S    May28   4:51 php-fpm: pool www
www-data 14294  0.1  3.4 196576 52952 ?        S    May28   4:47 php-fpm: pool www
www-data 14297  0.1  4.5 214756 70368 ?        S    May28   4:34 php-fpm: pool www
www-data 14298  0.1  3.1 192336 48900 ?        S    May28   4:46 php-fpm: pool www
www-data 14299  0.1  3.1 192332 48856 ?        S    May28   5:05 php-fpm: pool www
www-data 14300  0.1  2.9 191044 46132 ?        S    May28   4:58 php-fpm: pool www

ちゃんとユーザーwww-dataでphp-fpmのプロセスが起動されてますね。一番上のrootで実行されているのでphp-fpmの子プロセスを管理してるマスタープロセスです。起動していないければsudo service nginx startとsudo service php-fpm startで起動させて再度確認します。
起動するプロセス数やfastcgiのlisten URLなどは/etc/php5/php-fpm/pool.d/www.confにて設定します。

次に、nginxのバーチャルホストの設定。
下の設定だとwordpressとCakePHPは動くはず。

# /etc/nginx/sites-avilable/example.com

# www.example.comへのアクセスをexample.comへリダイレクト
server {
  listen 80;
  server_name www.example.com;
  rewrite ^(.*) http://example.com$1 permanent;
}
server {
  listen   80;
  server_name  example.com;

  access_log  /var/log/nginx/example.com.access.log;
  error_log  /var/log/nginx/example.com.error.log;

  root   /var/www/example.com;
  index  index.php index.html;

  try_files $uri $uri/ /index.php?q=$1;

  # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
  }

  # deny access to .htaccess files, if Apache's document root
  # concurs with nginx's one
  location ~ /\.ht {
    deny  all;
  }
}

この内容を/etc/nginx/sites-available/example.comに保存し、/etc/nginx/sites-enabled/example.comからシンボリックリンクを張ります。

$ sudo vi /etc/nginx/sites-available/example.com
$ cd
$ sudo ln -s /etc/nginx/sites-availabe/example.com /etc/nginx/sites-enabled/example.com

ここまでやったらnginxとphp-fpmを再起動して、http://example.comをブラウザで開き、正常に動けばOKです。ダメだったら何とかしましょう。

 

CakePHPにもRailsでいうscaffold generatorのような、DBスキーマからCRUD画面を生成するbakeという機能があります。このbakeで生成するコードのテンプレートを予めカスタマイズしておくと、簡単なシステムなら非常に効率よく作れます。

しかしこの方法を説明している日本語版Cookbookが古いままである為、そのまま実践しても出力結果がデフォルトのままになってしまい大変残念なので、英語版Cookbookの該当ページを見ましょうっていうお話。

f you wish to modify the default HTML output produced by the “bake” command, follow these simple steps:
For baking custom views:
Go into: cake/console/templates/default/views
Notice the 4 files there
Copy them to your: app/vendors/shells/templates/[themename]/views
Make changes to the HTML output to control the way “bake” builds your views
The [themename] path segment should be the name of the bake theme that you are creating. Bake theme names need to be unique, so don’t use ‘default’.

Modify default HTML produced by “baked” templates

– 以下適当な意訳で解説 –

bakeコマンドで出力されるデフォルトHTMLを変更する場合、以下の手順を実行して下さい。
viewをカスタマイズする場合:

  1. cake/console/templates/default/viewsに移動する
  2. 4つのファイルがあるはず(index.ctp, form.ctp, view.ctp, home.ctp)
  3. これら4つのファイルをあなたのアプリケーションのapp/vendors/shells/templates/[themename]/viewsにコピーする
  4. コピーしたファイルを好きに編集したらbakeがそれを使ってコードを作ってくれる

ちなみに、[themename]の部分のディレクトリ名はあなた独自のものを指定する。特に’default’という名前は使わないこと。

– 解説以上 –

というわけで、上記の手順を実行すると、cake bakeしたときにthemeを選ぶ作業が追加されます。他のテーマが無ければ[themename]と’default’の2つから選ぶことになるので遠慮無く前者を選ぶとよいです。

デフォルトのテンプレートがあるcake/console/templates/default以下にはbakeで出力できる全てのテンプレートが置いてあるので、controllerやmodelなんかも修正できて大変便利です。ちなみにプロジェクトのテンプレートを編集する方法は上記の参考ページに書いてあるので是非見ることをお勧めします。

蛇足ですがググっても古い方の情報しか出てこないのでこの記事が上がるといいと思うし、
これを見た@hiromi2424が書き直してくれる気がする。

 

標題の通り、PHPにおいて三項演算子をネストした時の挙動がおかしい話。
例えば、以下のようなコードの場合。

echo (1 == 1) ? "one" : (2 == 2) ? "two" : "false"; // "two"

他の主要な言語と同じように、”one”が表示されることを期待してたんですが”two”が表示されますね。何かの間違いかと思って3回ぐらい試した。

と思ったら、マニュアルにしっかり書いてありました。

三項演算子を “積み重ねて” 使用することは避けましょう。 ひとつの文の中で複数の三項演算子を使用した際の PHP の振る舞いは、 少々わかりにくいものです。

PHP: 比較演算子 – Manual

マニュアルの通り、基本的に三項演算子はネストすべきではないですが、どうしても使いたい場合は以下のように括弧を付けると期待通りの結果になります。

echo (1 == 1) ? "one" : ((2 == 2) ? "two" : "false"); // "one"

蛇足ですが、マニュアルの上記ページにあるようにPHP5.3からは三項演算子の真ん中が省略できるようになったようです。

$hoge = "hoge";
echo ($hoge) ?: "fuga"; // "hoge"

$hoge = null;
echo ($hoge) ?: "fuga";  // "fuga"

これはいい仕様変更ですね。

そもそも三項演算子をネストするのってどうなのよ、って話ではありますが、
ちょっとしたワンライナーを書くときなんかは便利に使えるので参考までに。

 

結構重要なことだと思うので、ちょっと挑発的なタイトルになってしまいました。
akiyan.comのQdmailは使うときだけnewするかvar $is_qmailを変更しよう (特にCakePHPで使う場合)という記事を見て、自分の環境で試したところ性能に無視できない変化が出たのでエントリ。

Continue reading »

 

CakePHP1.3のflashMessageをtwitter風にする方法をメモ。
1.2系の人は所々コードが違ってくるはずなのでお気を付けて。

そもそもflashMessageってなんぞや?

これ、正確に何て呼ぶのかわかりません。
ControllerでSessionComponentにセットした文字列をViewのSessionHelperでflash()すると表示されるメッセージ、です。
実際にコードを書くとわかるかも?

//コントローラー
$this->Session->setFlash('これ、これこれ!');
//ビュー
echo $this->Sesion->flash();

わかりますか?
もう、何て呼べばいいのかよくわからないので以下「アレ」と呼ぶことにします。

Continue reading »

© 2011 sanojimaru.com Suffusion theme by Sayontan Sinha