Home > PHP

PHP Archive

Lithiumのフィルタシステム

この記事の所要時間: 1624

次世代CakePHPとも言うべきフレームワークLithiumのフィルタシステムを見てみました。

Lithiumは対象がPHP5.3以上ということで、5.3ならではの機能を活用したアーキテクチャになっています。中でも特徴的なのがフィルタシステムです。

全体のアーキテクチャとしては、CakePHPの流れを汲んで標準的なMVCフレームワークになっています。ただそれを実現する手段としてフィルタシステムを多用しています。これまでのフレームワークとは異なる点があり、いざフレームワークの動きを掴もうとすると戸惑います。

そこでLithiumのフィルタシステムをざっくりと見てみましょう。

サンプルソース

サンプルとして、SampleControllerとそのビューテンプレートを用意します。

フィルタの動きを見るだけなので、indexアクションでは、ログに__METHOD__を記録するだけです。

[app/controllers/SampleController.php]

<?php
namespace app\controllers;

class SampleController extends \lithium\action\Controller {
  public function index() {
    \lithium\analysis\Logger::debug(__METHOD__);
  }
}
&#91;/php&#93;
<p>[app/views/sample/index.html.php]</p>

<h1>Sample</h1>

ログをファイル(app/resources/tmp/logs/debug.log)に出力するようにしておきます。

[app/webroot/index.php]

// ログをファイルに出力
\lithium\analysis\Logger::config(array('default' => array('adapter' => 'File')));

echo lithium\action\Dispatcher::run(new lithium\action\Request());

?>

これでブラウザから「http://localhost/sample」にアクセスすると以下の内容がログに記録されます。

2010-09-30 00:11:16 app\controllers\SamplesController::index

フィルタを追加する

では、Dispatcher#runメソッドにフィルタを追加してみましょう。

まず、フィルタをクロージャで定義します。(__invokeメソッドを定義したクラスでも可)

そしてフィルタを追加する際は、applyFilterメソッドを利用します。この際に、第一引数にフィルタを追加する対象のメソッド名を、第二引数にフィルタ(クロージャ)を指定します。

[app/webroot/index.php]

// フィルタを定義
$c = function($self, $params, $chain) {
  \lithium\analysis\Logger::debug('Before');

  // 次フィルタを実行
  $res = $chain->next($self, $params, $chain);

  \lithium\analysis\Logger::debug('After');

  // 次フィルタの戻り値を返す
  return $res;
};

// フィルタを追加
lithium\action\Dispatcher::applyFilter('run', $c);

echo lithium\action\Dispatcher::run(new lithium\action\Request());

フィルタの内容は単純で、lithium\action\Dispatcher::runの処理の前後でログに文字列を出力しています。

これを実行すると以下のログが出力されます。フィルタで定義したログ出力部分が実行されており、フィルタとして機能しているのが分かります。

2010-09-30 00:11:16 Before
2010-09-30 00:11:16 app\controllers\SamplesController::index
2010-09-30 00:11:16 After

フィルタ定義でのポイントは、$chain->nextの箇所です。LithiumのフィルタシステムはChain of Responsibilityパターンになっており、フィルタ自らが次ぎのフィルタを呼び出す構造になっています。

あくまでもフィルタ自身が次のフィルタを実行するかどうかを決定できるので、サンプルのように前後に処理を実行することもできますし、あえて次のフィルタを実行させないということもできます。

CakePHPのフックメソッド(beforeSave()/afterSave()等)をフィルタとして書くというように考えるとイメージしやすいかもしれません。フックメソッドと異なるのは、まずサブクラスと作ってメソッドを継承する必要が無いと言う点があります。さらに大きく違うのはフィルタは複数追加することできるので多彩な処理をプラグインのように加えることが可能という点です。

次は、複数のフィルタを追加してみます。

複数フィルタを追加する

3つのフィルタを追加します。フィルタの内容は単純で、文字列をログに出力するだけです。ただ、どのフィルタから出力された文字列かを識別するためにフィルタごとに番号を追加しています。

ここでは、applyFilterの第二引数に直接クロージャ定義を記載しています。

[app/webroot/index.php]

$no = 1;
lithium\action\Dispatcher::applyFilter('run', function($self, $params, $chain) use ($no) {
  \lithium\analysis\Logger::debug('Before'.$no);
  $res = $chain->next($self, $params, $chain);
  \lithium\analysis\Logger::debug('After'.$no);

  return $res;
});

$no = 2;
lithium\action\Dispatcher::applyFilter('run', function($self, $params, $chain) use ($no) {
  \lithium\analysis\Logger::debug('Before'.$no);
  $res = $chain->next($self, $params, $chain);
  \lithium\analysis\Logger::debug('After'.$no);

  return $res;
});

$no = 3;
lithium\action\Dispatcher::applyFilter('run', function($self, $params, $chain) use ($no) {
  \lithium\analysis\Logger::debug('Before'.$no);
  $res = $chain->next($self, $params, $chain);
  \lithium\analysis\Logger::debug('After'.$no);

  return $res;
});

echo lithium\action\Dispatcher::run(new lithium\action\Request());

実行すると以下のログが出力されます。これを見ると$chain->next前の処理は、フィルタ追加順に実行され、$chain->next後の処理はその逆順で実行されているのが分かります。

2010-10-01 01:20:46 Before1
2010-10-01 01:20:46 Before2
2010-10-01 01:20:46 Before3
2010-10-01 01:20:46 app\controllers\SampleController::index
2010-10-01 01:20:46 After3
2010-10-01 01:20:46 After2
2010-10-01 01:20:46 After1

このように複数の処理を任意に追加できるのがフィルタシステムの特徴です。

フィルタシステム

ここまでフィルタがどのように動作するかを見てきました。

ではフィルタがどのように呼ばれているかを掴むためにフレームワークのソースを見てみましょう。

まずlithium\action\Dispatcher#runメソッドのソースを見てみましょう。

20行弱のソースなのですが、実質は3行しかありません。まず2行でパラメータのセットを行います。あとはstatic::_filter()を実行して終わりです。

lithium\action\Dispatcher#runメソッドとしての実質の処理(MVCの実行)は、static::_filter()の第3引数にあるクロージャの中に記載されています。

[libraries/lithium/action/Dispatcher.php]

  public static function run($request, array $options = array()) {
    // パラメータセット
    $router = static::$_classes['router'];
    $params = compact('request', 'options');

    // static::_filter()を実行
    // クロージャの中身が、本当の処理
    return static::_filter(__FUNCTION__, $params, function($self, $params) use ($router) {
      $request = $params['request'];
      $options = $params['options'];

      if (($result = $router::process($request)) instanceof Response) {
        return $result;
      }   
      $params = $self::applyRules($result->params);

      if (!$params) {
        throw new DispatchException('Could not route request');
      }   
      $callable = $self::invokeMethod('_callable', array($result, $params, $options));
      return $self::invokeMethod('_call', array($callable, $result, $params));
    }); 
  }

では、次はstatic::_filter()のソースです。lithium\action\Dispatcherはlithium\core\StaticObjectを継承しているので、実際に実行されるのはlithium\core\StaticObject#_filterメソッドです。

第三引数の$callbackにlithium\action\Dispatcher#runメソッドで定義したクロージャが格納されています。applyFilterメソッドによってフィルタが追加されていれば、フィルタチェイン(フィルタリスト)の最後に追加され、Filters::run()でフィルタ実行が開始します。フィルタが無い場合は、直接$callbackを実行します。

つまりポイントは、lithium\action\Dispatcher#runメソッドの処理自体もフィルタとして扱われるということです。これによりフィルタチェインを順々に実行すれば、メソッド自身の処理が実行できるというわけです。

[libraries/lithium/core/StaticObject.php]

  protected static function _filter($method, $params, $callback, $filters = array()) {
    $class = get_called_class();
    $hasNoFilters = empty(static::$_methodFilters[$class][$method]);

    if ($hasNoFilters && !$filters && !Filters::hasApplied($class, $method)) {
      // フィルタが空ならそのまま$callbackを実行
      return $callback($class, $params, null);
    }   
    if (!isset(static::$_methodFilters[$class][$method])) {
      static::$_methodFilters += array($class => array());
      static::$_methodFilters[$class][$method] = array();
    }   
    // $callbackをフィルタチェインの最後に追加する
    $data = array_merge(static::$_methodFilters[$class][$method], $filters, array($callback));
    return Filters::run($class, $params, compact('data', 'class', 'method'));
  }

最後にフィルタチェインを実行している\lithium\util\collection\Filters#runメソッドです。

最後の3行で、フィルタチェインの先頭にあるフィルタを実行して終了します。ここではあくまで先頭のフィルタだけを実行しています。それ以降のフィルタを実行するかどうかはフィルタ自身に委ねられています。

[libraries/lithium/util/collection/Filters.php]

  public static function run($class, $params, array $options = array()) {
    $defaults = array('class' => null, 'method' => null, 'data' => array());
    $options += $defaults;
    $lazyFilterCheck = (is_string($class) && $options['method']);

    if (($lazyFilterCheck) && isset(static::$_lazyFilters[$class][$options['method']])) {
      $filters = static::$_lazyFilters[$class][$options['method']];
      unset(static::$_lazyFilters[$class][$options['method']]);
      $options['data'] = array_merge($filters, $options['data']);

      foreach ($filters as $filter) {
        $class::applyFilter($options['method'], $filter);
      }   
    }   

    // 先頭のフィルタを実行
    $chain = new Filters($options);
    $next = $chain->rewind();
    return $next($class, $params, $chain);
  }

フィルタ実行が可能なメソッド

このようにフィルタシステムでは、メソッド自身の処理をクロージャにして、フィルタとして動作させることで実現しています。つまりどのメソッドでもフィルタシステムが利用できるわけではなく、あらかじめそれを想定して実装されたメソッドのみにフィルタが適用できます。

lithium\core\StaticObjectもしくは\lithium\core\Object(同様のフィルタ実行メソッドがあります)を継承しており、メソッド内部でstatic::_filter()もしくは$this->_filter()を実行しているメソッドが対象となります。

具体的には、\lithium\action\Controller、\lithium\data\Model、\lithium\data\source\Database、\lithium\util\Validator、他といったクラスに該当メソッドがあります。クラス名を見るとCakePHPでフックメソッドが定義されていたクラスですね。

フィルタシステムは便利?

Lithiumのフィルタシステムを見てきました。なんとなくイメージできたでしょうか。

フレームワークの主要な箇所はフィルタシステムを利用しているので、ここをおさえておくと処理が追いやすくなります。(反対に理解しないと?になります。。。)

このようなフィルタシステムをフレームワークの中核に利用しているフレームワークは、PHPではあまりありませんでした。使ってみると上手く実装されているなあというのが率直な印象です。利用の仕方では柔軟な実装ができそうなので楽しみです。

ただ実際問題、不満というかマイナスな点もあります。

まず、理解しにくということ。フレームワークのソースを読むならCakePHPのようにフックメソッドになっている方がgrepもしやすいし、一目瞭然で分かります。

次に、フィルタチェインの中身が掴めないということ。フィルタが格納される変数をvar_dump()してもクロージャが入っていることしか分からず、どのフィルタが入っているのかが分かりません。特にlithium\core\StaticObjectのサブクラスでは、フィルタを様々な箇所で追加できるので、どのフィルタがどの順序で入っているかは掴んでおきたいところです。

最後に、フィルタならではの利用方法がまだ見えてません。CakePHPのフックメソッドは実用には十分なものでした。正直、私自身は実用でフィルタならではの有効性がそれほど見えていないところです。

今後、有効な利用方法を見ていきたいを思います。

PHP5.3を体感するフレームワーク

冒頭にも触れましたが、LithiumはPHP5.3の機能が多くの箇所で活用されています。

ここで取り上げたフィルタシステムはまさに5.3の機能を利用しているので、5.3を理解するにはもってこいの教材です。言語仕様と簡単なサンプルソースを触ったあとは、実際に動くシステムを触ることでさらに理解を深めることができます。

PHP5.3を体感したい方は、Lithiumに触れてみるのはいかがでしょうか。

=> Lithium

PHP5.3+古いCakePHPのDeprecated表示をPHPコードを書き換えずに抑制する

この記事の所要時間: 133

PHP5.3+古いCakePHPで表示される大量のDeprecatedを抑制する方法です。

これはPHP5.3からE_ALLにE_DEPRECATEDが含まれたためで、非推奨な関数や文法を使っているとDeprecatedエラーが表示されます。

Deprecated表示を抑制する一番単純なのは、error_reporting設定でE_ALLからE_DEPRECATEDを外す方法です。

error_reporting = E_ALL & ~E_DEPRECATED

CakePHPでもデバッグレベルが0であれば、この方法で回避可能です。

しかし開発環境でデバッグレベルを1以上にしていると、フレームワークがerror_reportingをE_ALLに上書きしてしまうため、Deprecatedが表示されてしまいます。

E_ALLを設定している箇所を書き換える方法もありなのですが、開発環境にある複数のプロジェクトについて書き換えていくのも中々骨の折れる作業です。

ということで、CakePHP側のソースは書き換えずにDeprecated表示を抑制する方法を考えてみました。

E_ALLからE_DEPRECATEDを外す

方法は単純。PHP自体のソースを変更してE_ALLからE_DEPRECATEDを外すだけです。

変更箇所は1箇所だけ。

[Zend/zend_errors.h]

— zend_errors.h.org 2010-09-23 18:10:00.000000000 +0900
+++ zend_errors.h 2010-09-23 17:05:30.000000000 +0900
@@ -38,7 +38,7 @@
#define E_DEPRECATED (1<<13L) #define E_USER_DEPRECATED (1<<14L) -#define E_ALL (E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_RECOVERABLE_ERROR | E_DEPRECATED | E_USER_DEPRECATED) +#define E_ALL (E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_RECOVERABLE_ERROR | E_USER_DEPRECATED) #define E_CORE (E_CORE_ERROR | E_CORE_WARNING) #endif /* ZEND_ERRORS_H */ [/code]

あとは通常どおりPHPをビルドしてインストールすればokです。

変更前後でE_ALLの値は以下のように変わります。

変更前(オリジナル)
% php -r 'var_dump(E_ALL);' 
int(30719)

変更後
% php -r 'var_dump(E_ALL);' 
int(22527)

これでerror_reportingにE_ALLが設定されても、Deprecatedは表示されません。

とりあえずですが

本来はDeprecatedな原因を取り除くことが一番なのですが、本番環境はPHP5.2や5.1で、開発環境はPHP5.3にしたいということがあったので、この方法で対処しました。

アプリケーション側のソースは一切変更する必要が無いので楽ですね。まあ開発環境限定ということで。

iPhone/iPadのスクリーンショットを共有する「iScreenShot」をリリースしました

この記事の所要時間: 421

iPhone(iPod touch)、iPadのスクリーンショットを投稿して、みんなで見て楽しむサービス「iScreenShot」をリリースしました。

iScreenShot
=> iPhone/iPadのスクリーショットを共有 | iScreenShot

機能の詳細は、実際にサイトにアクセスして頂くのが分かりやすいので(ヘルプもありますし)、ここではサイトを作る経緯までや公開してからについて、ざくっと書いてみます。

構築した経緯

iPhone 3GSを昨年(2009年)の夏に購入したのですが、アプリを入れてカスタムし出した頃から、他人のホーム画面にとても興味が出てきました。どうにかして色々な人のホーム画面が見れないかな、ということでこのサービスを考えました。

ただ、アイデアとしてはあったのですが、なかなか手が付けられずに結局そのまま放置されることになりました。

そして今年の春、iPadが発売され、さらにiPhone4(iOS4)が発表されました。デバイスがというよりOSのバージョンアップにより、当時よりホーム画面のカスタマイズの自由度が広がりました。つまりこれまで以上に多様なホーム画面が見られるわけです。

これは作るしかないでしょ、ということで公開にするにいたりました。

公開して

まだβ版ということで、8/1夜と翌日昼にこっそりTwitterにだけお知らせをpostしました。

TwitterでのRTやblog等でのご紹介で、少しづつですがスクリーンショットが投稿されはじめました。実際に投稿されたスクリーンショットを見ると、それぞれに個性があって面白いですね。知人がiPhoneを持っていてもホーム画面をじっくり見る機会はあまり無いので、こうやって色々なホーム画面が見られるのは想定していたより楽しいですね:D

使った方、見た方のコメントでも「面白そう」「人のホーム見たいよね」という話があって、作って良かったなーと思っています。

早速、iPhone/iPadのblogでお馴染みのtobu iPhoneさんととはてブニュースさんにも取り上げて頂けました!はじめは知っている人でボチボチ投稿してもらえれば良いかなと思っていたのですが、一気に色々な方に投稿して頂けるようになりました。ありがとうございます。やっぱり、すごい。

お気に入りのホーム画面

どんなスクリーンショットがあるかは見るのが一番なので、投稿されたスクリーンショットの中で自分のお気に入りのものをご紹介します。

=> お気に入り | iScreenShot

まずは公開早々に投稿頂いたitayanさんのホーム画面。

青系の背景とアイコンできれいにまとまっていますね。色合いを合わせるように意識してアイコンを並べているとのこと。なるほど。

ご自慢のホーム画面をキャプチャーしてみました。 | iScreenShot

次にユニークなホーム画面を投稿されているfool4rootさんのホーム画面。

アイコンを全面に置かずにあえて背景を見せるのがポイントですね。しかしFlashって:D

Flash 対応しました :-P | iScreenShot

そして今のいちおしがこれ!

Kzh_09Jpさんのホーム画面です。

はじめて見た時に驚いて、さっそくコメント入れました:D iOS4のホームボタン2度押しによるアイコングレーアウトを上手く利用して背景を浮かせています。これはすごい。ぜひ実機で次元が現れるところを見てみたいです。

ホームボタンを連続二度押したときに現れるこのショットが何気に気に入っ... | iScreenShot

あとおまけで、自分が投稿したものを。

文字アイコンを並べて作ってみました。この画面を2ページ以降に用意しておいて、相手に「ほら、スライドさせて」といって、見せると効果ありそうです:D

@crema おたんじょうび、おめでとうございます! | iScreenShot

ホーム画面ではなく、ロック中の画面です。iPhone使っている人ならいつも見るバッテリ表示の電池から液漏れです:D これは実際に人に見せると反応が良いです。

バッテリーから液漏れ | iScreenShot

ほかにもいろいろなスクリーンショットがあるのでサイトで見てみて下さい。

ホーム画面は楽しい!

投稿されたスクリーンショットを見て、あらためて思うのはやっぱりiPhone/iPadは画面が美しいですね。並べてみると惚れ惚れします。また、その美しい画面をそのままスクリーンショットで保存できる機能を標準で用意しているのは、さすがAppleだなーと思います。

これまで、それほどホーム画面の体裁を意識していなかったのですが、色々な人のホーム画面を見ると俄然やる気がでて来ますね。

見て楽しい、投稿して楽しい、というようにiPhone/iPadユーザ(+気になる人)が楽しめるサービスになればなあ、と思っています。ホーム画面コンテストとかやると楽しそうなので、企画も色々とやっていきますね。

どうですか?あなたのご自慢のスクリーンショットも投稿したくなってきましたか?

=> iPhone/iPadのスクリーショットを共有 | iScreenShot

書籍名に年齢が入っている本を探す

この記事の所要時間: 053

「35歳からの○○」「40歳までに身につける××」といった書籍名に年齢が含まれる本が数多くあります。

こういった書籍にはどういうものがあるだろう、ということで調べてみました。

日頃、電車の広告や日経の広告などに掲載されているビジネス書などで年齢が書籍名に含まれる本を良く見ます。

年齢は万人に必ずあるものですし、自分に合致する年齢であれば興味を引くので良く利用されているのかもしれません。

実際にどのような本が出版されているのかを楽天ブックス書籍検索APIを使って調べてみました。

調査対象

楽天ブックス書籍検索APIで取得できる書籍から書籍名に年齢が含まれている本を抽出しています。

「0歳児・1歳児・2歳児のための乳児保育」といった複数の年齢が書籍名に含まれる場合はそれぞれの年齢として加算しています。

「 1歳から100歳の夢」といった年齢を範囲で含めている書籍に関しては加算していません。

年齢別書籍数グラフ

集計結果のグラフは以下。

=> 年齢別書籍数グラフ

最も多く含まれている年齢は3歳

最も書籍名に多く含まれている年齢は3歳でした。続いて5歳、2歳、4歳、0歳、1歳と乳幼児向けが並ぶ結果となりました。

ビジネス書系が並ぶのかと思いきやこれは意外な結果ですね。

「3歳のお気に入りえほん集」「ディズニーの読み聞かせえほん(どきどき3歳)」「 3歳までこそ大切な子育て10則」など子育て本、知育系の本が並んでいました。このあたりは1年齢の本も多いですが、「3・4・5才のおりがみ」のようにいくつかの年齢をまたいでいるのも特徴ですね。

たしかに実体験としても幼児向け本は何冊も買っているので、言われてみれば納得です。

=> 3歳の本を見る

やはり多いのは10区切り

次に多いのが10区切りで、100、50、60歳と続きます。

他にも40、10、20、15、35歳が上位に来るのでキリの良い年齢は、その付記の年齢の人も含めて対象となりやすいようです。

ここでも意外なのが、100、50、60歳というやや諸先輩向けの本が多いということです。日頃、目にする30/40歳代の本よりも多いというのは本に親しみを持つ年齢が故なのでしょうか。

面白いことに、100歳が含まれる本には「100歳まで」という表現が多いのに対し、50、60歳が含まれる本では「50歳から」「60歳から」という本が多いです。50、60歳はまだまだこれからですね。

=> 100歳の本を見る
=> 50歳の本を見る
=> 60歳の本を見る

意外にビジネス書は少ない?

広告の印象ではビジネス書や自己啓発系の本が溢れているのかと思っていたのですが、上位の年齢層を見てみると必ずしもそうではありませんでした。

本のジャンルとしてはビジネス系を除くと、教育書や小説、あと闘病記的な本が目に付きました。

年齢が含まれる本から見るとある女性の20代

20歳(はたち)でお店を立ち上げる!
21歳アタシ的寂しさ
綾。ホステス、22歳。新装版
23歳のディールメーカー
24才・時給750円の私がベンチャー経営で劇的成長できた理由(わけ)
迷い続ける25歳の退職届
26歳のレクイエム
27歳からの就職術
28歳からのぶっつけ留学成功法
29歳までに“その他大勢”から脱けだす習慣
30歳からの安産

私はアゲ子。20歳で店を起ち上げたけど、寂しくなって、ホステスになったの。23歳でディールメーカーになったけど、24歳の時給は750円。25歳で迷ったあげく退職した。。。
26歳の時は無職でどん底ね。でも27歳に奮起して就職活動をしたけど、気が変わって28歳で留学。29歳に「私はその他大勢とは違うのよ!」と一歩抜け出す習慣を身につけた結果、30歳で無事に安産。今は幸せよ。

といった20代人生が思い浮かびますね:D

年齢が含まれる本を探すサービス「アゲ本」

こういった年齢が含まれる本を探すサービスを作ってみました。

「アゲ本」
http://agebook.in/

年齢を入力すると書籍名にマッチした書籍をランダムで表示します。こちらでは「1から100歳」といった年齢範囲の書籍にもマッチするようになっています。

自分の年齢を入力するもの良し、どなたかの誕生日プレゼントとして本を探すのも良し、一度遊んでみて下さい。

iPadでPHP開発ができるか試してみた

この記事の所要時間: 439

ついにiPadが日本でも発売になりました。早速入手できたので色々と触ってみました。

使ってみた印象としては「大きくなったiPhone」ですね。操作はiPhoneまんまなのですが、A4プロセッサのおかげか軽快に動作します。またソフトキーボードがかなり改良されていて特に日本語についてはPCのキーボードっぽく入力できます。

とはいえ、やはりMacBookほど多機能ではないので、立ち位置としては見た目どおりiPhone以上MacBook未満という印象です。

そんなiPadで購入前に気になっていたのが、「iPadで開発ができるか?」という点です。もしこれができるなら MacBookは事務所に置いてまま外出先や家ではiPadでこなせるのではと企んでいました。

そこでiPadでPHPが書けるか試してみました。

キーボード

ソフトウェアキーボード

冒頭でも触れたとおり、iPad搭載のソフトウェアキーボードはiPhoneのものと比べると格段に良くなっています。画面拡大によりキーピッチがひろがったおかげで英字だけならPCのキーボード感覚で入力できます。

ただ数字や記号を入力しようとするとキーボード画面の切り替えが必要となります。この切り替えが結構手間でした。

さらに物理的にキーに触れるわけではないのでミスタイプが多くなりがちです。(これは慣れもありますが)

外付けキーボード(App Wireless Keyboard)

ソフトウェアキーボードの欠点を解消すべくBluethoothで接続できるApple Wireless Keyboard (US) MC184LL/Aを購入しました。

さすがにPC用のキーボードだけあって、入力の不満は一切なくなりました。

欠点を一点挙げるなら外付けキーボードだと画面から手が離れるので、画面をタップしたい時やアプリを切り替えたい時に、手を画面まで持っていかないといけないことですね。PCでもマウスを触るのでキーボードから手が離れるのですが、マウスと画面では手を動かす距離が異なるのと、command+tab のようにキーボードだけで画面を切り替えることができないので、そこは慣れが必要です。

あと細かいことですが、キーバインドの変更ができないので、「A」の左のcaps lockをctrlに変更できないのも気になりますね。おかげでCTRLのつもりでcaps lockを押してしまいイラっとなることがしばしばあります><

とはいえ、やはり外付けキーボードの入力のしやすさは格段に良く、ソースコードを書く事自体は何とかなりそうです。

開発環境

ソースコードを書く事ができても実行する環境が無いと開発できません。

iPad自体にPHPの実行環境をインストールできれば一番良いのですが、残念ながらそれはできません。そこでSSHでサーバにログインして開発する方法を試してみました。

iSSH for iPad

SSHクライアントアプリです。

パスワード認証だけでなく公開鍵認証も可能な優れものものです。

ssh-keygenによりキーペアを生成することができ、生成した公開鍵をメールで送信する機能もあります(サーバ側で公開鍵を設定する必要があるので、これは地味に便利)。もちろん既にある秘密鍵をインポートする機能もあります。

そんなiSSHのiPad版ということで期待が膨らみます。早速iPadからサーバにログインしてみました。

バッチリログインできています。もちろんlsやfreeなどコマンドも動作しています。

ログインしてしまえばこっちのものです。次にvimでCakePHPのソースを開いてみました。

おおーいけますね。もちろんカーソル操作も編集もできます。

ただ少しばかり編集しだすと壁にぶちあたりました。

なんとiSSHでは外付けキーボードから、ESC、CTRL、TAB、といった特殊キーが入力できないのです。

画面上部には特殊キーが入力できるようにボタンが用意されているのですが、画面をタップする必要があり、毎回それをやるのはかなり面倒です。うーん、これはツライ。。。

CTRLやTABがキーボードから入力できないのはシェル上でも同じで、CTRLを使った操作や TABによる補完なども毎度画面をタップする必要があります。

ちなみに他のアプリではTABやCTRLが効くので、これはiSSHの問題ですね。

他にも日本語表示が若干おかしい、パフォーマンスが遅いなどの問題点があります。このあたりも実際に開発するなら厳しいところですね。

[「iSSH for iPad」をiTunes Storeで開く ]

現状では厳しいかと

試してみた感想としては、入力するという行為については、外付けキーボードを使えばかなりいけます。実はこのエントリの原文はiPadで書いているのですが、日本語をがしがし書いていく分にはそれほど問題ありません。

あとはSSH環境の問題で、現状はサーバにログインした後に面倒な操作を強いられます。もちろん緊急時にサーバに入ってメンテするくらいは問題ないのですが、エディタを起動してバリバリソースを書くというにはまだ厳しいですね。

ただ、これはあくまで現時点での話で、今後はアプリが改善されるかもしれませんし、別のアプリがが登場するかもしれません。さらにiPadのOSも新バージョンが秋に登場する予定です。

そう遠くない時期にiPadで開発ができる日が来ることを期待したいです。

CakePHPとnginx+memcachedで手軽にキャッシュを活用する

この記事の所要時間: 439

nginx+memcachedがめちゃ気になったので試してみました。

元ネタは下記です。

A 53,900% speedup: Nginx, Drupal, and Memcache bring concurrency up and page load time way down | TechnoSophos

nginxをリバースプロキシに利用した構成で、バックエンドの出力をmemcachedにキャッシュしておけば、次回リクエストではnginxがそのキャッシュを読み取ってそのまま出力してくれます。

つまりバックエンドにリクエストを経由させずにnginxから即出力するのでかなりの高速化が見込めるという優れものです。

リンク先ではバックエンドにDrupalを利用していたのですが、ここではCakePHPを利用してみます。

1. 全体構成

リバースプロキシにnginx(Port: 80)を使い、バックエンドにはapache+mod_php(Port: 8080)を使います。

nginxはリクエストが来るとURLをキーとしたキャッシュがmemcachedにあるかを調べます。キャッシュがあればそれをレスポンスとしてそのまま返します。

キャッシュが無い場合はバックエンドにリクエストが飛びます。バックエンドではページ生成処理を行い、出力内容をmemachedURLにキャッシュします。

これにより次回リクエストからはこのキャッシュがnginxから出力されます。

試したバージョンは以下。

  • nginx 0.7.65
  • memcahed 1.2.8
  • apache 2.2.14
  • PHP 5.2.10
  • CakePHP 1.2.6

2. nginx+memcached設定

nginxのmemcached連携を行うためにNginxHttpMemcachedModuleを利用します。NginxHttpMemcachedModuleはnginxをデフォルトでインストールすれば含まれています。

nginx.confを以下のように設定します。

# バックエンド
upstream backends {
    server 127.0.0.1:8080;
}

http {
    #(snip)

    server {
        #(snip)

        location / { 
            # POSTはそのままバックエンドへ
            if ($request_method = POST) {
                proxy_pass http://backend;
                break;
            }

            # memcachedにキャッシュがあればキャッシュを
            # 無ければバックエンドへ
            set $memcached_key $uri;
            memcached_pass     127.0.0.1:11211;
            default_type       text/html;
            error_page         404 502 = @fallback;
        }

        location @fallback {
            proxy_pass         http://backend;
        }
    }
} 

ここで注意が必要なのが「error_page 404 502 = @fallback;」の箇所。

memcachedにキャッシュが無い場合はバックエンドへリクエストを投げるのですが、この箇所を[=]抜きにすると404(キャッシュが無い)や502(memcachedが繋がらない)がステータスコードとして出力されてしまいます。

つまりキャッシュが無い場合だとページ自体はバックエンドが生成した内容が出力されるのですが、ステータスコードが404となってしまいます。

[=] を付けるとバックエンドで出力されたステータスコードがそのままレスポンスとして出力されるので、[=]を忘れないようにしましょう。(はまりました。。。)

3. バックエンドでキャッシュ処理

次にバックエンドで生成した内容をmemcachedにキャッシュします。

まずPHPからMemcacheにアクセスするために、pecl::memcacheをインストールします。

$ sudo pecl install memcache

ここではCakePHPでの処理を想定しており、MemcacheViewHelperというヘルパを作成してキャッシュを行います。

MemcacheViewHelperはafterLayoutメソッドでビューからの出力全体をmemcachedにキャッシュします。

ちなみにHelper#afterLayout()はフレームワークから呼ばれるフックメソッドで、下記のように出力全体に対して処理を行うことが出来ます。例えば文字エンコーディングを変換する時などなかなか便利なメソッドです。

[app/views/helpers/memcache_view.php]

<?php
class MemcacheViewHelper extends AppHelper {
  public function afterLayout() {
    if (!empty($this->params['memcache_view']['is_cache'])) {
      $view = ClassRegistry::getObject('view');

      $memcache = new Memcache();
      $memcache->addServer('localhost', 11211);
      $memcache->set(Router::url(), $view->output, false, 3600);
    }   
  }
}

コントローラでMemcacheViewHelperを読み込みます。キャッシュを有効にするアクションでは$this->params[‘memcache_view’][‘is_cache’]にtrueをセットします。

下のUsersControllerの場合、indexアクションはキャッシュされますが、viewアクションはキャッシュされません。

[app/controllers/users_controller.php]

<?php
class UsersController extends AppController {
  public $name = 'Users';
  public $helpers = array('MemcacheView');

  public function index() {
    $this->User->recursive = 0;
    $this->set('users', $this->paginate());
    // キャッシュする
    $this->params['memcache_view']['is_cache'] = true;
  }

  public function view($id = null) {
    if (!$id) {
      $this->Session->setFlash(__('Invalid User', true));
      $this->redirect(array('action' => 'index'));
    }   
    $this->set('user', $this->User->read(null, $id));
  }
}

これでブラウザからnginx(ex. http://localhost/users/index)にアクセスすると 1 回目はバックエンドにリクエストが飛びますが、2回目以降はmemcachedのキャッシュが出力されます。

4. パフォーマンス

キャッシュの効果を見るために、nginx+memachedを使ったパターンとバックエンドに直接接続した場合とでパフォーマンスを計測してみました。

計測はabで行っています。(-c 100 -n 1000)数値はRequests per secondで、5回行った平均値です。

参考にキャッシュをファイルに出力してnginxから出力するパターンも計測しました。

構成 Requests per second
apache+mod_php+CakePHP
(ダイレクト)
13.814 100%
nginx+memcached 2034.262 14726%
nginx+ファイル(参考) 5806.798 42037%

やはりapache+mod_php+CakePHPよりnginx+memcachedが圧倒的に早いですね:D ここまで大きな差があると使ってみたくなります。

5. memcached or ファイル

上記パフォーマンスを見るとmemcachedよりfileの方がより多くのリクエストを捌いています。

パフォーマンスだけを見るとファイルが有利ですが、memcachedの方はnginxサーバやWebサーバが複数台になった場合にキャッシュの取り扱いが簡単(複数台で共有しやすい)という利点があります。また、memcachedなら有効期限が簡単に付けられるというのもあります。

どちらを使うのが良いかはケースバイケースですが、キャッシュの取り扱いを考えると個人的にはmemcachedが良いバランスかなと思っています。更新頻度が少ないがアクセス数が多いページだけはファイルに出力してしまうという方法もあります。

いかに遅い箇所を経由させずにレスポンスを返すかという視点で見るとnginx+memcachedはまさに願ったり叶ったりの構成です。ここではバックエンドにCakePHPを使いましたが、他のフレームワークでも他のシステムでも他の言語でも何でも理屈は同じです。

多くの環境から操作できるmemcachedをキャッシュエンジンに使うということは、バックエンドを選ばずにキャッシュ機構が使えるということで、これはよく考えられていますね。

PHP基礎文法最速マスター

  • 2010-01-27 (水)
  • PHP
この記事の所要時間: 059

PerlとRubyの文法一覧がとても良くまとまっていたので、便乗してPHPでもやってみました。

他の言語をある程度知っている人はこれを読めばPHPの文法を何となく理解できると思います。

間違い、不足等々あれば教えて下さいm(_ _)m

バージョン

PHP5.3系がリリースされていますが、ここではPHP5.2系を対象としています。

1.基礎

コードブロック

PHPコードは「<?php」という開始タグから始まります。終了タグは「?>」です。HTMLにPHPコードを埋める際は終了タグを使いますが、ライブラリのようにPHPコードのみを記述する際は終了タグを省くことが慣例となっています。

終了タグを省く理由について id:Kiske さんに解説頂いています。ありがとうございます。
PHP基礎文法最速マスターの補足 – Absolute Playing!

<?php hoge(); ?>
<?php
$i = 1;
hoge($i);
?>
<?php
hoge();
&#91;/php&#93;

<h4>print文</h4>
<p>print/echoを使います。</p>

<?php
print "Hello World!";
echo "Hello World!";
&#91;/php&#93;
<p>デバッグにはvar_dump()をよく使います。var_dump()では変数の内容が出力されます。</p>

<?php
$array = array(1,2,3);
var_dump($array);

array(3) {
  &#91;0&#93;=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}

コメント

一行コメント

// コメント
# コメント

複数行コメント

/*
コメント
コメント
*/

変数の宣言

変数の宣言です。

$a = 'string';
$i = 1;

スクリプトの実行

コマンドラインでPHPファイルを実行します。

$ php hoge.php

PHPコードを直接記述することもできます。<?php ?>は不要です。

$ php -r "var_dump('a');" 

出力結果をファイルに書き出すにはリダイレクトを使います。

$ php hoge.php > out

スクリプトの文法チェック

phpコマンドで文法がチェックできます。

$ php -l hoge.php

2. 数値

数値の表現

数値には整数、浮動小数点があります。

<?php
$int = 100;
$float = 100.123;
&#91;/php&#93;
<h4>四則演算</h4>

<?php
$i = 1 + 1;
$i = 1 - 1;
$i = 1 * 1;
$i = 1 / 2;
&#91;/php&#93;
<p>余りと商。商を求めるには普通の除算を行った後にintval関数で整数部を取り出します。</p>

<?php
$div = intval(3 / 2); // 商
$mod = 3 % 2; // 余り
&#91;/php&#93;

<h4>インクリメントとデクリメント</h4>

<?php
$i++;  // インクリメント
$i--;   // デクリメント
&#91;/php&#93;

<h3>3. 文字列</h3>
<h4>文字列表現</h4>
<p>文字列はシングルクォートかダブルクォートで囲みます。ダブルクォートの中では\t(タブ)や\n(改行)などの特殊文字を利用することができます。またダブルクォートで囲まれた文字列の中では変数展開することができます。</p>

<?php
$str1 = "abc\tcde"; // abc  cde(\tがタブ&#91;0x09&#93;)
$str2 = 'abc\tcde'; // abc\tcde(\tが文字列)

$str3 = "$str1 100"  // abc  cde 100 //$str1が展開される
$str4 = "{$str1}100"  // 変数名に文字列が繋がる時は{}で囲む
&#91;/php&#93;

<h4>文字列操作</h4>

<?php
// 結合
$join1 = 'aaa' . 'bbb';
$join2 = implode(',', array('aaa', 'bbb', 'ccc'));

// 分割
$split = explode(',', 'aaa,bbb,ccc');

// 長さ
$length = strlen('abcdef');
// 長さ(マルチバイト)
// 内部エンコーディングの設定が必要
// mb_internal_encoding('UTF-8');  
$mb_length = mb_strlen('あいうえお');

// 切り出し
$substr = substr('abcd', 0, 2); // ab

// 検索
$index = strpos('abcd', 'bc'); // 見つかったらその位置(先頭が0)、見つからなかったらfalseが返る
&#91;/php&#93;

<h3>4. 配列、連想配列</h3>
<p>PHPには連想配列しかありません。配列はキーが数値の連想配列として表現されます。また順序を持っています。</p>

<?php
$array1 = array(1, 2, 3); // 配列(キーが0から始まる連想配列)
$array2 = array('a' => 1,  'b' => 2, 'c' => 3); // 連想配列
$array3 = array(1, 'a' => 1, 2); // 混在もok

要素の参照と代入

<?php
$i = $array1&#91;0&#93;;
$s = $array2&#91;'a'&#93;;
&#91;/php&#93;
&#91;php&#93;
<?php
$array1&#91;3&#93; = 1;
$array2&#91;'z'&#93; = 'zzz';
&#91;/php&#93;

<p>要素の個数</p>

<?php
$len = count($array1);
&#91;/php&#93;

<h4>配列の操作</h4>

<?php
$array = array(1, 2, 3);
// 先頭を取り出す
$first = array_shift($array); // $first = 1 / $arrayは、(2, 3)
// 先頭に追加
array_unshift($array, 5); // $arrayは、(5, 2, 3)
// 末尾を取り出す
$last = array_pop($array); // $last = 3 / $arrayは、(5, 2)
// 末尾に追加
array_push($array, 9); // $arrayは、(5, 2, 9)
&#91;/php&#93;

<h4>連想配列に関する関数</h4>

<?php
// キーの取得
$keys = array_keys($array);
// 値の取得
$values = array_values($array);
// キーの存在確認
$boolean = array_key_exists('key', $array);
// キーの削除
unset($array&#91;'key'&#93;);
&#91;/php&#93;

<h3>6. 制御文</h3>
<h4>if文</h4>

<?php
if (条件) {
}
&#91;/php&#93;
<p>HTML内で記載する際は以下のような記法も用いられます。</p>

<?php if (条件): ?>
  <span>hoge</span>
<?php endif; ?>

if 〜 else文

<?php
if (条件) {
} else {
}
&#91;/php&#93;
<p>HTML内で記載する際は以下のような記法も用いられます。</p>

<?php if (条件): ?>
  <span>hoge</span>
<?php else: ?>
  <span>foo</span>
<?php endif; ?>

if 〜 else if 文

elseif or else ifが可。

<?php
if (条件) {
} else if {
}
&#91;/php&#93;
<p>HTML内で記載する際は以下のような記法も用いられます。</p>

<?php if (条件): ?>
  <span>hoge</span>
<?php elseif (条件): ?>
  <span>foo</span>
<?php endif; ?>

while文

<?php
$i = 0;
while ($i < 5) {
  // 処理
  $i++;
}
&#91;/php&#93;
<p>HTML内で記載する際は以下のような記法も用いられます。</p>

<?php while ($i < 5): ?>
  <span><?php echo htmlspecialchars($i); ?></span>
  <?php $i++; ?>
<?php endwhile; ?>

for文

<?php
for ($i = 0 ; $i < 5 ;$i++) {
}
&#91;/php&#93;
<p>HTML内で記載する際は以下のような記法も用いられます。</p>

<?php for ($i = 0 ; $i < 5 ; $i++): ?>
  <span><?php echo htmlspecialchars($i); ?></span>
<?php endfor; ?>

foreach文

連想配列の各要素を処理できます。

<?php
foreach ($array as $v) {
  // $v が要素の値
}
foreach ($array as $k => $v) {
  // $k が要素のキー、$v が要素の値
}

HTML内で記載する際は以下のような記法も用いられます。

<?php foreach ($array as $v): ?>
  <span><?php echo htmlspecialchars($v); ?></span>
<?php endforeach; ?>

7. サブルーチン(関数)

PHPには関数があります。戻り値を返却するにはreturnを使用します。

<?php
function sum($v1, $v2) {
  return $v1 + $v2;
}
$total = sum(1, 2); // $total = 3

// 配列で多値を返すこともできます
function get_multi($v1, $v2) {
  $v1 += 100;
  $v2 += 200;
  return array($v1, $v2);
}

list($ret1, $ret2) = get_multi(1, 2); // $ret1 = 101 / $ret2 = 202
&#91;/php&#93;

<h3>8. ファイル入出力</h3>
<p>ファイル入出力にはいくつかの方法があります。</p>
<h4>fopen関数</h4>
<p>ファイルポインタを使ってファイルの入出力を行います。</p>

<?php
// 読み込み
$fp = fopen("/path/to/file", "r");
if (!is_resource($fp)) {
  die("can't open file");
}

while (!feof($fp)) {
  $line = fgets($fp, 4096);
  // 何か処理
}
fclose($fp);

// 書き込み
$fp = fopen("/path/to/file", "w");
if (!is_resource($fp)) {
  die("can't open file");
}

fputs($fp, $buff);
fclose($fp);

&#91;/php&#93;
<h4>file関数</h4>
<p>ファイル全体を読み込んで配列に格納します。</p>

<?php
$list = file("/path/to/file"); // ファイルの各行を連想配列で取得
&#91;/php&#93;
<h4>file_get_contents関数 / file_put_contents関数</h4>
<p>file_get_contents関数はファイル全体を読み込んで文字列として格納します。file_put_contents関数は変数の値を全てファイルに書き込みます。</p>

<?php
// 読み込み
$contents = file_get_contents("/path/to/file"); // ファイルの内容を取得

// 書き込み
file_put_contents("/path/to/file", $buff); // ファイルに $buff の内容を書き込み
&#91;/php&#93;

<h3>知っておいた方が良い文法</h3>
<h4>真偽値</h4>
<p>PHPでは以下の場合、偽と判断されます。</p>
<ul>
<li>boolean の FALSE</li>
<li>integer の 0 (ゼロ)</li>
<li>float の 0.0 (ゼロ)</li>
<li>空の文字列、 および文字列の "0"</li>
<li>要素の数がゼロである 配列</li>
<li>メンバ変数の数がゼロである オブジェクト (PHP 4のみ)</li>
<li>特別な値 NULL (値がセットされていない変数を含む)</li>
<li>空のタグから作成された SimpleXML オブジェクト</li>
</ul>
<h4>==と===</h4>
<p>==/!=といった比較演算子では、数値・文字列の自動変換が行われます。よって意図しない結果をもたらす場合があります。</p>

<?php
var_dump(1 == 1); // true
var_dump(1 == '1'); // true
var_dump(0 == 'a'); // true
var_dump(100 == '100a'); // true
var_dump('+1' == '1.0'); // true
&#91;/php&#93;
<p>こういった場合、===/!==を使うと変数の型も厳密に比較することができます。</p>

<?php
var_dump(1 === 1); // true
var_dump(1 === '1'); // false
var_dump(0 === '0'); // false
var_dump(100 === '100a'); // false
var_dump('+1' === '1.0'); // false
&#91;/php&#93;
<h4>変数が定義されているかどうか</h4>
<p>変数が定義されているかどうかを調べるにはisset関数を使用します。定義されている場合はtrueが返ります。ただしisset関数では変数の値がNULLの場合もfalseが返ります。</p>

<?php
isset($a);
&#91;/php&#93;
<h4>コマンドライン引数</h4>
<p>コマンドライン引数を受け取るには$argv変数を使用します。</p>

<?php
var_dump($argv);
&#91;/php&#93;
<h4>array_map</h4>
<p>array_map関数を使うと、連想配列の各要素に処理をして新たな連想配列として受け取ることができます。</p>

<?php
$array = array(1,2,3);
$mapped = array_map(create_function('$v', 'return $v *= 10;'), $array); 
&#91;/php&#93;
<h4>array_filter</h4>
<p>array_filter関数を使うと、条件に一致した要素のみを新たな連想配列として受け取ることができます。</p>

<?php
$array = array(1,2,3,4);
$filtered = array_filter($array, create_function('$v', 'return ($v > 2);'); 

複数の変数への代入

<?php
list($v1, $v2, $v3) = array(1, 2, 3);
&#91;/php&#93;
<h4>php.ini</h4>
<p>PHPには設定ファイルがあります。設定に応じて挙動が変わるので注意が必要です。この設定はphp.iniという設定ファイルの他に、httpd.conf、.htaccess、そしてソースコードにて設定が可能です。</p>
<p>設定方法は、項目に応じて変わりますが、ソースコードで設定を行う際はini_set()を使うことが多いです。</p>

<?php
ini_set('include_path', '.:/path/to/libs'); // include_pathに'.:/path/to/libs'を設定する
&#91;/php&#93;
<p>現在の設定は、phpinfo関数もしくはphpコマンドで確認できます。</p>

<?php
phpinfo();
&#91;/php&#93;
<pre class="code">
# 全ての設定値を出力
$ php -i
# grep で絞る
$ php -i | grep include_path
</pre>
<h4>クラス定義</h4>
<p>classでクラスを定義できます。</p>

class User {
  protected $name = null;
  
  public function __construct($name) {
    $this->name = $name;
  }

  public function hello() {
    printf("%s: Hello!\n", $this->name);
  }
}

$user = new User('Mike');
$user->hello();

継承もできます。単一継承のみ可能です。

class MyUser extends User {
}

例外

throwで例外を投げることができます。try/catchで例外をキャッチします。他の言語にあるfinallyに相当するものはありません。

function foo() {
  throw new Exception();
}

try {
  foo();
} catch (Exception $e) {
  echo $e->getTraceAsString();
}

PHP参考資料

公式マニュアル

PHPに関する書籍は多く出版されていますが、やはり一番参考になるのは公式マニュアルです。

PHP: PHP マニュアル – Manual

公式マニュアルを使う際にちょっとしたTIpsを。

ブラウザで公式マニュアルを開く際は、http://php.net/の後ろに調べた関数名を入力すると直接そのページが開きます。合致するものがなければ類似するキーワードが一番表示され、候補の中から選択することもできます。

http://php.net/array

コーディング規約

コーディング規約にはいくつか流派があるのですが、Zend Frameworkのコーディング規約が参考になるでしょう。

Zend Framework PHP 標準コーディング規約 – Zend Framework Manual

モダンPHP

PHPにはオブジェクト指向言語としても機能があります。以下の資料が参考になります。

モダンPHP勉強会を開催しました & 資料 – 肉とご飯と甘いもの @ sotarok

フレームワーク

PHPを使ったWebシステム開発ではフレームワークを用いることがメジャーになりつつあります。

多くのオープンソースフレームワークがありますが、主要なものは以下です。

同じエントリへのはてブがバラバラに付く

  • 2009-12-08 (火)
  • PHP
この記事の所要時間: 248

近頃、同じエントリへのはてブが別々のブックマークになっていませんか。

WordPressでこの現象の対応方法を考えました。

utm_source

utm_sourceというURLパラメータが

いつの頃か、FeedBurnerやTwitterFeedからのリンクに「utm_source=feedburner」といったURLパラメータが付くようになりました。これ自体はGoogle Analytics用なので、特に害は無いのですが、リンク先のblog側からすると1点だけ困ることがあります。

はてブがバラバラに付く

冒頭に触れたように、同じエントリに対するはてブなのにURLパラメータが付くことにより、別のPermlinkとして判定され、別のブックマークになってしまいます。つまり同じエントリへのはてブなのに、はてブ数が合算されません。

例えば以下のリンク先は同じエントリに対するはてブなのですが、FeedBurnerが付けるURLパラメータがあるため、5つに分かれています。

はてなブックマーク

同じエントリなのに別々のブックマークになっています。

same_entry_hatebu

小さな事といえば小さな事なのですが、なんだかちょっぴり悲しいですよね。

URLパラメータを取り除く

これはPermlinkに不要なURLパラメータが付いているのが原因なので、それを取り除いてしまおうという話です。

方法は至ってシンプルで、utm_source=がURLパラメータにあれば、mod_rewriteでURLパラメータナシURLにリダイレクトするだけです。

WordPressなら.htaccessに以下の記述を入れるだけです。/blogの箇所は各サイト毎に変更して下さい。また、この例ではPermlinkとして「*.html」という形式を想定しています。他のPermlinkの人は変更して下さい。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /blog/

# ↓を追加
RewriteCond %{QUERY_STRING} utm_source=
RewriteRule ^(.*\.html)$ /blog/$1? [R,L]
# ↑を追加

RewriteCond %{REQUEST_FILENAME} !-f 
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteRule . /blog/index.php [L] 
</IfModule>

対処療法

対処療法的ではありますが、この方法を使えば、FeedBurnerからのリンクでもPermlinkを揃えることができ、はてブが分散することもありません。

他のサービスで特殊なURLパラメータが付けられても、記述を追加していけば、URLパラメータを取り除くことができます。

なおFeedBurnerやTwitterFeedを利用している人がそれぞれのサービスで設定変更する方法は、以下のリンクが詳しいです。

たまには$を書かない日を -No Dollar Sign Day-

この記事の所要時間: 432

PHPを日々書いている人へ。

たまにはPHPから離れて他の言語でプログラムを書いてみましょうという話です。

no_dollar_sign-1

Webシステムのプログラムを書く手段としてPHPは実用的なのですが、他にも様々な言語が存在しているのは周知のとおりです。PHPにはPHPの、他の言語には他の言語の良いところ悪いところがあり、それらは普段使っている言語を離れて他の言語を知ることによって、初めて気付かされます。

また他の言語での実装方法を知ることにより、PHPで実装する際に別の角度からプログラムを見ることができ、さらに深い理解を得ることができます。

PHPを極めていくことはとても良いことなのですが、ある程度頭で描いた仕様が実装できるようになったのなら、是非他の言語を触ってみることをおすすめします。

では実際どの言語を触ってみるのが良いかという話になるのですが、普段PHPでWebシステムを開発しているなら、おすすめはPython + Google App Engine(GAE)です。

Python + GAE

Python+GAEがなぜおすすめなのかというと、とにかく環境が整っていることです。

言語は他のLLでもコンパイル言語でも何でも興味がある言語で良いと思うのですが、開発したものを動作、公開する環境として、GAEは本番サーバが無料で提供されていることが大きいです。

またGAEのSDKは本当に良くできています。

開発用サーバが内包されていてすぐに開発を始めることができます。本番サーバにデプロイするのもコマンド一発で勝手にやってくれます。FTPソフトを起ち上げる必要はありません。

そしてWebシステムな便利やライブラリが揃っている、詳細なドキュメントがあるなど至れり尽くせりなわけです。

Pythonはとても学びやすい言語で、言語のリファレンスもPHP同様にWebで公開されています。文法はPHPとは異なるものの、その違いがかえって「異なる言語を学んでいる」気にさせてくれます。$やセミコロン、{}が無いとこんなにもすっきりと書けるのかと感じています。

もちろんRubyでRailsを学ぶもの良いのですが、環境面でGAEほど整ったものが無いので、現時点ではPythonの方が良いのではないでしょうか。(好き好きですけどね:-D)

作るのはWebシステム

これも好みですが、他言語で何かを作るならWebシステムが良いです。

全く違うシステムを作るのも良いですが、あまり知らないシステムだと言語+仕様の両方を学ぶ必要があり、途中で息切れしてしまうかもしれません。Webシステムであればある程度やるべきことは分かっているので、それを他言語でどう実装すれば良いかという点に集中できます。

PHPならこう書くけど、Pythonだとどうすれば良いのかなーという感じです。

あとWebシステムなら作ったものを公開することもできるので、これも1つのモチベーションになりますね。

言語仕様を一から覚えない

新しい言語を学ぶには言語仕様をきちんと理解すべき、というような意見があります。

もちろん言語仕様を学ぶことは大切なことでそれは百も承知なのですが、いきなりそこだけをやっていると大した面白みが見いだせずに息切れしてしまう可能性大です。

なので、まずは作りたいものを決めて、それに必要なところだけをやっていけば良いぐらいの気持ちで良いと思います。

思い出してみて下さい。PHPを覚えだした頃、いきなり言語仕様から学びましたか?

おそらく多くの人はやりたいことがあって、それを実現するために断片的にPHPを勉強していったと思います。よって他言語を覚えるときも同じアプローチでいけば良いのではないでしょうか。

ただ、PHPを覚えだした時と違うのは、プログラムについてある程度の知識がある状態だということです。なので自然と言語仕様にも興味が出て来ます。また、PHPとの比較で新たな発見もあると思います。そういった時はリファレンスや書籍で少しづつ勉強していけば意外にすんなりと学んでいくことができます。

Python + GAEが楽しい!

今週末はPython + GAEでTwitter botを作りました。

Pythonを触るのは初めてでは無いのですが、いつもちょろっと触って忘れるを繰り返していたので、今回は何かしら公開できるものが出来上がるまで続けるつもりです。

PHPだけを学んでいた人でも、ある程度Webシステムが作れるようになっていれば、その経験は他言語を学ぶ際に大きな糧となります。もしPHPをやる前に挑戦して挫折した言語があれば、もう一度その言語をやってみるとびっくりするくらいに理解が進むかもしれません:-D

というわけで、たまには他の言語でプログラムを書いてみましょうという話でした。

Python + GAE 参考リンク

PostgreSQL Conference 2009 Japanで発表してきました。

この記事の所要時間: 437

JPUG10周年記念イベント「PostgreSQL Conference 2009 Japan」で発表してきました。

oregon

コアな内容や大規模向けな内容のセッションが多い中、明らかに場違い感満載な内容だったわけですが、とりあえず無事に発表できてホッとしています。

この場を借りて発表の機会を頂いたPostgreSQL Conference 2009 実行委員会の皆さんにお礼を言いたいと思います。ありがとうございました。

今回は自身では実に反省点の多い発表でして、発表のあとは一人へこんでいたのですが、そんな発表にも関わらず聞いて頂いた皆さんありがとうございました。

冒頭の写真は、発表の後にオレゴン州出身の方から頂いたシールです。ちょうどへこんでいたところだったので、とても勇気づけられました!ありがとうございます:-D
# www.heartinoregon.comのシールのようです。

以下、雑感をつらつらと。

雰囲気

普段参加しているLL系のイベントとは雰囲気が異なり、特に初日のビジネスDayは堅めでしたね。初日は仕事の関係で午後から参加したのですが、セッション中の堅めな雰囲気に翌日の自分の発表が不安になったりしました。

ただこの雰囲気はセッション後の懇親会では一変し、わいわいがやがやと良く知る賑やかな雰囲気となりました。これはかなり安心しました:-D

あと発表者、参加者ともに年齢層はWeb系に比べると若干高めだった気がします。

Twitter

公式ハッシュタグが用意されていたのですが、発言している人は少なめでしたね。スタッフの方を除くとポツポツという感じで、さらに発言されているのは海外の方の方が多かったように思います。

あと発表のスライドにTwitterのIDを書いている人がすごく少なかったです。これは個人での発表というより組織の代表者として発表している人が多かったのもあるかもしれません。

プレゼン

自分で発表するようになってから、他の人の発表を見ると、内容もさることながら、話し方やテンポ、仕草などを見るのが楽しいです。

今回は特に自分の発表でへこんだ後に聞いたDavid Wheelerさんの発表が素敵すぎました。英語はほとんど分かってないのですが、この発表では(他に比べると)理解することができました。スライド・言葉だけでなく、表情や仕草などで何かが伝わってくるこのプレゼンは良い勉強になりました。

他の方のプレゼンも色々と学ぶところがあり、そういったプレゼン手法を見ているだけでも楽しいイベントでした。

やはり上手い人のプレゼンにはおもてなしの心があるんだなーと感じましたね。

国際的なイベント

2日目のクロージングにてスタッフの方のお話に「国際的なイベント」という言葉がありましたが、まさにそういったイベントになっていたと思います。

海外のイベントには参加したことは無いので、ここまで海外の方の比率が多いイベントに参加したのは初でした。

印象的だったのは、発表の途中で突っ込みが入ったり(しかもその突っ込みにさらっと回答する)、質疑応答が白熱したりする光景でした。こういったものは日本と海外との違いということで語られていたり、動画などで見たりはしていたのですが、直に見るのは初めてだったので新鮮でした:-D

こういったプレゼンは是非自分たちでもやっていみたいです。

そういえば参加したセッションもほとんどが英語のセッションで、海外に行ったらこんな感じかなと想像しながら聞いてました。国内からの参加者でも英語を話す人が多かったです。やっぱり英語勉強したいです。

あと初日の同時通訳もすごかったです。よく国際会議とかで見てるイヤホンで聞いているアレですが、ホントに発表者が話して少しのタイムラグでどんどん訳が聞こえてきます。いやあすごい。

運営について

こういった素晴らしいイベントを運営されたスタッフの皆さんには頭が下がるばかりです。自分もつい先月にCakeMatsuriを運営していただけに、とにかくお疲れ様でしたという気持ちで一杯です。

素晴らしい運営の中で、気になった点を。

思うに発表者へのサポートがもう少しあると良かったように思います。PCとプロジェクタとの接続やピンマイクの使い方・調整など、手すきのスタッフの方が発表前に横についてサポートして上げれば、もっと安心して発表することができたと思います。

海外からの発表者についてはもちろんサポートされている方がおられたのですが、国内からの発表者はわりと放任主義な感じがしました。

自分が運営に関わるイベントでもできているわけではないので、自戒も込めて。

とにかく濃いイベント!

発表内容はどれも濃く興味深い内容が満載でした。特に監視を含めた「見える化」系の話やSE-PostgreSQLの考え方、破損データからレコードを取り出す方法などが面白かったです。

あとライトニングトークがどれも内容がしっかりしていて、5分で聞くにはもったい無い内容ばかりでした:-D

DBのイベントということで色々なバックエンドを持つ人が集まっていたため、色々な話を聞くことができたのが収穫でした。できればもっと色々な人とお話したかったです:-D

最後に、実行委員会の皆さん、参加された皆さん、お疲れ様でした&ありがとうございました!

  • コメント (Close): 0
  • トラックバック (Close): 0

ホーム > PHP

検索
フィード
メタ情報

Return to page top