Home

Shin x blog

Vagrant体験入門ハンズオンの資料を公開します

この記事の所要時間: 226

2014/04/24 DevLove関西にて、Vagrant体験入門を行いました。

vagrant

これからVagrantを使ってみようという方向けに、Vagrantを触ってみようというイベントです。

前半にVagrantの概要を話すセッションを行ない、その後ハンズオンで実際にVagrantに触れてみるという構成でした。

発表資料

前半で、Vagrantの概要を話した際の資料です。

Vagrant体験入門 from Masashi Shinbara

ハンズオンをはじめるにあたって、下地となる内容を話しました。

Vagrant に関する発表は何度か行っていますし、本やblogを書く際にも何度と無く調べていますが、Vagrant自体の進化が速いので、見直すたびに発見があって面白いです。

ハンズオン資料

ハンズオンの手順は Gist にまとめることが多いのですが、今回は Qiita に書きました。Kobito があるとサクサク書けるのが良いですね。

http://qiita.com/shin1x1/items/3288d9de7f04192b6ad8

ハンズオンでは、メモリスティックで、Vagrant、VirtualBox、Box を配布しました。Boxは下記を利用したので、参加されていない方もこれをダウンロードすれば、Qiitaの手順でハンズオンが試せるようになっています。

今回は、「Vagrantを体験する」内容となっていますが、もう少し踏み込んで、プロビジョニングを書き進めたり、Vagrant Shareで公開、AWS or DigitalOceanで公開なども面白そうです。

ハンズオンの内容が良かったよ、とお褒めの言葉を頂いたので、別のイベントや研修などで活用してやって下さい。

さいごに

ハンズオンは、見学のみ(自分では作業しないけど見るだけ)もありということになっていたのですが、全員がノートPC持参で真剣に取り組んでいました。準備した甲斐がありました:D

すでに現場でVagrantを活用されている方も数名おられて、ハンズオンの合間などでお話できて楽しかったです。

これからさらにVagrantを活用する人が増えていくと思うので、そういった人たちで情報交換できるようなイベントもやってみたいですね。

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

「VagrantユーザのためのDocker入門」を発表してきました

この記事の所要時間: 414

第3回 コンテナ型仮想化の情報交換会@大阪 で行った発表です。

docker

コンテナ超人みたいな人ばかりで、この内容で発表して良いものやらと思ったのですが、アプリケーションを書く側からの視点で話してきました。

Vagrant ユーザのための Docker 入門

VagrantユーザのためのDocker入門 from Masashi Shinbara

Docker を知った時は、速くなった Vagrant のようなものだと思っていたのですが、色々と見る内にそもそも別のもので、ユースケースとして重なるところはあれど、別のツールだと認識した方が良いです。

Docker の入り口としては、デモを見てもらうのが、手っ取り早いので、そのあたりが伝わったなら良かったです。

Introduction to Docker

発表で引用した「Introduction to Docker」は下記です。公式の資料なので、色々ググる前に、まずはこれを読むのがおすすめです:D

Docker introduction from Docker

CentOS で Docker を動かす Vagrantfile

デモで使った Docker が動く CentOS を作る Vagrantfile です。

https://github.com/shin1x1/vagrant-centos-docker.git

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "opscode-centos65"
  config.vm.box_url = "http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-6.5_chef-provisionerless.box"

  config.vm.provider :virtualbox do |vb|
    vb.name = "docker"
    vb.customize ["modifyvm", :id, "--memory", 1024]
  end

  config.vm.network :private_network, ip: "192.168.33.10"
  config.vm.network :forwarded_port, guest: 4243, host: 4243

  config.vm.provision :shell, :inline => <<-EOT
    #
    # iptables off
    #
    /sbin/iptables -F
    /sbin/service iptables stop
    /sbin/chkconfig iptables off
    #
    # yum repository
    #
    rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
    yum install -y vim-enhanced telnet
    #
    # docker
    #
    yum install -y docker-io
    sed -i 's,^other_args="",other_args="-H tcp://0.0.0.0:4243 -H unix:// -dns 8.8.8.8",g' /etc/sysconfig/docker
    chkconfig docker on
    service docker restart
  EOT
end

さいごに

Docker 盛り上がっていますね。

今後、本番環境としての Docker 活用例や、Docker コンテナをそのまま deploy できる DaaS(Docker as a Service)が増えてくると、面白くなりそうです。

なお、Docker が出たから、Vagrant は終わりとかそういう話では無いので、今後も Vagrant も活用していきたいですね。

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

Laravel ユーザなら知っておくべきAuthオートログインのこと

この記事の所要時間: 425

Authフィルタによるオートログインについてです。

laravel

「ひとり Laravel Japan ツアー 2014」と称して、Laravel 福岡Laravel Meetup Tokyo vol.3 に参加してきました。

どちらでも発表を行ったのですが、ここでは、Laravel Meetup Tokyo で発表した Auth オートログインの資料を公開します。

知っておくべきAuthオートログイン

Laravel では Auth という認証を行う機能があるのですが、標準でオートログイン機能が実装されています。

Login::attempt() というログイン処理を行うメソッドの第二引数にtrueを渡すだけで、オートログイン用のクッキーが発行され、もしログインセッションが切れても、自動でオートログインが行われます。

とても簡単に使えるのは良いのですが、暗号化したクッキーでのみ認証を行うので、利用には注意が必要です。

詳細は資料を確認してみてください。

私は、オートログイン処理を自作して、カスタムドライバとして組み込むことで対応しています。
Laravel でカスタムドライバを使って Remember Me(オートログイン)を実装する

Auth フィルタによるオートログイン

このオートログインは、Auth フィルタを使っていると常に有効となっているので、アプリケーションでオートログインを使っているか否かに関わらず、影響があります。

該当のコードは以下です。

まず、Auth フィルタの定義です。app/filters.php で定義されており、Auth::guest() が認証が行われます。

Route::filter('auth', function () {
    if (Auth::guest()) {
        return Redirect::guest('login');
    }
});

Auth::guest() はファサードクラスで、実体は\Illuminate\Auth\Guard::guest()です。check()メソッドが呼ばれており、さらにその中でuser()メソッドが呼ばれます。

    public function guest()
    {
        return ! $this->check();
    }

user()メソッドが、認証の中核になります。前半では、セッションからログインユーザ情報を取得しています。もしセッションにユーザ情報が無ければ、後半でオートログイン処理を行います。

user()メソッドは、Authフィルタを呼べば、常に実行されるので、アプリケーションでオートログインを利用しているか否かに関わらず、オートログイン処理が実行されることが分かります。(セッションにログインユーザ情報が無く、オートログインクッキーの値があれば常に実行される)

    public function user()
    {
        if ($this->loggedOut) return;

        if ( ! is_null($this->user))
        {
            return $this->user;
        }

        $id = $this->session->get($this->getName());

        $user = null;

        if ( ! is_null($id))
        {
            $user = $this->provider->retrieveByID($id);
        }

        $recaller = $this->getRecaller(); // <--- オートログインクッキー値取得

        if (is_null($user) && ! is_null($recaller))
        {
            $user = $this->getUserByRecaller($recaller); // <--- オートログイン処理
        }

        return $this->user = $user;
    }

さいごに

このオートログイン仕様を受け入れるかどうかは使う人次第ですが、その場合、暗号鍵( app/config/app.php の key )は絶対に漏洩しないように扱う必要があります。

なお、Laravel ツアーは、福岡(+大分)と東京の Laravel ユーザと色々なアツい話ができて楽しかったです:D

日本では、まだ知っている人が使っているという感じですが、コミュニティとしての動きも出てきて、今後は盛り上がっていきそうですね。

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

Mac OSX をクリーンインストールしてからの環境構築メモ

  • 2014-04-04 (金)
  • mac
この記事の所要時間: 72

Macbook Air の OS をクリーンインストールしたので、OS 起動後に行った環境構築のメモです。

1. OSX 環境設定

OSX 関連の設定は、以前に書いた下記エントリの内容で行います。
下記は Moutain Lion のものなので、若干、設定項目の表記や位置が異なりますが、概ねこの通りで。

http://www.1×1.jp/blog/2012/06/macbook_air_system_preferences.html

2. Homebrew、コマンドラインツール、XCode インストール

アプリは、Homebrew cask と Brewfile を使ってインストールしたいので、Homebrew をインストールします。

$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

コマンドラインツールが必要になるので、インストールするかダイアログが表示されます。

コマンドラインツールとついでに XCode もインストールしておきます。両ツールをインストール後に、利用許諾を承諾します。

再度、下記コマンドを実行すると Homebrew がインストールされます。

$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
$ brew
Example usage:
  brew [info | home | options ] [FORMULA...]
  brew install FORMULA...
  brew uninstall FORMULA...
(snip)

3. brew bundle でアプリケーションインストール

Brewfile は、PHP の composer.json のようなものです。あれの OSX アプリケーション版ですね。

Brewfile にインストールしたいアプリケーションを記述しておくだけで、brew bundle コマンドで一気にインストールしてくれます。これは楽ちん。

Brewfile を下記のように書きました。これを見るだけで普段必要なアプリが分かるので備忘録としても便利です。

update
upgrade

tap homebrew/versions
tap phinze/homebrew-cask
tap homebrew/binary
tap josegonzalez/php

install brew-cask
install zsh
install autojump
install git
install ctags
install tmux
install tig
install ansible
install wget
install curl
install jq
install vim
install boot2docker
install reattach-to-user-namespace
install php55 --with-pgsql --with-pdo-pgsql
install php55-mcrypt php55-xdebug php55-opcache composer

cask install dash
cask install phpstorm
cask install iterm2
cask install google-chrome
cask install firefox
cask install virtualbox
cask install vagrant
cask install kobito
cask install alfred
cask install dropbox
cask install evernote
cask install skitch
cask install github
cask install sequel-pro
cask install lastpass-universal
cask install openoffice
cask install bettertouchtool
cask install google-japanese-ime
cask install the-unarchiver
cask install coteditor
cask install istat-menus
cask install bartender
cask install skype

cask alfred link # for Alfred

Brewfile があるディレクトリで、brew bundleコマンドを実行します。

$ brew bundle

アプリケーションが続々とインストールされるので、しばし待ちます。

Brewfile は github に push したので、次回からはこれを使います。

$ git clone https://github.com/shin1x1/Brewfile
$ cd Brewfile
$ brew bundle

3. Dropbox, Evernote の同期

Dropbox と Evernote を起動して、同期を行います。

とくに Dropbox の同期は時間がかかるので、早めに実行しておきます。

4. ライセンスの登録

有償ライセンスを購入しているアプリケーションがあるので、ライセンスキーを設定していきます。

  • PhpStorm
  • Dash
  • Alfred Powerpack
  • Bartender
  • iStat Menus

5. CUIアプリケーションの設定

CUIアプリケーションの設定を行います。

下記の設定ファイルは Dropbox で共有しているので、インストール用シェルを実行して、シンボリックリンクを張ります。

  • ~/.vimrc
  • ~/.vim
  • ~/.zsh
  • ~/.zshrc
  • ~/.zshenv
  • ~/.tmux.conf
  • ~/.gitconfig
  • ~/.gitignore_global

ただ、Dropbox がまだ同期中なので、一部のディレクトリにプラグインのファイルなどがなく、正常に動作しない可能性があります。慌てて設定ファイルをいじらないように注意。
(今回、Vim で起動時にエラーが出ていましたが、Dropbox の同期が進むと自然に解消されました。)

6. GUIアプリケーションの設定

必要なものを順次設定していきます。

下記は、OS 起動時に自動で起動して欲しいので、設定しておきます。

  • Alfred 2
  • Bartender
  • iStat Menus
  • BetterTouchTools
  • Dropbox

Google 日本語入力は起動時にエラーが出たので、OS の再起動を行いました。

7. 秘密鍵、VPN の設定

あちらこちらに登録している公開鍵用の秘密鍵をインストールしておきます。もしくは新規の鍵ペアを作って、サーバやサービスに公開鍵を登録します。

あと外出前に VPN の設定を行っておきます。忘れると外から入れなくなるので注意を。

まとめ

OS インストール後の環境構築を一気にやりました。Dropbox の同期を除けば、4 時間程度あれば十分に構築できました。(今回は、Brewfile を書きながら調整していったので、次回はもっと短くできそう)

いやあ、Brewfile は、ほんと良いですね。OSX 環境の自動構築といえば Boxen が有名ですが、自分のニーズであれば Brewfile で十分です。インストールに割り切ってるのがシンプルで良いですね。

何度か Brewfile に追加して、brew bundle の実行を繰り返していたのですが、ちょうどリリースされたばかりの Vagrant 1.5.2 がインストールされました。地味に毎回インストールするの面倒だったので、Vagrant と VirtualBox のインストールだけでも使う価値ありそうです:D

参考

http://nanapi.co.jp/blog/2014/03/05/homebrew-cask/

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

Laravel コードで見るファサードクラスの仕組み

この記事の所要時間: 839

Laravel の特徴として良く挙げられるファサードクラスの仕組みをコードで見てみました。

laravel

Laravel のファサードクラス

Laravel では、Input::get()Route::get()など、クラスメソッドでフレームワークの機能を利用する場面が多くあります。

これは一見すると、InputやRouteクラスで提供されているクラスメソッドを実行しているだけに見えますが、これらのクラスにメソッドの実装があるわけではなく、実際はIoCコンテナに格納されているインスタンスのメソッドを実行しています。

例えば、InputであればIlluminate\Http\Requestクラス、RouteであればIlluminate\Routing\Routerのインスタンスメソッドが実行されます。

これらのインスタンスは、IoCコンテナにて管理されており、ファサードクラスのクラスメソッドが実行されると、IoCコンテナから定められたインスタンスを取得して、そのインスタンスメソッドを実行する仕組みになっています。

ファサードクラスの利用により、コードの記述がシンプルになります(ここは意見が分かれるところですが)。また、ファサードクラスでのメソッド呼び出しでは、自身では処理を行わずに、IoCコンテナに格納されたインスタンスなどに委譲します。つまり、ファサードクラスが呼び出すインスタンスを差し替えることで実際に処理を行うクラスを変えることができます。

Laravel のファサードクラスでは、shouldReceive()メソッドにて、テスト時に実行するインスタンスをモックオブジェクトと差し替える機能が標準で用意されています。

ファサードクラス実行の流れ

ではRoute::get()を例にして、ファサードクラスがどのように実行されるか見て行きましょう。

  • 1) ファサードクラスの解決
  • 2) ファサードクラスのクラスメソッド実行
  • 3) IoCコンテナからインスタンス取得
  • 4) インスタンスメソッドの実行

1. ファサードクラスの解決

まずRouteクラスの解決を行います。実は、Routeクラスは、フレームワークでは定義されていません。

では、なぜRouteクラスのクラスメソッドが実行できるのかというと、class_alias()関数にて、存在するクラスの別名として、Routeを定義するためです。

この処理はIlluminate\Foundation\AliasLoaderにて行います。

Routeクラスへのアクセスがあると、クラス定義を探すためにオートローダが起動します。オートローダには、Illuminate\Foundation\AliasLoaderとComposerのものが登録されているのですが、はじめにIlluminate\Foundation\AliasLoaderが実行されます。

Illuminate\Foundation\AliasLoaderでオートロードを行う箇所が以下です。

$this->aliasesに対象のクラス名(ここではRoute)があれば、class_alias()で、クラス別名を設定します。

	public function load($alias)
	{
		if (isset($this->aliases[$alias]))
		{
			return class_alias($this->aliases[$alias], $alias);
		}
	}

$this->aliasesは、app/config/app.phpで定義しているaliasesキーの内容がセットされています。

Routeは、下記のように設定されているため、Illuminate\Support\Facades\Routeへの別名として設定されます。

		&#039;Route&#039;           => &#039;IlluminateSupportFacadesRoute&#039;,

次にIlluminate\Support\Facades\Routeについて、オートロードによる読み込みが行われるので、RouteIlluminate\Support\Facades\Routeの別名として利用できるようになります。

2. Routeクラスのクラスメソッドを実行

次に、Routeクラスのget()メソッドを実行します。

Routeクラス(Illuminate\Support\Facades\Route)を見ると、get()というメソッドは存在しません。

Illuminate\Support\Facades\Routeの基底クラスであるIlluminate\Support\Facade\Facadeには、__callStatic()メソッドが定義されているため、このメソッドが呼ばれます。

__callStatic()メソッドが下記です。

	public static function __callStatic($method, $args)
	{
		$instance = static::resolveFacadeInstance(static::getFacadeAccessor());

		switch (count($args))
		{
			case 0:
				return $instance->$method();

			case 1:
				return $instance->$method($args[0]);

			case 2:
				return $instance->$method($args[0], $args[1]);

			case 3:
				return $instance->$method($args[0], $args[1], $args[2]);

			case 4:
				return $instance->$method($args[0], $args[1], $args[2], $args[3]);

			default:
				return call_user_func_array(array($instance, $method), $args);
		}
	}

}

3. IoC コンテナからインスタンス取得

__callStatic()メソッドの先頭では、resolveFacadeInstance()メソッドを実行して、IoC コンテナから処理対象のインスタンスを取得します。

まず、static::getFacadeAccessor()を実行して、どのインスタンスを取得するかを決定します。このメソッドは各ファサードクラスで定義されており、Routeクラスの場合は下記のようになっています。ここでは、routerという文字列を返しています。

このようにファサードクラスではgetFacadeAccessor()の戻り値で、実行するインスタンスを指定します。

	protected static function getFacadeAccessor() { return &#039;router&#039;; }

このrouterを引数にresolveFacadeInstance()メソッドを実行して、インスタンスを取得します。

resolveFacadeInstance()メソッドの実装は以下です。

	protected static function resolveFacadeInstance($name)
	{
		if (is_object($name)) return $name;

		if (isset(static::$resolvedInstance[$name]))
		{
			return static::$resolvedInstance[$name];
		}

		return static::$resolvedInstance[$name] = static::$app[$name];
	}

引数で与えられた$name(ここではrouter)がオブジェクトであれば、そのまま返します。もし、すでにファサードクラスで解決済ならそのインスタンスを返します。そうでなければ、IoCコンテナからインスタンスを取得して返します。

IoCコンテナからインスタンスを取得した場合は、Fasadeクラスのクラス定数$resolvedInstanceにキャッシュされる仕組みになっているので、常に同じインスタンスが利用されます。

もし実行インスタンスを変えたい場合はswap()clearResolvedInstance()を使うと良いでしょう。

4. インスタンスメソッドの実行

  1. で取得したインスタンスについて、指定されたインスタンスメソッド(この場合get())を実行します。

さいごに

フレームワークのソースから Laravel のファサードクラスの仕組みを見てきました。

ファサードクラスの作り方として、ServiceProvider の構築が良く手順に含まれていますが、実はファサードクラスを作る上ではこれは必須ではありません。ファサードクラスが IoC コンテナからインスタンスを取得するため、そのインスタンスを事前にコンテナに設定するために ServiceProvider を利用することが多いだけです。

ファサードクラスを自作する際もこうした動きを知っていると作りやすいですね。

なお、Laravel のファサードクラスは、GoF のファサードパターンとは異なり、IoCコンテナにあるインスタンスを透過的に呼び出す仕組みとなっており、便利なサービスロケータと言った方がイメージしやすいかもしれません。( ちなみに、Laravel のファサードと GoF のファサードパターンは違うものだそうです。)

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

Vagrant 1.5 で追加された Rsync Synced Folder が良い

この記事の所要時間: 1025

Vagrant 1.5 がリリースされました。1.1 以来の big change ということで、目新しい機能が追加されています。

vagrant

目玉はやはり Vagrant Share だと思うのですが、その他にも Web システムの開発に Vagrant を使っている人には嬉しい機能が追加されています。

その一つが Rsync Synced Folder です。

Rsync Synced Folder

Vagrant には、ホストマシンとゲストマシンとでファイルを共有、同期する機能があります。

これにより、開発はホストマシンで行い、実行は LA(MP)P 環境であるゲストマシンで行うという、いいとこ取りができました。

この機能を実現する方法として、shared folder(VirtualBox の機能)や NFS を利用することができたのですが、これに rsync による同期が追加されました。

ホストマシンからゲストマシンへ rsync で同期を行います。

嬉しいこと

Rsync Synced Folder は、rsyncで同期を行うだけなので、ゲストマシン側はネイティブなファイルシステムが利用できます。つまり、synced folder の仕様に影響されなくなります。

パフォーマンス

分かりやすいメリットとしては、VirtualBox の shared folder に比べると、パフォーマンスが向上します。手元にある PHP アプリケーションでは、shared folder で 90 秒程度かかっていたユニットテストが、60 秒程度に短縮しました。

また、NFS と違って、ホスト側でデーモンを起動しておく必要が無いのも大きなメリットです。

パーミッション

shared folder では、パーミッションをディレクトリやファイルごとに設定することができなかったのですが、rsync を利用すれば、ゲストマシンからは通常のファイルシステムなので何も支障がありません。

ただ、現状の Rsync Synced Folder では、同期時にファイルオーナーを強制的に書き換える仕様になっているので、この点については留意しておく必要があります。(後述)

自動更新機能

この rsync は、基本的にはvagrant up, vagrant reload, vagrant provision、そして今回新設されたvagrant rsyncコマンド実行時に行われます。

これだとファイルを変更する度にコマンドを実行するのが面倒になるのですが、vagrant rsync-autoコマンドを実行することで、自動で同期を行うことができます。設定で同期対象となったディレクトリに変更があれば、自動で同期処理を行います。

どのようなプロバイダでも同期可能

AWS や DigitalOcean のようなクラウドサービスをプロバイダとして使っている場合も、rsync-auto による自動同期が可能です。

コードを変更すると、クラウド上のサーバに自動で同期されます。

要件

この機能を利用するには、ホストとゲストの双方にrsyncコマンドが必要になります。

ホストについては、OSX であればデフォルトでインストールされています。

ゲストについては、もしインストールされていなくても、主要な OS については、Vagrant が自動でインストールしてくれます。例えば、RedHat 系であれば yum -y install rsyncが実行されます。

これは気が効いてますね。

設定

Vagrantfile の synced folder の設定で、type=rsyncを指定します。

config.vm.synced_folder "src", "/share", type: "rsync"

設定には下記のようなオプションがあります。

  • rsync__args
    rsync コマンドに渡す引数です。デフォルトでは["--verbose", "--archive", "--delete", "-z"]が指定されています。

  • rsync__auto
    rsync-auto による自動同期の対象にするかどうかです。デフォルトは、trueです。

  • rsync__exclude
    同期の対象外にするファイルやディレクトリを指定します。デフォルトでは[".vagrant/"]が指定されています。

例えば、.git ディレクトリは同期対象から外すなら、下記のようにrsync__excludeを指定します。

config.vm.synced_folder "src", "/share", type: "rsync", rsync__exclude: [".git/"]

手動で同期する

vagrant upvagrant reloadvagrant provisionvagrant rsyncを実行すると、Vagrantfile の synced_folder で指定したディレクトリについて、rsync による同期を行います。

ファイルを同期したいだけなら、vagrant rsyncを実行します。

$ vagran rsync
==> default: Rsyncing folder: /path/to/src/ => /share

自動で同期する

自動で同期するには、vagrant rsync-autoコマンドを実行します。コマンドを実行すると、synced_folder で指定した同期対象のディレクトリを監視します。

$ vagrant rsync-auto
==> default: Watching: /path/to/src/

同期対象のディレクトリに変更があると、自動でrsyncが実行され、同期されます。

$ vagrant rsync-auto
==> default: Watching: /path/to/src/
==> default: Rsyncing folder: /path/to/src/ => /share

ホストで開発して、ゲストで実行するというスタイルなら、常に rsync-auto を実行しておくと良いでしょう。

注意点

ホストからゲストの同期のみ

現在のところ、同期はホストからゲストの一方向のみです。

ゲストからホストへファイルを転送するvagrant rsync --pullというのが、issue で提案されているので、いずれ実装されるかもしれません。

https://github.com/mitchellh/vagrant/issues/3062

rsync 実行でファイルオーナーが変更される

rsync-auto を使っている時に気づいたのですが、rsyncの実行前にゲスト側の同期ディレクトリに対してchown -R vagrant /pathを実行して、ファイルオーナーを変更しています。

同期対象ディレクトリを丸ごとホスト側と同期するなら特に問題無いのですが、ゲストでアプリケーションが実行時に生成するファイル(ログファイル等)をrsync__excludeオプションで同期対象外にしている時に問題が発生します。

例えば、アプリケーションがapp/storage/log/以下にログファイルを出力する場合、このディレクトリを同期対象外とします。

config.vm.synced_folder "src", "/share", type: "rsync", rsync__exclude: [".git/", "app/storage/log/*"]

この時、アプリケーションを実行するとゲストでは、下記のようにアプリケーションの実行ユーザである apache ユーザによってログファイル(laravel.log)が生成されます。

[vagrant@localhost /share]$ ls -la app/storage/log/
drwxrwxrwx. 2 vagrant vagrant     4096  3月 13 05:51 2014 .
drwxrwxrwx. 7 vagrant vagrant     4096  2月 13 07:50 2014 ..
-rw-rw-rw-. 1 vagrant vagrant       13  2月 13 07:50 2014 .gitignore
-rw-r--r--. 1 apache  apache      5394  3月 13 05:51 2014 laravel.log

次にホスト側で vagrant rsyncで同期します。

$ vagrant rsync
==> default: Rsyncing folder: /path/to/src/ => /share

ゲスト側では laravel.log は、同期対象外となっているのでファイルはそのまま残っています。しかし、ファイルオーナーが vagrant に変更されています。

[vagrant@localhost /share]$ ls -la app/storage/log/
drwxrwxrwx. 2 vagrant vagrant     4096  3月 13 05:51 2014 .
drwxrwxrwx. 7 vagrant vagrant     4096  2月 13 07:50 2014 ..
-rw-rw-rw-. 1 vagrant vagrant       13  2月 13 07:50 2014 .gitignore
-rw-r--r--. 1 vagrant apache      5394  3月 13 05:51 2014 laravel.log

この状態でアプリケーションを実行すると、ログファイルへの書き込み権限が無いためにエラーが発生します。

Vagrant のソースを見ると、plugins/guests/linux/cap/rsync.rb で、rsync 前に実行する処理が定義されているのですが、たしかにsudo chown -Rが実行されています。

        def self.rsync_pre(machine, folder_opts)
          username = machine.ssh_info[:username]

          machine.communicate.tap do |comm|
            comm.sudo("mkdir -p &#039;#{folder_opts[:guestpath]}&#039;")
            comm.sudo("chown -R #{username} &#039;#{folder_opts[:guestpath]}&#039;")
          end
        end

おそらく、vagrant rsync を実行する際に、Permission Denied が出ないように、一括でchownしているのだと思うのですが、ここは注意しておく必要があります。

rsync-auto を忘れる

これはケアレスミスなのですが、VirtualBox の shared folder や NFS であれば、自動で同期するので、ホストでコードを変更したのに、ゲストに反映されずに「あれ?」となったことが何度かありました。

vagrant rsync-autoを実行しないと、自動で同期されないので、忘れずに。

さいごに

Rsync Synced Folder を使うことで、shared folder で気になっていた点が解決しそうです。なんといっても、ユニットテストが速くなるのはありがたいですね。

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

composer install をどこで実行するか

  • 2014-03-06 (木)
  • PHP
この記事の所要時間: 32

最近の PHPer が集まれば、一度は話題に上がるのが、この composer install をどこで実行するのか問題。

Composer

これまで聞いた話をまとめると、大きく分けて、以下の2パターンになります。どちらの方法を取っているか教えて下さい 😀

0. 前提

前提ですが、以下のような方法で、Composer 関連のファイルは管理しているとします。おそらく多くはこのような形になっていると思います。

  • PHP コードは、Git などの VCS で管理する。
  • composer.json, composer.lock は、VCS で管理する。
  • composer.phar, vendor/ は、VCS で管理しない。

また、今回対象としているのはアプリケーションで、Packagist に登録して、配布するようなフレームワークやライブラリは対象外です。

1. 本番サーバで実行

本番PHPサーバ上で composer install を実行するパターンです。

本番PHPサーバに何かしらの方法で、PHP コードと共にcomposer.jsoncomposer.lockを設置し、composer installを実行します。

複数台サーバがある場合、それぞれのサーバで実行します。

コマンドの実行は、SSH でログインして手でする人もいるでしょうし、CapistranoやFabricなどで、自動化している人もいます。

順番としては、PHPコードをデプロイしてから、依存解決(composer install)になります。

2. 別サーバで実行して、実行結果を本番サーバに設置

本番PHPサーバ以外の場所で、composer installを実行して、生成されたvendor/を本番サーバに設置する方法です。

composer installを実行する場所は、ビルドサーバの場合もありますし、開発者の PC の場合もあります。

本番サーバではcomposer installを実行しないのがポイントです。

こちらは、依存解決してからデプロイの順になります。

私の場合

私自身は、本番環境については、2. の方法で行っています。

依存解決は、デプロイの前に行っておき、すでに依存解決されたものを本番環境にデプロイします。

The Twelve-Factor App で言うところのビルドステージで行うものという認識です。

ビルドステージ は、コードリポジトリを ビルド と呼ばれる実行可能な塊へと変える変換である。デプロイプロセスで指定したコミットのコードで指定されたバージョンを使って、ビルドステージは依存関係を取得してローカル環境に配置し、バイナリやアセットファイルをコンパイルする。

ビルドステージで可変部分は全て処理しておき、本番環境には、イミュータブルなパッケージとしてデプロイします。(実際は、パーミッションの設定など多少の操作は必要ですが)

複数台の場合、あちこちでcomposer installを実行するのが単純に無駄というのもありますし、大きな可変部分を各サーバで実行するより、1箇所で生成して、全く同じものを各サーバに配布した方が安全かと。

ただ、1. の方が手軽なので、こちらを採用しているという意見も良く聞きますね。

さいごに

Composer が普及してきたからこそ出てくる議論だと思うので、すっかり利用するのが当たり前になりましたね。今後は、こうした運用面に関するノウハウも共有していきたいです。

上記案に限らず、こうしてるよ、などあれば教えて下さい。

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

Laravel コードからフレームワークの起動から終了までの流れを追う

この記事の所要時間: 1633

Laravel フレームワークが起動してから終了するまでの流れについて、コードを読んでみました。

laravel_code

今回読んだフレームワークのバージョンは、4.1.21 です。

エントリポイント

エントリポイントは、public/index.php です。

このファイルではコメントが多数ありますが、実際に処理を行っている行は、下記の 3 行だけです。

このコードから想像できるように、オートローダの設定、フレームワークの設定、そして実行という流れです。

require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/start.php';
$app->run();

それぞれについて見ていきます。

1. bootstrap/autoload.php

ここでは、オートローダの設定を行います。Composerのオートローダもここで読み込みます。

先頭で現在時刻が定数として定義されているので、$_SERVER['REQUEST_TIME']の代わりに使えるかもしれません。

define('LARAVEL_START', microtime(true));

2. bootstrap/start.php

次にフレームワークとアプリケーションの起動を行います。

フレームワークの中核を成すIlluminate\Foundation\Applicationクラスのインスタンスを生成して、実行環境の設定、ファイルパスの設定を行って、フレームワークの起動ファイルを読み込みます。

最後に、Illuminate\Foundation\Applicationクラスのインスタンスを返します。

$app = new IlluminateFoundationApplication;

$env = $app->detectEnvironment(function() {
    return getenv('ENV_STAGE') ?: 'local';
});

$app->bindInstallPaths(require __DIR__.'/paths.php');

$framework = $app['path.base'].'/vendor/laravel/framework/src';
require $framework.'/Illuminate/Foundation/start.php';

return $app;

下記は、require文で読み込むファイルについてです。

2-1. bootstrap/paths.php

アプリケーション関連のパスが連想配列で定義されています。必要があれば、このファイルを編集します。

2-2. vendor/laravel/framework/src/Illuminate/Foundation/start.php

フレームワークの起動処理を行います。

Laravel をはじめる際につまづくことの多いmcrypt拡張チェックはここで行われています。

  • error_reporting(-1)
  • mcrypt 拡張チェック
  • IoC コンテナに$appを入れる
  • ユニットテストなら、$app['env']に’testing`をセット
  • ファサードクラスの初期処理
  • コアクラスを短縮名で呼べるように IoC コンテナに別名をセット
    => Application#registerCoreContainerAliases()
  • 環境変数をセット
    => .env.php or .env.${enviroment}.php の値を $_ENV / $_SERVER / putenv() にセット
  • Configクラス(Illuminate\Config\Repository)を$app['config']にセット
  • 例外ハンドリングの開始
  • display_errors を Off にする
  • タイムゾーン設定(date_default_timezone_set()
  • AliasLoader()の設定
  • HTTP メソッドのオーバーライド有効化
  • コアのServiceProvider有効化
  • bootedハンドラの登録(アプリケーション起動時に実行)
    => start/global.php の読み込み
    => start/{$env}.php の読み込み
    => routes.php の読み込み

3. $app->run()

アプリケーションを実行します。run()メソッドは、わずか 4 行です。

まず、Symfony\Component\HttpFoundation\Requestクラスのインスタンスを取得します。index.phpでは、引数をセットしていないので、IoCコンテナからインスタンスを取得します。

次に、実行するミドルウェアのツリーを構築して、handle()メソッドでアプリケーションの実行を行います。この行がアプリケーション実行の中心となります。

Symfony\Component\HttpFoundation\Responseクラスのインスタンスが戻り値として返るので、send()メソッドでレスポンスを出力します。

そして、最後に先ほど構築したミドルウェアツリーのterminate()メソッドを実行して、終了処理を行います。

これでアプリケーションが終了します。

	public function run(SymfonyRequest $request = null)
	{
		$request = $request ?: $this['request'];

		$response = with($stack = $this->getStackedClient())->handle($request); // (1)

		$response->send();

		$stack->terminate($request, $response);
	}

下記では、(1) の行について見ていきます。

3-1. $this->getStackedClient()

getStackedClient()メソッドでは、Stack/Builder を使って、実行するミドルウェアを組み合わせていきます。

ミドルウェアは、Illuminate\Foundation\Applicationクラス(正確には、`Symfony\Component\HttpKernel\HttpKernelInterface を implement したクラス)の Decorator となっており、それぞれの処理をアプリケーション実行の前後に挟むことができます。

デフォルトでは、以下の 4 クラスがミドルウェアとして登録されます。(handle()メソッド呼び出し順)

  • Illuminate\Http\FrameGuard
  • Illuminate\Session\Middleware
  • Illuminate\Cookie\Queue
  • Illuminate\Cookie\Guard

ミドルウェアはApp::middleware()で任意のものを追加することもできます。ただ、追加する場合は、Application#run()が呼ばれる前に追加しておく必要があります。

	protected function getStackedClient()
	{
		$sessionReject = $this->bound('session.reject') ? $this['session.reject'] : null;

		$client = with(new StackBuilder)
						->push('IlluminateCookieGuard', $this['encrypter'])
						->push('IlluminateCookieQueue', $this['cookie'])
						->push('IlluminateSessionMiddleware', $this['session'], $sessionReject);

		$this->mergeCustomMiddlewares($client);

		return $client->resolve($this);
	}

3-2. Illuminate\Cookie\Guard

直接Illuminate\Foundation\Applicationクラスのhandle()メソッドを呼び出すミドルウェアがこのクラスです。

実行するhandle()メソッドは、下記です。

	public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
	{
		return $this->encrypt($this->app->handle($this->decrypt($request), $type, $catch));
	}

1行で一気に書いているので、個々の文をばらしてみます。

	public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
	{
		$request = $this->decrypt($request);
		$response = $this->app->handle($request, $type, $catch);
		$response = $this->encrypt($response);		
		return $response;
	}

まず、decrypt()メソッドでリクエストされたクッキーの複合を行います。

次に、自身が保持しているミドルウェアのhandle()メソッドを実行します。ここで保持しているミドルウェアは、Applicationクラスなので、アプリケーションが実行されます。

そして、最後にレスポンスのクッキーを暗号化して、レスポンスを返します。

つまり、アプリケーションが実行している間は、クッキーの内容は平文ですが、その前後で複合、暗号化を行っているので、この範囲外では、クッキーの内容は暗号化されている状態になります。

3-3. Illuminate\Foundation\Application#handle()

前途のとおり、Illuminate\Cookie\Guard#handle()から呼び出されます。

boot()メソッドでアプリケーションの起動処理を行い、dispatch()メソッドでアプリケーションを実行します。

この処理で、もし例外が発生した場合、IoC コンテナにある例外ハンドラで処理します。ユニットテスト中であれば、そのままスローします。

	public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
	{
		try
		{
			$this->refreshRequest($request = Request::createFromBase($request));

			$this->boot();

			return $this->dispatch($request);
		}
		catch (Exception $e)
		{
			if ($this->runningUnitTests()) throw $e;

			return $this['exception']->handleException($e);
		}
	}

3-4. Illuminate\Foundation\Application#boot()

Illuminate\Foundation\Application#handle()から呼び出されます。

アプリケーションのServiceProviderのboot()メソッドを実行していきます。

また、2-2. で登録したbootedハンドラは、ここから呼ばれるbootApplication()メソッドで実行されます。

	public function boot()
	{
		if ($this->booted) return;

		array_walk($this->serviceProviders, function($p) { $p->boot(); });

		$this->bootApplication();
	}

3-5. Illuminate\Foundation\Application#dispatch()

Illuminate\Foundation\Application#handle()から呼び出されます。

まず、メンテナンス中かを確認して、もしそうならilluminate.app.downイベントを発火してレスポンスを返します。

次に、ユニットテスト中かつセッション開始前なら、セッションを開始します。

そして、Illuminate\Routing\Routerクラスのdispatch()メソッドを実行します。

	public function dispatch(Request $request)
	{
		if ($this->isDownForMaintenance())
		{
			$response = $this['events']->until('illuminate.app.down');

			if ( ! is_null($response)) return $this->prepareResponse($response, $request);
		}

		if ($this->runningUnitTests() && ! $this['session']->isStarted())
		{
			$this['session']->start();
		}

		return $this['router']->dispatch($this->prepareRequest($request));
	}

3-6. Illuminate\Routing\Router#dispatch()

Illuminate\Foundation\Application#dispatch()から呼び出されます。

ここで、リクエストをアプリケーションに渡して実行します。dispatchRoute()メソッドがアプリケーション実行のメインです。

その前後では、beforeafterフィルタが呼ばれます。それぞれrouter.befiorerouter.afterイベンドが発火されます。


	public function dispatch(Request $request)
	{
		$this->currentRequest = $request;

		$response = $this->callFilter('before', $request);

		if (is_null($response))
		{
			$response = $this->dispatchToRoute($request);
		}

		$response = $this->prepareResponse($request, $response);

		$this->callFilter('after', $request, $response);

		return $response;
	}

3-7. Illuminate\Routing\Router#dispatchRoute()

Illuminate\Routing\Router#dispatch()から呼び出されます。

リクエスト内容から実行するRouteクラスを取得して、run()メソッドを実行します。これにより、app/routes.phpで定義したルーティングの内容が実行され、アプリケーションの処理が行われます。

run()メソッドの戻り値は、最終的にクライアントへのレスポンスとなります。

callRouteBefore()callRouteAfter()では、Routeクラスで定義したbeforeafterフィルタが実行されます。(ex. Route::get(‘/’, [‘before’ => ‘auth’,… ))

callRouterBefore()メソッドの戻り値がnull以外なら、run()メソッドは実行されないので、authフィルタなど、アプリケーション実行前にチェックをかける場合はこれを利用することになります。

	public function dispatchToRoute(Request $request)
	{
		$route = $this->findRoute($request);

        $this->events->fire('router.matched', array($route, $request));

		$response = $this->callRouteBefore($route, $request);

		if (is_null($response))
		{
			$response = $route->run($request);
		}

		$response = $this->prepareResponse($request, $response);

		$this->callRouteAfter($route, $request, $response);

		return $response;
	}

さいごに

Laravel フレームワークの起動から終了までの全体の流れを見てみました。

流れとしてはそれほど複雑ではないのですが、Stack/Builder によるミドルウェアやイベント、フィルタなど、多くの箇所でアプリケーション固有の処理を差し込めるような仕組みになっています。

拡張できるポイントを分かっておくと、いつどこでどのように処理を追加すれば良いかが判断しやすくなります。

こうしたフレームワークのコードを読む勉強会なんかもやってみたいですね。

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

Laravel でカスタムドライバを使って Remember Me(オートログイン)を実装する

この記事の所要時間: 743

Laravel には remeber me 機能があるのですが、これは暗号化した Cookie の情報だけで認証を行うので、やや心許ない実装です。

rememberme

そこで、Laravel フレームワークを拡張して、独自の認証処理ドライバを実装してみました。

ログイントークンテーブル

認証用ログイントークンを保存するテーブルを作成します。

まず、artisanコマンドでマイグレーションクラスを作成します。

$ php artisan migrate:make make_login_tokens

生成されたマイグレーションクラスにテーブル定義を書いていきます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class MakeLoginTokens extends Migration {
	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
        Schema::create('login_tokens', function($table) {
            $table->increments('id');
            $table->text('token')->unique();
            $table->integer('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade')->onUpdate('cascade');
            $table->timestamps();
        });
	}

	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
        Schema::drop('login_tokens');
	}
}

マイグレーションを実行して、login_tokensテーブルを生成します。

$ php artisan migrate

認証カスタムドライバの作成

ログイン認証処理は、Authクラスが担っています。ただこれは只のファサードでしかなく、実体はIlluminate\Auth\AuthManagerです。Illuminate\Auth\AuthManagerクラスは、認証処理をドライバに委譲しているので、このドライバを差し替えることで、独自の認証処理を行うことができます。

フレームワークにはIlluminate\Auth\Guardというドライバが用意されているので、これを継承してカスタムドライバを実装します。

今回、実装(オーバーライド)したのはgetUserByRecaller()メソッドとqueueRecallerCookie()メソッドです。

getUserByRecaller()メソッドは、オートログイン時に呼ばれるので、ブラウザから送信された Cookie の値を使って自動ログインを行うようにしています。

queueRecallerCookie ()メソッドは、ログイン時に呼ばれるので、ログイントークンを生成してDBに登録し、Cookie に埋め込むようにしました。

<?php
namespace Acme;

use Carbon\Carbon;
use Config;
use DB;
use Illuminate\Auth\Guard;
use LoginToken;

/**
 * Class AuthLoginGuard
 * @package Acme
 */
class AutoLoginGuard extends Guard
{
    /**
     * @param mixed $id
     * @return mixed
     */
    protected function getUserByRecaller($id)
    {
        $limit = Carbon::now()->subDays(Config::get('auth.login_token_limit_day'));

        // gc
        DB::table(with(new LoginToken)->getTable())->where('updated_at', '<=', $limit)->delete();

        $token = LoginToken::where('token', $id)->where('updated_at', '>', $limit)->first();
        if (empty($token)) {
            return null;
        }

        $user = parent::getUserByRecaller($token->user_id);
        if (empty($user)) {
            return null;
        }

        $token->touch();
        $this->updateSession($user->id);

        return $user;
    }

    /**
     * @param string $id
     */
    protected function queueRecallerCookie($id)
    {
        $bytes = Config::get('auth.login_token_length') / 2;
        $token = bin2hex(openssl_random_pseudo_bytes($bytes));

        $loginToken = new LoginToken();
        $loginToken->token = $token;
        $loginToken->user_id = $id;
        $loginToken->save();

        parent::queueRecallerCookie($token);
    }
}

カスタムドライバを有効にする

作成したカスタムドライバをフレームワークで有効にします。

Auth::extend()

Auth::extend()で、Acme\AutologinGuardクラスをインスタンス化するクロージャを定義します。ドライバ名にはautologinを指定しています。

Auth::extend('autologin', function($app) {
    $provider = new EloquentUserProvider($app['hash'], $app['config']['auth.model']);
    return new AutoLoginGuard($provider, $app['session.store']);
});

app/config/auth.php

app/config/auth.php に、このautologinドライバを認証ドライバとして利用するように設定します。

'driver' => 'autologin',

さらに、Acme\AutologinGuardクラスに必要なログイントークンに関する設定も追加します。

  'login_token_limit_day' => 30, // ログイントークン有効期間
  'login_token_length' => 60, // ログイントークンの長さ

まとめ

独自のカスタムドライバを作成して、Authクラスの認証処理を差し替えてみました。

このように Laravel では、フレームワークの処理を置き換えることができるような機構が用意されているので、要件に合わない部分に関しては、好きに変更することができます。

<

p>こうした柔軟性の高さも Laravel の面白いところですね。

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

Laravel IoC コンテナの使い方

この記事の所要時間: 1149

laravel

最近は Laravel + AngularJS で Web アプリケーションを開発する毎日です。

少し時間が取れたので Laravel フレームワークのソースを読んでいます。その中から Laravel の肝でもある IoC コンテナの使い方をまとめてみました。

Laravel の IoC コンテナ

Laravel で使われている IoC コンテナは、Illiminate\Foundation\Applicationクラスです。これはIlliminate\Container\Containerクラスを継承したもので、コンテナとしての基本機能はIlliminate\Container\Containerクラスが担っています。

Laravel アプリケーションで、この IoC コンテナを利用する際は、AppクラスというIlliminate\Foundation\Applicationのファサードクラスが用意されているので、こちらを利用することが多いです。

// Foo インスタンスを取得
$foo = App::make('Foo');

AppクラスのgetFacadeAccessor()メソッドを使うと、Illiminate\Foundation\Applicationクラスのインスタンスを取得することができるので、直接メソッドを実行できます。

// Foo インスタンスを取得
$foo = App::getFacadeAccessor()->make('Foo');

下準備

ここでは、IoC コンテナの動きを見ていくので、Laravel フレームワークを起動せずにシンプルなコードを書きます。

ファサードクラスは定義されていないので、Illiminate\Foundation\Applicationクラスのインスタンスを直接操作します。(Appクラスを使う場合は、メソッド呼び出しをクラスメソッドとして読み替えて下さい。)

<?php
require __DIR__.'/vendor/autoload.php';

class Foo
{
    /**
     * @var string
     */
    protected $message;

    /**
     * @var string $message
     */
    public function __construct($message = '')
    {
        $this->message = $message;
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }
}


$app = new \Illuminate\Foundation\Application();

コンテナからインスタンス取得

IoC コンテナからインスタンスを取得するには、make()メソッドを使います。引数には、コンテナにバインドした際の名前を指定します。コンテナに指定した名前がバインドされていない場合、クラスとしてインスタンスを生成します。下記では、’Foo’という名前のバインドは行っていませんが、Fooクラスのインスタンスを返します。make()メソッドの第2引数に連想配列を渡すと、生成するインスタンスのコンストラクタに引数として渡されます。

$foo = $app->make('Foo'); // message = ''
$foo = $app->make('Foo', ['Hello']); // message = 'Hello'

IoC コンテナは、AraryAccessインターフェースを実装しているので、配列としてアクセスすると、内部でmake()メソッドを実行して、該当インスタンスを返します。

$foo = $app['Foo']; // message = ''

コンテナにファクトリをバインド

コンテナにインスタンスを生成するファクトリをバインドしてみます。バインドを行うメソッドはいくつかあり、それぞれ用途に応じて使い分けます。

bind()

インスタンスを生成するファクトリをバインドします。make()メソッド実行毎にファクトリが実行され、インスタンスが生成されます。

$app->bind('foo_bind', 'Foo');
$foo = $app->make('foo_bind');
$app->bind('foo_bind', function($app) {
    return new Foo('foo_bind');
});
$foo = $app->make('foo_bind');

bindShared()

bind()と同じですが、make()実行時に生成されたインスタンスをコンテナが保持します。再度make()を実行した際は同じインスタンスを返します。下記コードでは、$foo1$foo2は同じインスタンスです。

$app->bindShared('foo_bind_shared', function($app) {
    return new Foo('foo_bind_shared');
});
$foo1 = $app->make('foo_bind_shared');
$foo2 = $app->make('foo_bind_shared');

bindIf()

コンテナに同じ名前のバインドが無ければ、バインドします。すでにあれば何も行いません。

$app->bindIf('foo_bind', function($app) {
    return new Foo('foo_bind_if');
});
$foo = $app->make('foo_bind'); // 変わっていない

instance()

生成したインスタンスをバインドします。make()メソッド実行時は、常にこのインスタンスを返します。

$app->instance('foo_instance', new Foo('instance'));
$foo1 = $app->make('foo_instance'); // message == 'instance'
$foo2 = $app->make('foo_instance'); // message == 'instance'

singleton()

bindShared()と同じくmake()実行時に生成したインスタンスをコンテナが保持するので、以後同じインスタンスを返します。

$app->singleton('foo_singleton', 'Foo');
$foo1 = $app->make('foo_singleton'); // message == 'foo_singleton'
$foo2 = $app->make('foo_singleton'); // message == 'foo_singleton'
assert($foo1 === $foo2);
$app->singleton('foo_singleton_closure', function($app) {
    return new Foo('foo_singleton_closure');
});
$foo1 = $app->make('foo_singleton_clusure'); // message == 'foo_singleton_closure'
$foo2 = $app->make('foo_singleton_closure'); // message == 'foo_singleton_closure'
assert($foo1 === $foo2);

alias()

コンテナにバインドした名前に別名を付けます。

$app->share('bar', 'foo');
$bar = $app->make('bar'); // message == 'foo'

extend()

コンテナにバインドされたファクトリを拡張します。クロージャの第1引数に元にバインドされたファクトリが返すインスタンスが渡されるので、追加処理を記述して、そのインスタンスを返すようにします。

$app->extend('foo_bind', function($foo, $app) {
    $foo->extend = 'ex';
    return $foo;
});
$foo = $app->make('foo_bind'); // message == 'foo_bind', extend = 'ex'

連想配列として代入

make()メソッドと同じく連想配列として代入することでバインドすることができます。この場合、bind()メソッドの実行と同じ扱いになります。

$app['foo_set'] = function() {
  return Foo('foo_set');
};
$foo = $app->make('foo_set');

具象クラスをインジェクトするDI

では IoC コンテナを使った DI を行ってみます。サンプルのソースは以下です。

このソースでは、HelloクラスとBarクラスがあり、Barクラスではコンストラクタで、Helloクラスのインスタンスを必要としています。

IoC コンテナのmake()メソッドを使って Bar クラスのインスタンスを取得しようとすると、コンテナがリフレクションを使って、コンストラクタの引数を判別して、Helloクラスのインスタンを生成し、Barクラスのコンストラクタに渡してくれます。

結果として、Helloクラスのインスタンスを保持したBarクラスのインスタンスを取得することができます。

このように具象クラスであればコンテナへのバインドを行わなくても、よしなにインスタンスをインジェクトしてくれます。

インターフェースをインジェクトするDI

次は、インスタンスを生成するファクトリをコンテナにバインドしてDIを行います。

サンプルソースを変更しました。Greetableインターフェースを定義して、Helloクラスはこのインターフェースをimplementしています。Barクラスのコンストラクタでは、Helloクラスではなく、Greetableインターフェースのインスタンスを引数として取るように変更しました。

Barクラスを$app->make('bar')で取得しようとするとIlluminate\Container\BindingResolutionExceptionという例外が発生します。これはコンストラクタで必要としているGreetableはインターフェースのため、インスタンス化ができないためです。

これを解決するにはいくつかの方法があります。

Greetableという名前で取得できるインスタンスをコンテナにバインドしておきます。ここではHelloクラスを指定しています。これにより、BarクラスのコンストラクタにはHelloクラスのインスタンスがセットされるようになります。

$app->bind('Greetable', 'Hello');
$app->make('Bar')->say();

Greetableという名前にHelloクラスのインスタンスを生成するファクトリを指定することもできます。

$app->bind('Greetable', function($app) {
    return new Hello();
});
$app->make('Bar')->say();

もちろん、Barという名前でBarクラスのファクトリをバインドする方法もokです。

$app->bind('Bar', function($app) {
    return new Bar($app->make('Hello'));
});
$app->make('Bar')->say();

インジェクトするクラスを変える

IoC コンテナを使って、DI できるようにしておくと、インジェクトするクラスを簡単に変更することができます。

Helloクラスの代わりにGreetableを実装したByeクラスをインジェクトするには、下記のようにコンテナのバインドを変更するだけです。Barクラスの実装は変更する必要がありません。

class Bye implements Greetable
{
    /**
     *
     */
    public function greet()
    {
        echo 'Bye';
    }
}

$app->bind('Greetable', 'Bye');
$app->make('Bar')->say(); // Bye

おわりに

Laravel に実装されている IoC コンテナの使い方を見てみました。

IoC コンテナは、Laravelを構成する重要な機能なので、使い方を理解しておくとフレームワークへの理解も深まります。

コンテナを連想配列ライクに利用すると Pimple と似ているので、Pimple を使ったことがある人には馴染みやすいかもしれませんね。

(Laravel 作者Taylor Otwell氏の著書(Laravel: From Apprentice To Artisanでは「Laravel の IoC コンテナは、のPimple IoC コンテナとそのまま置き 換えることができます。」と書かれていたりします:D)

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

Home

検索
フィード
メタ情報

Return to page top