今更だけどhubot触ってみた~前編~[hubot][slack][coffeescript][heroku]
色々と記事も上がっているけれど自分の言葉に落とすためにも記事を書く。
hubotって?
github製のbotフレームワークです。 botをさくさくっと作れてかつ色々なチャットに簡単に登場させることのできるわくてかなものです。
Hubot is your company's robot.
イイカンジですね。
Today's version of Hubot is open source, written in CoffeeScript on Node.js, and easily deployed on platforms like Heroku. More importantly, Hubot is a standardized way to share scripts between everyone's robots.
Node.js上で動くCoffeeScriptで書かれていて、Herokuのようなプラットフォームに簡単にdeployできます。
bot作ってみたかったしCoffeeScript触ったことなかったしHerokuもなんだかんだ使ったことなかったのでちょうどいいやと好奇心で使ってみました!楽しい✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
インストールしてみる
環境はmacです。Node.jsは入っているとして。。
npm install -g hubot coffee-script
install終了!!
installの簡単具合がすごいなと思いました本当に。
実際にbotを作ってみます。
hubot --create <bot名>
ディレクトリ作成終了!
ディレクトリ構成は以下のようになっています。
. ├── Procfile ├── README.md ├── bin ├── external-scripts.json ├── hubot-scripts.json ├── node_modules ├── package.json └── scripts
DBを使いたい場合はRedisもinstallする必要がありますが、はじめは特段必要でなかったので使わない方向にしました。使わない時はhubot-scripts.json
からredis-brain.coffee
は外してしまってください。使うときはRedisを入れましょう。
起動してみる
./bin/hubot
Hubot> hubot help Hubot> Events: debug - {user: <user object to send message to>} Hubot <user> is a badass guitarist - assign a role to a user Hubot <user> is not a badass guitarist - remove a role from a user Hubot animate me <query> - The same thing as `image me`, except adds a few parameters to try to return an animated GIF instead. Hubot die - End Hubot process Hubot echo <text> - Reply back with <text> Hubot fake event <event> - Triggers the <event> event for debugging reasons Hubot help - Displays all of the help commands that Hubot knows about. Hubot help <query> - Displays all help commands that match <query>. Hubot image me <query> - The Original. Queries Google Images for <query> and returns a random top result. Hubot map me <query> - Returns a map view of the area returned by `query`. Hubot mustache me <query> - Searches Google Images for the specified query and mustaches it. Hubot mustache me <url> - Adds a mustache to the specified URL. Hubot ping - Reply with pong Hubot pug bomb N - get N pugs Hubot pug me - Receive a pug Hubot show storage - Display the contents that are persisted in the brain Hubot show users - Display all users that Hubot knows about Hubot the rules - Make sure Hubot still knows the rules. Hubot time - Reply with current time Hubot translate me <phrase> - Searches for a translation for the <phrase> and then prints that bad boy out. Hubot translate me from <source> into <target> <phrase> - Translates <phrase> from <source> into <target>. Both <source> and <target> are optional Hubot who is <user> - see what roles a user has Hubot youtube me <query> - Searches YouTube for the query and returns the video embed link. ship it - Display a motivation squirrel
hubotにははじめからこのようなサンプル機能が搭載されていてぶっちゃけinstallするだけでもだいぶ楽しいですw
Hubot> hubot ping Hubot> PONG Hubot> hubot echo hoge Hubot> hoge
pingと言ったらpongと返してくれるしecho hogeと言ったら命令通りhogeと返してくれる。可愛いやつですね(?)
どう動いているのか見てみる
先ほどのhubot ping
や、hubot echo hoge
はどのように動作しているのでしょうか。実際にコードを見てみましょう。
module.exports = (robot) -> robot.respond /PING$/i, (msg) -> msg.send "PONG" robot.respond /ECHO (.*)$/i, (msg) -> msg.send msg.match[1]
これがcoffeescript...! coffeescript関連の話は今回は省略しますmm javascriptと対比しながら調べていくとだいぶわかる気がします。
robot.respond
がhubot
という呼びかけに対応していて、その後ろの正規表現と入力された引数がマッチしたらマッチした内容(msg.send "PONG"
)を実行するといったかんじですね。
msg.send
で引数に渡された値を出力します。
robot.respond
以外にも色々応答するためのバリエーションがありますので調べてみると楽しめます。
hubot/robot.coffee at master · github/hubot · GitHub とか。
次書くときはこのbotをherokuに上げてslackと接続してみます!
githubにエディタライクなカラースキーマを適用する[github]
今回はライフチェンジングなpluginのご紹介です。
StylishThemes/GitHub-Dark · GitHub
このプラグインを入れるとgithubが黒い画面ライクなカラースキーマに早変わりします!!
テーマのバリエーションもかなり豊富です。
- 256 Jungle
- 3024 Night
- Anotherdark
- Asmanian-blood
- Birds of Paradise
- BlackSea
- Brookstream
- BusyBee
- Candycode
- Darcula
- Dark
- Dark2
- DarkBlue2
- DarkBone
- DarkBurn
- DarkDevel
- DarkOcean
- DarkRobot
- DarkSpectrum
- DarkZ
- DarkerDesert
- Desert
- Deveiate
- Digerati
- Dim
- Dim2
- Dracula
- Freya
- Fruity
- Github-Dark
- Herald
- Idle Fingers
- Inkpot
- Jellybeans
- Monokai
- Mustang
- Native
- Neverland
- No Quarter
- Pastels on Dark
- Peaksea
- Railscasts
- Rdark
- Refactor
- Sahara
- Slate
- Solarized-Dark
- Synic
- Tomorrow Night
- Tomorrow Night Blue
- Tomorrow Night Bright
- Tomorrow Night Eighties
- Twilight
- Up
- Vim
- Vitamins
- Wombat
- Zenburn
圧巻の58種類!!
僕はこの中でもみんなだいすきMonokaiを使っています。
その他にも、リンクの色や背景のデザインも変更できます。
こんな感じ。
おしゃれ!✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
皆さんもおしゃれで見やすいgithubライフを送りましょう!
行った気になれるSymfony勉強会#10総集編[PHP][Symfony][Silex]
昨日は日本Symfonyユーザー会主催のSymfony勉強会 #10に参加してきました!
Symfony勉強会 #10 - 日本Symfonyユーザー会 | Doorkeeper
当日の流れとしては
- Silexについてのワークショップ
- LT
- 懇親会
という感じでした。
Silexについてのワークショップ
今回はSymfony勉強会ですが、弟的な存在であるSilexについてのワークショップでした。
Homepage - Silex - The PHP micro-framework based on Symfony2 Components
題材
ワークショップの題材としては
Creating a simple REST application with Silex.
こちらのサイト、Creating A Simple REST Application With Sliex の写経・コード読みがメインでした。
写経やコード読みをするなかで、テスティング関連の話も交わったり、Symfonyのルーティングとの違いや、SilexへSymfonyのルーティングを組み込む方法などのはなしも飛び交い、有意義でした:)
成果
写経+α(写経だけでは実は全然上手く行かないトラップが仕掛けられていたのでw)をしていったワークショップの成果をgithubに一応上げておきました。
gomachan46/symfony10_study · GitHub
LT
LTは以下の皆さんが行って下さいました。(Doorkeeperより抜粋)
- 4年ほどSilexを使ってきた経験の話 (@Tetsujin)
- 1000万ユーザーのソーシャルゲームのバックエンドにSymfonyを使った話(仮) (@modeelf)
- EventListener使いこなし術 (@okapon_pon)
- そろそろComposerについてひとこと言っておくか (@_nishigori)
- Symfony2 を drone.io でCI する (@HideyukiTakei)
- パーフェクトRailsで個人的に良かったところ(仮) (@tdakak)
- Symfonyでの実装パターン、のような話 (@hidenorigoto)
- DBマイグレーション管理どうしてますか?(@brtriver)
4年ほどSilexを使ってきた経験の話 (@Tetsujin)
マイクロフレームワークを使っていくと結局マイクロにはなるわけではなくどんどんど大きくなっていくので、自分たちで自由に拡張していけるのが面白いと感じたり上手く拡張して付き合っていけるようなチームだと合うんだろうなぁと感じました。
でもそれが結局オレオレSymfonyに近いような形になってしまうとパフォーマンスやドキュメント量、疎結合度等どんどんただの劣化版Symfonyになってしまう思うのでここだって言えるような自分たちの尖りをつけるくらいの気持ちで使わないと活かせないのかなぁとか聞いていて感じたりしました。
Silex-Skeltonに乗って開発を行うのはすごく良いなぁ、と感じました!
それ、SymfonySEで良くね?と言われないような規模で使ったり拡張の仕方をしたりして長期的に使うとしたら見ていきたい。
でもそういった巨大化するニオイがするならやっぱり自分ならSilexかSymfonyかと言ったらSymfonyにするかなーというのが今の感触でした。
Silexにはもっとlight weightな所で真価を発揮していただきたいかな、とw
長年付き合ってきてのお話、とてもためになりました。ありがとうございました!
マイクロフレームワークとは何だったのか #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
"マイクロフレームワーク、結局マイクロにならない" #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
Silex-Skeletonってあるのか #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
規模感によるよなぁ...マイクロにならないのは良いとしてオレオレSymfonyまでは行かないように使えれば良いんだろうな #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
https://t.co/FZhHd8H63F #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
1000万ユーザーのソーシャルゲームのバックエンドにSymfonyを使った話(仮) (@modeelf)
エンジニアただ肉に会場が沸いてました!wぜひ活用させていただきたいです><
無料で焼き肉が食べれるだと…. #symfony_ja
— Masao Maeda (@brtriver) 2014, 7月 26
にく… #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
http://t.co/6PpPs1U5la #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
ただ肉たべたい #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
Symfony1.0系を使いつづけていくという非常にマニアックなお話でしたw
熱いSymfony1.0系愛を感じましたw
バージョンアップする気がないから後方互換とか迷わない!wという振り切った意見、よかったですw
バージョンアップする気がないから迷わない!!w #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
EventListener使いこなし術 (@okapon_pon)
SymfonyのEventListenerを上手く使っていく、非常に有益なお話でした!
再利用しづらかったりテストしづらかったりするので、Controllerにbeforeメソッド等で書くのではなく、その前にEventListenerで仕留めてしまおうよ、というお話でした。
この辺りをEventListenerでさばいてあげるときれいに処理がまとまりそう。
ぜひ試してみたい!!この辺りは良い話を聞けたので要調査したいなぁ。
コントローラに before 的なメソッドを書かない/書かせない #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
オレオレ作ってもいいじゃない #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
http://t.co/jYrcKH7ys7 #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
コントローラーを拡張して何かやりたいことってだいたい trait でできるよね #symfony_ja
— Masao Maeda (@brtriver) 2014, 7月 26
そろそろComposerについてひとこと言っておくか (@_nishigori)
非常にセンスの良い画像のチョイスでとても楽しいスライドでしたw本当素晴らしいw
Composerはまだまだalpha版なのでバージョン固定して利用する等して気をつけて付き合っていかねば...
Composerは便利だけどまだまだ至らないところもあるし何も考えずに使っていると苦労するから本当気をつけよう...w
composerのアップデートでautoload_psr4.phpがいきなり増えたりして焦ったことがあるので、できればバージョンを固定して使いたい。--versionオプションは朗報 #symfony_ja
— Noriyuki Takano (@euphoryx1) 2014, 7月 26
composerのつらみを感じる #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
composer の alpha9 / beta / stable はいつ?系の質問は中の人からスルーされているらしい #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
composerは気をつけて使うしか無いのか・・・ #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
"composer は気をつけて使いましょう" つらい #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
composerの闇をみた #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
Can we get an 1.0.0-alpha9 release? :) · Issue #3029 · composer/composer
https://t.co/rM4EMktcHL #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
Can we get an 1.0.0-alpha9 release? :) が気になった人はコメントを増やしてこのissueを盛り上げていきましょうw
Symfony2 を drone.io でCI する (@HideyukiTakei)
drone.ioを使ってCIしていくお話でした。
drone.io自体知らなかったので勉強になりました...!
Continuous Integration · drone.io
travisCIと異なりgithub以外のリポジトリに対してもCIできるということで、使える幅は広そうです。
このあたりをdockerと組み合わせて遊ぶと楽しそう。
Drone and Docker, Open Source CI · drone.io
travis-ciっぽいやつ Continuous Integration · drone.io
https://t.co/Obe8cJOYsR #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
droneioのcookbookあるのか。これは導入したいかも #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
パーフェクトRailsで個人的に良かったところ(仮) (@tdakak)
割と最近出たパーフェクトRuby on Railsについての感想をベースとして、SymfonyとRailsの対比や若者のRails離れ(?w)のお話でした。
とっても絵が可愛い+わかりやすくて良かったです!ありがとうございました!
パーフェクトRails、買おうかなーと思いつつまだ買えないでいたので、これを機に買おうと思います( ¯•ω•¯ )
「RailsやSymfonyなど大きいFWでは初心者向けチュートリアルの一歩先で躓きがち、パRailsはそこをカバーできる良い本」との事だったので、楽しみです!
確かに大きなフレームワークは強力がゆえにハードルがとても高くなるように感じます。(中のコードを追えば追うほど)
だからそのあたりを上手く付き合えるようにするサポートがあるとかなり一歩目が早く踏み出せそうだなと感じています。
Rubyは最近あまり触れてないので久々にRuby系の記事を書こうと思いました:-)
パーフェクトRails #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
パーフェクトRuby on Rails!!! #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
http://t.co/JbXCkJbzyf #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
パーフェクトRuby on Rails on Symfony勉強会 #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
まさかの若者ののRails離れ #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
一部発言が不参加の方に誤解されてたっぽいので補足。「RailsやSymfonyなど大きいFWでは初心者向けチュートリアルの一歩先で躓きがち、パRailsはそこをカバーできる良い本」て話で、RailsがダメでSymfonyがよいという話では全然ないです。 #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
Symfonyでの実装パターン、のような話 (@hidenorigoto)
非常にためになるお話ありがとうございましたmm
EntityとRepositoryの2つだけだとどうしても実装が集中してしまう、そこで
- specification (仕様) パターン
- criteria (検索条件) パターン
という2人の役者をEntityとRepositoryに加え参加させることで分散させようとする実装パターンのお話でした。
- 静的な条件 Entity + specification
- 動的な条件 Repository + Criteria
仕様であるspecificationは全てサービス化させておいてDIしてしまって、必要に応じて扱う。
使ってみているサンプル等があればとても頭に入りやすそうだけれどないかなぁ...
まだまだ勉強勉強です:-(
エンティティとリポジトリしか構造が無いと、どうやってもこの2つに実装が集中する → 構造を追加する(Specificationパターン/Criteriaパターン) #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
リポジトリはすぐに肥大化する #symfony_ja
— Masao Maeda (@brtriver) 2014, 7月 26
検索条件とかだとLINQ的な構造の方が合成しやすくなるかもね #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
自分たちの問題をうまく解決するための構造は自分たちで作っていかなければならない #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
Symfony温泉合宿のお話もありました!めっちゃ行きたい!w
めぐり合わせが良ければぜひ行きたいなと思っておりますのでよろしくお願いしますmm
http://cdn-ak.f.st-hatena.com/images/fotolife/j/ja9/20110727/20110727210727.jpg
途中出てきたこの画像で笑ってしまったw
シンポニーww #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
Symfony温泉合宿 #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
お、温泉合宿だと!!! 行きたい! #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
温泉行きたい! #symfony_ja
— Yudai Fujita (@fujiyan0121) 2014, 7月 26
"日本シンポニーユーザー会様" #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
"温泉合宿はガチ翻訳やってたよ。もう最近はやる気がなくて…ね” ww #symfony_ja
— にしごおり (@_nishigori) 2014, 7月 26
シンポニー温泉合宿行きたい #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
“温泉入ろう” #symfony_ja
— にしごおり (@_nishigori) 2014, 7月 26
DBマイグレーション管理どうしてますか?(@brtriver)
トリは我らが@brtriverさんのお話でした!←
端的に言うと、DBマイグレーション、downいりますか?といったお話でした。 本番でdownできるケースなんてまぁない(そこにはすでに生きているデータがある)ので、だったらそもそもdown出来る機能なくていいよね、という感じでした。
いちいちdown時の挙動書くのもめんどくさいしその割にほぼ使うことはないので、確かにdownはなくてもいいんじゃないの、と思いました。
ただ開発時には欲しくなったりはするかもなーとは思います。試行錯誤したくはなったりする時がありそう。(現状わりとある)
まぁdownじゃなくてもリバートコミットのように打ち消すコミットを重ねれば良いとも思うのでdownなしは割と賛成でした!ありがとうございました!
Flywayというのを使っているそうで、シンプルな構成でなかなかよさそうでした。もう少し見てみたい。
Flyway • Database Migrations Made Easy.
Dbup - UPコマンドだけのPHP製シンプルマイグレーションツール : http://t.co/9NUbe0jZK4 #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
Migration困ってるんですよねえ… #symfony_ja
— Hiraku (@Hiraku) 2014, 7月 26
migrate downに潜む事故るニオイ #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
本番環境で migration の down しない/させない #symfony_ja
— tadaaki (@tdakak) 2014, 7月 26
たしかに本番環境でdownは怖過ぎてできない #symfony_ja
— インフラ修行したいポリドッグ (@polidog) 2014, 7月 26
Flyway • Database Migrations Made Easy. : http://t.co/UXgcDxeZoJ #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
"downつかってないけど困ってません" #symfony_ja
— なかにしごう (@gomachan46) 2014, 7月 26
おわりに
懇親会含め、とっても楽しい勉強会でした!ありがとうございました!!
他の勉強会で話はしていなかったけど覚えていてくれたようで、嬉しい経験もしたり:)
やっぱり勉強会中にも思ったけれど、勉強会のいいところは勉強会の題材だけじゃなくて周辺知識に関しても本当にいろんな学びがあるところだなぁと改めて。
やっぱ勉強会とかの良い所って周辺知識が勝手に入ってくることだよなぁ〜 symfony/silexについてっていうよりもろもろの知識についての学びがすごく大きい
— なかにしごう (@gomachan46) 2014, 7月 26
もっともっと参加していきます!いずれは発表させてください:)
(今回、#symfony_jaタグがついていたもの関してのみ、tweetを引用させていただきました。何かあれば対応しますのでご連絡くださいmm)
Symfonyユーザ会の方々、会場を提供してくださった株式会社ヒトメディア様、楽しい時間をありがとうございました!
.tmux.confを再読み込みする
何度も何度も忘れるので終止符を打つ...
tmux source-file ~/.tmux.conf
alias作っとくのもありだけどそこまでの頻度でもないんだよなぁ...
DIとは?DIコンテナとは?試してみた(後編)[PHP][Pimple][DI]
DIとは?DIコンテナとは?試してみた(前編)[PHP][DI] - あざらし備忘録。 の後編です。
今回はDIコンテナについて。
DIコンテナとは
DIをするにあたって、使用する度に毎回依存性のあるものを生成して注入して、...というのが面倒くさいのでその依存性を定義してコンテナという形で保管しておくことでそのコンテナを取り出すだけで依存関係の整ったものを手に入れることができる代物です。
前回のおさらい
前回、DIの例として書いたコードは以下の様でした。
<?php interface SongInterface { public function getTitle(); // ...など曲に関する諸々 } class Song implements SongInterface { public function __construct($title=null) { $this->title = $title; } public function getTitle() { return $this->title; } // ... } interface MusicPlayerInterface { public function play(); } class MusicPlayer implements MusicPlayerInterface { /** * @var SongInterface */ private $song; public function __construct(SongInterface $song) { $this->song = $song; } public function play() { echo '"'.$this->song->getTitle().'"を再生するよ'; } } interface TwitterClientInterface { public function post(); } class TwitterClient implements TwitterClientInterface { public function post() { // 投稿処理 echo '音ゲーをプレイしたよとつぶやくよ'; } } class Otoge { /** * @var SongInterface */ private $song; /** * @var MusicPlayerInterface */ private $music_player; /** * @var TwitterClientInterface */ private $twitter_client; public function __construct(SongInterface $song, MusicPlayerInterface $music_player, TwitterClientInterface $twitter_client) { $this->song = $song; $this->music_player = $music_player; $this->twitter_client = $twitter_client; } public function play() { echo '"'.$this->song->getTitle().'"で音ゲーを開始するよ'; $this->music_player->play(); } public function tweet() { $this->twitter_client->post(); } }
実行例は、こんな感じでした。
<?php $song = new Song('test'); $music_player = new MusicPlayer($song); $twitter_client = new TwitterClient(); $otoge = new Otoge($song, $music_player, $twitter_client); $otoge->play(); $otoge->tweet();
songもmusic_playerもtwitter_clientも...毎回インスタンスを生成してDIして、...面倒くさそうですね。
DIコンテナを使ってみる
今回は、PHPの中で最も有名なDIコンテナの一つであるPimpleを使ってサンプルを作ってみます。
Pimple - A simple PHP Dependency Injection Container
早速ですがこちら。
クラス/インターフェース定義については変更はありません。実行例は次のようになります。
<?php require_once './config_container.php'; $container['song.title'] = 'hoge'; $otoge = $container['otoge']; $otoge->play(); $otoge->tweet();
先ほどのDIの例ではあったsongやmusic_playerやtwitter_clientといったotogeに依存関係のあった面倒くさいインスタンスの生成がぱっと見なくなっています。 また、containerという見慣れないもの見えますね。
このcontainerというやつがよしなにいい感じにしてくれているようです。
まぁもうちょっと言うと名前と文脈からDIコンテナですねw
原理はわからないですが呼び出し側としては曲情報(今回はtitleだけだけど)をセットするだけでその曲情報を持った状態の$otogeを作ることができ、また、表では特に何もしていないのにtweet機能やplay機能を扱う事ができるようになり、非常に使いやすくなりました。
原理を見てみる
requireしているconfig_container.phpを見てみましょう。
<?php use Pimple\Container; $container = new Container(); $container['song.title'] = ''; $container['song'] = $container->factory(function ($c) { return new Song($c['song.title']); }); $container['music_player'] = $container->factory(function ($c) { return new MusicPlayer($c['song']); }); $container['twitter_client'] = function ($c) { return new TwitterClient(); }; $container['otoge'] = $container->factory(function ($c) { return new Otoge($c['song'], $c['music_player'], $c['twitter_client']); });
これがDIコンテナ設定の中身です。
ここで依存関係を定義しています。
仕組みとしては、$container['otoge']が実行時に呼ばれることで無名関数が呼ばれてその際にインスタンスが生成されるという寸法です。
Pimpleについては一DIコンテナ例として用いただけなので詳しい説明は割愛しますが、
factory()を用いることで無名関数が呼ばれる度に新たなインスタンスを生成します。
用いない場合は無名関数が一度呼ばれた時にキャッシュし、以後同一インスタンスを返すいわゆるシングルトンな振る舞いをします。
メール配信やツイッター送信など、サービスレイヤーなものについてはfactory()なしで呼ぶことで無駄にインスタンス生成することなく扱え、DIコンテナとしてはそのような振る舞いを期待して用いることが多いかと思います(例ではfactory()が多めになってしまってアレですが。。。)
また、$container['song.title']のように普通に文字列等も扱うことはできます。
このように、DIコンテナを上手く使うことで呼び出し側としては非常にスッキリとした呼び出し方をすることができ、意図のわかりやすいコードが書けるようになります。
呼び出し側がすっきりするのは非常に良い!
DIコンテナを使ってあげることで呼び出し側が非常に見通しが良くなり何をしたいのかが明確になるという恩恵が得られるのは非常に大きいなぁと感じました。
上手に付き合っていきたい代物ですね!
今回用いたサンプルコードは以下に置いてありますのでよろしければ。
DIとは?DIコンテナとは?試してみた(前編)[PHP][DI]
DIはダイジだーDIコンテナはベンリだーとかは色々聴いていたのですが、DIもDIコンテナも何もわかっていないので勉強してみようと思い少し試してみたのでメモ。
まずはDI編。
DIとは
Dependency Injectionです。
依存性の注入です。
...?(´・ω・`)
依存性?(´・ω・)注入?(´・ω・
)っていう感じなのでもう少し詳しく深掘ってみます。
依存性とは
「クラスAを正しく動かすためには、クラスBが既に出来上がってないとだめ」という状態を、「クラスAはクラスBに依存している」といいます。
例を示します。(もろもろ言うことはあれどとりあえず依存している事がわかる実装になっていればよしとします)
<?php class Song { public function __construct($title=null) { $this->title = $title; } public function getTitle() { return $this->title; } } class MusicPlayer { private $song; public function __construct() { $this->song = new Song('test'); } public function play() { echo '"'.$this->song->getTitle().'"を再生するよ'; } } class TwitterClient { public function post() { // 投稿処理 echo '音ゲーをプレイしたよとつぶやくよ'; } } class Otoge { private $song; private $music_player; private $twitter_client; public function __construct() { $this->song = new Song('test'); $this->music_player = new MusicPlayer(); $this->twitter_client = new TwitterClient(); } public function play() { echo '"'.$this->song->getTitle().'"で音ゲーを開始するよ'; $this->music_player->play(); // その他得点記録したりとかの処理 } public function tweet() { $this->twitter_client->post(); } }
実行例はこんな感じです。
<?php $otoge = new Otoge(); $otoge->play(); $otoge->tweet();
この例では、
という要件があるとします。
この例だと、次のような依存関係があると言えます。
- クラスOtogeはクラスSongに依存している
- クラスOtogeはクラスMusicPlayerに依存している
- クラスOtogeはクラスTwitterClientに依存している
- クラスMusicPlayerはクラスSongに依存している
依存自体は悪いことではありません。要件があって役割に応じてクラスを分けていったら自然と要件上依存関係にあるクラスたちは生まれてくるでしょう。
じゃあ何がいけないのか
依存関係が「ベタ書き」されていることです。
クラスOtogeのコンストラクタに注目して欲しいのですが、コンストラクタ内でSong, MusicPlayer, TwitterClientのインスタンスが生成されてプロパティにセットされていますね。
また、クラスMusicPlayerのコンストラクタでも、Songのインスタンスが生成されてプロパティにセットされています。
これは依存しているクラスに依存関係が「ベタ書き」されていると言えるでしょう。(大事なことなので2回(ry)
この状態だと、以下のようなよろしくない点が考えられます。
- Song, MusicPlayer, TwitterClientを別のものに替えたいときにOtogeに修正を加えなければならなくなる
- Song, MusicPlayer, TwitterClientが上手く動かないとOtogeに対してのテストができなくなる
- Song, MusicPlayer, TwitterClientが出来上がるまでOtogeの開発ができなくなる
- Songを別のものに替えたいときにMusicPlayerに修正を加えなければならなくなる
- Songが上手く動かないとMusicPlayerに対してのテストができなくなる
- Songが出来上がるまでMusicPlayerの開発ができなくなる
- Otoge->tweet()をテストするたびツイッターに投稿されてしまう
- MusicPlayer->play()をテストするたび音楽が再生されてしまう
こういうよろしくない点が出てきてしまうのはまずいので、なんとかしたいですね。
じゃあどうしたらいいのか
諸悪の根源は依存関係が「ベタ書き」されていることです。(大事なことなので3回(ry)
なので「ベタ書き」しないようにしましょう。
「ベタ書き」しない = クラス内でインスタンスを生成しない
つまり
「ベタ書き」しない = クラス外でインスタンスを生成して外から渡してあげる
といえます。
これをDI(依存性の注入)と言うわけです!!
DIしよう
具体的には、DIとはどのような形で行われていくのでしょうか。どうしたら先ほど挙げたよろしくない点が解消できるでしょうか。例を示します。
<?php interface SongInterface { public function getTitle(); // ...など曲に関する諸々 } class Song implements SongInterface { public function __construct($title=null) { $this->title = $title; } public function getTitle() { return $this->title; } // ... } interface MusicPlayerInterface { public function play(); } class MusicPlayer implements MusicPlayerInterface { /** * @var SongInterface */ private $song; public function __construct(SongInterface $song) { $this->song = $song; } public function play() { echo '"'.$this->song->getTitle().'"を再生するよ'; } } interface TwitterClientInterface { public function post(); } class TwitterClient implements TwitterClientInterface { public function post() { // 投稿処理 echo '音ゲーをプレイしたよとつぶやくよ'; } } class Otoge { /** * @var SongInterface */ private $song; /** * @var MusicPlayerInterface */ private $music_player; /** * @var TwitterClientInterface */ private $twitter_client; public function __construct(SongInterface $song, MusicPlayerInterface $music_player, TwitterClientInterface $twitter_client) { $this->song = $song; $this->music_player = $music_player; $this->twitter_client = $twitter_client; } public function play() { echo '"'.$this->song->getTitle().'"で音ゲーを開始するよ'; $this->music_player->play(); } public function tweet() { $this->twitter_client->post(); } }
実行例は、こんな感じです。
<?php $song = new Song('test'); $music_player = new MusicPlayer($song); $twitter_client = new TwitterClient(); $otoge = new Otoge($song, $music_player, $twitter_client); $otoge->play(); $otoge->tweet();
DIしていない状態から変わったことは、以下のような点です。
こうすると、OtogeやMusicPlayerのインスタンスを生成する際に引数で必要なものを注入してあげればいいので、依存性を「ベタ書き」することは回避できました!
なお、Interfaceは必ずしも実装しなさい、というわけではありません。
実装を知ってしまっていても良い場合はクラスでやってしまって、外部に公開する必要があるような部分はinterfaceでやってしまう、というのもよくあります。
今回はDIをわかりやすくするためにすべてinterfaceを実装していますが、適宜使用するようにすると修正の手間が少なくなってより効率よく開発が行えるかと思います。
DIしていくことの副作用
依存性を注入できるようになったわけですが、気になる点がひとつ。
「引数爆発しません?」
そうですよね。依存性が生まれるたびに引数がひとつ増えて...となったら引数が多くなって管理するのが面倒になってきそうです。
呼び出し先での実装もいちいち各インスタンスを生成していって面倒ですし「俺はOtogeクラスのインスタンスを作りたかっただけなのに...」ってなりそうですね。
そこでDI「コンテナ」の登場
そんな悩みが、DIコンテナ、というものを使うとすっきりして管理が楽になります。
後編ではPHPのDIコンテナの一つであるPimpleを用いてDIコンテナについて書いていきたいと思います。
後編はこちら
アプリからsafariを起動して指定したURLを開く[Objective-C][iOS][iPhone]
めっちゃ簡単でちょっと感動したのでメモ。
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://shiro-goma.hatenablog.com/entry/2014/06/01/041811"]];
これを起動したいタイミングで挟んであげればそれだけでsafariが立ち上がってくれます。
例えば、tableView内のCellがタップされた時にsafariを起動したいときは
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://shiro-goma.hatenablog.com/entry/2014/06/01/041811"]];
こんな感じで。簡単!