※Nagoya.phpは名古屋でだいたい隔月開催しているPHPの勉強会です。勉強会ですが、LTや発表にはそれほど重点はありません。プログラム問題を課題に皆で黙々と時にわいわいとPHPのコードを書いている勉強会です。
今日の反省点:再帰は思った以上にハードル高い。ピザがすぐ無くなった。#nagoyaphp
— Takashi Kanemoto (@qckanemoto) 2014, 11月 16
参考 “再帰能力は人間の言語器官の基本的構成要素である。再帰能力は人間の言語を規定する唯一の特徴である。再帰はヒトの言語器官だけに固有の特徴である。再帰構造はあらゆる自然言語に備わっている。再帰構造処理は人間に固有の能力である。” http://t.co/Loxqg80ebQ
— Hidenori Goto (@hidenorigoto) 2014, 11月 19
@77web 多分プログラマ的思考の分岐点なんだと思うんですよね。なので是非ハノイの塔を。。。
— Hidenori Goto (@hidenorigoto) 2014, 11月 18
今年はLTではなく、フレームワーク四本勝負というもの(東京のPHPカンファレンスで毎年(?)行われるフレームワークアップデートの関西版のようなもの)でSymfonyについて話をしました。
発表に使ったスライドはこちら。
@koriymさんの基調講演に感銘を受け、お話の中にあった「継続」という言葉をどうしても入れたくなって、登壇直前までスライドを書き直して臨みました。
言うはずのことを言い忘れたり、言わなくて良いことも言ったりと個人的な失敗は色々ありましたが、聞いてくださった方に、基調講演からのつながり(つなぐ力!)を少しでも感じてもらえたら嬉しいです。
<愚痴>
しかし、運営スタッフの方からの発表依頼の際にはレギュレーションが指定されていたにも関わらず、指定を守ったのが私とLTの『遅れて来た5番目のフレームワーク』だけだったという(ーー;)
一言で表すと〜のために何日も悩んだのはなんだったのでしょう…。
</愚痴>
懇親会で、今後Symfony Componentを使うオープンソースプロジェクトの方や、Symfonyに興味があるという方から声をかけていただき、それぞれ少しお話ししました。はからずも、Symfonyが重い・難しいという印象が浸透してしまっているのを痛感することになりました。
ファイル数が多いというのを気にする向きもありましたが、軽量と言われるフレームワークでもcomposerを使うのがスタンダードになってきた以上、そこは気にしなくていいのではないでしょうか。
幸い、Symfonyは、浮気しても待っててくれる心が広い嫁なので、食わず嫌いせずに一度は付き合ってみてほしいと思います!(付き合ってみたら、ギャップ萌えもあるかもしれませんよ?)
今日一日おつきあいくださった皆様、ありがとうございましたm(_ _)m
スタッフの皆様もお疲れさまでした。ぜひ来年も来たいです!
$crawler = new Crawler(); $crawler->addContent('', 'text/html; charset=UTF-8'); $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string');
一人で開発
開発機のOSはWindows7 professional(メモリが64GBというモンスターマシン^p^)
gitはmsysgit利用
主に使うフレームワークはsymfony1又はSymfony2 たまにwordpressとかcakeとか
visual c++等の自力でphp及びその拡張モジュールをビルドできる環境は無い
新規開発案件(PHP5.4/5.5)と保守案件(PHP5.3)を使い分けたい
開発に使うIDEはphpstorm
原則としてTDDするのでphpunitを使いたい(symfony1の場合はlime使う)
*プロジェクトルートをsynced_folderにする
*コードはホスト側のphpstormで書き、テストはゲスト側のコンソールで手動実行(作業中は、phpstormとmsysgitのGit Bashとvagrant sshの三窓を行ったり来たり)
*Symfony2のassets:installは常に--symlinkなしで使う
(*symfony1のplugin:publish-assetsはホスト側で実行する)
*composer installはゲスト側で実行(synced_folderでホスト側にも共有されてくるので、IDEはそれを参照できる)
*本番とのphpバージョン差異に悩まされることがなくなった
*windows版php特有の問題(strftimeのフォーマット文字列とか)に悩まされることがなくなった
*よく言われる速度問題はホスト側の超性能でカバーできてた(と、思う。特に速度で不満感じたことなし)
*jsやcssを更新したときにassets:installを手動実行するのが面倒 *シンボリックリンクが必須のバンドルが使えない
*プロジェクトルートをtype: "rsync"でsynced_folderとして指定
*ホスト側のwindowsはcwrsyncを利用してrsyncコマンドを使えるようにしておく
*.git .idea app/cache/* app/log/* bin vendor web/bundlesを:rsync__excludeに指定(assetic使う場合はweb/assetic web/js web/cssも除外した方が良いかも)
*利用するboxにもよるが、hashicorp/precise64の場合は、初回vagrant up時にいったんVagrantfile上のsynced_folder設定とprovision設定をコメントアウトし、ゲスト側rsyncのバージョンをホスト側rsyncのバージョンと合わせてから、synced_folder設定とprovision設定を再度有効化してvagrant reload --provisionを実行する
*作業中に必要なウィンドウがphpstormとmsysgitのGit Bashとvagrant sshの三窓から、rsync-auto専用のGit Bashも加えた四窓に増えた
*composer installはゲスト側のプロジェクトルートで実行後、--no-scriptつきでゲスト側の/vagrantでも実行する(IDEで補完を利用するために必要)
*シンボリックリンクが必須の一部のバンドルを使っていてもwindowsホストで問題なく開発できるようになった
*rsync-autoにしておくと、excludeにしているにも関わらず時々ゲスト側だけにあってホスト側にないファイル・ディレクトリ(vendorなど)が削除されてしまうことがある
Vagrant1.5.2以下でrsyncしたディレクトリのパーミッションがおかしい場合→https://github.com/mitchellh/vagrant/issues/3256
Vagrant1.5.2以下でrsyncがNo such file or directoryで失敗する場合→https://github.com/mitchellh/vagrant/issues/3230(4/21 12:40訂正1.5.3でも発生してました)
- "symfony/symfony": "2.3.*" + "symfony/symfony": "~2.3"
$ composer update symfony/symfonyあとは、自分の書いたバンドルについて2.4でエラーが出るようなら修正しましょう。
Argument 1 passed to Knp¥Component¥Pager¥Paginator::__construct() must be an instance of Symfony¥Component¥EventDispatcher¥EventDispatcher, instance of Symfony¥Component¥HttpKernel¥Debug¥TraceableEventDispatcher given,
- "knplabs/knp-paginator-bundle": "2.3.*", + "knplabs/knp-components": "~1.2.4", + "knplabs/knp-paginator-bundle": "dev-master",
- "minimum-stability": "stable", + "minimum-stability": "dev", + "prefer-stable": true,
@77web 試していないのですが、symfony/symfonyのcomposer.jsonを見る限りでは要求するsymfony/icuは1.0以上で、symfony/icuを1.0にすればlib-icuの4.4は要求されないようです。
― Hidenori Goto (@hidenorigoto) September 10, 2013
結果、無事にicu4.2の環境でSymfony2.3のシステムを動かすことに成功しました!(略) "symfony/icu" : "1.0.*", "symfony/symfony": "2.3.*", (略)
結論:Vagrant使え!
え?ここで終わっちゃ、だめ?w
Vagrantって最近よく聞くけど何?という方も多いでしょう。
Vagrantは仮想OSを開発者にとって嬉しい状態にセットしてくれるありがたいソフトです。ぜんぶ無料で使えます(お金払うと快適な速度のほうが使えるらしいですが)。ま、私も2か月前に初めて知ったんですけどね!
さっそくやり方を書きます。
config.vm.box = "base" を config.vm.box = "precise64" に変更
# config.vm.box_url = "http://domain.com/path/to/above.box" を config.vm.box_url = "http://files.vagrantup.com/precise64.box" に変更
# config.vm.network :private_network, ip: "192.168.33.10" を config.vm.network :private_network, ip: "192.168.33.10" に変更
# config.vm.synced_folder "../data", "/vagrant_data" を config.vm.synced_folder "./data", "/var/www/vagrant_data" に変更
でOKです。今日(2013/07/25)の時点では、PHP5.3.10が入りました。sudo apt-get update sudo apt-get install php5
extension=xhprof.soをphp.iniに追記したらインストール完了です!Vagrant万歳!sudo apt-get install php-pear make sudo pecl install xhprof-beta
後は、Vagrantfileのあるフォルダの下に作ったdataフォルダの中にxhprofを試したいPHPアプリ(wordpressとかOpenPNEとか自作の何かとか)のソースを入れて、Ubuntu側に何かhttpサーバーを入れて、/var/www/vagrant_data(アプリによっては/var/www/vagrant_data/webとかになりますが)をドキュメントルートに設定するだけで、Windowsでも好きなようにxhprofでのプロファイリング結果を出して見ることができます。
xhprofの設定方法やプロファイル結果の見方については検索するとたくさん記事が出て来るのでググってみてください。
これだけなら単にVirtualBoxかVmwarePlayerを使えば従来でもできたことですが、Vagrantのすごいところは、dataフォルダに入れたPHPのソースをプロファイル結果に従ってその場で、しかもWindowsのPC側の使い慣れたエディタやIDEで編集できるところ(synced_folderのすごいところ、というべきか)。
色々と開発中の不便に悩むたびに「YOU macにしちゃいなYO!」と言われることが多いWindowsですが、Vagrantという強力な武器をゲットした今では、もうmacの誘惑も怖くないですね!
]]>わざわざ札幌まで行った理由としては、今すごいコミュニティ欲が高いシーズンが来ていて、更に、札幌は私の生まれ故郷で実家があるので子供達を連れて行っても預け先がある(子供を自宅に置いて行かなくて済むのでDAuth=ダンナauthが取りやすい)というだけです。ついでに、春先に一度倒れた祖父の見舞い(超元気でした)も行きたかったし、単に暑すぎる名古屋から涼しい札幌に少しでも涼みに行きたかったというのもありました。不純な動機ばかりですがw ともかく子供二人と自分、三人分の飛行機代をかけて札幌まで行きました。
OpenPNE3のcookbookと、そのcookbookを使ったVagrant環境を作りました。
なぜ作ろうと思ったかと言うと、素人さんは素人さんでもmac使いの人はいざとなれば自力でビルドしてもらえばいいのでまぁいいとして、windows使いの人にOpenPNE3が動く環境を作ってもらうのが難しいと前から考えていて、唯一おすすめできると思っていたのがxamppぐらい。でも、xamppの最新版はPHP5.4で、OpenPNE3が今のところPHP5.4で動かない(PNEが悪いわけではなくsymfony1.4とdoctrine1.2の問題で)という状態になっていました。
私自身、開発用xamppを5.4にしてからPNE3の案件が戻って来たときに非常に困っていたという事情もあります。私もPHPカンファレンス関西で安藤さんが紹介してくださったvagrantを使って開発環境を用意して回避したので、同様に一般向けに作ったらいいんじゃないかと思ったわけです。
開発は苦労しました。
まず、私のruby力不足。随分前にこのブログで「rubyを始めてみた」的なエントリーを書いた記憶があるのですが、必要に迫られなかったこともあり、実際はhello world以上のことをやったことがなかったのです。Vagrantfileはまだいいとしても、cookbookのほうを書くためには、制御構造(if,each…)の書き方やら連想配列の書き方からグーグル先生に訊きながらの作業になりました。しかも一日目夜のcandycaneのワークショップで「if文のブロックで囲むとインデントが深くなるのでnot_ifやonly_ifを使え」とか言われて慌てて直したり…。
さらに、会場の回線環境があまり良くなくてほぼスマフォのテザリングで頑張ったのですが、大量の依存パッケージ(apache,mysql,php…)をインストールさせることになるので、タイムアウトエラーが頻発してしまい、実際のPNEインストールに辿り着かせるまでが大変という状態でした。
最終的には、OSにprecise64を使い、かつsynced_folderなしであればインストール完了して画面が表示できるところまではなんとか作ることができました。
phpmatsuriは終わりましたが、phpmatsuriだけのために作ったcookbookやvagrant環境ではないので、今後も開発を続けたいと考えています。
具体的には、
* Vagrantを使う場合にsynced_folder内にソースを入れられるようにする
* 設定項目でもっと柔軟性を持たせたい(インストールするプラグインとか)
* precise64以外のOSにも対応したい(特にRedhat,CentOSはマストかと)
* ruby力をつける&モジュール分割するなどしてもう少しcookbook自体のコードを綺麗にしたい
という野望を持っています。目標は大きく!
初日に講演されたどなたか(失念…ごめんなさい><)を見習ってスライドは英語、喋りは日本語にしました。一人同時通訳にしたら絶対に時間が足りないと思ったので。結局、日本語だけ喋っても時間は足りませんでした…。
少しだけウケを狙った「〜〜済のものがこちらになります」が滑って悲しかったです。タイトルのひねりが足りなかったのではないかと反省。せっかくLT時間が3分間だったり、ネタがchefだったりしたので、『OpenPNE3 3分間クッキング』とかつけるべきでしたね。←反省するところ間違ってる?
結果20位台(魂抜けてて正確な順位は聞いてなかった)という評価をいただきました。次はもっと上を狙えるよう頑張りたいです!
OpenPNE関係で普段からお世話になっている手嶋屋の手嶋さん(鳩サブレありがとうございましたm(_ _)m)と「リアルでは初めまして」。川原さん・NEKOGETさん・cakephperさんとも以下同文。PHPメンターズの道場生仲間の佐藤さんとも初めまして。
更に、LTで「名古屋から来た」と言ったお陰で、岐阜から来た外人さんお二人からも声を掛けていただきました。名古屋在住と岐阜在住が札幌のphpmatsuriまで来て、子供の話とか保育園の話で盛り上がるという謎の展開^^;お二人が日本語で喋ってくれてるのに私が英語交じりで返すというアホっぷりを晒してしまい、申し訳なかったです。
偶然、福岡のfusicから来た前回・前々回の優勝者の方とずっと同じテーブルでハックするという幸運に恵まれました。が、お隣の同僚さんも含めて、すごすぎて何も盗んで来れなかった…orz
来年もぜったい行くぞー!というか名古屋でやろうよ!←
]]>// src/Acme/DemoBundle/Controller/BaseController.php public function barAction(Request $request) { //何かの処理 return $this->render('AcmeDemoBundle:Foo:bar.html.twig', array('name' => $name) ); } // src/Acme/DemoBundle/Controller/FooController.php public function barAction(Request $request) { //parent::barAction($request)だとResponseが返って来ちゃうから… //何かの処理(BaseController::barAction()と同じ内容コピペ) //追加の処理(カスタマイズ) return $this->render('AcmeDemoBundle:Foo:bar.html.twig', array('name' => $name, 'tel' => $tel) ); }
// src/Acme/DemoBundle/Controller/BaseController.php /** * @Template() */ public function barAction(Request $request) { //何かの処理 return array('name' => $name); } // src/Acme/DemoBundle/Controller/FooController.php /** * @Template() */ public function barAction(Request $request) { //返ってくるのは配列だからparent::barAction()使ってOK $vars = parent::barAction($request); //追加の処理(カスタマイズ) $vars['tel'] = $tel; return $vars; }
//src/Acme/DemoBundle/Entity/Customer.php class Customer { /** * @var integer * * @ORM¥Column(name="id", type="integer") * @ORM¥Id * @ORM¥GeneratedValue(strategy="AUTO") */ private $id; /** * @var string * * @ORM¥Column(name="name", type="string", length=255) */ private $name; /** * @var integer * * @ORM¥Column(name="seibetsu", type="integer") */ private $seibetsu; }
// src/Acme/DemoBundle/Admin/CustomerAdmin.php use Sonata¥AdminBundle¥Admin¥Admin; use Sonata¥AdminBundle¥Datagrid¥DatagridMapper; use Sonata¥AdminBundle¥Datagrid¥ListMapper; use Sonata¥AdminBundle¥Form¥FormMapper; class CustomerAdmin extends Admin { protected $baseRouteName = 'admin_customer'; /** * @param FormMapper $formMapper */ protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('name') ->add('seibetsu') ; } /** * @param DatagridMapper $datagridMapper */ protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('name') ->add('seibetsu') ; } /** * @param ListMapper $listMapper */ protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('seibetsu') ; } }
/** * @param FormMapper $formMapper */ protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('name') ->add('seibetsu', 'gender') ; } /** * @param DatagridMapper $datagridMapper */ protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('name') ->add('seibetsu', 'gender') ; }
専用TwigExtensionはサービスとして登録しておきます。// src/Acme/DemoBundle/Twig/Extension/GenderNameExtension.php class GenderNameExtension extends ¥Twig_Extension { public function getName() { return 'acme_demo_gender_name'; } public function getFunctions() { return array( 'gender_name' => new ¥Twig_Function_Method($this, 'getGenderName'), ); } /** * @param string $value * @return string */ public function getGenderName($value) { $list = array( 1 => '男', 2 => '女', 3 => 'その他', ); return isset($list[$value]) ? $list[$value] : '-'; } }
{# src/Acme/DemoBundle/Resources/views/sonata_customer_list_gender.html.twig #} {% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %} {% block field %} {% spaceless %} {% if value is empty %} {% else %} {{ gender_name(value) }} {% endif %} {% endspaceless %} {% endblock %}
/** * @param ListMapper $listMapper */ protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('seibetsu', 'string', array( 'template' => 'AcmeDemoBundle::sonata_customer_list_gender.html.twig', )) ; }