2012年9月10日月曜日

PHPのinterfaeを使った簡単なDiContainerを考えた

性懲りもなく、こんなことを考えてます。

DIコンテナは便利なのですが、設定を作るのが面倒です。
クラスのコード内に、必要なオブジェクトを記述して、コンテナがよしなに注入してくれる方法が楽そうです。

BEAR.SundayのDIモジュールがまさにそうです。

ただアノテーションの処理にDoctrineが必要など、それはそれで面倒な感です。そこで、PHPのインターフェースを使った簡単Diコンテナについて考えてみました。

簡単な流れは:
  1. コンテナ内で、オブジェクトを生成。
  2. オブジェクトのインターフェースを検索。
  3. インターフェース名から注入するクラスを決定。
  4. オブジェクトを生成して、適当なメソードに注入。

具体例です。
namespace Database {
  interface InjectDbaInterface {}
  class Dba {...}
}
namespace Invoice {
  class Billing implements \Database\InjectDbaInterface {
    function injectDba( $Dba ) {
      $this->Dba = $Dba;
    }
  }
  $bill = $container( 'Billing' ); // 注入済みのオブジェクト
}
ポイントは:
  • 必要とされる(であろう)Dbaのクラスと同じnamespaceに、注入用のinterfaceを前もって作っておく。
  • インターフェース名は、\name\space\Inject{クラス名}Interface、と決めておくことで、注入するクラスとネームスペースを特定できる。
  • Invoiceのクラス宣言で、注入してほしいオブジェクトのinterfaceを実装(implements)する。
なお、コンテナ側のコードは、こんな感じ。
if( $interfaces = class_implements( $object ) )
foreach( $interfaces as $interface ) { // インターフェース一覧
  if( preg_match( '/^(.*)Inject([_a-zA-Z0-9]+)Interface$/i',
    $interface, $matches ) ) {
    $className = $matches[1] . $matches[2]; // 注入するクラス
    $injector  = "inject" . $matches[2]; // 注入方式は決め打ち
    $injObj = new $className;
    $object->$injector( $injObj );
  }
}
非常に簡単に動く(はず)。
【追記:2012年9月15日】
簡単なサンプルコードを書きました(https://gist.github.com/3725826)
確かに簡単に動きました。

インターフェースを使って依存性の自動解決をするコンテナがないか気になったのですが、ちょっと探したところでは見当たりません。見つけたのはアノテーションを使っているものばかりでした。(例:Seasarの自動バインディング

2012年8月12日日曜日

ssh_exchange_identification: Connection closed by remote host

今までのように、sshでサーバーに入ろうとしたら、怒られた。
ssh_exchange_identification: Connection closed by remote host

最近、何もいじくってないのに。
いや、MacBook Airから鍵認証で入れるようにしたが・・・
サーバー側はいじってないはず。

ググると、いろいろかかれてた。
ひとつずつ、対応してみた・・・

/etc/hosts.deny


まずは、一番怪しそうなこのファイル。
中身を見たら、いろんなサイトが追加されてた。

そういえばdenyhostsをインストールしていた。
アタックしてるサイトのIPアドレスが追加されてる。

えっ、こんなにアタックがあるんだ。
ポート変更しとこう・・・

ちなみに、自分のアクセス元はありませんでした。
これが原因ではない。

/etc/ssh/sshd_config


このなかの「MaxStartups」を設定しておくのがよいらしい。
見たら、コメントアウトされてたので、「#」マークを削除。

リスタートして・・・まだ動かない。

/etc/hosts.allow


まさかね。
これじゃないよね。でも何も書いてないよな。

このページを参考にして「sshd:all」と一行追加。


動いた。


こうなると、今まで動いていたのが不思議だが、

ま、少しセキュリティも向上して、よかった、よかった。

2012年8月11日土曜日

gitのレポジトリをサクラVPSに作ってみた

サクラさんのVPSを借りた理由の一つが、自分のgitレポジトリを持ちたいということ。これでパスワード付きのソースコードだって気にしないで管理できる。

最初はgitosisかgitoliteをインストールする予定だった。
が、Stackoverflowの両者の比較ページにgitだけでレポジトリを作る方法が説明されていた。簡単そうだったので、まずはこれでゆくことにした。

ちなみに、日本語での説明はこのページに
もう書いてあるそのまま。

サーバー側でgitがインストールされているとして、
ssh [user@]server
cd repos/
mkdir test.git
cd test.git
git init --bare

ローカル側でもgitが入っていれば、
cd src/test
git init
git remote add origin [user@]server:repos/test.git
git push
これでレポジトリができて動いた。

ひとつだけ、ローカル側でgit remote add originを走らせると、こんなエラーが出た。
fatal: Not a git repository (or any of the parent directories): .git

原因はgit initを走らせていないこと、だって。

2012年7月28日土曜日

BEAR.Sundayでコンタクトフォームでも作ってみる

BEAR.Sundayをインストールしたので、
簡単なコンタクトフォームでも作ってみることにした。
何しろtwigすら使ったこと無いので、何もかもが手作業・・・

そして、コードが見にくいですね。すいません。
bloggerがmarkdownに対応してほしい。

Contact.phpを作る



sandbox/Resource/Page/Contact.phpとして以下を作成。

<?phpnamespace sandbox\Resource\Page;
use BEAR\Framework\Resource\AbstractPage as Page;
class Contact extends Page{
    public $body = [];
    public function onGet()
    {
        $this->body[ 'message' ] = 'Please fill in the form.';
        return $this;
    }
}

これが「リソースオブジェクト」になる。
namespaceとファイルの場所を一致させる。違うと何かエラーが出た。
ちなみに、これだけだと何も表示されない。
内容は$this->bodyに入る。そしてonGetメソードの最後のreturn $thisで、内容とともに全ての情報をフレームワークに返している。


テンプレートファイルを作成


sandbox/Resource/Page/Contact.tplとして以下を作成。


<!DOCTYPE html>
<html lang="en">
<body>
<h1>Contact Me</h1>

<p>{$message}</p>
</body>
</html>
アクセスすると「Contact Me」と「Please fill in the form」と表示された。

リソースオブジェクトのクラス名からテンプレートファイルを決定(言うなれば拡張子をtplに変えてる)、テンプレートファイルから表示しているようです。

contact  フォームを作る


次はコンタクトフォームを作ってみる。
以下のformをbody内に書き込んでみた。

<form action="/contact" method="POST">
    <input name="X-HTTP-Method-Override" type="hidden" value="POST" />
    <div class="control-group">
        <div class="controls">
            <input type="text" id="title" name="title" >
            <p class="help-inline"></p>
        </div>
        <div class="controls">
            <textarea type="text" id="body" name="body" rows="8"></textarea>
            <p class="help-inline"></p>
        </div>
    </div>
    <input type="submit" value="Submit">
</form>
大事なのは隠しタグでHTTPメソードをPOSTにしてるところ。
SubmitするとonPostメソードが無いと怒られるので、次のメソードをContact.phpに追加してみた。

    public function onPost()
    {
        $this->body[ 'message' ] = 'You have tried to contact me.';
        return $this;
    }
まさにHTTPメソードがオブジェクトのメソード名に一対一に対応しているのがよくわかります。アクセスすると、メッセージが「コンタクトしてくれたね」みたいな感じになったので動いてますね。結構、簡単です。

フォームの内容を受け取る


onPostの引数に「$title」と「$body」を追加。
    public function onPost( $title, $body )
すると、フォームの内容を受け取れました。

簡単すぎる。
どうやって動いてるのだろう?

本来なら、フォームを受け取ったら、データをモデルに渡して処理を行ってもらうのが正しいと思います。Blogのサンプルコードを見ると、次のようにして処理を受け渡せるようです。
$this->resource->post
        ->uri('app://self/blog/contact')
        ->withQuery(['title' => $title, 'body' => $body])
        ->eager->request();
この場合、Resource/App/Contact.phpクラスを作り、その中で実際の処理を行うことになります。

今回はサンプルですし、メールを出すだけなので、ここではPageのクラス内で処理してしまいます。mail_jp( $mailto, $title, $body );とか書くだけですし。(サンプルなので実際にメールを送信はしません。)

Page内で処理を行うべきか?
行うべきではないですが、まぁいいか、と思う場合はありますよね?

エラー処理をPageとTemplateに加える


フォームと言えばエラー処理。
必須項目の処理を加えました。
エラー表示も他のテンプレートを参考にして修正。

結果:

sandbox/Resource/Page/Contact.php:
<?php
namespace sandbox\Resource\Page;
use BEAR\Framework\Resource\AbstractPage as Page;
class Contact extends Page
{
    public $body = [
        'title' => '',
        'body'  => '',
        'error' => [ 'title' => '', 'body' => '' ],
    ];
    /**
     * @return Contact
     */
    public function onGet()
    {
        $this->body[ 'message' ] = 'Please fill in the contact form.';
        return $this;
    }
    /**
     * @param $title
     * @param $body
     * @return Contact
     */
    public function onPost( $title, $body )
    {
        $this->body[ 'message' ] = 'You have tried to contact me.';
        $this->body[ 'title' ]   = $title;
        $this->body[ 'body' ]    = $body;
     
        if( !$title ) $this->body[ 'error' ][ 'title' ] = 'missing title';
        if( !$body  ) $this->body[ 'error' ][ 'body'  ] = 'missing message body';
     
        if( $title && $body ) {
            // mail_jp( $mail_to, $title, $body );
        }
        return $this;
    }
}


sandbox/Resource/Page/Contact.tpl:

<!DOCTYPE html>
<html lang="en">
<body>
<h1>Contact Me</h1>
<p>{$message}</p>
<form action="/contact" method="POST">
    <input name="X-HTTP-Method-Override" type="hidden" value="POST" />
    <div class="control-group {if $error.title}error{/if}">
        <div class="controls">
            <input type="text" id="title" name="title" value="{$title}">
            <p class="help-inline">{$error.title}</p>
        </div>
    </div>
    <div class="control-group {if $error.body}error{/if}">
        <div class="controls">
            <textarea type="text" id="body" name="body" rows="8">{$body}</textarea>
            <p class="help-inline">{$error.body}</p>
        </div>
    </div>
    <input type="submit" value="Submit">
</form>
</body>
</html>

これでエラー処理が出来ました。


もっとも、メール送信が出来た場合の処理が不十分です。

送信できた場合、今のままだと同じテンプレートを読み込んでしまいます。新たにコントロール変数を追加して、表示内容を変えてもいいのですが、間違った対応方法な気がします。

別に「送信完了、ありがとう」ページを作って、そこに飛ばすのが一番簡単でしょう。

あるいはPageオブジェクト内で、処理の結果により読み込むテンプレートを変更できるとよいのですが。今のところ可能なのかどうか、どうやるのか、さらにはそれが正しい処理なのか、分かりません。

2012年7月23日月曜日

MacにPHP5.4をインストール

PHP5.4を使いたくなった。
最初はVirtualPCインストールしてUbuntuでLAMP環境作ったが、動くのはいいが開発には今ひとつな感じ。

homebrewを使ってインストール


まずは、本体のMac OS X Lionにhomebrewを使ってインストールすることにした。参考にしたのは「Mac OS X Lion: Homebrew, PHP54, MySQL and PEAR」で、ほぼ書いてある通り。


brew tap josegonzalez/php
brew update
brew install php54


多少、あれが無い、これが無いと言われた。
が正確には、あれが無いのでこうしろ、という親切なメッセージなので英語さえ理解できれば簡単に対応できそう。

さ、動いた。
phpinfoを見ると・・・インストールされてない。

pear絡みでエラーが出てるようだ。
Xcodeを4.3.3にアップデートしろと書いてあるので、アップデートしようとしたら・・・何もしてくれない。

仕方が無い。

Macで一番簡単にPHP5.4をインストールする方法


を試してみた。
書いてある通りにcurlをコピペして待つこと5分ぐらいか。
何も応答がなくなったので、心配になったが、ちゃんとインストールしてくれた。
localhostで確認したらphp5.4.5になっていた。


2012年7月21日土曜日

BEAR.Sundayミートアップ#0でLTしました

BEAR.Sunday meetup#0 (2012年7月19日開催)で
LTしてきました。

タイトルは
「Cena-DTA:リソース指向データ転送プロトコル」



Cena-DTAとは2009年の後半から考え始めて、2010年の春頃から開発を始めた技術です。一応、動くところまで持ってきましたが、開発方針で行き詰まってしまい、この一年ほど開発が止まってました。

BEAR.Sundayのmeetup#0に参加すること決めてから何かひらめく物が無いか考えてました。適当なキーワードを散りばめたツイートを書いたり…

そんなとき、LTしませんか?というツイートが!


即、応募しました。
何となく感じてたことを言葉にするいい機会です。

BEAR.Sundayとは関係ない話なのですが、
「気にしないで」という@koriymの言葉に励まされてLTしてきました。

珍しい内容だと思うので、何かの参考になればと思います。

リソース指向とURI


個人的には、
BEAR.Sundayは、全てをURIで表している、という話が面白かったです。

URIというのは間違った理解かも知れません。
ページも含めて、全てを表現するディレクトリ風階層構造、とでもいうのでしょうか。パッと見、URIのような記述

一方Cena-DTAは、ばっさりURIを切り落としてます。
少なくとも細かなリソースごとにURIを用意することはしてません。

これはアプリケーションのモデルが違うからかもしれません。
何しろ「クライアントデバイス上にしか存在してないデータもリソースとして考える」という無茶ぶりで理屈を構築してみたもので。


ともあれ、いまだ興奮冷めやらぬmeetupでした。

2012年7月17日火曜日

UbuntuでJavaが走らない(Error: could not open `/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/jvm.cfg')

Ubuntuで快適に仕事していたが、
突然PhpStormが動かなくなった。

直接端末からPHPStormを起動したらJavaのエラーメッセージが出た。

Error: could not open `/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/jvm.cfg'

そこで、端末から
java -version

と打ってみたら同じエラーが出た。
Javaのインストールエラーだ。

適当に修正



ちょっとググったが、ぴったりのがなかったので、
適当にcdしながら追いかけてみた。
cd /usr/lib/jvm/java-6-sun/jre/lib/amd64

何のことはない、上記のファイルはリンクになっていた。
jvm.cfg -> /etc/java-6-sun/jvm.cfg
リンク先を見ると、java-6-sunというディレクトリがない。
代わりに「java-sun」というのがある。

そこで
sudo ln -s /etc/java-sun/jvm.cfg .

としたら治った。

ググるより、自分で解決したほうが早そうな、珍しいパターンでした。

壊れた原因は・・・


思いつくのは、さくらインターネットさんでVPSを借りた。
Ubuntuをインストールするには、VNCクライアントが必要で、それを動かすためにJREをインストールした覚えがある。それで壊れたのか。

些細な原因ですが、いちいち面倒だな。
その点、WindowsやMac/OS Xとかは、きちんとしてそう。
その代わり有償だし色々と不自由だけど。