Provisioning Frameworks Casual Talks いてきた感想

きたる5/10、定時ダッシュで会社を飛び出しProvisioning Frameworks Casual Talks
参加してきました。

内容はchefとpuppetなどのサーバの構成管理ツールのあれこれというお話で、個人的には「サーバの構成をコード化して共有する」「サーバの構成をテストする」という考え方を持つ事ができるようになったのが良かったでした。

今回は参加予約のないMySQL Casual Talks 方式でしたが、うまく回っている感じがしました。
ただ、20~30人の狭い会場だと入れない人がでたりして微妙かもしれないなとも思いました。

あと、LINEさんのセミナールーム?とてもお洒落空間でした。うらやましい!

で、個別の感想

1. 新卒研修でserverspecとChefを使った話

http://dl.dropboxusercontent.com/u/224433/pfcasual_1/index.html#1

カヤックの@fujiwaraさんによる前半はカヤックでのchefの利用、後半は新卒研修の話

serverspecでのインフラ版のTDDみたいな事を新卒研修をやったそうです。
1.最初に全部失敗するserverspecでのテストを用意
2.新卒に手でインストールさせる
3.無慈悲なrollback
4.chefを使って再度インストール

これの狙いはインフラ担当とアプリ開発担当が分離しがちだけど、なるべくアプリ側にも理解はして欲しい、そのため実務に追われてしまうと身に着きにくい基礎的な内容を教えるとの事で、この辺の感覚はそうっすよねーと心の中で頷いてました。
うちの会社もインフラとアプリ開発で部署がわかれているため、最近の若者はインフラに触れる事がなくて良くないなーとは感じてたので是非来年の研修で取り入れると良いなと思いました(他力本願)

ちなみにカヤックでの研修内容は公開されているようです。

https://github.com/kayac/newbie-training


2. 宣伝『入門Puppet』

https://speakerdeck.com/kentaro/puppet-book

ぺぱぼの@kentaroさんによる「入門puppet」の宣伝wとポイントの説明と最後にchefとpuppetの比較

アプリケーション開発者のような人がとりあえず触ってシステム構築してみようという時に入りやすいように本を書いたそうです。

個人的に刺さったのは
サーバがコードでいじれるこの時代に、エンジニアの共通言語としての「コード」の役割はますます大きくなる。DevOpsで仲良くやりつつ、コミュニケーションとしてコードが重要みたいなお話で、システム状態をコードで記述してpull requestで相互レビューみたいなシステム構築を自分の会社でもワークするようにできると面白そうと感じました。
あとpuppetもやれる事はかわらないし、意外とシンプルだよとの事だったのでchef派だったのですがpuppet入門を購入しちゃいましたw

3. 入門Chef Solo落ち穂拾い

https://speakerdeck.com/naoya/ru-men-chef-sololuo-tisui-shi-i

@naoya_itoさんによる入門Chef Soloの宣伝は特になくて出版後の反応(疑問)に対する回答

自分がchefを使おうと思ったきっかけが「入門Chef Solo」だったのでこいう話はありがたかったです。

特に覚えとこうと思った話は
サーバの構成管理は基本はchefだけでやる。インスタンス起動後の構成変更はすべてchefで行う。(ただし、アプリのデプロイは例外)
大事な考え方としては、chefは自動化ツールではなくノードの状態管理を行うもの。
あと、@naoya_itoさんはserverspecによるテスト推し(chefに依存しないので)

4. Chef市場動向

@jhottaさんによるChefConf2013の動向的なお話

chef使い10人に1人は日本人らしいです。(曖昧)
geekなおもちゃからビジネスツールへ(曖昧)
小さいデプロイから初めて上の人も巻き込め(曖昧)

みたいな話をされていたような記憶があります(曖昧)

5. environment変数の安全な使い方向

https://speakerdeck.com/sethvargo/chef-plus-environments-equals-safer-infrastructure

@sethvargoさんによる・・・なんの話だったのでしょうね・・・(汗

遠隔地からの英語の講演で多分バージョン管理の事なんだろうなぐらいしか理解できませんでした(貧弱)
でも、1セッション位はこうした海外の人の話を直接聞く機会はあっても良いかなと思います。

6. SqaleのChefとテストの話

ぺぱぼの@hiboma さんによるペパボのSqaleの裏側をchef/puppertでどう管理しているという話でした。

個人的に興味深かったのは
ホスティング用途だと1台のサーバの中で複数の環境を持つ事になるのでホスト名ベースの構成管理ツールだと利用できないので、そこだけchef-soloを使ってあとはpuppertを使っているみたいな話で、ぺぱぼの場合は構成管理の領域が異なるのでchefとpuppetが混在していても問題は無いが本来はおすすめしないよとの事。
あと構成管理の「テストでできない事をテストする」のも重要ってのも心のメモ帳にメモしときました。

感想書くまでが勉強会という事でようやく終わった!

Amazon Web ServicesでSymfony2.1の動作環境を10分で構築する手順

去年は少し忙しくてブログの更新をさぼった一年でしたが、今年は更新頻度を上げていこうかなと思います(笑
awsでSymfony2.1の検証的な環境を構築する手順です。
Symfony2を触ってみたいけど、わざわざ設定するのはメンドクサイなーってな人向けです。awsなのですぐ廃棄できます。
まじめにやるならpuppetがお勧めです。

0. 注意事項

  1. 環境を壊す可能性がありますのでawsの新規環境で行ってください。
  2. 運用する場合、設定を行いましょう
  3. php5.3.3なのでSymfony2.1の推奨設定は満たせてないです。
  4. 動作させるとお金がかかるので動作させたらインスタンス(ami)の削除をすると良いです

1. 準備するもの

awsのアカウント

2. awsにインスタンスを作成

awsにSymfony2をインストールするためのインスタンスを作成します。

2.1 キーペアの作成

メニューの「Key Pairs」から作成します。
名前はsymfony21とすると「symfony21.pem」というファイルをダウンロードが取得できます。
これはログインするために必要となるので大事に保存しておきましょう

2.2 インスタンスの起動

次にインスタンスの起動を行います。

f:id:taka512:20130112195718p:plain

メニューの「Instance」から「Launch Instance」を選択します。

「AWS Marketplace」を選択しテキストボックスに「CentOS」を入力してGOボタンを押す

f:id:taka512:20130112195813p:plain

マーケットプレイスに「CentOS 6.3 x86_64 Release Media」が表示されるので選択する。
次の画面でContinueボタンを押すと「Launch on EC2」の画面が表示されるので下記を選択

・Region
→Asia Pacific(Tokyo)を選択

・EC2 Instance Type
→Standard Small(m1.small)を選択

・Key Pair
→symfony21を選択

あとはデフォルトの選択で「Launch with 1-Click」ボタンを押すとインスタンスが起動します。

2.3 セキュリティグループの設定

awsでのアクセスを許可するポートの設定を行います。

f:id:taka512:20130112200329p:plain

メニューのSecurty Groupsを開くと新しいセキュリティグループが追加されてます。
「Inbound」から80と443を「add rule」した後に「Apply Rule Changes」で設定を反映します。

2.4 アクセスの確認

2.1で作成したキーをどっかのlinux環境にコピーしましょう。
で以下のコマンドでアクセスできたらインスタンスの作成は終了です!

ssh -i symfony21.pem root@[インスタンスのホスト名]

インスタンスのホスト名はawsのmanagement consoleから取得できます

3. サーバの設定

サーバ設定します。下記コマンドで途中で入力を求められる所はEnterを押すと良いです。
速いと10分ぐらいで終わります

# yum -y install make git
# git clone https://github.com/taka512/make_symfony2.git
# cd make_symfony2
# make

で、最後に再起動
ちなみに再起動するとaws内での名前解決ができなくなります。
原因不明ですがdnsをgoolgeのpublic dnsを参照するよう書き換えると解決します(が、再起動すると元に戻ります)
原因がわかる方がおりましたら教えていただけると助かります!

再起動

# init 6

DNSgooglednsに書き換えたい場合は下記コマンド

# make setup_resolve

再起動してアクセスすると下記のような画面が表示されれば成功です。

http://[awsドメイン名]/app_dev.php

f:id:taka512:20130119160937p:plain


4. TODO

  1. 設定ファイルはちゃんと設定した上でgitに登録した物を取得するようにしたい
  2. phpはmakeして最新版を入れたい
  3. ENTERの入力の必要性も無くしたい

Symfony2 ドキュメントの翻訳手順 cookbookを翻訳

前回の記事で確認環境の準備ができたので今回はcookbookを翻訳をしてみます。
基本的に本家と日本語版のディレクトリ構造は同じではありますが、
日本語版が本家について行けてない部分がありますので、そこは推測する感じで行きます。

本家のドキュメントのリポジトリ
http://github.com/symfony/symfony-docs

日本語版のドキュメントのリポジトリ
https://github.com/symfony-japan/symfony-docs-ja

(1)翻訳の対象を決める

cookbookはだいぶ翻訳をされてますが、本家リポジトリから翻訳されていない文章を探します。
本家のgithubを参照する場合はmasterを見ずに、2.0、2.1と訳したいバージョンのブランチに切り替えます。
例えばroutingディレクトリを見てみると日本語版に「redirect_in_config.rst」のファイルが無いので
翻訳されていない事が解ります。
手頃なので今回はこのファイルを和訳してみます。

https://github.com/symfony/symfony-docs/tree/2.0/cookbook/routing
https://github.com/symfony-japan/symfony-docs-ja/tree/master/cookbook/routing

※もちろん既存のファイルで翻訳されてない部分を翻訳するのもありです。

また、翻訳担当のページもあるのでここで他の人が翻訳しようとしていないか確認し、
作業しようとしているファイルを追記すると良いです。
https://github.com/symfony-japan/symfony-docs-ja/wiki/%E7%BF%BB%E8%A8%B3%E6%8B%85%E5%BD%93

(2)作業準備

前回の環境構築後に日本語版ドキュメントのリポジトリ
修正が掛かっていないか確認します。

# 日本語版のリモートを追加
$ git remote add symfony-japan git://github.com/symfony-japan/symfony-docs-ja
# pullして変更が無い事を確認
$ git pull symfony-japan master
From git://github.com/symfony-japan/symfony-docs-ja
 * branch            master     -> FETCH_HEAD
Already up-to-date.

特に変更はないようなのでmasterより今回の作業のためのブランチを切ります。

# 日本語翻訳環境に移動
$ cd /home/sphinx/symfony-docs-ja
# 今回の翻訳用ブランチを作成
$ git checkout -b translate-routing-redirect_in_config
$ git branch -a
  master
* translate-routing-redirect_in_config
  remotes/origin/2.0
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

本家のドキュメントを取り出して、日本語環境にコピーします。
本家はSymfony2.0用と2.1用でドキュメントのブランチを分けてるようです。
日本語版の方は2.0用時に作成されたものなので、本家の2.0ブランチからドキュメントを取得します。

# 本家のドキュメントを取得
$ cd /home/sphinx
$ git clone https://github.com/symfony/symfony-docs.git
# 本家のドキュメントのブランチを2.0に切り替える
$ cd symfony-docs
$ git branch -a
* master
  remotes/origin/2.0
  remotes/origin/2.1
  remotes/origin/HEAD -> origin/master
  remotes/origin/charset-fix
  remotes/origin/exception-handling-note
  remotes/origin/master
$ git checkout -b 2.0 remotes/origin/2.0
$ git branch -a
* 2.0
  master
  remotes/origin/2.0
# 英語ドキュメントのデータを日本語翻訳環境にコピー
$ cp cookbook/routing/redirect_in_config.rst ../symfony-docs-ja/cookbook/routing/redirect_in_config.rst

原文が更新されても追跡できるように末尾に日付・名前・SHAを記載します。
SHAの値は対象ファイルの本家commit履歴の下記から取得できます。
(git logからもてっとり早く見れます)

f:id:taka512:20120916162124p:plain

# 日本語翻訳環境のドキュメントを修正
$ cd /home/sphinx/symfony-docs-ja
$ vi cookbook/routing/redirect_in_config.rst
.. index::
   single: Routing; Configure redirect to another route without a custom controller

------略-------

The ``permanent`` switch tells both methods to issue a 301 HTTP status code.

.. 2012/09/16 taka512 ed4ea54e486600e07f17215b0b2999730f0666e1

これで準備は完了です。

(3)翻訳する

とりあえず、下記点に気をつければ問題無いかなと思っています。

・既存のsphinxっぽい記述は崩さない。
・空白はなるべく原文に準じる
・他の翻訳の日本語の文体を眺める

今回は新規追加なので一覧からの導線も追加します。

# redirect_in_configを追加します
$ vi cookbook/map.rst.inc
* **ルーティング**

  * :doc:`/cookbook/routing/scheme`
  * :doc:`/cookbook/routing/slash_in_parameter`
  * :doc:`/cookbook/routing/redirect_in_config`

(4)確認する

sphinxでmakeして確認します。

(5)コミットしてpull requestを送信する

自分のgithub上のリモートレポジトリにpushします。

$ git add cookbook/routing/redirect_in_config.rst
$ git commit cookbook/map.rst.inc cookbook/routing/redirect_in_config.rst
$ git push origin translate-routing-redirect_in_config
Enter passphrase for key '/home/sh-takahashi/.ssh/id_rsa':
Counting objects: 10, done.
Delta compression using up to 3 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 1.40 KiB, done.
Total 6 (delta 3), reused 0 (delta 0)
To ssh://git@github.com/taka512/symfony-docs-ja.git
 * [new branch]      translate-routing-redirect_in_config -> translate-routing-redirect_in_config


pushが成功したら自分のgithubにログインします。
ブランチを切り替えて最新の更新が自分になっていれば成功です。
pullリクエストを送信します。pullリクエストが取り込まれれば成功となります。

f:id:taka512:20120916174835p:plain

(6)後始末

後始末は大事です。翻訳担当から自分の記述を削除します。

これで一連の流れは終わりです。

Symfony2 ドキュメントの翻訳手順 環境準備

久しぶりにユーザ会のコンテンツを翻訳してみようと思い立ったけど
割と忘れていたので、振り返りがてらメモした手順となります。

Symfony2のドキュメントはsphinxというPython製ドキュメンテーションビルダーで
構築されています。
まずはsphinxでSymfony2のドキュメントを表示する所までの環境の準備を行います

(1)sphinxのインストール

# yum install python-pip python-setuptools
# pip-python install sphinx

(2)ドキュメントの作業ディレクトリの作成

日本語ドキュメントのリポジトリは下記となります。

https://github.com/symfony-japan/symfony-docs-ja

まずはforkボタンを押してforkします。

f:id:taka512:20120820210636p:plain


翻訳用の作業ディレクトリを作成し、forkしたリポジトリをcloneします。

$ mkdir /home/sphinx
$ cd /home/sphinx
$ git clone https://github.com/taka512/symfony-docs-ja.git

(3)sphinxプロジェクトの作成

index.rstをそのままにしておくと、プロジェクト作成の際に上書きされそうになるので
一旦退避させます。

$ cd symfony-docs-ja/
// 一旦退避
$ mv index.rst index.rst_

3つの質問以外はデフォルトで問題ないです。

// プロジェクト名
> Project name: symfony2_translate
// 作者名
> Author name(s): taka512
// プロジェクトバージョン(当日の日付でも設定しておく)
> Project version: 20120820

sphinxプロジェクトの作成

$ sphinx-quickstart
Welcome to the Sphinx 1.1.3 quickstart utility.

Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).

Enter the root path for documentation.
> Root path for the documentation [.]:

You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/N) [n]:

Inside the root directory, two more directories will be created; "_templates"
for custom HTML templates and "_static" for custom stylesheets and other static
files. You can enter another prefix (such as ".") to replace the underscore.
> Name prefix for templates and static dir [_]:

The project name will occur in several places in the built documentation.
> Project name: symfony2_translate
> Author name(s): taka512

Sphinx has the notion of a "version" and a "release" for the
software. Each version can have multiple releases. For example, for
Python the version is something like 2.5 or 3.0, while the release is
something like 2.5.1 or 3.0a1.  If you don't need this dual structure,
just set both to the same value.
> Project version: 20120820
> Project release [20120820]:

The file name suffix for source files. Commonly, this is either ".txt"
or ".rst".  Only files with this suffix are considered documents.
> Source file suffix [.rst]:

One document is special in that it is considered the top node of the
"contents tree", that is, it is the root of the hierarchical structure
of the documents. Normally, this is "index", but if your "index"
document is a custom template, you can also set this to another filename.
> Name of your master document (without suffix) [index]:

Sphinx can also add configuration for epub output:
> Do you want to use the epub builder (y/N) [n]:

Please indicate if you want to use one of the following Sphinx extensions:
> autodoc: automatically insert docstrings from modules (y/N) [n]:
> doctest: automatically test code snippets in doctest blocks (y/N) [n]:
> intersphinx: link between Sphinx documentation of different projects (y/N) [n]:
> todo: write "todo" entries that can be shown or hidden on build (y/N) [n]:
> coverage: checks for documentation coverage (y/N) [n]:
> pngmath: include math, rendered as PNG images (y/N) [n]:
> mathjax: include math, rendered in the browser by MathJax (y/N) [n]:
> ifconfig: conditional inclusion of content based on config values (y/N) [n]:
> viewcode: include links to the source code of documented Python objects (y/N) [n]:

A Makefile and a Windows command file can be generated for you so that you
only have to run e.g. `make html' instead of invoking sphinx-build
directly.
> Create Makefile? (Y/n) [y]:
> Create Windows command file? (Y/n) [y]:

Creating file ./conf.py.
Creating file ./index.rst.
Creating file ./Makefile.
Creating file ./make.bat.

Finished: An initial directory structure has been created.

You should now populate your master file ./index.rst and create other documentation
source files. Use the Makefile to build the docs, like so:
   make builder
where "builder" is one of the supported builders, e.g. html, latex or linkcheck.

退避したindexファイルを戻す

$ mv index.rst_ index.rst

(4)sphinx拡張を設定

Symfony2のドキュメントではsphinxの拡張を利用しているので設定する必要があります。

$ git clone https://github.com/fabpot/sphinx-php
$ mv sphinx-php _exts
// conf.pyファイルの一番下に追記
$ vi conf.py
sys.path.append(os.path.abspath('_exts'))

# adding PhpLexer
from sphinx.highlighting import lexers
from pygments.lexers.web import PhpLexer

# add the extensions to the list of extensions
extensions = ['sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode']

# enable highlighting for PHP code not between ``<?php ... ?>`` by default
lexers['php'] = PhpLexer(startinline=True)
lexers['php-annotations'] = PhpLexer(startinline=True)

# use PHP as the primary domain
primary_domain = 'php'

# set url for API links
api_url = 'http://api.symfony.com/master/%s'

(5)ドキュメント(html)を生成

ここまでで準備ができましたのでドキュメントが作成できるか確認を行います。

「make html」コマンドを実行すると「_build/html」の下にhtmlファイルが書き出されます。
これをブラウザで見れるディレクトリにコピーして確認を行います。

$ make html
sphinx-build -b html -d _build/doctrees   . _build/html
Making output directory...
Running Sphinx v1.1.3
loading pickled environment... not yet created
building [html]: targets for 189 source files that are out of date
updating environment: 189 added, 0 changed, 0 removed
reading sources... [100%] reference/requirements

----略----

writing additional files... genindex search
copying images... [100%] images/request-flow.png
copying static files... done
dumping search index... done
dumping object inventory... done
build succeeded, 163 warnings.

Build finished. The HTML pages are in _build/html.

// 生成されたファイルをドキュメントルートにコピー
$ cp -r _build/html [DocumentRoot]


http://yourdomain/html」にアクセスすると「http://docs.symfony.gr.jp/symfony2/」に
あたるドキュメントが表示されます。
こんな感じに表示されたら成功です。

f:id:taka512:20120820212151p:plain


■参考

sphinxの設定
http://sphinx-users.jp/gettingstarted/index.html

sphinx拡張のインストール
http://symfony.com/doc/master/contributing/documentation/format.html

・Symfony2 ドキュメントの作り方
http://d.hatena.ne.jp/shimooka/20120621/1340252628

10分ぐらいで学べるSymfony2 〜Doctrine2のassociationsを見てみる〜

masterとcategoryのように複数テーブルに情報が分かれている場合でもDoctrine2はよろしくMAPしてくれます。
ただ、ORMを使っているとSQLが直接見えない分、残念なSQLになってしまう事があるので挙動を調べたメモです。


Doctirne2のテーブル間の関係性において「1対1」「1対多」「多対多」の3つがあります。
今回は「1対多」で参照の場合のメモとなります。


1. テーブル構成

hoge1が1でhoge_typeが多の関係性で初期データやデータ定義は下記とします。

# テーブル定義
mysql> desc hoge1;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(32) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> desc hoge1_type;
+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int(11)     | NO   | PRI | NULL    | auto_increment |
| hoge1_id | int(11)     | NO   | MUL | NULL    |                |
| name     | varchar(32) | YES  |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

# テーブルデー
mysql> select * from hoge1;
+----+---------+
| id | name    |
+----+---------+
|  1 | master1 |
|  2 | master2 |
|  3 | master3 |
|  4 | master4 |
|  5 | master5 |
+----+---------+

mysql> select * from hoge1_type;
+----+----------+-------------+
| id | hoge1_id | name        |
+----+----------+-------------+
|  1 |        1 | hoge1_type1 |
|  2 |        2 | hoge2_type1 |
|  3 |        2 | hoge2_type2 |
|  4 |        4 | hoge4_type1 |
|  5 |        4 | hoge4_type2 |
|  6 |        4 | hoge4_type3 |
|  7 |        4 | hoge4_type4 |
+----+----------+-------------+
7 rows in set (0.00 sec)
mysql>


2. リレーション設定

「mappedBy」と「inversedBy」には相手側のプロパティ名が入るので注意しましょう。

# one側
Root\TestBundle\Entity\Hoge1:
  type: entity
  oneToMany:
    Hoge1Types:
      targetEntity: Hoge1Type
      mappedBy:     hoge1
      cascade:      ['all']

# many側
Root\TestBundle\Entity\Hoge1Type:
  type:  entity
  table: hoge1_type
  manyToOne:
    hoge1:
      targetEntity: Hoge1
      inversedBy:   Hoge1Types
      joinColumn:
        name: hoge1_id
        referencedColumnName: id


3. hoge1(one)からhoge1_type(many)のデータにアクセス

(1)typeデータにアクセスしない場合
typeのデータにアクセスしなければhoge1データだけをselectします。

# 処理
  $em = $this->getDoctrine()->getEntityManager();
  $all = $em->getRepository('RootTestBundle:Hoge1')->findAll();
  foreach($all as $hoge1)
  {
      print $hoge1->getName().'<br />';
  }

# 実行結果
master1
master2
master3
master4
master5

# 発行SQL
  SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0


(2)typeデータにアクセスするがjoinしない場合

typeデータにアクセスする場合毎回SQLが発生します。
リストなどでTypeのデータが必要な場合limitの数だけSELECT文が発生して悲惨な事になりますので注意が必要です。

# 処理
  $em = $this->getDoctrine()->getEntityManager();
  $all = $em->getRepository('RootTestBundle:Hoge1')->findAll();
  foreach($all as $hoge1)
  {
      print $hoge1->getName().'<br />';
      foreach($hoge1->getHoge1Types() as $type)
          print $type->getName().'<br />';
  }

# 実行結果
master1
hoge1_type1
master2
hoge2_type1
hoge2_type2
master3
master4
hoge4_type1
hoge4_type2
hoge4_type3
hoge4_type4
master5

# 発行SQL
SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0
SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([1]) 
SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([2]) 
SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([3]) 
SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([4]) 
SELECT t0.id AS id1, t0.hoge1_id AS hoge1_id2, t0.name AS name3, t0.hoge1_id AS hoge1_id4 FROM hoge1_type t0 WHERE t0.hoge1_id = ? ([5]) 


(3)INNER JOINする場合

INNER JOINすると1回SQLでデータを取得できますが、typeデータに関連データが無い場合のhoge1データが欠落するので注意が必要です。

# 処理
  $em  = $this->getDoctrine()->getEntityManager();
  $all = $em->createQuery('SELECT h1, t FROM RootTestBundle:Hoge1 h1 JOIN h1.Hoge1Types t')
            ->getResult()
  ;
  foreach($all as $hoge1)
  {
      print $hoge1->getName().'<br />';
      foreach($hoge1->getHoge1Types() as $type)
          print $type->getName().'<br />';
  }

# 実行結果
master1
hoge1_type1
master2
hoge2_type1
hoge2_type2
master4
hoge4_type1
hoge4_type2
hoge4_type3
hoge4_type4

# 発行SQL
SELECT h0_.id AS id0, h0_.name AS name1, h1_.id AS id2, h1_.hoge1_id AS hoge1_id3, h1_.name AS name4, h1_.hoge1_id AS hoge1_id5 
 FROM hoge1 h0_ INNER JOIN hoge1_type h1_ ON h0_.id = h1_.hoge1_id


(4)LEFT JOINする場合

これでmaster3、master5もデータを取得できました。
ただし、この場合はlimit句で5などと区切った場合master4のtypeデータに欠落が生じる事になるので注意が必要です。

# 処理
  $em  = $this->getDoctrine()->getEntityManager();
  $all = $em->createQuery('SELECT h1, t FROM RootTestBundle:Hoge1 h1 LEFT JOIN h1.Hoge1Types t')
            ->getResult()
  ;
  foreach($all as $hoge1)
  {
      print $hoge1->getName().'<br />';
      foreach($hoge1->getHoge1Types() as $type)
          print $type->getName().'<br />';
  }
# 実行結果
master1
hoge1_type1
master2
hoge2_type1
hoge2_type2
master3
master4
hoge4_type1
hoge4_type2
hoge4_type3
hoge4_type4
master5

# 発行SQL
SELECT h0_.id AS id0, h0_.name AS name1, h1_.id AS id2, h1_.hoge1_id AS hoge1_id3, h1_.name AS name4, h1_.hoge1_id AS hoge1_id5
 FROM hoge1 h0_ LEFT JOIN hoge1_type h1_ ON h0_.id = h1_.hoge1_id


4. hoge1_type(many)からhoge1(one)のデータにアクセス


(1)hoge1を取得
一度取得したhoge1はキャッシュされる。

# 処理
  $em  = $this->getDoctrine()->getEntityManager();
  $all = $em->createQuery('SELECT t FROM RootTestBundle:Hoge1Type t')
            ->getResult()
  ;
  foreach($all as $type)
  {
      print $type->getHoge1()->getName().':'. $type->getName().'<br />';
  }

# 実行結果
master1:hoge1_type1
master2:hoge2_type1
master2:hoge2_type2
master4:hoge4_type1
master4:hoge4_type2
master4:hoge4_type3

# 発行SQL
SELECT h0_.id AS id0, h0_.hoge1_id AS hoge1_id1, h0_.name AS name2, h0_.hoge1_id AS hoge1_id3 FROM hoge1_type h0_
SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 WHERE t0.id = ? (["1"])
SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 WHERE t0.id = ? (["2"])
SELECT t0.id AS id1, t0.name AS name2 FROM hoge1 t0 WHERE t0.id = ? (["4"])


(2)JOINしてhoge1を取得


JOINしてデータが重複した場合でも、よろしくORMにMAPしてくれる

# 処理
  $em  = $this->getDoctrine()->getEntityManager();
  $all = $em->createQuery('SELECT t, h1 FROM RootTestBundle:Hoge1Type t JOIN t.hoge1 h1')
            ->getResult()
  ;
  foreach($all as $type)
  {
      print $type->getHoge1()->getName().':'. $type->getName().'<br />';
  }

# 実行結果
master1:hoge1_type1
master2:hoge2_type1
master2:hoge2_type2
master4:hoge4_type1
master4:hoge4_type2
master4:hoge4_type3
master4:hoge4_type4

# 発行SQL
SELECT h0_.id AS id0, h0_.hoge1_id AS hoge1_id1, h0_.name AS name2, h1_.id AS id3, h1_.name AS name4, h0_.hoge1_id AS hoge1_id5 
FROM hoge1_type h0_ INNER JOIN hoge1 h1_ ON h0_.hoge1_id = h1_.id


以上です。

Symfony2コマンドの出力先を変更 : Symfony Advent Calender 2011 JP - 4日目 -

taka512です。Symfony Advent Calendar JP 2011の4日目の記事です。
Symfonyユーザ会の大御所から引き続いての4日目ということで緊張で手がプルプル震えつつ書いてます。
今回はSymfony2コマンドを実行した際の出力先を変更する方法を紹介します。


Symfony2にはコマンドという仕組みがあります。
例えばTestCommand.phpを下記のように作成したとします。

$ vi src/Taka512/TestBundle/Command/TestCommand.php
  1 <?php
  2
  3 namespace Taka512\TestBundle\Command;
  4
  5 use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
  6 use Symfony\Component\Console\Input\InputInterface;
  7 use Symfony\Component\Console\Output\OutputInterface;
  8
  9 class TestCommand extends ContainerAwareCommand{
 10     protected function execute(InputInterface $input, OutputInterface $output){
 11         $output->writeln('TEST');
 12         $output->writeln('DAYO');
 13     }
 14     protected function configure(){
 15         $this->setName('taka512:test');
 16     }
 17 }

下記コマンドを実行すると標準出力に出力されます。

$ php app/console taka512:test
TEST
DAYO

さて、ここで自分はcronからこのコマンド実行したいと考えるようになりました。
ただし、cronから実行するには2つの修正したい点があります。
・標準出力には出力しない
・結果は最後に一度メールで通知を受けたい
「writeln」を使わなければ良いというのも良い判断だと思いますが、今回はwritelnの内容をメール送信する方法を探りました。


1.「$output」は何者か確認

まず、writelnが存在するクラスである$outputが何者かを見ていきます

$ vi app/console
  1 #!/usr/bin/env php
  2 <?php
  3
  4 require_once __DIR__.'/bootstrap.php.cache';
  5 require_once __DIR__.'/AppKernel.php';
  6
  7 use Symfony\Bundle\FrameworkBundle\Console\Application;
  8 use Symfony\Component\Console\Input\ArgvInput;
  9
 10 $input = new ArgvInput();
 11 $env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV'    ) ?: 'dev');
 12 $debug = !$input->hasParameterOption(array('--no-debug', ''));
 13
 14 $kernel = new AppKernel($env, $debug);
 15 $application = new Application($kernel);
 16 $application->run();


$ vi vendor/symfony/src/Symfony/Component/Console/Application.php 
107     public function run(InputInterface $input = null, OutputInterface $output = null)
108     {
109         if (null === $input) {
110             $input = new ArgvInput();
111         }
112
113         if (null === $output) {
114             $output = new ConsoleOutput();
115         }

「app/console」の16行目で何も引数がわたされなければ「ConsoleOutput」クラスとなるということがわかりました。
ConsoleOutputクラスをメール送信を行うクラスに置き換えができれば良さそうです。
ConsoleOutputクラスのソースは下記構成となってます。


(1)でコンソールはストリーム出力と設定
(2)でストリームへの出力処理実装
(3)で出力フォーマット処理の実装

(1) ./vendor/symfony/src/Symfony/Component/Console/Output/ConsoleOutput.php
↓を継承
(2) ./vendor/symfony/src/Symfony/Component/Console/Output/StreamOutput.php
↓を継承
(3) ./vendor/symfony/src/Symfony/Component/Console/Output/Output.php
↓を実装
(4) ./vendor/symfony/src/Symfony/Component/Console/Output/OutputInterface.php


2. メール送信クラスの実装

前項の(1)〜(2)は標準出力への出力の実装クラスであるので(3)を継承しつつ新規にメール送信クラスを作成します。
構成は下記となります。ソースは少し長くなるので貼り付けせずにリンクしてます。
ソースは 「./vendor/Taka512/lib/Taka512/Console/Output」ディレクトリを作成してファイルを置いてます。
(オートローダに「'Taka512' => __DIR__ . '/../vendor/Taka512/lib',」を登録する必要があります)


(1)バッファされた結果をメールする
https://github.com/taka512/Symfony2-lib/blob/master/lib/Taka512/Console/Output/MailOutput.php
(2)writelnされた結果をバッファ+バッファをフラッシュするflushメソッドを追加
https://github.com/taka512/Symfony2-lib/blob/master/lib/Taka512/Console/Output/BufferOutput.php

(1) ./vendor/Taka512/lib/Taka512/Console/Output/MailOutput.php
↓を継承
(2) ./vendor/Taka512/lib/Taka512/Console/Output/BufferOutput.php
↓を継承
(3) ./vendor/symfony/src/Symfony/Component/Console/Output/Output.php
↓を実装
(4) ./vendor/symfony/src/Symfony/Component/Console/Output/OutputInterface.php


3. flushメソッドの追加

前項の(2)で実装したflushメソッドを「TestCommand.php」に追加します。

$ vi src/Taka512/TestBundle/Command/TestCommand.php
  9 class TestCommand extends ContainerAwareCommand{
 10     protected function execute(InputInterface $input, OutputInterface $output){
 11         $output->writeln('TEST');
 12         $output->writeln('DAYO');
 13         $output->flush();  // 追加
 13     }


4. ConsoleOutputクラスをMailOutputクラスに置換

まだ、「$output」はConsoleOutputクラスのままなのでここをMailOutputクラスに置き換えを行います。
「app/console」ファイルは修正すると危険なので「app/batch」ファイルを新規に作成します。
16行目と25、26行目を追加してます。

$ cp app/console app/batch
$ vi app/batch
  1 #!/usr/bin/env php
  2 <?php
  3
  4 // if you don't want to setup permissions the proper way, just uncomment the following PHP line
  5 // read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
  6 //umask(0000);
  7
  8 set_time_limit(0);
  9
 10 require_once __DIR__.'/bootstrap.php.cache';
 11 require_once __DIR__.'/AppKernel.php';
 12
 13 use Symfony\Bundle\FrameworkBundle\Console\Application;
 14 use Symfony\Component\Console\Input\ArgvInput;
 15 // add
 16 use Taka512\Console\Output\MailOutput;
 17
 18 $input = new ArgvInput();
 19 $env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
 20 $debug = !$input->hasParameterOption(array('--no-debug', ''));
 21
 22 $kernel = new AppKernel($env, $debug);
 23 $application = new Application($kernel);
 24 // need Container
 25 $kernel->boot();
 26 $output = new MailOutput($kernel->getContainer()->get('mailer'));
 27 $application->run(null, $output);


これでコマンドを実行すると標準出力には何も出力されずにメールが送信されるようになります。

$ php app/batch taka512:test
$

以上長くなってしまったのですが、Symfony2の挙動を変更したい場合の例を紹介してみました。
(実際はメールの送信/受信先をパラメータから取得するとか他にも処理を追加する必要があります)
ということで5日目はたくさんネタを持ってそうなfivestrさんです。よろしくお願いします。

10分ぐらいで学べるSymfony2 〜configの読込編〜

configの読込についてメモです。
app/configの下には各dev/test/prod環境の設定ファイルも含めて4つ存在します。
(1)config.yml
(2)config_dev.yml
(3)config_test.yml
(4)config_prod.yml


・dev環境の場合
(2)→(1)
・prod環境の場合
(4)→(1)
・test環境の場合
(3)→(2)→(1)
と設定ファイルを読み込みします。


例えばID/PASSは各環境で同じでデータベースの名前だけを各環境で変更したかった場合、下記のように設定します。
(1)にprodと全体の設定を記載、(2)にdev固有の設定を記載、(3)にtest固有の設定を記載します。


データベース接続設定

$ vi app/config/config.yml
doctrine:
    dbal:
        driver:   %database_driver%
        host:     %database_host%
        port:     %database_port%
        dbname:   %database_name%
        user:     %database_user%
        password: %database_password%
        charset:  UTF8

$ vi app/config/config_test.yml
doctrine:
    dbal:
        dbname:   %database_name_dev%

$ vi app/config/config_dev.yml
doctrine:
    dbal:
        dbname:   %database_name_test%